Merge "Increment paging version for bugfix" into oc-mr1-support-27.0-dev
am: cd3ca74ce6

Change-Id: Ie3bc65bdb8a20fa2cf849068f2c9db72bb6e4f73
diff --git a/annotations/src/main/java/android/support/annotation/AnyThread.java b/annotations/src/main/java/android/support/annotation/AnyThread.java
index 4c379d3..b006922 100644
--- a/annotations/src/main/java/android/support/annotation/AnyThread.java
+++ b/annotations/src/main/java/android/support/annotation/AnyThread.java
@@ -17,6 +17,7 @@
 
 import static java.lang.annotation.ElementType.CONSTRUCTOR;
 import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
 import static java.lang.annotation.ElementType.TYPE;
 import static java.lang.annotation.RetentionPolicy.CLASS;
 
@@ -41,6 +42,6 @@
  */
 @Documented
 @Retention(CLASS)
-@Target({METHOD,CONSTRUCTOR,TYPE})
+@Target({METHOD,CONSTRUCTOR,TYPE,PARAMETER})
 public @interface AnyThread {
 }
diff --git a/annotations/src/main/java/android/support/annotation/BinderThread.java b/annotations/src/main/java/android/support/annotation/BinderThread.java
index 0b821d5..5d9a3c2 100644
--- a/annotations/src/main/java/android/support/annotation/BinderThread.java
+++ b/annotations/src/main/java/android/support/annotation/BinderThread.java
@@ -17,6 +17,7 @@
 
 import static java.lang.annotation.ElementType.CONSTRUCTOR;
 import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
 import static java.lang.annotation.ElementType.TYPE;
 import static java.lang.annotation.RetentionPolicy.CLASS;
 
@@ -37,6 +38,6 @@
  */
 @Documented
 @Retention(CLASS)
-@Target({METHOD,CONSTRUCTOR,TYPE})
+@Target({METHOD,CONSTRUCTOR,TYPE,PARAMETER})
 public @interface BinderThread {
 }
\ No newline at end of file
diff --git a/annotations/src/main/java/android/support/annotation/MainThread.java b/annotations/src/main/java/android/support/annotation/MainThread.java
index 2f50306..78541d5 100644
--- a/annotations/src/main/java/android/support/annotation/MainThread.java
+++ b/annotations/src/main/java/android/support/annotation/MainThread.java
@@ -17,6 +17,7 @@
 
 import static java.lang.annotation.ElementType.CONSTRUCTOR;
 import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
 import static java.lang.annotation.ElementType.TYPE;
 import static java.lang.annotation.RetentionPolicy.CLASS;
 
@@ -45,6 +46,6 @@
  */
 @Documented
 @Retention(CLASS)
-@Target({METHOD,CONSTRUCTOR,TYPE})
+@Target({METHOD,CONSTRUCTOR,TYPE,PARAMETER})
 public @interface MainThread {
 }
diff --git a/annotations/src/main/java/android/support/annotation/UiThread.java b/annotations/src/main/java/android/support/annotation/UiThread.java
index 0a9a0c1..1d7aeca 100644
--- a/annotations/src/main/java/android/support/annotation/UiThread.java
+++ b/annotations/src/main/java/android/support/annotation/UiThread.java
@@ -17,6 +17,7 @@
 
 import static java.lang.annotation.ElementType.CONSTRUCTOR;
 import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
 import static java.lang.annotation.ElementType.TYPE;
 import static java.lang.annotation.RetentionPolicy.CLASS;
 
@@ -46,6 +47,6 @@
  */
 @Documented
 @Retention(CLASS)
-@Target({METHOD,CONSTRUCTOR,TYPE})
+@Target({METHOD,CONSTRUCTOR,TYPE,PARAMETER})
 public @interface UiThread {
 }
diff --git a/annotations/src/main/java/android/support/annotation/WorkerThread.java b/annotations/src/main/java/android/support/annotation/WorkerThread.java
index 237aa66..8b08b14 100644
--- a/annotations/src/main/java/android/support/annotation/WorkerThread.java
+++ b/annotations/src/main/java/android/support/annotation/WorkerThread.java
@@ -17,6 +17,7 @@
 
 import static java.lang.annotation.ElementType.CONSTRUCTOR;
 import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
 import static java.lang.annotation.ElementType.TYPE;
 import static java.lang.annotation.RetentionPolicy.CLASS;
 
@@ -37,6 +38,6 @@
  */
 @Documented
 @Retention(CLASS)
-@Target({METHOD,CONSTRUCTOR,TYPE})
+@Target({METHOD,CONSTRUCTOR,TYPE,PARAMETER})
 public @interface WorkerThread {
 }
\ No newline at end of file
diff --git a/buildSrc/dependencies.gradle b/buildSrc/dependencies.gradle
index 470551b..48d3eb9 100644
--- a/buildSrc/dependencies.gradle
+++ b/buildSrc/dependencies.gradle
@@ -16,10 +16,6 @@
 // Add ext.libs for library versions
 def libs = [:]
 
-libs.exclude_annotations = {
-    exclude module: 'support-annotations'
-}
-
 libs.exclude_bytebuddy = {
     exclude group: 'net.bytebuddy'
 }
diff --git a/buildSrc/init.gradle b/buildSrc/init.gradle
index 18c3114..a0df393 100644
--- a/buildSrc/init.gradle
+++ b/buildSrc/init.gradle
@@ -62,7 +62,7 @@
 }
 
 def setSdkInLocalPropertiesFile() {
-    ext.buildToolsVersion = '27.0.0'
+    ext.buildToolsVersion = '27.0.1'
     final String fullSdkPath = getFullSdkPath();
     if (file(fullSdkPath).exists()) {
         gradle.ext.currentSdk = 26
@@ -148,8 +148,7 @@
         // Only modify Android projects.
         if (project.name.equals('doclava')
                 || project.name.equals('jdiff')
-                || project.name.equals('noto-emoji-compat')
-                || project.name.equals('support-media-compat-test-lib')) {
+                || project.name.equals('noto-emoji-compat')) {
             // disable tests and return
             project.tasks.whenTaskAdded { task ->
                 if (task instanceof org.gradle.api.tasks.testing.Test) {
diff --git a/buildSrc/src/main/java/android/support/LibraryVersions.java b/buildSrc/src/main/java/android/support/LibraryVersions.java
index ba74080..c8dd1e3 100644
--- a/buildSrc/src/main/java/android/support/LibraryVersions.java
+++ b/buildSrc/src/main/java/android/support/LibraryVersions.java
@@ -23,7 +23,7 @@
     /**
      * Version code of the support library components.
      */
-    public static final Version SUPPORT_LIBRARY = new Version("27.0.2");
+    public static final Version SUPPORT_LIBRARY = new Version("27.1.0-SNAPSHOT");
 
     /**
      * Version code for flatfoot 1.0 projects (room, lifecycles)
diff --git a/buildSrc/src/main/java/android/support/checkapi/UpdateApiTask.java b/buildSrc/src/main/java/android/support/checkapi/UpdateApiTask.java
index de2db91..15e9104 100644
--- a/buildSrc/src/main/java/android/support/checkapi/UpdateApiTask.java
+++ b/buildSrc/src/main/java/android/support/checkapi/UpdateApiTask.java
@@ -29,7 +29,6 @@
 import java.io.File;
 import java.nio.charset.Charset;
 import java.util.HashSet;
-import java.util.List;
 import java.util.Set;
 
 /**
@@ -120,11 +119,6 @@
         }
 
         if (mWhitelistErrorsFile != null && !mWhitelistErrors.isEmpty()) {
-            if (mWhitelistErrorsFile.exists()) {
-                List<String> lines =
-                        Files.readLines(mWhitelistErrorsFile, Charset.defaultCharset());
-                mWhitelistErrors.removeAll(lines);
-            }
             try (BufferedWriter writer = Files.newWriter(
                     mWhitelistErrorsFile, Charset.defaultCharset())) {
                 for (String error : mWhitelistErrors) {
diff --git a/buildSrc/src/main/kotlin/android/support/DiffAndDocs.kt b/buildSrc/src/main/kotlin/android/support/DiffAndDocs.kt
index 9f0e402..3432cbf 100644
--- a/buildSrc/src/main/kotlin/android/support/DiffAndDocs.kt
+++ b/buildSrc/src/main/kotlin/android/support/DiffAndDocs.kt
@@ -578,12 +578,12 @@
     root.subprojects { subProject ->
         subProject.afterEvaluate { project ->
             if (project.hasProperty("noDocs") && (project.properties["noDocs"] as Boolean)) {
-                project.logger.warn("Project $project.name specified noDocs, ignoring API tasks.")
+                project.logger.info("Project $project.name specified noDocs, ignoring API tasks.")
                 return@afterEvaluate
             }
             if (project.hasProperty("supportLibrary")
                     && !(project.properties["supportLibrary"] as SupportLibraryExtension).publish) {
-                project.logger.warn("Project ${project.name} is not published, ignoring API tasks.")
+                project.logger.info("Project ${project.name} is not published, ignoring API tasks.")
                 return@afterEvaluate
             }
             val library = project.extensions.findByType(LibraryExtension::class.java)
@@ -595,7 +595,7 @@
                             return@all
                         }
                         if (!hasApiFolder(project)) {
-                            project.logger.warn("Project ${project.name} doesn't have " +
+                            project.logger.info("Project ${project.name} doesn't have " +
                                     "an api folder, ignoring API tasks.")
                             return@all
                         }
@@ -609,7 +609,7 @@
                 val compileJava = project.properties["compileJava"] as JavaCompile
                 registerJavaProjectForDocsTask(generateDocsTask, compileJava)
                 if (!hasApiFolder(project)) {
-                    project.logger.warn("Project $project.name doesn't have an api folder, " +
+                    project.logger.info("Project $project.name doesn't have an api folder, " +
                             "ignoring API tasks.")
                     return@afterEvaluate
                 }
diff --git a/buildSrc/src/main/kotlin/android/support/SupportAndroidLibraryPlugin.kt b/buildSrc/src/main/kotlin/android/support/SupportAndroidLibraryPlugin.kt
index c83d740..5eaa7dd 100644
--- a/buildSrc/src/main/kotlin/android/support/SupportAndroidLibraryPlugin.kt
+++ b/buildSrc/src/main/kotlin/android/support/SupportAndroidLibraryPlugin.kt
@@ -37,6 +37,8 @@
                 SupportLibraryExtension::class.java, project)
         apply(project, supportLibraryExtension)
 
+        val isCoreSupportLibrary = project.rootProject.name == "support"
+
         project.afterEvaluate {
             val library = project.extensions.findByType(LibraryExtension::class.java)
                     ?: return@afterEvaluate
@@ -75,6 +77,21 @@
         project.apply(mapOf("plugin" to "com.android.library"))
         project.apply(mapOf("plugin" to ErrorProneBasePlugin::class.java))
 
+        project.configurations.all { configuration ->
+            if (isCoreSupportLibrary) {
+                // In projects which compile as part of the "core" support libraries (which include
+                // the annotations), replace any transitive pointer to the deployed Maven
+                // coordinate version of annotations with a reference to the local project. These
+                // usually originate from test dependencies and otherwise cause multiple copies on
+                // the classpath. We do not do this for non-"core" projects as they need to
+                // depend on the Maven coordinate variant.
+                configuration.resolutionStrategy.dependencySubstitution.apply {
+                    substitute(module("com.android.support:support-annotations"))
+                            .with(project(":support-annotations"))
+                }
+            }
+        }
+
         val library = project.extensions.findByType(LibraryExtension::class.java)
                 ?: throw Exception("Failed to find Android extension")
 
@@ -120,12 +137,15 @@
 
                         // Enforce the following checks.
                         "-Xep:RestrictTo:OFF",
+                        "-Xep:ParameterNotNullable:ERROR",
                         "-Xep:MissingOverride:ERROR",
+                        "-Xep:JdkObsolete:ERROR",
                         "-Xep:NarrowingCompoundAssignment:ERROR",
                         "-Xep:ClassNewInstance:ERROR",
                         "-Xep:ClassCanBeStatic:ERROR",
                         "-Xep:SynchronizeOnNonFinalField:ERROR",
-                        "-Xep:OperatorPrecedence:ERROR"
+                        "-Xep:OperatorPrecedence:ERROR",
+                        "-Xep:IntLongMath:ERROR"
                 )
             }
         }
diff --git a/car/Android.mk b/car/Android.mk
new file mode 100644
index 0000000..fa20f26
--- /dev/null
+++ b/car/Android.mk
@@ -0,0 +1,39 @@
+# 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.
+
+LOCAL_PATH := $(call my-dir)
+
+# Here is the final static library that apps can link against.
+# Applications that use this library must specify
+#
+#   LOCAL_STATIC_ANDROID_LIBRARIES := \
+#       android-support-car
+#
+# in their makefiles to include the resources and their dependencies in their package.
+include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
+LOCAL_MODULE := android-support-car
+LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
+LOCAL_SRC_FILES := $(call all-java-files-under,src/main/java)
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+LOCAL_SHARED_ANDROID_LIBRARIES := \
+        android-support-annotations \
+        android-support-v4 \
+        android-support-v7-appcompat \
+        android-support-v7-cardview \
+        android-support-v7-recyclerview
+LOCAL_JAR_EXCLUDE_FILES := none
+LOCAL_JAVA_LANGUAGE_VERSION := 1.8
+LOCAL_AAPT_FLAGS := --add-javadoc-annotation doconly
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/car/AndroidManifest.xml b/car/AndroidManifest.xml
new file mode 100644
index 0000000..854e097
--- /dev/null
+++ b/car/AndroidManifest.xml
@@ -0,0 +1,18 @@
+<?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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="androidx.car">
+</manifest>
diff --git a/car/OWNERS b/car/OWNERS
new file mode 100644
index 0000000..d226975
--- /dev/null
+++ b/car/OWNERS
@@ -0,0 +1 @@
+ajchen@google.com
\ No newline at end of file
diff --git a/car/README.txt b/car/README.txt
new file mode 100644
index 0000000..50a019b
--- /dev/null
+++ b/car/README.txt
@@ -0,0 +1 @@
+Library Project including Car Support UI Components and associated utilities.
diff --git a/car/build.gradle b/car/build.gradle
new file mode 100644
index 0000000..3970df9
--- /dev/null
+++ b/car/build.gradle
@@ -0,0 +1,40 @@
+import static android.support.dependencies.DependenciesKt.*
+import android.support.LibraryGroups
+
+plugins {
+    id("SupportAndroidLibraryPlugin")
+}
+
+dependencies {
+    api project(':appcompat-v7')
+    api project(':cardview-v7')
+    api project(':support-annotations')
+    api project(':support-v4')
+    api project(':recyclerview-v7')
+
+    androidTestImplementation(TEST_RUNNER)
+    androidTestImplementation(ESPRESSO_CORE)
+    androidTestImplementation(ESPRESSO_CONTRIB, libs.exclude_support)
+    androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
+    androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
+}
+
+android {
+    defaultConfig {
+        minSdkVersion 24
+    }
+
+    sourceSets {
+        main.res.srcDirs 'res', 'res-public'
+    }
+}
+
+supportLibrary {
+    name = "Android Car Support UI"
+    publish = false
+    mavenGroup = LibraryGroups.SUPPORT
+    inceptionYear = "2017"
+    description = "Android Car Support UI"
+    java8Library = true
+    legacySourceLocation = true
+}
diff --git a/car/res-public/values/public_attrs.xml b/car/res-public/values/public_attrs.xml
new file mode 100644
index 0000000..5351366
--- /dev/null
+++ b/car/res-public/values/public_attrs.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.
+-->
+
+<!-- Definitions of attributes to be exposed as public. -->
+<resources>
+    <!-- ColumnCardView -->
+    <public type="attr" name="columnSpan" />
+</resources>
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/drawable/car_borderless_button_text_color.xml b/car/res/drawable/car_borderless_button_text_color.xml
new file mode 100644
index 0000000..ff27db5
--- /dev/null
+++ b/car/res/drawable/car_borderless_button_text_color.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.
+-->
+<!-- Default text colors for car buttons when enabled/disabled. -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="@color/car_grey_700" android:state_enabled="false"/>
+    <item android:color="?android:attr/colorPrimary"/>
+</selector>
diff --git a/car/res/drawable/car_button_background.xml b/car/res/drawable/car_button_background.xml
new file mode 100644
index 0000000..1a8995c
--- /dev/null
+++ b/car/res/drawable/car_button_background.xml
@@ -0,0 +1,31 @@
+<?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.
+-->
+<!-- Default background styles for car buttons when enabled/disabled. -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false">
+        <shape android:shape="rectangle">
+            <corners android:radius="@dimen/car_button_radius"/>
+            <solid android:color="@color/car_grey_300"/>
+        </shape>
+    </item>
+    <item>
+        <shape android:shape="rectangle">
+            <corners android:radius="@dimen/car_button_radius"/>
+            <solid android:color="?android:attr/colorPrimary"/>
+        </shape>
+    </item>
+</selector>
diff --git a/car/res/drawable/car_button_text_color.xml b/car/res/drawable/car_button_text_color.xml
new file mode 100644
index 0000000..bb8c681
--- /dev/null
+++ b/car/res/drawable/car_button_text_color.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.
+-->
+<!-- Default text colors for car buttons when enabled/disabled. -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="@color/car_grey_700" android:state_enabled="false"/>
+    <item android:color="@color/car_action1"/>
+</selector>
diff --git a/car/res/drawable/car_drawer_list_item_background.xml b/car/res/drawable/car_drawer_list_item_background.xml
new file mode 100644
index 0000000..c5fc36b
--- /dev/null
+++ b/car/res/drawable/car_drawer_list_item_background.xml
@@ -0,0 +1,22 @@
+<?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.
+  -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="@color/car_card_ripple_background">
+    <item android:id="@android:id/mask">
+        <color android:color="#ffffffff" />
+    </item>
+</ripple>
diff --git a/car/res/drawable/car_pagination_background.xml b/car/res/drawable/car_pagination_background.xml
new file mode 100644
index 0000000..6d3ad3e
--- /dev/null
+++ b/car/res/drawable/car_pagination_background.xml
@@ -0,0 +1,19 @@
+<?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.
+  -->
+<ripple
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="@color/car_card_ripple_background" />
\ No newline at end of file
diff --git a/car/res/drawable/car_pagination_background_day.xml b/car/res/drawable/car_pagination_background_day.xml
new file mode 100644
index 0000000..a4370e9
--- /dev/null
+++ b/car/res/drawable/car_pagination_background_day.xml
@@ -0,0 +1,19 @@
+<?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.
+  -->
+<ripple
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="@color/car_card_ripple_background_dark" />
\ No newline at end of file
diff --git a/car/res/drawable/car_pagination_background_inverse.xml b/car/res/drawable/car_pagination_background_inverse.xml
new file mode 100644
index 0000000..3c07ecf
--- /dev/null
+++ b/car/res/drawable/car_pagination_background_inverse.xml
@@ -0,0 +1,19 @@
+<?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.
+  -->
+<ripple
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="@color/car_card_ripple_background_inverse" />
\ No newline at end of file
diff --git a/car/res/drawable/car_pagination_background_night.xml b/car/res/drawable/car_pagination_background_night.xml
new file mode 100644
index 0000000..c1b03c1
--- /dev/null
+++ b/car/res/drawable/car_pagination_background_night.xml
@@ -0,0 +1,19 @@
+<?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.
+  -->
+<ripple
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="@color/car_card_ripple_background_light" />
\ No newline at end of file
diff --git a/car/res/drawable/ic_down.xml b/car/res/drawable/ic_down.xml
new file mode 100644
index 0000000..c6bb32d
--- /dev/null
+++ b/car/res/drawable/ic_down.xml
@@ -0,0 +1,16 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="76dp"
+        android:height="76dp"
+        android:viewportWidth="76.0"
+        android:viewportHeight="76.0">
+    <path
+        android:pathData="M38,0.96C17.01,0.96 0,17.75 0,38.47C0,59.18 17.01,75.97 38,75.97C58.99,75.97 76,59.18 76,38.47C76,17.75 58.99,0.96 38,0.96M38,3.3C57.64,3.3 73.62,19.08 73.62,38.47C73.62,57.85 57.64,73.63 38,73.63C18.36,73.63 2.38,57.86 2.38,38.47C2.38,19.08 18.36,3.3 38,3.3"
+        android:strokeColor="#00000000"
+        android:fillColor="#212121"
+        android:strokeWidth="1"/>
+    <path
+        android:pathData="M26.63,31.09l11.37,11.08l11.37,-11.08l3.5,3.42l-14.87,14.5l-14.87,-14.5z"
+        android:strokeColor="#00000000"
+        android:fillColor="#212121"
+        android:strokeWidth="1"/>
+</vector>
diff --git a/car/res/drawable/ic_list_view_disable.xml b/car/res/drawable/ic_list_view_disable.xml
new file mode 100644
index 0000000..8649423
--- /dev/null
+++ b/car/res/drawable/ic_list_view_disable.xml
@@ -0,0 +1,33 @@
+<?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="176dp"
+        android:height="176dp"
+        android:viewportWidth="176.0"
+        android:viewportHeight="176.0">
+    <path
+        android:pathData="M88.99,55.55l15.71,15.71l46.13,0l0,-15.71z"
+        android:fillColor="#212121"/>
+    <path
+        android:pathData="M25.19,119.06h66.5v15.71h-66.5z"
+        android:fillColor="#212121"/>
+    <path
+        android:pathData="M114.58,103.35l-15.71,-15.71l-0.12,0l-16.38,-16.38l0.12,0l-15.71,-15.71l-0.12,0l-30.29,-30.29l-11.11,11.11l19.19,19.18l-19.28,0l0,15.71l34.98,0l16.39,16.38l-51.37,0l0,15.71l67.08,0l47.38,47.39l11.11,-11.11l-36.28,-36.28z"
+        android:fillColor="#212121"/>
+    <path
+        android:pathData="M136.79,103.35l14.04,0l0,-15.71l-29.74,0z"
+        android:fillColor="#212121"/>
+</vector>
diff --git a/car/res/drawable/ic_up.xml b/car/res/drawable/ic_up.xml
new file mode 100644
index 0000000..05f69b9
--- /dev/null
+++ b/car/res/drawable/ic_up.xml
@@ -0,0 +1,16 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="76dp"
+        android:height="76dp"
+        android:viewportWidth="76.0"
+        android:viewportHeight="76.0">
+    <path
+        android:pathData="M38,75.04C58.99,75.04 76,58.27 76,37.57C76,16.88 58.99,0.11 38,0.11C17.01,0.11 0,16.88 0,37.57C0,58.27 17.01,75.04 38,75.04M38,72.7C18.36,72.7 2.38,56.94 2.38,37.57C2.38,18.21 18.36,2.45 38,2.45C57.64,2.45 73.62,18.21 73.62,37.57C73.62,56.94 57.64,72.7 38,72.7"
+        android:strokeColor="#00000000"
+        android:fillColor="#212121"
+        android:strokeWidth="1"/>
+    <path
+        android:pathData="M49.37,44.9l-11.37,-11.08l-11.37,11.08l-3.5,-3.42l14.87,-14.5l14.87,14.5z"
+        android:strokeColor="#00000000"
+        android:fillColor="#212121"
+        android:strokeWidth="1"/>
+</vector>
diff --git a/car/res/layout/car_drawer.xml b/car/res/layout/car_drawer.xml
new file mode 100644
index 0000000..c4ce405
--- /dev/null
+++ b/car/res/layout/car_drawer.xml
@@ -0,0 +1,40 @@
+<?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.
+-->
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/drawer_content"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_marginEnd="@dimen/car_drawer_margin_end"
+    android:background="@color/car_card"
+    android:paddingTop="@dimen/car_app_bar_height" >
+
+  <androidx.car.widget.PagedListView
+      android:id="@+id/drawer_list"
+      android:layout_width="match_parent"
+      android:layout_height="match_parent"
+      app:listEndMargin="@dimen/car_drawer_margin_end"
+      app:offsetScrollBar="true" />
+
+  <ProgressBar
+      android:id="@+id/drawer_progress"
+      android:layout_width="@dimen/car_drawer_progress_bar_size"
+      android:layout_height="@dimen/car_drawer_progress_bar_size"
+      android:layout_gravity="center"
+      android:indeterminate="true"
+      android:visibility="gone" />
+</FrameLayout>
diff --git a/car/res/layout/car_drawer_activity.xml b/car/res/layout/car_drawer_activity.xml
new file mode 100644
index 0000000..751ef0d
--- /dev/null
+++ b/car/res/layout/car_drawer_activity.xml
@@ -0,0 +1,40 @@
+<?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.
+-->
+<FrameLayout
+      xmlns:android="http://schemas.android.com/apk/res/android"
+      android:layout_width="match_parent"
+      android:layout_height="match_parent">
+
+    <android.support.v4.widget.DrawerLayout
+        android:id="@+id/drawer_layout"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+      <!-- The main content view. Fragments will be added here. -->
+      <FrameLayout
+          android:id="@+id/content_frame"
+          android:layout_width="match_parent"
+          android:layout_height="match_parent" />
+
+      <include
+          android:layout_width="match_parent"
+          android:layout_height="match_parent"
+          android:layout_gravity="start"
+          layout="@layout/car_drawer" />
+    </android.support.v4.widget.DrawerLayout>
+
+    <include layout="@layout/car_toolbar" />
+</FrameLayout>
diff --git a/car/res/layout/car_drawer_list_item_empty.xml b/car/res/layout/car_drawer_list_item_empty.xml
new file mode 100644
index 0000000..c2e35ac
--- /dev/null
+++ b/car/res/layout/car_drawer_list_item_empty.xml
@@ -0,0 +1,45 @@
+<?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.
+-->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/container"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_marginStart="16dp"
+    android:focusable="false"
+    android:orientation="vertical"
+    android:background="@drawable/car_drawer_list_item_background" >
+    <FrameLayout
+        android:id="@+id/icon_container"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:visibility="visible">
+        <ImageView
+            android:id="@+id/icon"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_horizontal"
+            android:layout_marginTop="48dp"
+            android:layout_marginBottom="22dp" />
+    </FrameLayout>
+    <TextView
+        android:id="@+id/title"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="16dp"
+        android:gravity="center"
+        style="@style/CarBody1" />
+</LinearLayout>
diff --git a/car/res/layout/car_drawer_list_item_normal.xml b/car/res/layout/car_drawer_list_item_normal.xml
new file mode 100644
index 0000000..9136aae
--- /dev/null
+++ b/car/res/layout/car_drawer_list_item_normal.xml
@@ -0,0 +1,59 @@
+<?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.
+-->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="@dimen/car_double_line_list_item_height"
+    android:focusable="true"
+    android:orientation="horizontal"
+    android:background="@drawable/car_drawer_list_item_background" >
+    <ImageView
+        android:id="@+id/icon"
+        android:layout_width="@dimen/car_drawer_list_item_icon_size"
+        android:layout_height="@dimen/car_drawer_list_item_icon_size"
+        android:layout_marginEnd="@dimen/car_drawer_list_item_icon_end_margin"
+        android:layout_gravity="center_vertical"
+        android:scaleType="centerCrop" />
+    <LinearLayout
+        android:id="@+id/text_container"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:layout_gravity="center_vertical"
+        android:orientation="vertical" >
+        <TextView
+            android:id="@+id/title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="@dimen/car_text_vertical_margin"
+            android:maxLines="1"
+            style="@style/CarBody1" />
+        <TextView
+            android:id="@+id/text"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:ellipsize="end"
+            android:maxLines="1"
+            style="@style/CarBody2" />
+    </LinearLayout>
+    <ImageView
+        android:id="@+id/end_icon"
+        android:layout_width="@dimen/car_drawer_list_item_end_icon_size"
+        android:layout_height="@dimen/car_drawer_list_item_end_icon_size"
+        android:scaleType="fitCenter"
+        android:layout_marginEnd="@dimen/car_drawer_list_item_end_margin"
+        android:layout_gravity="center_vertical" />
+</LinearLayout>
diff --git a/car/res/layout/car_drawer_list_item_small.xml b/car/res/layout/car_drawer_list_item_small.xml
new file mode 100644
index 0000000..2818eef
--- /dev/null
+++ b/car/res/layout/car_drawer_list_item_small.xml
@@ -0,0 +1,46 @@
+<?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.
+-->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="@dimen/car_single_line_list_item_height"
+    android:focusable="true"
+    android:orientation="horizontal"
+    android:background="@drawable/car_drawer_list_item_background" >
+    <ImageView
+        android:id="@+id/icon"
+        android:layout_width="@dimen/car_drawer_list_item_small_icon_size"
+        android:layout_height="@dimen/car_drawer_list_item_small_icon_size"
+        android:layout_marginEnd="@dimen/car_drawer_list_item_icon_end_margin"
+        android:layout_gravity="center_vertical"
+        android:scaleType="centerCrop" />
+    <TextView
+        android:id="@+id/title"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:layout_gravity="center_vertical"
+        android:layout_marginBottom="@dimen/car_text_vertical_margin"
+        android:maxLines="1"
+        style="@style/CarBody1" />
+    <ImageView
+        android:id="@+id/end_icon"
+        android:layout_width="@dimen/car_drawer_list_item_end_icon_size"
+        android:layout_height="@dimen/car_drawer_list_item_end_icon_size"
+        android:scaleType="fitCenter"
+        android:layout_marginEnd="@dimen/car_drawer_list_item_end_margin"
+        android:layout_gravity="center_vertical"/>
+</LinearLayout>
diff --git a/car/res/layout/car_paged_list_card.xml b/car/res/layout/car_paged_list_card.xml
new file mode 100644
index 0000000..fe5de89
--- /dev/null
+++ b/car/res/layout/car_paged_list_card.xml
@@ -0,0 +1,28 @@
+<?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.
+  -->
+<android.support.v7.widget.CardView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_height="wrap_content"
+    android:layout_width="match_parent"
+    android:layout_marginBottom="@dimen/car_padding_1"
+    app:cardCornerRadius="@dimen/car_radius_1"
+    app:cardBackgroundColor="@color/car_card">
+
+    <include layout="@layout/car_paged_list_item_content" />
+
+</android.support.v7.widget.CardView>
diff --git a/car/res/layout/car_paged_list_item.xml b/car/res/layout/car_paged_list_item.xml
new file mode 100644
index 0000000..c0861d9
--- /dev/null
+++ b/car/res/layout/car_paged_list_item.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.
+  -->
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_height="wrap_content"
+    android:layout_width="match_parent"
+    android:background="@color/car_card">
+
+    <include layout="@layout/car_paged_list_item_content" />
+
+</FrameLayout>
diff --git a/car/res/layout/car_paged_list_item_content.xml b/car/res/layout/car_paged_list_item_content.xml
new file mode 100644
index 0000000..0e6b809
--- /dev/null
+++ b/car/res/layout/car_paged_list_item_content.xml
@@ -0,0 +1,102 @@
+<?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.
+  -->
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/container"
+    android:layout_width="wrap_content"
+    android:layout_height="match_parent">
+
+    <!-- Primary Action. -->
+    <ImageView
+        android:id="@+id/primary_icon"
+        android:layout_width="@dimen/car_single_line_list_item_height"
+        android:layout_height="@dimen/car_single_line_list_item_height"
+        android:layout_centerVertical="true"/>
+
+    <!-- Text. -->
+    <TextView
+        android:id="@+id/title"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_toStartOf="@id/supplemental_actions"
+        android:singleLine="true"
+        android:ellipsize="end"/>
+    <TextView
+        android:id="@+id/body"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_toStartOf="@id/supplemental_actions"/>
+
+    <!-- Supplemental action(s) - supports either 1 supplemental icon or up to 2 action buttons. -->
+    <LinearLayout
+        android:id="@+id/supplemental_actions"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:layout_alignParentEnd="true"
+        android:layout_marginEnd="@dimen/car_keyline_1"
+        android:layout_centerVertical="true"
+        android:gravity="center_vertical"
+        android:orientation="horizontal">
+        <!-- End icon with divider. -->
+        <View
+            android:id="@+id/supplemental_icon_divider"
+            android:layout_width="@dimen/car_vertical_line_divider_width"
+            android:layout_height="@dimen/car_vertical_line_divider_height"
+            android:layout_marginStart="@dimen/car_padding_4"
+            android:background="@color/car_list_divider"/>
+        <ImageView
+            android:id="@+id/supplemental_icon"
+            android:layout_width="@dimen/car_primary_icon_size"
+            android:layout_height="@dimen/car_primary_icon_size"
+            android:layout_marginStart="@dimen/car_padding_4"
+            android:scaleType="fitCenter"/>
+
+        <!-- Up to 2 action buttons with dividers. -->
+        <View
+            android:id="@+id/action2_divider"
+            android:layout_width="@dimen/car_vertical_line_divider_width"
+            android:layout_height="@dimen/car_vertical_line_divider_height"
+            android:layout_marginStart="@dimen/car_padding_4"
+            android:background="@color/car_list_divider"/>
+        <Button
+            android:id="@+id/action2"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="@dimen/car_padding_4"
+            android:ellipsize="end"
+            android:maxLength="@integer/car_borderless_button_text_length_limit"
+            android:maxLines="1"
+            android:background="@color/car_card"
+            style="@style/CarButton.Borderless"/>
+        <View
+            android:id="@+id/action1_divider"
+            android:layout_width="@dimen/car_vertical_line_divider_width"
+            android:layout_height="@dimen/car_vertical_line_divider_height"
+            android:layout_marginStart="@dimen/car_padding_4"
+            android:background="@color/car_list_divider"/>
+        <Button
+            android:id="@+id/action1"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="@dimen/car_padding_4"
+            android:ellipsize="end"
+            android:maxLength="@integer/car_borderless_button_text_length_limit"
+            android:maxLines="1"
+            android:background="@color/car_card"
+            style="@style/CarButton.Borderless"/>
+    </LinearLayout>
+</RelativeLayout>
diff --git a/car/res/layout/car_paged_recycler_view.xml b/car/res/layout/car_paged_recycler_view.xml
new file mode 100644
index 0000000..d3ca4a3
--- /dev/null
+++ b/car/res/layout/car_paged_recycler_view.xml
@@ -0,0 +1,36 @@
+<?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.
+  -->
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <androidx.car.widget.CarRecyclerView
+        android:id="@+id/recycler_view"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
+    <!-- Putting this as the last child so that it can intercept any touch events on the
+         scroll buttons. -->
+    <androidx.car.widget.PagedScrollBarView
+        android:id="@+id/paged_scroll_view"
+        android:layout_width="@dimen/car_margin"
+        android:layout_height="match_parent"
+        android:paddingBottom="@dimen/car_scroll_bar_padding"
+        android:paddingTop="@dimen/car_scroll_bar_padding"
+        android:visibility="invisible" />
+</FrameLayout>
diff --git a/car/res/layout/car_paged_scrollbar_buttons.xml b/car/res/layout/car_paged_scrollbar_buttons.xml
new file mode 100644
index 0000000..d49b532
--- /dev/null
+++ b/car/res/layout/car_paged_scrollbar_buttons.xml
@@ -0,0 +1,60 @@
+<?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.
+  -->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_gravity="center"
+    android:gravity="center"
+    android:orientation="vertical">
+
+    <ImageView
+        android:id="@+id/page_up"
+        android:layout_width="@dimen/car_scroll_bar_button_size"
+        android:layout_height="@dimen/car_scroll_bar_button_size"
+        android:background="@drawable/car_pagination_background"
+        android:focusable="false"
+        android:hapticFeedbackEnabled="false"
+        android:scaleType="center"
+        android:src="@drawable/ic_up" />
+
+    <FrameLayout
+        android:id="@+id/filler"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1"
+        android:layout_marginBottom="@dimen/car_scroll_bar_thumb_margin"
+        android:layout_marginTop="@dimen/car_scroll_bar_thumb_margin" >
+
+        <ImageView
+            android:id="@+id/scrollbar_thumb"
+            android:layout_width="@dimen/car_scroll_bar_thumb_width"
+            android:layout_height="0dp"
+            android:layout_gravity="center_horizontal"
+            android:background="@color/car_scrollbar_thumb" />
+    </FrameLayout>
+
+    <ImageView
+        android:id="@+id/page_down"
+        android:layout_width="@dimen/car_scroll_bar_button_size"
+        android:layout_height="@dimen/car_scroll_bar_button_size"
+        android:background="@drawable/car_pagination_background"
+        android:focusable="false"
+        android:hapticFeedbackEnabled="false"
+        android:scaleType="center"
+        android:src="@drawable/ic_down" />
+</LinearLayout>
diff --git a/car/res/layout/car_toolbar.xml b/car/res/layout/car_toolbar.xml
new file mode 100644
index 0000000..88f05e3
--- /dev/null
+++ b/car/res/layout/car_toolbar.xml
@@ -0,0 +1,26 @@
+<?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.
+-->
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="@dimen/car_app_bar_height">
+    <android.support.v7.widget.Toolbar
+        android:id="@+id/car_toolbar"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        style="@style/CarToolbarTheme" />
+</FrameLayout>
diff --git a/car/res/values-h1752dp/dimens.xml b/car/res/values-h1752dp/dimens.xml
new file mode 100644
index 0000000..4b6a23d
--- /dev/null
+++ b/car/res/values-h1752dp/dimens.xml
@@ -0,0 +1,53 @@
+<?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>
+    <!-- Car Component Dimensions -->
+    <!-- Type Sizings -->
+    <dimen name="car_title2_size">40sp</dimen>
+    <dimen name="car_headline1_size">56sp</dimen>
+    <dimen name="car_headline2_size">50sp</dimen>
+    <dimen name="car_body1_size">40sp</dimen>
+    <dimen name="car_body2_size">32sp</dimen>
+    <dimen name="car_action1_size">32sp</dimen>
+
+    <!-- Icons and Buttons -->
+    <!-- Icons -->
+    <dimen name="car_primary_icon_size">56dp</dimen>
+    <dimen name="car_secondary_icon_size">36dp</dimen>
+
+    <!-- Avatars -->
+    <dimen name="car_avatar_size">96dp</dimen>
+
+    <!-- Minimum touch target size. -->
+    <dimen name="car_touch_target_size">96dp</dimen>
+
+    <!-- Application Bar -->
+    <dimen name="car_app_bar_height">112dp</dimen>
+
+    <!-- List Items -->
+    <dimen name="car_single_line_list_item_height">128dp</dimen>
+    <dimen name="car_double_line_list_item_height">128dp</dimen>
+
+    <!-- Cards -->
+    <dimen name="car_card_header_height">96dp</dimen>
+    <dimen name="car_card_action_bar_height">96dp</dimen>
+
+    <!-- Slide Up Menu -->
+    <dimen name="car_slide_up_menu_initial_height">128dp</dimen>
+
+    <!-- Sub Header -->
+    <dimen name="car_sub_header_height">96dp</dimen>
+</resources>
diff --git a/car/res/values-h684dp/dimens.xml b/car/res/values-h684dp/dimens.xml
new file mode 100644
index 0000000..039d377
--- /dev/null
+++ b/car/res/values-h684dp/dimens.xml
@@ -0,0 +1,41 @@
+<?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>
+    <!-- Car Component Dimensions -->
+    <!-- Application Bar -->
+    <dimen name="car_app_bar_height">96dp</dimen>
+
+    <!-- List Items -->
+    <dimen name="car_single_line_list_item_height">116dp</dimen>
+    <dimen name="car_double_line_list_item_height">116dp</dimen>
+
+    <!-- Slide Up Menu -->
+    <dimen name="car_slide_up_menu_initial_height">116dp</dimen>
+
+    <!-- Scroll Bar -->
+    <dimen name="car_scroll_bar_padding">@dimen/car_padding_4</dimen>
+
+    <!-- Scroll Bar Thumb -->
+    <dimen name="car_scroll_bar_thumb_margin">@dimen/car_padding_2</dimen>
+
+    <!-- Scroll Bar Buttons -->
+    <dimen name="car_scroll_bar_button_size">76dp</dimen>
+
+    <!-- Drawer Dimensions -->
+    <dimen name="car_drawer_list_item_icon_size">108dp</dimen>
+    <dimen name="car_drawer_list_item_small_icon_size">56dp</dimen>
+    <dimen name="car_drawer_list_item_end_icon_size">56dp</dimen>
+</resources>
diff --git a/car/res/values-night/colors.xml b/car/res/values-night/colors.xml
new file mode 100644
index 0000000..2ca5b02
--- /dev/null
+++ b/car/res/values-night/colors.xml
@@ -0,0 +1,32 @@
+<?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>
+    <color name="car_title">@color/car_title_light</color>
+    <color name="car_body1">@color/car_body1_light</color>
+    <color name="car_body2">@color/car_body2_light</color>
+
+    <color name="car_tint">@color/car_tint_light</color>
+    <color name="car_tint_inverse">@color/car_tint_dark</color>
+
+    <color name="car_card">@color/car_card_dark</color>
+    <color name="car_card_ripple_background">@color/car_card_ripple_background_light</color>
+    <color name="car_card_ripple_background_inverse">@color/car_card_ripple_background_dark</color>
+
+    <color name="car_list_divider">@color/car_list_divider_dark</color>
+    <color name="car_scrollbar_thumb">@color/car_scrollbar_thumb_light</color>
+    <color name="car_scrollbar_thumb_inverse">@color/car_scrollbar_thumb_dark</color>
+</resources>
diff --git a/car/res/values-w1280dp/dimens.xml b/car/res/values-w1280dp/dimens.xml
new file mode 100644
index 0000000..418e51f
--- /dev/null
+++ b/car/res/values-w1280dp/dimens.xml
@@ -0,0 +1,24 @@
+<?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>
+    <!-- Framework -->
+    <!-- Margin -->
+    <dimen name="car_margin">148dp</dimen>
+
+    <!-- Keylines -->
+    <dimen name="car_keyline_4">182dp</dimen>
+    <dimen name="car_keyline_4_neg">-182dp</dimen>
+</resources>
diff --git a/car/res/values-w1280dp/integers.xml b/car/res/values-w1280dp/integers.xml
new file mode 100644
index 0000000..62fcf37
--- /dev/null
+++ b/car/res/values-w1280dp/integers.xml
@@ -0,0 +1,23 @@
+<?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>
+    <!-- Application Components -->
+    <!-- Cards -->
+    <integer name="column_card_default_column_span">8</integer>
+
+    <!-- Dialogs -->
+    <integer name="car_dialog_column_number">8</integer>
+</resources>
diff --git a/car/res/values-w1920dp/dimens.xml b/car/res/values-w1920dp/dimens.xml
new file mode 100644
index 0000000..b02ec00
--- /dev/null
+++ b/car/res/values-w1920dp/dimens.xml
@@ -0,0 +1,29 @@
+<?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>
+    <!-- Framework -->
+    <!-- Margin -->
+    <dimen name="car_margin">192dp</dimen>
+
+    <!-- Gutters -->
+    <dimen name="car_gutter_size">32dp</dimen>
+
+    <!-- Keylines -->
+    <dimen name="car_keyline_1">48dp</dimen>
+    <dimen name="car_keyline_3">152dp</dimen>
+    <dimen name="car_keyline_1_neg">-48dp</dimen>
+    <dimen name="car_keyline_3_neg">-152dp</dimen>
+</resources>
diff --git a/car/res/values-w1920dp/integers.xml b/car/res/values-w1920dp/integers.xml
new file mode 100644
index 0000000..6519af5
--- /dev/null
+++ b/car/res/values-w1920dp/integers.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.
+-->
+<resources>
+    <!-- Framework -->
+    <!-- Columns -->
+    <integer name="car_column_number">16</integer>
+</resources>
diff --git a/car/res/values-w690dp/dimens.xml b/car/res/values-w690dp/dimens.xml
new file mode 100644
index 0000000..2d43ac8
--- /dev/null
+++ b/car/res/values-w690dp/dimens.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.
+-->
+<resources>
+    <!-- Framework -->
+    <!-- Margin -->
+    <dimen name="car_margin">112dp</dimen>
+</resources>
diff --git a/car/res/values-w690dp/integers.xml b/car/res/values-w690dp/integers.xml
new file mode 100644
index 0000000..0eb5837
--- /dev/null
+++ b/car/res/values-w690dp/integers.xml
@@ -0,0 +1,30 @@
+<?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>
+    <!-- Framework -->
+    <!-- Columns -->
+    <integer name="car_column_number">12</integer>
+
+    <!-- Application Components -->
+    <!-- Cards -->
+    <integer name="column_card_default_column_span">12</integer>
+
+    <!-- Dialogs -->
+    <integer name="car_dialog_column_number">10</integer>
+
+    <!-- Slide Up Menu -->
+    <integer name="car_slide_up_menu_column_number">12</integer>
+</resources>
diff --git a/car/res/values-w930dp/dimens.xml b/car/res/values-w930dp/dimens.xml
new file mode 100644
index 0000000..6d7714e
--- /dev/null
+++ b/car/res/values-w930dp/dimens.xml
@@ -0,0 +1,30 @@
+<?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>
+    <!-- Framework -->
+    <!-- Gutters -->
+    <dimen name="car_gutter_size">24dp</dimen>
+
+    <!-- Keylines -->
+    <dimen name="car_keyline_1">32dp</dimen>
+    <dimen name="car_keyline_2">108dp</dimen>
+    <dimen name="car_keyline_3">128dp</dimen>
+    <dimen name="car_keyline_4">168dp</dimen>
+    <dimen name="car_keyline_1_neg">-32dp</dimen>
+    <dimen name="car_keyline_2_neg">-108dp</dimen>
+    <dimen name="car_keyline_3_neg">-128dp</dimen>
+    <dimen name="car_keyline_4_neg">-168dp</dimen>
+</resources>
diff --git a/car/res/values-w930dp/integers.xml b/car/res/values-w930dp/integers.xml
new file mode 100644
index 0000000..60ddfa1
--- /dev/null
+++ b/car/res/values-w930dp/integers.xml
@@ -0,0 +1,23 @@
+<?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>
+    <!-- Application Components -->
+    <!-- Cards -->
+    <integer name="column_card_default_column_span">10</integer>
+
+    <!-- Dialogs -->
+    <integer name="car_dialog_column_number">10</integer>
+</resources>
diff --git a/car/res/values/attrs.xml b/car/res/values/attrs.xml
new file mode 100644
index 0000000..01ed95f
--- /dev/null
+++ b/car/res/values/attrs.xml
@@ -0,0 +1,93 @@
+<?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>
+    <!-- The configurable attributes for a ColumnCardView. -->
+    <declare-styleable name="ColumnCardView">
+        <!-- The number of columns that this ColumnCardView should span across. This value will
+             determine the width of the card. -->
+        <attr name="columnSpan" format="integer" />
+    </declare-styleable>
+
+    <!-- The configurable attributes in PagedListView. -->
+    <declare-styleable name="PagedListView">
+        <!-- Fade duration in ms -->
+        <attr name="fadeLastItem" format="boolean" />
+        <!-- Set to true/false to offset rows as they slide off screen. Defaults to true -->
+        <attr name="offsetRows" format="boolean" />
+        <!-- Whether or not to offset the list view by the width of scroll bar. Setting this to
+             true will ensure that any views within the list will not overlap the scroll bar.
+             Deprecated: use gutter instead. If gutter is specified, this value is ignored.-->
+        <attr name="offsetScrollBar" format="boolean" />
+        <!-- Whether to include a gutter to the start, end or both sides of the list view items.
+             The gutter width will be the width of the scrollbar, and by default will be set to
+             both. -->
+        <attr name="gutter" format="enum">
+            <!-- No gutter on either side, the list view items will extend the full width of the
+                 PagedListView. -->
+            <enum name="none" value="0" />
+            <!-- Include a gutter on the start side only (i.e. the side with the scrollbar). -->
+            <enum name="start" value="1" />
+            <!-- Include a gutter on the end side only (i.e. the opposite side to the
+                 scrollbar). -->
+            <enum name="end" value="2" />
+            <!-- Include a gutter on both sides of the list view items. -->
+            <enum name="both" value="3" />
+        </attr>
+        <!-- Whether to display the scrollbar or not. Defaults to true. -->
+        <attr name="scrollBarEnabled" format="boolean" />
+        <!-- Whether or not to show a diving line between each item of the list. -->
+        <attr name="showPagedListViewDivider" format="boolean" />
+        <!-- An optional id that specifies a child View whose starting edge will be used to
+             determine the start position of the dividing line. -->
+        <attr name="alignDividerStartTo" format="reference" />
+        <!-- An optional id that specifies a child View whose ending edge will be used to
+             determine the end position of the dividing line. -->
+        <attr name="alignDividerEndTo" format="reference" />
+        <!-- A starting margin before the drawing of the dividing line. This margin will be an
+             offset from the view specified by "alignDividerStartTo" if given. -->
+        <attr name="dividerStartMargin" format="dimension" />
+        <!-- The width of the margin on the right side of the list.
+             Deprecated: use gutter instead. If gutter is specified, this value is ignored.-->
+        <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
+       drawer. -->
+    <declare-styleable name="DrawerArrowDrawable">
+        <!-- The color of the arrow. -->
+        <attr name="carArrowColor" format="color"/>
+        <!-- Whether the arrow will animate when switches directions. -->
+        <attr name="carArrowAnimate" format="boolean"/>
+        <!-- The size of the arrow's bounding box. -->
+        <attr name="carArrowSize" format="dimension"/>
+        <!-- The length of the top and bottom bars that merge to form the point of the arrow. -->
+        <attr name="carArrowHeadLength" format="dimension"/>
+        <!-- The length of arrow shaft. -->
+        <attr name="carArrowShaftLength" format="dimension"/>
+        <!-- The thickness of each of the bars that form the arrow. -->
+        <attr name="carArrowThickness" format="dimension"/>
+        <!-- The spacing between the menu bars (i.e. the "hamburger" icon). -->
+        <attr name="carMenuBarSpacing" format="dimension"/>
+        <!-- The size of the menu bars (i.e. the "hamburger" icon). -->
+        <attr name="carMenuBarThickness" format="dimension"/>
+    </declare-styleable>
+</resources>
diff --git a/car/res/values/colors.xml b/car/res/values/colors.xml
new file mode 100644
index 0000000..00c6cf9
--- /dev/null
+++ b/car/res/values/colors.xml
@@ -0,0 +1,160 @@
+<?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>
+    <!-- These colors are from
+         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>
+    <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. -->
+    <color name="car_title_light">@color/car_grey_100</color>
+    <color name="car_title_dark">@color/car_grey_900</color>
+    <color name="car_title">@color/car_title_dark</color>
+
+    <color name="car_headline1_light">@color/car_grey_100</color>
+    <color name="car_headline1_dark">@color/car_grey_800</color>
+    <color name="car_headline1">@color/car_headline1_dark</color>
+
+    <color name="car_headline2_light">@color/car_grey_100</color>
+    <color name="car_headline2_dark">@color/car_grey_900</color>
+    <color name="car_headline2">@color/car_headline2_dark</color>
+
+    <color name="car_headline3_light">@android:color/white</color>
+    <color name="car_headline3_dark">@color/car_grey_900</color>
+    <color name="car_headline3">@color/car_headline3_dark</color>
+
+    <color name="car_headline4_light">@android:color/white</color>
+    <color name="car_headline4_dark">@android:color/black</color>
+    <color name="car_headline4">@color/car_headline4_dark</color>
+
+    <color name="car_body1_light">@color/car_grey_100</color>
+    <color name="car_body1_dark">@color/car_grey_900</color>
+    <color name="car_body1">@color/car_body1_dark</color>
+
+    <color name="car_body2_light">@color/car_grey_300</color>
+    <color name="car_body2_dark">@color/car_grey_650</color>
+    <color name="car_body2">@color/car_body2_dark</color>
+
+    <color name="car_body3_light">@android:color/white</color>
+    <color name="car_body3_dark">@android:color/black</color>
+    <color name="car_body3">@color/car_body3_dark</color>
+
+    <color name="car_body4_light">@android:color/white</color>
+    <color name="car_body4_dark">@android:color/black</color>
+    <color name="car_body4">@color/car_body4_dark</color>
+
+    <color name="car_action1_light">@color/car_grey_900</color>
+    <color name="car_action1_dark">@color/car_grey_50</color>
+    <color name="car_action1">@color/car_action1_dark</color>
+
+    <!-- The tinting colors to create a light- and dark-colored icon respectively. -->
+    <color name="car_tint_light">@color/car_grey_50</color>
+    <color name="car_tint_dark">@color/car_grey_900</color>
+
+    <!-- The tinting color for an icon. This icon is assumed to be on a light background. -->
+    <color name="car_tint">@color/car_tint_dark</color>
+
+    <!-- An inverted tinting from car_tint. -->
+    <color name="car_tint_inverse">@color/car_tint_light</color>
+
+    <!-- The color of the divider. The color here is a lighter shade. -->
+    <color name="car_list_divider_light">#1fffffff</color>
+
+    <!-- The color of the divider. The color here is a darker shade. -->
+    <color name="car_list_divider_dark">#1f000000</color>
+
+    <!-- The color of the dividers in the list. This color is assumed to be on a light colored
+         view. -->
+    <color name="car_list_divider">@color/car_list_divider_dark</color>
+
+    <!-- A light and dark colored card. -->
+    <color name="car_card_light">@color/car_grey_50</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>
+
+    <!-- The ripple colors. The "dark" and "light" designation here refers to the color of the
+         ripple  itself. -->
+    <color name="car_card_ripple_background_dark">#8F000000</color>
+    <color name="car_card_ripple_background_light">#27ffffff</color>
+
+    <!-- The ripple color for a light colored card. -->
+    <color name="car_card_ripple_background">@color/car_card_ripple_background_dark</color>
+
+    <!-- The ripple color for a dark-colored card. This color is the opposite of
+         car_card_ripple_background. -->
+    <color name="car_card_ripple_background_inverse">@color/car_card_ripple_background_light</color>
+
+    <!-- The top margin before the start of content in an application. -->
+    <dimen name="app_header_height">96dp</dimen>
+
+    <!-- The lighter and darker color for the scrollbar thumb. -->
+    <color name="car_scrollbar_thumb_light">#99ffffff</color>
+    <color name="car_scrollbar_thumb_dark">#7f0b0f12</color>
+
+    <!-- The color of the scroll bar indicator in the PagedListView. This color is assumed to be on
+         a light-colored background. -->
+    <color name="car_scrollbar_thumb">@color/car_scrollbar_thumb_dark</color>
+
+    <!-- The inverted color of the scroll bar indicator. This color is always the opposite of
+         car_scrollbar_thumb. -->
+    <color name="car_scrollbar_thumb_inverse">@color/car_scrollbar_thumb_light</color>
+
+    <!-- Misc colors -->
+    <color name="car_highlight_light">@color/car_teal_700</color>
+    <color name="car_highlight_dark">@color/car_teal_200</color>
+    <color name="car_highlight">@color/car_highlight_light</color>
+</resources>
diff --git a/car/res/values/dimens.xml b/car/res/values/dimens.xml
new file mode 100644
index 0000000..8e8621c
--- /dev/null
+++ b/car/res/values/dimens.xml
@@ -0,0 +1,179 @@
+<?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>
+    <!-- Framework -->
+    <!-- Margin -->
+    <dimen name="car_margin">20dp</dimen>
+
+    <!-- Gutters -->
+    <dimen name="car_gutter_size">16dp</dimen>
+
+    <!-- Keylines -->
+    <dimen name="car_keyline_1">24dp</dimen>
+    <dimen name="car_keyline_2">96dp</dimen>
+    <dimen name="car_keyline_3">112dp</dimen>
+    <dimen name="car_keyline_4">148dp</dimen>
+    <dimen name="car_keyline_1_neg">-24dp</dimen>
+    <dimen name="car_keyline_2_neg">-96dp</dimen>
+    <dimen name="car_keyline_3_neg">-112dp</dimen>
+    <dimen name="car_keyline_4_neg">-148dp</dimen>
+
+    <!-- Paddings -->
+    <dimen name="car_padding_1">4dp</dimen>
+    <dimen name="car_padding_2">10dp</dimen>
+    <dimen name="car_padding_3">16dp</dimen>
+    <dimen name="car_padding_4">28dp</dimen>
+    <dimen name="car_padding_5">32dp</dimen>
+
+    <!-- Radii -->
+    <dimen name="car_radius_1">4dp</dimen>
+    <dimen name="car_radius_2">8dp</dimen>
+    <dimen name="car_radius_3">16dp</dimen>
+    <dimen name="car_radius_5">100dp</dimen>
+
+    <!-- Car Component Dimensions -->
+    <!-- Type Sizings -->
+    <dimen name="car_title_size">32sp</dimen>
+    <dimen name="car_title2_size">32sp</dimen>
+    <dimen name="car_headline1_size">45sp</dimen>
+    <dimen name="car_headline2_size">36sp</dimen>
+    <dimen name="car_headline3_size">24sp</dimen>
+    <dimen name="car_headline4_size">20sp</dimen>
+    <dimen name="car_body1_size">32sp</dimen>
+    <dimen name="car_body2_size">26sp</dimen>
+    <dimen name="car_body3_size">16sp</dimen>
+    <dimen name="car_body4_size">14sp</dimen>
+    <dimen name="car_body5_size">18sp</dimen>
+    <dimen name="car_action1_size">26sp</dimen>
+
+    <!-- Icons and Buttons -->
+    <!-- Icons -->
+    <dimen name="car_primary_icon_size">44dp</dimen>
+    <dimen name="car_secondary_icon_size">24dp</dimen>
+
+    <!-- Avatars -->
+    <dimen name="car_avatar_size">56dp</dimen>
+
+    <!-- Minimum touch target size. -->
+    <dimen name="car_touch_target_size">76dp</dimen>
+
+    <!-- Buttons -->
+    <dimen name="car_button_height">64dp</dimen>
+    <dimen name="car_button_min_width">158dp</dimen>
+    <dimen name="car_button_horizontal_padding">@dimen/car_padding_4</dimen>
+    <dimen name="car_borderless_button_horizontal_padding">0dp</dimen>
+    <dimen name="car_button_radius">@dimen/car_radius_1</dimen>
+
+    <!-- Application Bar -->
+    <dimen name="car_app_bar_height">80dp</dimen>
+
+    <!-- Action Bars -->
+    <dimen name="car_action_bar_height">128dp</dimen>
+    <dimen name="car_secondary_single_action_bar_height">@dimen/car_action_bar_height</dimen>
+    <dimen name="car_secondary_double_action_bar_height">256dp</dimen>
+
+    <!-- Lists -->
+    <dimen name="car_single_line_list_item_height">76dp</dimen>
+    <dimen name="car_double_line_list_item_height">96dp</dimen>
+    <dimen name="car_list_divider_height">1dp</dimen>
+    <!-- The height of a vertical line divider. -->
+    <dimen name="car_vertical_line_divider_height">60dp</dimen>
+    <dimen name="car_vertical_line_divider_width">1dp</dimen>
+
+    <!-- Cards -->
+    <dimen name="car_card_header_height">76dp</dimen>
+    <dimen name="car_card_action_bar_height">76dp</dimen>
+
+    <!-- Dialogs -->
+    <dimen name="car_dialog_header_height">@dimen/car_card_header_height</dimen>
+    <dimen name="car_dialog_action_bar_height">@dimen/car_card_action_bar_height</dimen>
+
+    <!-- Slide Up Menu -->
+    <dimen name="car_slide_up_menu_initial_height">76dp</dimen>
+
+    <!-- Slide Down Menu -->
+    <dimen name="car_slide_down_menu_initial_height">@dimen/car_slide_up_menu_initial_height</dimen>
+
+    <!-- Sub Header -->
+    <dimen name="car_sub_header_height">76dp</dimen>
+
+    <!-- Slider -->
+    <dimen name="car_slider_height">6dp</dimen>
+    <dimen name="car_slider_knob_size">20dp</dimen>
+
+    <!-- Scroll Bar -->
+    <dimen name="car_scroll_bar_padding">@dimen/car_padding_2</dimen>
+
+    <!-- Scroll Bar Thumb -->
+    <dimen name="car_scroll_bar_thumb_width">@dimen/car_slider_height</dimen>
+    <dimen name="car_min_scroll_bar_thumb_height">48dp</dimen>
+    <dimen name="car_max_scroll_bar_thumb_height">128dp</dimen>
+    <dimen name="car_scroll_bar_thumb_margin">@dimen/car_padding_1</dimen>
+
+    <!-- Scroll Bar and Alpha Jump Buttons -->
+    <dimen name="car_scroll_bar_button_size">56dp</dimen>
+    <dimen name="car_alpha_jump_button_size">@dimen/car_scroll_bar_button_size</dimen>
+
+    <!-- Progress Bar -->
+    <dimen name="car_progress_bar_height">@dimen/car_slider_height</dimen>
+
+    <!-- Text Input -->
+    <dimen name="car_text_input_line_height">2dp</dimen>
+
+    <!-- PagedListView Dimensions -->
+    <!-- Sample row height used for scroll bar calculations in the off chance that a view hasn't
+        been measured. It's highly unlikely that this value will actually be used for more than
+        a frame max. The sample row is a 96dp card + 16dp margin on either side. -->
+    <dimen name="car_sample_row_height">128dp</dimen>
+
+    <!-- The amount of space the LayoutManager will make sure the last item on the screen is
+         peeking before scrolling down -->
+    <dimen name="car_last_card_peek_amount">16dp</dimen>
+
+    <!-- Drawer Dimensions -->
+    <!-- Size of progress-bar in Drawer -->
+    <dimen name="car_drawer_progress_bar_size">48dp</dimen>
+
+    <!-- The ending margin of the drawer. Is is the amount that the navigation drawer does not
+       cover the screen. -->
+    <dimen name="car_drawer_margin_end">96dp</dimen>
+
+    <!-- Dimensions of the back arrow in the drawer. -->
+    <dimen name="car_arrow_size">96dp</dimen>
+    <dimen name="car_arrow_thickness">3dp</dimen>
+    <dimen name="car_arrow_shaft_length">34dp</dimen>
+    <dimen name="car_arrow_head_length">18dp</dimen>
+    <dimen name="car_menu_bar_spacing">6dp</dimen>
+    <dimen name="car_menu_bar_length">40dp</dimen>
+
+    <!-- The size of the starting icon. -->
+    <dimen name="car_drawer_list_item_icon_size">64dp</dimen>
+
+    <!-- The margin after the starting icon. -->
+    <dimen name="car_drawer_list_item_icon_end_margin">32dp</dimen>
+
+    <!-- The ending margin on a list view. -->
+    <dimen name="car_drawer_list_item_end_margin">32dp</dimen>
+
+    <!-- The size of the starting icon in a small list item.-->
+    <dimen name="car_drawer_list_item_small_icon_size">56dp</dimen>
+
+    <!-- The size of the ending icon in a list item. -->
+    <dimen name="car_drawer_list_item_end_icon_size">56dp</dimen>
+
+    <!-- The margin between text is lies on top of each other. -->
+    <dimen name="car_text_vertical_margin">2dp</dimen>
+</resources>
diff --git a/car/res/values/integers.xml b/car/res/values/integers.xml
new file mode 100644
index 0000000..6352d7c
--- /dev/null
+++ b/car/res/values/integers.xml
@@ -0,0 +1,41 @@
+<?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>
+    <!-- Framework -->
+    <!-- Columns -->
+    <integer name="car_column_number">4</integer>
+
+    <!-- Application Components -->
+    <!-- Action Bar -->
+    <integer name="action_bar_column_number">@integer/car_column_number</integer>
+
+    <!-- Cards -->
+    <integer name="column_card_default_column_span">4</integer>
+
+    <!-- Dialogs -->
+    <integer name="car_dialog_column_number">10</integer>
+
+    <!-- Slide Up Menu -->
+    <integer name="car_slide_up_menu_column_number">4</integer>
+
+    <!-- The length limit of body text in a paged list item. String longer than this limit should be
+         truncated. -->
+    <integer name="car_list_item_text_length_limit">120</integer>
+
+    <!-- The length limit of text in a borderless button. String longer than this limit should be
+         truncated. -->
+    <integer name="car_borderless_button_text_length_limit">20</integer>
+</resources>
diff --git a/car/res/values/strings.xml b/car/res/values/strings.xml
new file mode 100644
index 0000000..1fb4cf4
--- /dev/null
+++ b/car/res/values/strings.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.
+-->
+<resources>
+    <!-- NOTE: Although these strings won't really be used for accessibility
+         in an auto context, integration tests will use them to open/close
+         drawer. See:
+         google_testing/integration/libraries/app-helpers/first-party/auto/
+         -->
+    <string name="car_drawer_open" translatable="false">Open drawer</string>
+    <string name="car_drawer_close" translatable="false">Close drawer</string>
+    <string name="ellipsis" translatable="false">&#8230;</string>
+</resources>
diff --git a/car/res/values/styles.xml b/car/res/values/styles.xml
new file mode 100644
index 0000000..d84f4c8
--- /dev/null
+++ b/car/res/values/styles.xml
@@ -0,0 +1,135 @@
+<?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>
+    <!-- The styling for title text. The color of this text changes based on day/night mode. -->
+    <style name="CarTitle">
+        <item name="android:textStyle">normal</item>
+        <item name="android:textSize">@dimen/car_title_size</item>
+        <item name="android:textColor">@color/car_title</item>
+    </style>
+
+    <!-- Title text that is permanently a dark color. -->
+    <style name="CarTitle.Dark">
+        <item name="android:textColor">@color/car_title_dark</item>
+    </style>
+
+    <!-- Title text that is permanently a light color. -->
+    <style name="CarTitle.Light">
+        <item name="android:textColor">@color/car_title_light</item>
+    </style>
+
+    <!-- The styling for the main headline text. The color of this text changes based on the
+         day/night mode. -->
+    <style name="CarHeadline1">
+        <item name="android:textStyle">normal</item>
+        <item name="android:textSize">@dimen/car_headline1_size</item>
+        <item name="android:textColor">@color/car_headline1</item>
+    </style>
+
+    <!-- The styling for a sub-headline text. The color of this text changes based on the
+         day/night mode. -->
+    <style name="CarHeadline2">
+        <item name="android:textStyle">normal</item>
+        <item name="android:textSize">@dimen/car_headline2_size</item>
+        <item name="android:textColor">@color/car_headline2</item>
+    </style>
+
+    <!-- The styling for a smaller alternate headline text. The color of this text changes based on
+         the day/night mode. -->
+    <style name="CarHeadline3">
+        <item name="android:textStyle">normal</item>
+        <item name="android:textSize">@dimen/car_headline3_size</item>
+        <item name="android:textColor">@color/car_headline3</item>
+    </style>
+
+    <!-- The styling for the smallest headline text. The color of this text changes based on the
+         day/night mode. -->
+    <style name="CarHeadline4">
+        <item name="android:textStyle">normal</item>
+        <item name="android:textSize">@dimen/car_headline4_size</item>
+        <item name="android:textColor">@color/car_headline4</item>
+    </style>
+
+    <!-- The styling for body text. The color of this text changes based on the day/night mode. -->
+    <style name="CarBody1">
+        <item name="android:textStyle">normal</item>
+        <item name="android:textSize">@dimen/car_body1_size</item>
+        <item name="android:textColor">@color/car_body1</item>
+    </style>
+
+    <!-- An alternate styling for body text that is both a different color and size than
+         CarBody1. -->
+    <style name="CarBody2">
+        <item name="android:textStyle">normal</item>
+        <item name="android:textSize">@dimen/car_body2_size</item>
+        <item name="android:textColor">@color/car_body2</item>
+    </style>
+
+    <!-- A smaller styling for body text. The color of this text changes based on the day/night
+         mode. -->
+    <style name="CarBody3">
+        <item name="android:textStyle">normal</item>
+        <item name="android:textSize">@dimen/car_body3_size</item>
+        <item name="android:textColor">@color/car_body3</item>
+    </style>
+
+    <!-- The smallest styling for body text. The color of this text changes based on the day/night
+         mode. -->
+    <style name="CarBody4">
+        <item name="android:textStyle">normal</item>
+        <item name="android:textSize">@dimen/car_body4_size</item>
+        <item name="android:textColor">@color/car_body4</item>
+    </style>
+
+    <!-- The style for the menu bar (i.e. hamburger) and back arrow in the navigation drawer. -->
+    <style name="DrawerArrowStyle" parent="Widget.AppCompat.DrawerArrowToggle">
+        <item name="color">@color/car_title_light</item>
+        <item name="spinBars">true</item>
+        <item name="barLength">@dimen/car_menu_bar_length</item>
+        <item name="thickness">@dimen/car_arrow_thickness</item>
+        <item name="gapBetweenBars">@dimen/car_menu_bar_spacing</item>
+        <item name="arrowShaftLength">@dimen/car_arrow_shaft_length</item>
+        <item name="arrowHeadLength">@dimen/car_arrow_head_length</item>
+        <item name="drawableSize">@dimen/car_arrow_size</item>
+    </style>
+
+    <!-- The styles for the regular and borderless buttons -->
+    <style name="CarButton" parent="android:Widget.Material.Button">
+        <item name="android:layout_height">@dimen/car_button_height</item>
+        <item name="android:minWidth">@dimen/car_button_min_width</item>
+        <item name="android:paddingStart">@dimen/car_button_horizontal_padding</item>
+        <item name="android:paddingEnd">@dimen/car_button_horizontal_padding</item>
+        <item name="android:textStyle">normal</item>
+        <item name="android:textSize">@dimen/car_action1_size</item>
+        <item name="android:textColor">@drawable/car_button_text_color</item>
+        <item name="android:textAllCaps">true</item>
+        <item name="android:background">@drawable/car_button_background</item>
+    </style>
+
+    <style name="CarButton.Borderless" parent="android:Widget.Material.Button.Borderless">
+        <item name="android:layout_height">@dimen/car_button_height</item>
+        <item name="android:paddingStart">@dimen/car_borderless_button_horizontal_padding</item>
+        <item name="android:paddingEnd">@dimen/car_borderless_button_horizontal_padding</item>
+        <item name="android:textStyle">normal</item>
+        <item name="android:textSize">@dimen/car_action1_size</item>
+        <item name="android:textColor">@drawable/car_borderless_button_text_color</item>
+        <item name="android:textAllCaps">true</item>
+    </style>
+
+    <!-- Style for the progress bars -->
+    <style name="CarProgressBar.Horizontal"
+           parent="android:Widget.Material.ProgressBar.Horizontal"/>
+</resources>
diff --git a/car/res/values/themes.xml b/car/res/values/themes.xml
new file mode 100644
index 0000000..4244a22
--- /dev/null
+++ b/car/res/values/themes.xml
@@ -0,0 +1,28 @@
+<?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>
+    <!-- A Theme that activities should use to have correct arrow styling. -->
+    <style name="CarDrawerActivityTheme" parent="Theme.AppCompat.Light.NoActionBar">
+        <item name="drawerArrowStyle">@style/DrawerArrowStyle</item>
+    </style>
+
+    <!-- The styling for the action bar. -->
+    <style name="CarToolbarTheme">
+        <item name="titleTextAppearance">@style/CarTitle.Light</item>
+        <item name="contentInsetStart">@dimen/car_keyline_1</item>
+        <item name="contentInsetEnd">@dimen/car_keyline_1</item>
+    </style>
+</resources>
diff --git a/car/src/main/java/androidx/car/drawer/CarDrawerActivity.java b/car/src/main/java/androidx/car/drawer/CarDrawerActivity.java
new file mode 100644
index 0000000..55bb23c
--- /dev/null
+++ b/car/src/main/java/androidx/car/drawer/CarDrawerActivity.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.car.drawer;
+
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.support.annotation.LayoutRes;
+import android.support.annotation.Nullable;
+import android.support.v4.widget.DrawerLayout;
+import android.support.v7.app.ActionBarDrawerToggle;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.Toolbar;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.car.R;
+
+/**
+ * Common base Activity for car apps that need to present a Drawer.
+ *
+ * <p>This Activity manages the overall layout. To use it, sub-classes need to:
+ *
+ * <ul>
+ *   <li>Provide the root-items for the Drawer by implementing {@link #getRootAdapter()}.
+ *   <li>Add their main content using {@link #setMainContent(int)} or {@link #setMainContent(View)}.
+ *       They can also add fragments to the main-content container by obtaining its id using
+ *       {@link #getContentContainerId()}
+ * </ul>
+ *
+ * <p>This class will take care of drawer toggling and display.
+ *
+ * <p>The rootAdapter can implement nested-navigation, in its click-handling, by passing the
+ * CarDrawerAdapter for the next level to
+ * {@link CarDrawerController#pushAdapter(CarDrawerAdapter)}.
+ *
+ * <p>Any Activity's based on this class need to set their theme to CarDrawerActivityTheme or a
+ * derivative.
+ */
+public abstract class CarDrawerActivity extends AppCompatActivity {
+    private CarDrawerController mDrawerController;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.car_drawer_activity);
+
+        DrawerLayout drawerLayout = findViewById(R.id.drawer_layout);
+        ActionBarDrawerToggle drawerToggle = new ActionBarDrawerToggle(
+                this /* activity */,
+                drawerLayout, /* DrawerLayout object */
+                R.string.car_drawer_open,
+                R.string.car_drawer_close);
+
+        Toolbar toolbar = findViewById(R.id.car_toolbar);
+        setSupportActionBar(toolbar);
+
+        mDrawerController = new CarDrawerController(toolbar, drawerLayout, drawerToggle);
+        mDrawerController.setRootAdapter(getRootAdapter());
+
+        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+        getSupportActionBar().setHomeButtonEnabled(true);
+    }
+
+    /**
+     * Returns the {@link CarDrawerController} that is responsible for handling events relating
+     * to the drawer in this Activity.
+     *
+     * @return The {@link CarDrawerController} linked to this Activity. This value will be
+     * {@code null} if this method is called before {@code onCreate()} has been called.
+     */
+    @Nullable
+    protected CarDrawerController getDrawerController() {
+        return mDrawerController;
+    }
+
+    @Override
+    protected void onPostCreate(Bundle savedInstanceState) {
+        super.onPostCreate(savedInstanceState);
+        mDrawerController.syncState();
+    }
+
+    /**
+     * @return Adapter for root content of the Drawer.
+     */
+    protected abstract CarDrawerAdapter getRootAdapter();
+
+    /**
+     * Set main content to display in this Activity. It will be added to R.id.content_frame in
+     * car_drawer_activity.xml. NOTE: Do not use {@link #setContentView(View)}.
+     *
+     * @param view View to display as main content.
+     */
+    public void setMainContent(View view) {
+        ViewGroup parent = findViewById(getContentContainerId());
+        parent.addView(view);
+    }
+
+    /**
+     * Set main content to display in this Activity. It will be added to R.id.content_frame in
+     * car_drawer_activity.xml. NOTE: Do not use {@link #setContentView(int)}.
+     *
+     * @param resourceId Layout to display as main content.
+     */
+    public void setMainContent(@LayoutRes int resourceId) {
+        ViewGroup parent = findViewById(getContentContainerId());
+        LayoutInflater inflater = getLayoutInflater();
+        inflater.inflate(resourceId, parent, true);
+    }
+
+    /**
+     * Get the id of the main content Container which is a FrameLayout. Subclasses can add their own
+     * content/fragments inside here.
+     *
+     * @return Id of FrameLayout where main content of the subclass Activity can be added.
+     */
+    protected int getContentContainerId() {
+        return R.id.content_frame;
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        mDrawerController.closeDrawer();
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        mDrawerController.onConfigurationChanged(newConfig);
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        return mDrawerController.onOptionsItemSelected(item) || super.onOptionsItemSelected(item);
+    }
+}
diff --git a/car/src/main/java/androidx/car/drawer/CarDrawerAdapter.java b/car/src/main/java/androidx/car/drawer/CarDrawerAdapter.java
new file mode 100644
index 0000000..ca16413
--- /dev/null
+++ b/car/src/main/java/androidx/car/drawer/CarDrawerAdapter.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.car.drawer;
+
+import android.content.Context;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.car.R;
+import androidx.car.widget.PagedListView;
+
+/**
+ * Base adapter for displaying items in the car navigation drawer, which uses a
+ * {@link PagedListView}.
+ *
+ * <p>Subclasses must set the title that will be displayed when displaying the contents of the
+ * drawer via {@link #setTitle(CharSequence)}. The title can be updated at any point later on. The
+ * title of the root adapter will also be the main title showed in the toolbar when the drawer is
+ * closed. See {@link CarDrawerController#setRootAdapter(CarDrawerAdapter)} for more information.
+ *
+ * <p>This class also takes care of implementing the PageListView.ItemCamp contract and subclasses
+ * should implement {@link #getActualItemCount()}.
+ */
+public abstract class CarDrawerAdapter extends RecyclerView.Adapter<DrawerItemViewHolder>
+        implements PagedListView.ItemCap, DrawerItemClickListener {
+    private final boolean mShowDisabledListOnEmpty;
+    private final Drawable mEmptyListDrawable;
+    private int mMaxItems = PagedListView.ItemCap.UNLIMITED;
+    private CharSequence mTitle;
+    private TitleChangeListener mTitleChangeListener;
+
+    /**
+     * Interface for a class that will be notified a new title has been set on this adapter.
+     */
+    interface TitleChangeListener {
+        /**
+         * Called when {@link #setTitle(CharSequence)} has been called and the title has been
+         * changed.
+         */
+        void onTitleChanged(CharSequence newTitle);
+    }
+
+    protected CarDrawerAdapter(Context context, boolean showDisabledListOnEmpty) {
+        mShowDisabledListOnEmpty = showDisabledListOnEmpty;
+
+        mEmptyListDrawable = context.getDrawable(R.drawable.ic_list_view_disable);
+        mEmptyListDrawable.setColorFilter(context.getColor(R.color.car_tint),
+                PorterDuff.Mode.SRC_IN);
+    }
+
+    /** Returns the title set via {@link #setTitle(CharSequence)}. */
+    CharSequence getTitle() {
+        return mTitle;
+    }
+
+    /** Updates the title to display in the toolbar for this Adapter. */
+    public final void setTitle(@NonNull CharSequence title) {
+        if (title == null) {
+            throw new IllegalArgumentException("setTitle() cannot be passed a null title!");
+        }
+
+        mTitle = title;
+
+        if (mTitleChangeListener != null) {
+            mTitleChangeListener.onTitleChanged(mTitle);
+        }
+    }
+
+    /** Sets a listener to be notified whenever the title of this adapter has been changed. */
+    void setTitleChangeListener(@Nullable TitleChangeListener listener) {
+        mTitleChangeListener = listener;
+    }
+
+    @Override
+    public final void setMaxItems(int maxItems) {
+        mMaxItems = maxItems;
+    }
+
+    @Override
+    public final int getItemCount() {
+        if (shouldShowDisabledListItem()) {
+            return 1;
+        }
+        return mMaxItems >= 0 ? Math.min(mMaxItems, getActualItemCount()) : getActualItemCount();
+    }
+
+    /**
+     * Returns the absolute number of items that can be displayed in the list.
+     *
+     * <p>A class should implement this method to supply the number of items to be displayed.
+     * Returning 0 from this method will cause an empty list icon to be displayed in the drawer.
+     *
+     * <p>A class should override this method rather than {@link #getItemCount()} because that
+     * method is handling the logic of when to display the empty list icon. It will return 1 when
+     * {@link #getActualItemCount()} returns 0.
+     *
+     * @return The number of items to be displayed in the list.
+     */
+    protected abstract int getActualItemCount();
+
+    @Override
+    public final int getItemViewType(int position) {
+        if (shouldShowDisabledListItem()) {
+            return R.layout.car_drawer_list_item_empty;
+        }
+
+        return usesSmallLayout(position)
+                ? R.layout.car_drawer_list_item_small
+                : R.layout.car_drawer_list_item_normal;
+    }
+
+    /**
+     * Used to indicate the layout used for the Drawer item at given position. Subclasses can
+     * override this to use normal layout which includes text element below title.
+     *
+     * <p>A small layout is presented by the layout {@code R.layout.car_drawer_list_item_small}.
+     * Otherwise, the layout {@code R.layout.car_drawer_list_item_normal} will be used.
+     *
+     * @param position Adapter position of item.
+     * @return Whether the item at this position will use a small layout (default) or normal layout.
+     */
+    protected boolean usesSmallLayout(int position) {
+        return true;
+    }
+
+    @Override
+    public final DrawerItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        View view = LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false);
+        return new DrawerItemViewHolder(view);
+    }
+
+    @Override
+    public final void onBindViewHolder(DrawerItemViewHolder holder, int position) {
+        if (shouldShowDisabledListItem()) {
+            holder.getTitle().setText(null);
+            holder.getIcon().setImageDrawable(mEmptyListDrawable);
+            holder.setItemClickListener(null);
+        } else {
+            holder.setItemClickListener(this);
+            populateViewHolder(holder, position);
+        }
+    }
+
+    /**
+     * Whether or not this adapter should be displaying an empty list icon. The icon is shown if it
+     * has been configured to show and there are no items to be displayed.
+     */
+    private boolean shouldShowDisabledListItem() {
+        return mShowDisabledListOnEmpty && getActualItemCount() == 0;
+    }
+
+    /**
+     * Subclasses should set all elements in {@code holder} to populate the drawer-item. If some
+     * element is not used, it should be nulled out since these ViewHolder/View's are recycled.
+     */
+    protected abstract void populateViewHolder(DrawerItemViewHolder holder, int position);
+
+    /**
+     * Called when this adapter has been popped off the stack and is no longer needed. Subclasses
+     * can override to do any necessary cleanup.
+     */
+    public void cleanup() {}
+}
diff --git a/car/src/main/java/androidx/car/drawer/CarDrawerController.java b/car/src/main/java/androidx/car/drawer/CarDrawerController.java
new file mode 100644
index 0000000..e26054f
--- /dev/null
+++ b/car/src/main/java/androidx/car/drawer/CarDrawerController.java
@@ -0,0 +1,335 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.car.drawer;
+
+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.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.ArrayDeque;
+
+import androidx.car.R;
+import androidx.car.widget.PagedListView;
+
+/**
+ * A controller that will handle the set up of the navigation drawer. It will hook up the
+ * necessary buttons for up navigation, as well as expose methods to allow for a drill down
+ * 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 popped from this list.
+     */
+    private final ArrayDeque<CarDrawerAdapter> mAdapterStack = new ArrayDeque<>();
+
+    private final Context mContext;
+
+    private final Toolbar mToolbar;
+    private final DrawerLayout mDrawerLayout;
+    private final ActionBarDrawerToggle mDrawerToggle;
+
+    private final PagedListView mDrawerList;
+    private final ProgressBar mProgressBar;
+    private final View mDrawerContent;
+
+    /**
+     * Creates a {@link CarDrawerController} that will control the navigation of the drawer given by
+     * {@code drawerLayout}.
+     *
+     * <p>The given {@code drawerLayout} should either have a child View that is inflated from
+     * {@code R.layout.car_drawer} or ensure that it three children that have the IDs found in that
+     * layout.
+     *
+     * @param toolbar The {@link Toolbar} that will serve as the action bar for an Activity.
+     * @param drawerLayout The top-level container for the window content that shows the
+     * interactive drawer.
+     * @param drawerToggle The {@link ActionBarDrawerToggle} that bridges the given {@code toolbar}
+     * and {@code drawerLayout}.
+     */
+    public CarDrawerController(Toolbar toolbar,
+            DrawerLayout drawerLayout,
+            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);
+
+        setupDrawerToggling();
+    }
+
+    /**
+     * Sets the {@link CarDrawerAdapter} that will function as the root adapter. The contents of
+     * this root adapter are shown when the drawer is first opened. It is also the top-most level of
+     * navigation in the drawer.
+     *
+     * @param rootAdapter The adapter that will act as the root. If this value is {@code null}, then
+     *                    this method will do nothing.
+     */
+    public void setRootAdapter(@Nullable CarDrawerAdapter rootAdapter) {
+        if (rootAdapter == null) {
+            return;
+        }
+
+        // The root adapter is always the last item in the stack.
+        if (!mAdapterStack.isEmpty()) {
+            mAdapterStack.removeLast();
+        }
+        mAdapterStack.addLast(rootAdapter);
+
+        setToolbarTitleFrom(rootAdapter);
+        mDrawerList.setAdapter(rootAdapter);
+    }
+
+    /**
+     * Switches to use the given {@link CarDrawerAdapter} as the one to supply the list to display
+     * in the navigation drawer. The title will also be updated from the adapter.
+     *
+     * <p>This switch is treated as a navigation to the next level in the drawer. Navigation away
+     * from this level will pop the given adapter off and surface contents of the previous adapter
+     * that was set via this method. If no such adapter exists, then the root adapter set by
+     * {@link #setRootAdapter(CarDrawerAdapter)} will be used instead.
+     *
+     * @param adapter Adapter for next level of content in the drawer.
+     */
+    public final void pushAdapter(CarDrawerAdapter adapter) {
+        mAdapterStack.peek().setTitleChangeListener(null);
+        mAdapterStack.push(adapter);
+        setDisplayAdapter(adapter);
+        runLayoutAnimation(DRILL_DOWN_ANIM);
+    }
+
+    /** Close the drawer. */
+    public void closeDrawer() {
+        if (mDrawerLayout.isDrawerOpen(Gravity.LEFT)) {
+            mDrawerLayout.closeDrawer(Gravity.LEFT);
+        }
+    }
+
+    /** Opens the drawer. */
+    public void openDrawer() {
+        if (!mDrawerLayout.isDrawerOpen(Gravity.LEFT)) {
+            mDrawerLayout.openDrawer(Gravity.LEFT);
+        }
+    }
+
+    /** Sets a listener to be notified of Drawer events. */
+    public void addDrawerListener(@NonNull DrawerLayout.DrawerListener listener) {
+        mDrawerLayout.addDrawerListener(listener);
+    }
+
+    /** Removes a listener to be notified of Drawer events. */
+    public void removeDrawerListener(@NonNull DrawerLayout.DrawerListener listener) {
+        mDrawerLayout.removeDrawerListener(listener);
+    }
+
+    /**
+     * Sets whether the loading progress bar is displayed in the navigation drawer. If {@code true},
+     * the progress bar is displayed and the navigation list is hidden and vice versa.
+     */
+    public void showLoadingProgressBar(boolean show) {
+        mDrawerList.setVisibility(show ? View.INVISIBLE : View.VISIBLE);
+        mProgressBar.setVisibility(show ? View.VISIBLE : View.GONE);
+    }
+
+    /** Scroll to given position in the list. */
+    public void scrollToPosition(int position) {
+        mDrawerList.getRecyclerView().smoothScrollToPosition(position);
+    }
+
+    /**
+     * Retrieves the title from the given {@link CarDrawerAdapter} and set its as the title of this
+     * controller's internal Toolbar.
+     */
+    private void setToolbarTitleFrom(CarDrawerAdapter adapter) {
+        if (adapter.getTitle() == null) {
+            throw new RuntimeException("CarDrawerAdapter must supply a title via setTitle()");
+        }
+
+        mToolbar.setTitle(adapter.getTitle());
+        adapter.setTitleChangeListener(mToolbar::setTitle);
+    }
+
+    /**
+     * Sets up the necessary listeners for {@link DrawerLayout} so that the navigation drawer
+     * hierarchy is properly displayed.
+     */
+    private void setupDrawerToggling() {
+        mDrawerLayout.addDrawerListener(mDrawerToggle);
+        mDrawerLayout.addDrawerListener(
+                new DrawerLayout.DrawerListener() {
+                    @Override
+                    public void onDrawerSlide(View drawerView, float slideOffset) {
+                        // Correctly set the title and arrow colors as they are different between
+                        // the open and close states.
+                        updateTitleAndArrowColor(slideOffset >= COLOR_SWITCH_SLIDE_OFFSET);
+                    }
+
+                    @Override
+                    public void onDrawerClosed(View drawerView) {
+                        // If drawer is closed, revert stack/drawer to initial root state.
+                        cleanupStackAndShowRoot();
+                        scrollToPosition(0);
+                    }
+
+                    @Override
+                    public void onDrawerOpened(View drawerView) {}
+
+                    @Override
+                    public void onDrawerStateChanged(int newState) {}
+                });
+    }
+
+    /** Sets the title and arrow color of the drawer depending on if it is open or not. */
+    private void updateTitleAndArrowColor(boolean drawerOpen) {
+        // When the drawer is open, use car_title, which resolves to appropriate color depending on
+        // day-night mode. When drawer is closed, we always use light color.
+        int titleColorResId = drawerOpen ? R.color.car_title : R.color.car_title_light;
+        int titleColor = mContext.getColor(titleColorResId);
+        mToolbar.setTitleTextColor(titleColor);
+        mDrawerToggle.getDrawerArrowDrawable().setColor(titleColor);
+    }
+
+    /**
+     * Synchronizes the display of the drawer with its linked {@link DrawerLayout}.
+     *
+     * <p>This should be called from the associated Activity's
+     * {@link android.support.v7.app.AppCompatActivity#onPostCreate(Bundle)} method to synchronize
+     * after teh DRawerLayout's instance state has been restored, and any other time when the
+     * state may have diverged in such a way that this controller's associated
+     * {@link ActionBarDrawerToggle} had not been notified.
+     */
+    public void syncState() {
+        mDrawerToggle.syncState();
+
+        // In case we're restarting after a config change (e.g. day, night switch), set colors
+        // again. Doing it here so that Drawer state is fully synced and we know if its open or not.
+        // NOTE: isDrawerOpen must be passed the second child of the DrawerLayout.
+        updateTitleAndArrowColor(mDrawerLayout.isDrawerOpen(mDrawerContent));
+    }
+
+    /**
+     * Notify this controller that device configurations may have changed.
+     *
+     * <p>This method should be called from the associated Activity's
+     * {@code onConfigurationChanged()} method.
+     */
+    public void onConfigurationChanged(Configuration newConfig) {
+        // Pass any configuration change to the drawer toggle.
+        mDrawerToggle.onConfigurationChanged(newConfig);
+    }
+
+    /**
+     * An analog to an Activity's {@code onOptionsItemSelected()}. This method should be called
+     * when the Activity's method is called and will return {@code true} if the selection has
+     * been handled.
+     *
+     * @return {@code true} if the item processing was handled by this class.
+     */
+    public boolean onOptionsItemSelected(MenuItem item) {
+        // Handle home-click and see if we can navigate up in the drawer.
+        if (item != null && item.getItemId() == android.R.id.home && maybeHandleUpClick()) {
+            return true;
+        }
+
+        // DrawerToggle gets next chance to handle up-clicks (and any other clicks).
+        return mDrawerToggle.onOptionsItemSelected(item);
+    }
+
+    /**
+     * Sets the given adapter as the one displaying the current contents of the drawer.
+     *
+     * <p>The drawer's title will also be derived from the given adapter.
+     */
+    private void setDisplayAdapter(CarDrawerAdapter adapter) {
+        setToolbarTitleFrom(adapter);
+        // 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);
+    }
+
+    /**
+     * Switches to the previous level in the drawer hierarchy if the current list being displayed
+     * is not the root adapter. This is analogous to a navigate up.
+     *
+     * @return {@code true} if a navigate up was possible and executed. {@code false} otherwise.
+     */
+    private boolean maybeHandleUpClick() {
+        // Check if already at the root level.
+        if (mAdapterStack.size() <= 1) {
+            return false;
+        }
+
+        CarDrawerAdapter adapter = mAdapterStack.pop();
+        adapter.setTitleChangeListener(null);
+        adapter.cleanup();
+        setDisplayAdapter(mAdapterStack.peek());
+        runLayoutAnimation(NAVIGATE_UP_ANIM);
+        return true;
+    }
+
+    /** Clears stack down to root adapter and switches to root adapter. */
+    private void cleanupStackAndShowRoot() {
+        while (mAdapterStack.size() > 1) {
+            CarDrawerAdapter adapter = mAdapterStack.pop();
+            adapter.setTitleChangeListener(null);
+            adapter.cleanup();
+        }
+        setDisplayAdapter(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/androidx/car/drawer/DrawerItemClickListener.java b/car/src/main/java/androidx/car/drawer/DrawerItemClickListener.java
new file mode 100644
index 0000000..4c0c7a2
--- /dev/null
+++ b/car/src/main/java/androidx/car/drawer/DrawerItemClickListener.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.car.drawer;
+
+/**
+ * Listener for handling clicks on items/views managed by {@link DrawerItemViewHolder}.
+ */
+public interface DrawerItemClickListener {
+    /**
+     * Callback when item is clicked.
+     *
+     * @param position Adapter position of the clicked item.
+     */
+    void onItemClick(int position);
+}
diff --git a/car/src/main/java/androidx/car/drawer/DrawerItemViewHolder.java b/car/src/main/java/androidx/car/drawer/DrawerItemViewHolder.java
new file mode 100644
index 0000000..8bbded9
--- /dev/null
+++ b/car/src/main/java/androidx/car/drawer/DrawerItemViewHolder.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.car.drawer;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.car.R;
+
+/**
+ * Re-usable {@link RecyclerView.ViewHolder} for displaying items in the
+ * {@link androidx.car.drawer.CarDrawerAdapter}.
+ */
+public class DrawerItemViewHolder extends RecyclerView.ViewHolder {
+    private final ImageView mIcon;
+    private final TextView mTitle;
+    private final TextView mText;
+    private final ImageView mEndIcon;
+
+    DrawerItemViewHolder(View view) {
+        super(view);
+        mIcon = view.findViewById(R.id.icon);
+        if (mIcon == null) {
+            throw new IllegalArgumentException("Icon view cannot be null!");
+        }
+
+        mTitle = view.findViewById(R.id.title);
+        if (mTitle == null) {
+            throw new IllegalArgumentException("Title view cannot be null!");
+        }
+
+        // Next two are optional and may be null.
+        mText = view.findViewById(R.id.text);
+        mEndIcon = view.findViewById(R.id.end_icon);
+    }
+
+    /** Returns the view that should be used to display the main icon. */
+    @NonNull
+    public ImageView getIcon() {
+        return mIcon;
+    }
+
+    /** Returns the view that will display the main title. */
+    @NonNull
+    public TextView getTitle() {
+        return mTitle;
+    }
+
+    /** Returns the view that is used for text that is smaller than the title text. */
+    @Nullable
+    public TextView getText() {
+        return mText;
+    }
+
+    /** Returns the icon that is displayed at the end of the view. */
+    @Nullable
+    public ImageView getEndIcon() {
+        return mEndIcon;
+    }
+
+    /**
+     * Sets the listener that will be notified when the view held by this ViewHolder has been
+     * clicked. Passing {@code null} will clear any previously set listeners.
+     */
+    void setItemClickListener(@Nullable DrawerItemClickListener listener) {
+        itemView.setOnClickListener(listener != null
+                ? v -> listener.onItemClick(getAdapterPosition())
+                : null);
+    }
+}
diff --git a/car/src/main/java/androidx/car/utils/ColumnCalculator.java b/car/src/main/java/androidx/car/utils/ColumnCalculator.java
new file mode 100644
index 0000000..35b1a91
--- /dev/null
+++ b/car/src/main/java/androidx/car/utils/ColumnCalculator.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.car.utils;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.WindowManager;
+
+import androidx.car.R;
+
+/**
+ * Utility class that calculates the size of the columns that will fit on the screen. A column's
+ * width is determined by the size of the margins and gutters (space between the columns) that fit
+ * on-screen.
+ *
+ * <p>Refer to the appropriate dimens and integers for the size of the margins and number of
+ * columns.
+ */
+public class ColumnCalculator {
+    private static final String TAG = "ColumnCalculator";
+
+    private static ColumnCalculator sInstance;
+    private static int sScreenWidth;
+
+    private int mNumOfColumns;
+    private int mNumOfGutters;
+    private int mColumnWidth;
+    private int mGutterSize;
+
+    /**
+     * Gets an instance of the {@link ColumnCalculator}. If this is the first time that this
+     * method has been called, then the given {@link Context} will be used to retrieve resources.
+     *
+     * @param context The current calling Context.
+     * @return An instance of {@link ColumnCalculator}.
+     */
+    public static ColumnCalculator getInstance(Context context) {
+        if (sInstance == null) {
+            WindowManager windowManager = (WindowManager) context.getSystemService(
+                    Context.WINDOW_SERVICE);
+            DisplayMetrics displayMetrics = new DisplayMetrics();
+            windowManager.getDefaultDisplay().getMetrics(displayMetrics);
+            sScreenWidth = displayMetrics.widthPixels;
+
+            sInstance = new ColumnCalculator(context);
+        }
+
+        return sInstance;
+    }
+
+    private ColumnCalculator(Context context) {
+        Resources res = context.getResources();
+        int marginSize = res.getDimensionPixelSize(R.dimen.car_margin);
+        mGutterSize = res.getDimensionPixelSize(R.dimen.car_gutter_size);
+        mNumOfColumns = res.getInteger(R.integer.car_column_number);
+
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, String.format("marginSize: %d; numOfColumns: %d; gutterSize: %d",
+                    marginSize, mNumOfColumns, mGutterSize));
+        }
+
+        // The gutters appear between each column. As a result, the number of gutters is one less
+        // than the number of columns.
+        mNumOfGutters = mNumOfColumns - 1;
+
+        // Determine the spacing that is allowed to be filled by the columns by subtracting margins
+        // on both size of the screen and the space taken up by the gutters.
+        int spaceForColumns = sScreenWidth - (2 * marginSize) - (mNumOfGutters * mGutterSize);
+
+        mColumnWidth = spaceForColumns / mNumOfColumns;
+
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "mColumnWidth: " + mColumnWidth);
+        }
+    }
+
+    /**
+     * Returns the total number of columns that fit on the current screen.
+     *
+     * @return The total number of columns that fit on the screen.
+     */
+    public int getNumOfColumns() {
+        return mNumOfColumns;
+    }
+
+    /**
+     * Returns the size in pixels of each column. The column width is determined by the size of the
+     * screen divided by the number of columns, size of gutters and margins.
+     *
+     * @return The width of a single column in pixels.
+     */
+    public int getColumnWidth() {
+        return mColumnWidth;
+    }
+
+    /**
+     * Returns the total number of gutters that fit on screen. A gutter is the space between each
+     * column. This value is always one less than the number of columns.
+     *
+     * @return The number of gutters on screen.
+     */
+    public int getNumOfGutters() {
+        return mNumOfGutters;
+    }
+
+    /**
+     * Returns the size of each gutter in pixels. A gutter is the space between each column.
+     *
+     * @return The size of a single gutter in pixels.
+     */
+    public int getGutterSize() {
+        return mGutterSize;
+    }
+
+    /**
+     * Returns the size in pixels for the given number of columns. This value takes into account
+     * the size of the gutter between the columns as well. For example, for a column span of four,
+     * the size returned is the sum of four columns and three gutters.
+     *
+     * @return The size in pixels for a given column span.
+     */
+    public int getSizeForColumnSpan(int columnSpan) {
+        int gutterSpan = columnSpan - 1;
+        return columnSpan * mColumnWidth + gutterSpan * mGutterSize;
+    }
+}
diff --git a/car/src/main/java/androidx/car/widget/CarItemAnimator.java b/car/src/main/java/androidx/car/widget/CarItemAnimator.java
new file mode 100644
index 0000000..e6bfd05
--- /dev/null
+++ b/car/src/main/java/androidx/car/widget/CarItemAnimator.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.car.widget;
+
+import android.support.v7.widget.DefaultItemAnimator;
+import android.support.v7.widget.RecyclerView;
+
+/** {@link DefaultItemAnimator} with a few minor changes where it had undesired behavior. */
+public class CarItemAnimator extends DefaultItemAnimator {
+
+    private final PagedLayoutManager mLayoutManager;
+
+    public CarItemAnimator(PagedLayoutManager layoutManager) {
+        mLayoutManager = layoutManager;
+    }
+
+    @Override
+    public boolean animateChange(RecyclerView.ViewHolder oldHolder,
+            RecyclerView.ViewHolder newHolder,
+            int fromX,
+            int fromY,
+            int toX,
+            int toY) {
+        // The default behavior will cross fade the old view and the new one. However, if we
+        // have a card on a colored background, it will make it appear as if a changing card
+        // fades in and out.
+        float alpha = 0f;
+        if (newHolder != null) {
+            alpha = newHolder.itemView.getAlpha();
+        }
+        boolean ret = super.animateChange(oldHolder, newHolder, fromX, fromY, toX, toY);
+        if (newHolder != null) {
+            newHolder.itemView.setAlpha(alpha);
+        }
+        return ret;
+    }
+
+    @Override
+    public void onMoveFinished(RecyclerView.ViewHolder item) {
+        // The item animator uses translation heavily internally. However, we also use translation
+        // to create the paging affect. When an item's move is animated, it will mess up the
+        // translation we have set on it so we must re-offset the rows once the animations finish.
+
+        // isRunning(ItemAnimationFinishedListener) is the awkward API used to determine when all
+        // animations have finished.
+        isRunning(mFinishedListener);
+    }
+
+    private final ItemAnimatorFinishedListener mFinishedListener =
+            new ItemAnimatorFinishedListener() {
+                @Override
+                public void onAnimationsFinished() {
+                    mLayoutManager.offsetRows();
+                }
+            };
+}
diff --git a/car/src/main/java/androidx/car/widget/CarRecyclerView.java b/car/src/main/java/androidx/car/widget/CarRecyclerView.java
new file mode 100644
index 0000000..1d89ed1
--- /dev/null
+++ b/car/src/main/java/androidx/car/widget/CarRecyclerView.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.car.widget;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.support.annotation.NonNull;
+import android.support.v7.widget.RecyclerView;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * Custom {@link RecyclerView} that helps {@link PagedLayoutManager} properly fling and paginate.
+ *
+ * <p>It also has the ability to fade children as they scroll off screen that can be set with {@link
+ * #setFadeLastItem(boolean)}.
+ */
+public class CarRecyclerView extends RecyclerView {
+    private boolean mFadeLastItem;
+    /**
+     * If the user releases the list with a velocity of 0, {@link #fling(int, int)} will not be
+     * called. However, we want to make sure that the list still snaps to the next page when this
+     * happens.
+     */
+    private boolean mWasFlingCalledForGesture;
+
+    public CarRecyclerView(Context context) {
+        this(context, null);
+    }
+
+    public CarRecyclerView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public CarRecyclerView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        setFocusableInTouchMode(false);
+        setFocusable(false);
+    }
+
+    @Override
+    public boolean fling(int velocityX, int velocityY) {
+        mWasFlingCalledForGesture = true;
+        return ((PagedLayoutManager) getLayoutManager()).settleScrollForFling(this, velocityY);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent e) {
+        // We want the parent to handle all touch events. There's a lot going on there,
+        // and there is no reason to overwrite that functionality. If we do, bad things will happen.
+        final boolean ret = super.onTouchEvent(e);
+
+        int action = e.getActionMasked();
+        if (action == MotionEvent.ACTION_UP) {
+            if (!mWasFlingCalledForGesture) {
+                ((PagedLayoutManager) getLayoutManager()).settleScrollForFling(this, 0);
+            }
+            mWasFlingCalledForGesture = false;
+        }
+
+        return ret;
+    }
+
+    @Override
+    public boolean drawChild(@NonNull Canvas canvas, @NonNull View child, long drawingTime) {
+        if (mFadeLastItem) {
+            float onScreen = 1f;
+            if ((child.getTop() < getBottom() && child.getBottom() > getBottom())) {
+                onScreen = ((float) (getBottom() - child.getTop())) / (float) child.getHeight();
+            } else if ((child.getTop() < getTop() && child.getBottom() > getTop())) {
+                onScreen = ((float) (child.getBottom() - getTop())) / (float) child.getHeight();
+            }
+            float alpha = 1 - (1 - onScreen) * (1 - onScreen);
+            fadeChild(child, alpha);
+        }
+
+        return super.drawChild(canvas, child, drawingTime);
+    }
+
+    public void setFadeLastItem(boolean fadeLastItem) {
+        mFadeLastItem = fadeLastItem;
+    }
+
+    /**
+     * Scrolls the contents of this {@link CarRecyclerView} up one page. A page is defined as the
+     * number of items that fit completely on the screen.
+     */
+    public void pageUp() {
+        PagedLayoutManager lm = (PagedLayoutManager) getLayoutManager();
+        int pageUpPosition = lm.getPageUpPosition();
+        if (pageUpPosition == -1) {
+            return;
+        }
+
+        smoothScrollToPosition(pageUpPosition);
+    }
+
+    /**
+     * Scrolls the contents of this {@link CarRecyclerView} down one page. A page is defined as the
+     * number of items that fit completely on the screen.
+     */
+    public void pageDown() {
+        PagedLayoutManager lm = (PagedLayoutManager) getLayoutManager();
+        int pageDownPosition = lm.getPageDownPosition();
+        if (pageDownPosition == -1) {
+            return;
+        }
+
+        smoothScrollToPosition(pageDownPosition);
+    }
+
+    /**
+     * Fades child by alpha. If child is a {@link ViewGroup} then it will recursively fade its
+     * children instead.
+     */
+    private void fadeChild(@NonNull View child, float alpha) {
+        if (child instanceof ViewGroup) {
+            ViewGroup vg = (ViewGroup) child;
+            for (int i = 0; i < vg.getChildCount(); i++) {
+                fadeChild(vg.getChildAt(i), alpha);
+            }
+        } else {
+            child.setAlpha(alpha);
+        }
+    }
+}
diff --git a/car/src/main/java/androidx/car/widget/ColumnCardView.java b/car/src/main/java/androidx/car/widget/ColumnCardView.java
new file mode 100644
index 0000000..9ec2bb6
--- /dev/null
+++ b/car/src/main/java/androidx/car/widget/ColumnCardView.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.car.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.support.v7.widget.CardView;
+import android.util.AttributeSet;
+import android.util.Log;
+
+import androidx.car.R;
+import androidx.car.utils.ColumnCalculator;
+
+/**
+ * A {@link CardView} whose width can be specified by the number of columns that it will span.
+ *
+ * <p>The {@code ColumnCardView} works similarly to a regular {@link CardView}, except that
+ * its {@code layout_width} attribute is always ignored. Instead, its width is automatically
+ * calculated based on a specified {@code columnSpan} attribute. Alternatively, a user can call
+ * {@link #setColumnSpan(int)}. If no column span is given, the {@code ColumnCardView} will have
+ * a default span value that it uses.
+ *
+ * <pre>
+ * &lt;androidx.car.widget.ColumnCardView
+ *     android:layout_width="wrap_content"
+ *     android:layout_height="wrap_content"
+ *     app:columnSpan="4" /&gt;
+ * </pre>
+ *
+ * @see ColumnCalculator
+ */
+public final class ColumnCardView extends CardView {
+    private static final String TAG = "ColumnCardView";
+
+    private ColumnCalculator mColumnCalculator;
+    private int mColumnSpan;
+
+    public ColumnCardView(Context context) {
+        super(context);
+        init(context, null, 0 /* defStyleAttrs */);
+    }
+
+    public ColumnCardView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init(context, attrs, 0 /* defStyleAttrs */);
+    }
+
+    public ColumnCardView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        init(context, attrs, defStyleAttr);
+    }
+
+    private void init(Context context, AttributeSet attrs, int defStyleAttrs) {
+        mColumnCalculator = ColumnCalculator.getInstance(context);
+
+        int defaultColumnSpan = getResources().getInteger(
+                R.integer.column_card_default_column_span);
+
+        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ColumnCardView,
+                defStyleAttrs, 0 /* defStyleRes */);
+        mColumnSpan = ta.getInteger(R.styleable.ColumnCardView_columnSpan, defaultColumnSpan);
+        ta.recycle();
+
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "Column span: " + mColumnSpan);
+        }
+    }
+
+    @Override
+    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        // Override any specified width so that the width is one that is calculated based on
+        // column and gutter span.
+        int width = mColumnCalculator.getSizeForColumnSpan(mColumnSpan);
+        super.onMeasure(
+                MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
+                heightMeasureSpec);
+    }
+
+    /**
+     * Sets the number of columns that this {@code ColumnCardView} will span. The given span is
+     * ignored if it is less than 0 or greater than the number of columns that fit on screen.
+     *
+     * @param columnSpan The number of columns this {@code ColumnCardView} will span across.
+     */
+    public void setColumnSpan(int columnSpan) {
+        if (columnSpan <= 0 || columnSpan > mColumnCalculator.getNumOfColumns()) {
+            return;
+        }
+
+        mColumnSpan = columnSpan;
+        requestLayout();
+    }
+
+    /**
+     * Returns the currently number of columns that this {@code ColumnCardView} spans.
+     *
+     * @return The number of columns this {@code ColumnCardView} spans across.
+     */
+    public int getColumnSpan() {
+        return mColumnSpan;
+    }
+}
diff --git a/car/src/main/java/androidx/car/widget/DayNightStyle.java b/car/src/main/java/androidx/car/widget/DayNightStyle.java
new file mode 100644
index 0000000..6e3ecbe
--- /dev/null
+++ b/car/src/main/java/androidx/car/widget/DayNightStyle.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.car.widget;
+
+import android.support.annotation.IntDef;
+
+/**
+ * Specifies how the system UI should respond to day/night mode events.
+ *
+ * <p>By default, the Android Auto system UI assumes the app content background is light during the
+ * day and dark during the night. The system UI updates the foreground color (such as status bar
+ * icon colors) to be dark during day mode and light during night mode. By setting the
+ * DayNightStyle, the app can specify how the system should respond to a day/night mode event. For
+ * example, if the app has a dark content background for both day and night time, the app can tell
+ * the system to use {@link #FORCE_NIGHT} style so the foreground color is locked to light color for
+ * both cases.
+ *
+ * <p>Note: Not all system UI elements can be customized with a DayNightStyle.
+ */
+@IntDef({
+        DayNightStyle.AUTO,
+        DayNightStyle.AUTO_INVERSE,
+        DayNightStyle.FORCE_NIGHT,
+        DayNightStyle.FORCE_DAY,
+})
+public @interface DayNightStyle {
+    /**
+     * Sets the foreground color to be automatically changed based on day/night mode, assuming the
+     * app content background is light during the day and dark during the night.
+     *
+     * <p>This is the default behavior.
+     */
+    int AUTO = 0;
+
+    /**
+     * Sets the foreground color to be automatically changed based on day/night mode, assuming the
+     * app content background is dark during the day and light during the night.
+     */
+    int AUTO_INVERSE = 1;
+
+    /**
+     * Sets the foreground color to be locked to the night version, which assumes the app content
+     * background is always dark during both day and night.
+     */
+    int FORCE_NIGHT = 2;
+
+    /**
+     * Sets the foreground color to be locked to the day version, which assumes the app content
+     * background is always light during both day and night.
+     */
+    int FORCE_DAY = 3;
+}
diff --git a/car/src/main/java/androidx/car/widget/ListItem.java b/car/src/main/java/androidx/car/widget/ListItem.java
new file mode 100644
index 0000000..c5b93d9
--- /dev/null
+++ b/car/src/main/java/androidx/car/widget/ListItem.java
@@ -0,0 +1,708 @@
+/*
+ * 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 androidx.car.widget;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.DrawableRes;
+import android.support.annotation.IntDef;
+import android.support.v7.widget.RecyclerView;
+import android.text.TextUtils;
+import android.view.View;
+import android.widget.RelativeLayout;
+
+import java.lang.annotation.Retention;
+import java.util.ArrayList;
+import java.util.List;
+
+import androidx.car.R;
+
+/**
+ * Class to build a list item.
+ *
+ * <p>An item supports primary action and supplemental action(s).
+ *
+ * <p>An item visually composes of 3 parts; each part may contain multiple views.
+ * <ul>
+ *     <li>{@code Primary Action}: represented by an icon of following types.
+ *     <ul>
+ *         <li>Primary Icon - icon size could be large or small.
+ *         <li>No Icon
+ *         <li>Empty Icon - different from No Icon by how much margin {@code Text} offsets
+ *     </ul>
+ *     <li>{@code Text}: supports any combination of the follow text views.
+ *     <ul>
+ *         <li>Title
+ *         <li>Body
+ *     </ul>
+ *     <li>{@code Supplemental Action(s)}: represented by one of the following types; aligned toward
+ *     the end of item.
+ *     <ul>
+ *         <li>Supplemental Icon
+ *         <li>One Action Button
+ *         <li>Two Action Buttons
+ *     </ul>
+ * </ul>
+ *
+ * {@link ListItem} can be built through its {@link ListItem.Builder}. It binds data
+ * to {@link ListItemAdapter.ViewHolder} based on components selected.
+ */
+public class ListItem {
+
+    private Builder mBuilder;
+
+    private ListItem(Builder builder) {
+        mBuilder = builder;
+    }
+
+    /**
+     * Applies all {@link ViewBinder} to {@code viewHolder}.
+     */
+    void bind(ListItemAdapter.ViewHolder viewHolder) {
+        setAllSubViewsGone(viewHolder);
+        for (ViewBinder binder : mBuilder.mBinders) {
+            binder.bind(viewHolder);
+        }
+    }
+
+    void setAllSubViewsGone(ListItemAdapter.ViewHolder vh) {
+        View[] subviews = new View[] {
+                vh.getPrimaryIcon(),
+                vh.getTitle(), vh.getBody(),
+                vh.getSupplementalIcon(), vh.getSupplementalIconDivider(),
+                vh.getAction1(), vh.getAction1Divider(), vh.getAction2(), vh.getAction2Divider()};
+        for (View v : subviews) {
+            v.setVisibility(View.GONE);
+        }
+    }
+
+    /**
+     * Used by {@link ListItemAdapter} to choose layout to inflate for view holder.
+     * New view type needs support in {@link ListItemAdapter}.
+     */
+    protected int getViewType() {
+        return mBuilder.mIsCard
+                ? ListItemAdapter.CAR_PAGED_LIST_CARD
+                : ListItemAdapter.CAR_PAGED_LIST_ITEM;
+    }
+
+    /**
+     * Functional interface to provide a way to interact with views in
+     * {@link ListItemAdapter.ViewHolder}. {@code ViewBinder}s added to a
+     * {@code ListItem} will be called when {@code ListItem} {@code bind}s to
+     * {@link ListItemAdapter.ViewHolder}.
+     */
+    public interface ViewBinder {
+        /**
+         * Provides a way to interact with views in view holder.
+         */
+        void bind(ListItemAdapter.ViewHolder viewHolder);
+    }
+
+    /**
+     * Builds a {@link ListItem}.
+     *
+     * <p>With conflicting methods are called, e.g. setting primary action to both primary icon and
+     * no icon, the last called method wins.
+     */
+    public static class Builder {
+
+        @Retention(SOURCE)
+        @IntDef({
+                PRIMARY_ACTION_TYPE_NO_ICON, PRIMARY_ACTION_TYPE_EMPTY_ICON,
+                PRIMARY_ACTION_TYPE_LARGE_ICON, PRIMARY_ACTION_TYPE_SMALL_ICON})
+        private @interface PrimaryActionType {}
+
+        private static final int PRIMARY_ACTION_TYPE_NO_ICON = 0;
+        private static final int PRIMARY_ACTION_TYPE_EMPTY_ICON = 1;
+        private static final int PRIMARY_ACTION_TYPE_LARGE_ICON = 2;
+        private static final int PRIMARY_ACTION_TYPE_SMALL_ICON = 3;
+
+        @Retention(SOURCE)
+        @IntDef({SUPPLEMENTAL_ACTION_NO_ACTION, SUPPLEMENTAL_ACTION_SUPPLEMENTAL_ICON,
+                SUPPLEMENTAL_ACTION_ONE_ACTION, SUPPLEMENTAL_ACTION_TWO_ACTIONS})
+        private @interface SupplementalActionType {}
+
+        private static final int SUPPLEMENTAL_ACTION_NO_ACTION = 0;
+        private static final int SUPPLEMENTAL_ACTION_SUPPLEMENTAL_ICON = 1;
+        private static final int SUPPLEMENTAL_ACTION_ONE_ACTION = 2;
+        private static final int SUPPLEMENTAL_ACTION_TWO_ACTIONS = 3;
+
+        private final Context mContext;
+        private final List<ViewBinder> mBinders = new ArrayList<>();
+        // Store custom binders separately so they will bind after binders are created in build().
+        private final List<ViewBinder> mCustomBinders = new ArrayList<>();
+
+        private boolean mIsCard;
+
+        private View.OnClickListener mOnClickListener;
+
+        @PrimaryActionType private int mPrimaryActionType = PRIMARY_ACTION_TYPE_NO_ICON;
+        private int mPrimaryActionIconResId;
+        private Drawable mPrimaryActionIconDrawable;
+
+        private String mTitle;
+        private String mBody;
+        private boolean mIsBodyPrimary;
+
+        @SupplementalActionType private int mSupplementalActionType = SUPPLEMENTAL_ACTION_NO_ACTION;
+        private int mSupplementalIconResId;
+        private View.OnClickListener mSupplementalIconOnClickListener;
+        private boolean mShowSupplementalIconDivider;
+
+        private String mAction1Text;
+        private View.OnClickListener mAction1OnClickListener;
+        private boolean mShowAction1Divider;
+        private String mAction2Text;
+        private View.OnClickListener mAction2OnClickListener;
+        private boolean mShowAction2Divider;
+
+        public Builder(Context context) {
+            mContext = context;
+        }
+
+        /**
+         * Builds a {@link ListItem}. Adds {@link ViewBinder}s that will adjust layout in
+         * {@link ListItemAdapter.ViewHolder} depending on sub-views used.
+         */
+        public ListItem build() {
+            setItemLayoutHeight();
+            setPrimaryAction();
+            setText();
+            setSupplementalActions();
+            setOnClickListener();
+
+            mBinders.addAll(mCustomBinders);
+
+            return new ListItem(this);
+        }
+
+        /**
+         * Sets the height of item depending on which text field is set.
+         */
+        private void setItemLayoutHeight() {
+            if (TextUtils.isEmpty(mBody)) {
+                // If the item only has title or no text, it uses fixed-height as single line.
+                int height = (int) mContext.getResources().getDimension(
+                        R.dimen.car_single_line_list_item_height);
+                mBinders.add((vh) -> {
+                    RecyclerView.LayoutParams layoutParams =
+                            (RecyclerView.LayoutParams) vh.itemView.getLayoutParams();
+                    layoutParams.height = height;
+                    vh.itemView.setLayoutParams(layoutParams);
+                });
+            } else {
+                // If body is present, the item should be at least as tall as min height, and wraps
+                // content.
+                int minHeight = (int) mContext.getResources().getDimension(
+                        R.dimen.car_double_line_list_item_height);
+                mBinders.add((vh) -> {
+                    vh.itemView.setMinimumHeight(minHeight);
+                    vh.getContainerLayout().setMinimumHeight(minHeight);
+
+                    RecyclerView.LayoutParams layoutParams =
+                            (RecyclerView.LayoutParams) vh.itemView.getLayoutParams();
+                    layoutParams.height = RecyclerView.LayoutParams.WRAP_CONTENT;
+                    vh.itemView.setLayoutParams(layoutParams);
+                });
+            }
+        }
+
+        private void setPrimaryAction() {
+            setPrimaryIconContent();
+            setPrimaryIconLayout();
+        }
+
+        private void setText() {
+            setTextContent();
+            setTextVerticalMargin();
+            // Only setting start margin because text end is relative to the start of supplemental
+            // actions.
+            setTextStartMargin();
+        }
+
+        private void setOnClickListener() {
+            if (mOnClickListener != null) {
+                mBinders.add(vh -> vh.itemView.setOnClickListener(mOnClickListener));
+            }
+        }
+
+        private void setPrimaryIconContent() {
+            switch (mPrimaryActionType) {
+                case PRIMARY_ACTION_TYPE_SMALL_ICON:
+                case PRIMARY_ACTION_TYPE_LARGE_ICON:
+                    mBinders.add((vh) -> {
+                        vh.getPrimaryIcon().setVisibility(View.VISIBLE);
+
+                        if (mPrimaryActionIconDrawable != null) {
+                            vh.getPrimaryIcon().setImageDrawable(mPrimaryActionIconDrawable);
+                        } else if (mPrimaryActionIconResId != 0) {
+                            vh.getPrimaryIcon().setImageResource(mPrimaryActionIconResId);
+                        }
+                    });
+                    break;
+                case PRIMARY_ACTION_TYPE_EMPTY_ICON:
+                case PRIMARY_ACTION_TYPE_NO_ICON:
+                    // Do nothing.
+                    break;
+                default:
+                    throw new IllegalStateException("Unrecognizable primary action type.");
+            }
+        }
+
+        /**
+         * Sets layout params of primary icon.
+         *
+         * <p>Large icon will have no start margin, and always align center vertically.
+         *
+         * <p>Small icon will have start margin. When body text is present small icon uses a top
+         * margin otherwise align center vertically.
+         */
+        private void setPrimaryIconLayout() {
+            switch (mPrimaryActionType) {
+                case PRIMARY_ACTION_TYPE_SMALL_ICON:
+                    mBinders.add(vh -> {
+                        int iconSize = mContext.getResources().getDimensionPixelSize(
+                                R.dimen.car_primary_icon_size);
+                        // Icon size.
+                        RelativeLayout.LayoutParams layoutParams =
+                                (RelativeLayout.LayoutParams) vh.getPrimaryIcon().getLayoutParams();
+                        layoutParams.height = iconSize;
+                        layoutParams.width = iconSize;
+
+                        // Start margin.
+                        layoutParams.setMarginStart(mContext.getResources().getDimensionPixelSize(
+                                R.dimen.car_keyline_1));
+
+                        if (!TextUtils.isEmpty(mBody)) {
+                            // Set top margin.
+                            layoutParams.topMargin = mContext.getResources().getDimensionPixelSize(
+                                    R.dimen.car_padding_4);
+                        } else {
+                            // Centered vertically.
+                            layoutParams.addRule(RelativeLayout.CENTER_VERTICAL);
+                        }
+                        vh.getPrimaryIcon().setLayoutParams(layoutParams);
+                    });
+                    break;
+                case PRIMARY_ACTION_TYPE_LARGE_ICON:
+                    mBinders.add(vh -> {
+                        int iconSize = mContext.getResources().getDimensionPixelSize(
+                                R.dimen.car_single_line_list_item_height);
+                        // Icon size.
+                        RelativeLayout.LayoutParams layoutParams =
+                                (RelativeLayout.LayoutParams) vh.getPrimaryIcon().getLayoutParams();
+                        layoutParams.height = iconSize;
+                        layoutParams.width = iconSize;
+
+                        // No start margin.
+                        layoutParams.setMarginStart(0);
+
+                        // Always centered vertically.
+                        layoutParams.addRule(RelativeLayout.CENTER_VERTICAL);
+
+                        vh.getPrimaryIcon().setLayoutParams(layoutParams);
+                    });
+                    break;
+                case PRIMARY_ACTION_TYPE_EMPTY_ICON:
+                case PRIMARY_ACTION_TYPE_NO_ICON:
+                    // Do nothing.
+                    break;
+                default:
+                    throw new IllegalStateException("Unrecognizable primary action type.");
+            }
+        }
+
+        private void setTextContent() {
+            if (!TextUtils.isEmpty(mTitle)) {
+                mBinders.add(vh -> {
+                    vh.getTitle().setVisibility(View.VISIBLE);
+                    vh.getTitle().setText(mTitle);
+                });
+            }
+            if (!TextUtils.isEmpty(mBody)) {
+                mBinders.add(vh -> {
+                    vh.getBody().setVisibility(View.VISIBLE);
+                    vh.getBody().setText(mBody);
+                });
+            }
+
+            if (mIsBodyPrimary) {
+                mBinders.add((vh) -> {
+                    vh.getTitle().setTextAppearance(R.style.CarBody2);
+                    vh.getBody().setTextAppearance(R.style.CarBody1);
+                });
+            } else {
+                mBinders.add((vh) -> {
+                    vh.getTitle().setTextAppearance(R.style.CarBody1);
+                    vh.getBody().setTextAppearance(R.style.CarBody2);
+                });
+            }
+        }
+
+        /**
+         * Sets start margin of text view depending on icon type.
+         */
+        private void setTextStartMargin() {
+            final int startMarginResId;
+            switch (mPrimaryActionType) {
+                case PRIMARY_ACTION_TYPE_NO_ICON:
+                    startMarginResId = R.dimen.car_keyline_1;
+                    break;
+                case PRIMARY_ACTION_TYPE_EMPTY_ICON:
+                    startMarginResId = R.dimen.car_keyline_3;
+                    break;
+                case PRIMARY_ACTION_TYPE_SMALL_ICON:
+                    startMarginResId = R.dimen.car_keyline_3;
+                    break;
+                case PRIMARY_ACTION_TYPE_LARGE_ICON:
+                    startMarginResId = R.dimen.car_keyline_4;
+                    break;
+                default:
+                    throw new IllegalStateException("Unrecognizable primary action type.");
+            }
+            int startMargin = mContext.getResources().getDimensionPixelSize(startMarginResId);
+            mBinders.add(vh -> {
+                RelativeLayout.LayoutParams titleLayoutParams =
+                        (RelativeLayout.LayoutParams) vh.getTitle().getLayoutParams();
+                titleLayoutParams.setMarginStart(startMargin);
+                vh.getTitle().setLayoutParams(titleLayoutParams);
+
+                RelativeLayout.LayoutParams bodyLayoutParams =
+                        (RelativeLayout.LayoutParams) vh.getBody().getLayoutParams();
+                bodyLayoutParams.setMarginStart(startMargin);
+                vh.getBody().setLayoutParams(bodyLayoutParams);
+            });
+        }
+
+        /**
+         * Sets top/bottom margins of {@code Title} and {@code Body}.
+         */
+        private void setTextVerticalMargin() {
+            if (!TextUtils.isEmpty(mTitle) && TextUtils.isEmpty(mBody)) {
+                // Title only - view is aligned center vertically by itself.
+                mBinders.add(vh -> {
+                    RelativeLayout.LayoutParams layoutParams =
+                            (RelativeLayout.LayoutParams) vh.getTitle().getLayoutParams();
+                    layoutParams.addRule(RelativeLayout.CENTER_VERTICAL);
+                    vh.getTitle().setLayoutParams(layoutParams);
+                });
+            } else if (TextUtils.isEmpty(mTitle) && !TextUtils.isEmpty(mBody)) {
+                mBinders.add(vh -> {
+                    // Body uses top and bottom margin.
+                    int margin = mContext.getResources().getDimensionPixelSize(
+                            R.dimen.car_padding_3);
+                    RelativeLayout.LayoutParams layoutParams =
+                            (RelativeLayout.LayoutParams) vh.getBody().getLayoutParams();
+                    layoutParams.addRule(RelativeLayout.CENTER_VERTICAL);
+                    layoutParams.topMargin = margin;
+                    layoutParams.bottomMargin = margin;
+                    vh.getBody().setLayoutParams(layoutParams);
+                });
+            } else {
+                mBinders.add(vh -> {
+                    // Title has a top margin
+                    Resources resources = mContext.getResources();
+                    int padding1 = resources.getDimensionPixelSize(R.dimen.car_padding_1);
+                    int padding3 = resources.getDimensionPixelSize(R.dimen.car_padding_3);
+                    RelativeLayout.LayoutParams titleLayoutParams =
+                            (RelativeLayout.LayoutParams) vh.getTitle().getLayoutParams();
+                    titleLayoutParams.topMargin = padding3;
+                    vh.getTitle().setLayoutParams(titleLayoutParams);
+                    // Body is below title with a margin, and has bottom margin.
+                    RelativeLayout.LayoutParams bodyLayoutParams =
+                            (RelativeLayout.LayoutParams) vh.getBody().getLayoutParams();
+                    bodyLayoutParams.addRule(RelativeLayout.BELOW, R.id.title);
+                    bodyLayoutParams.topMargin = padding1;
+                    bodyLayoutParams.bottomMargin = padding3;
+                    vh.getBody().setLayoutParams(bodyLayoutParams);
+                });
+            }
+        }
+
+        /**
+         * Sets up view(s) for supplemental action.
+         */
+        private void setSupplementalActions() {
+            switch (mSupplementalActionType) {
+                case SUPPLEMENTAL_ACTION_SUPPLEMENTAL_ICON:
+                    mBinders.add((vh) -> {
+                        vh.getSupplementalIcon().setVisibility(View.VISIBLE);
+                        if (mShowSupplementalIconDivider) {
+                            vh.getSupplementalIconDivider().setVisibility(View.VISIBLE);
+                        }
+
+                        vh.getSupplementalIcon().setImageResource(mSupplementalIconResId);
+                        vh.getSupplementalIcon().setOnClickListener(
+                                mSupplementalIconOnClickListener);
+                    });
+                    break;
+                case SUPPLEMENTAL_ACTION_TWO_ACTIONS:
+                    mBinders.add((vh) -> {
+                        vh.getAction2().setVisibility(View.VISIBLE);
+                        if (mShowAction2Divider) {
+                            vh.getAction2Divider().setVisibility(View.VISIBLE);
+                        }
+
+                        vh.getAction2().setText(mAction2Text);
+                        vh.getAction2().setOnClickListener(mAction2OnClickListener);
+                    });
+                    // Fall through
+                case SUPPLEMENTAL_ACTION_ONE_ACTION:
+                    mBinders.add((vh) -> {
+                        vh.getAction1().setVisibility(View.VISIBLE);
+                        if (mShowAction1Divider) {
+                            vh.getAction1Divider().setVisibility(View.VISIBLE);
+                        }
+
+                        vh.getAction1().setText(mAction1Text);
+                        vh.getAction1().setOnClickListener(mAction1OnClickListener);
+                    });
+                    break;
+                case SUPPLEMENTAL_ACTION_NO_ACTION:
+                    // Do nothing
+                    break;
+                default:
+                    throw new IllegalArgumentException("Unrecognized supplemental action type.");
+            }
+        }
+
+        /**
+         * Builds the item in a {@link android.support.v7.widget.CardView}.
+         *
+         * <p>Each item will have rounded corner, margin between items, and elevation.
+         *
+         * @return This Builder object to allow for chaining calls to set methods.
+         */
+        public Builder withCardLook() {
+            mIsCard = true;
+            return this;
+        }
+
+        /**
+         * Sets {@link View.OnClickListener} of {@code ListItem}.
+         *
+         * @return This Builder object to allow for chaining calls to set methods.
+         */
+        public Builder withOnClickListener(View.OnClickListener listener) {
+            mOnClickListener = listener;
+            return this;
+        }
+
+        /**
+         * Sets {@code Primary Action} to be represented by an icon.
+         *
+         * @param iconResId the resource identifier of the drawable.
+         * @param useLargeIcon the size of primary icon. Large Icon is a square as tall as an item
+         *                     with only title set; useful for album cover art.
+         * @return This Builder object to allow for chaining calls to set methods.
+         */
+        public Builder withPrimaryActionIcon(@DrawableRes int iconResId, boolean useLargeIcon) {
+            return withPrimaryActionIcon(null, iconResId, useLargeIcon);
+        }
+
+        /**
+         * Sets {@code Primary Action} to be represented by an icon.
+         *
+         * @param drawable the Drawable to set, or null to clear the content.
+         * @param useLargeIcon the size of primary icon. Large Icon is a square as tall as an item
+         *                     with only title set; useful for album cover art.
+         * @return This Builder object to allow for chaining calls to set methods.
+         */
+        public Builder withPrimaryActionIcon(Drawable drawable, boolean useLargeIcon) {
+            return withPrimaryActionIcon(drawable, 0, useLargeIcon);
+        }
+
+        private Builder withPrimaryActionIcon(Drawable drawable, @DrawableRes int iconResId,
+                boolean useLargeIcon) {
+            mPrimaryActionType = useLargeIcon
+                    ? PRIMARY_ACTION_TYPE_LARGE_ICON
+                    : PRIMARY_ACTION_TYPE_SMALL_ICON;
+            mPrimaryActionIconResId = iconResId;
+            mPrimaryActionIconDrawable = drawable;
+            return this;
+        }
+
+        /**
+         * Sets {@code Primary Action} to be empty icon.
+         *
+         * {@code Text} would have a start margin as if {@code Primary Action} were set to
+         * primary icon.
+         *
+         * @return This Builder object to allow for chaining calls to set methods.
+         */
+        public Builder withPrimaryActionEmptyIcon() {
+            mPrimaryActionType = PRIMARY_ACTION_TYPE_EMPTY_ICON;
+            return this;
+        }
+
+        /**
+         * Sets {@code Primary Action} to have no icon. Text would align to the start of item.
+         *
+         * @return This Builder object to allow for chaining calls to set methods.
+         */
+        public Builder withPrimaryActionNoIcon() {
+            mPrimaryActionType = PRIMARY_ACTION_TYPE_NO_ICON;
+            return this;
+        }
+
+        /**
+         * Sets the title of item.
+         *
+         * <p>Primary text is {@code title} by default. It can be set by
+         * {@link #withBody(String, boolean)}
+         *
+         * @param title text to display as title.
+         * @return This Builder object to allow for chaining calls to set methods.
+         */
+        public Builder withTitle(String title) {
+            mTitle = title;
+            return this;
+        }
+
+        /**
+         * Sets the body text of item.
+         *
+         * <p>Text beyond length required by regulation will be truncated. Defaults {@code Title}
+         * text as the primary.
+         *
+         * @return This Builder object to allow for chaining calls to set methods.
+         */
+        public Builder withBody(String body) {
+            return withBody(body, false);
+        }
+
+        /**
+         * Sets the body text of item.
+         *
+         * <p>Text beyond length required by regulation will be truncated.
+         *
+         * @param asPrimary sets {@code Body Text} as primary text of item.
+         * @return This Builder object to allow for chaining calls to set methods.
+         */
+        public Builder withBody(String body, boolean asPrimary) {
+            int limit = mContext.getResources().getInteger(
+                    R.integer.car_list_item_text_length_limit);
+            if (body.length() < limit) {
+                mBody = body;
+            } else {
+                mBody = body.substring(0, limit) + mContext.getString(R.string.ellipsis);
+            }
+            mIsBodyPrimary = asPrimary;
+            return this;
+        }
+
+        /**
+         * Sets {@code Supplemental Action} to be represented by an {@code Supplemental Icon}.
+         *
+         * @return This Builder object to allow for chaining calls to set methods.
+         */
+        public Builder withSupplementalIcon(int iconResId, boolean showDivider) {
+            return withSupplementalIcon(iconResId, showDivider, null);
+        }
+
+        /**
+         * Sets {@code Supplemental Action} to be represented by an {@code Supplemental Icon}.
+         *
+         * @param iconResId drawable resource id.
+         * @return This Builder object to allow for chaining calls to set methods.
+         */
+        public Builder withSupplementalIcon(int iconResId, boolean showDivider,
+                View.OnClickListener listener) {
+            mSupplementalActionType = SUPPLEMENTAL_ACTION_SUPPLEMENTAL_ICON;
+
+            mSupplementalIconResId = iconResId;
+            mSupplementalIconOnClickListener = listener;
+            mShowSupplementalIconDivider = showDivider;
+            return this;
+        }
+
+        /**
+         * Sets {@code Supplemental Action} to be represented by an {@code Action Button}.
+         *
+         * @param text button text to display.
+         * @return This Builder object to allow for chaining calls to set methods.
+         */
+        public Builder withAction(String text, boolean showDivider, View.OnClickListener listener) {
+            if (TextUtils.isEmpty(text)) {
+                throw new IllegalArgumentException("Action text cannot be empty.");
+            }
+            mSupplementalActionType = SUPPLEMENTAL_ACTION_ONE_ACTION;
+
+            mAction1Text = text;
+            mAction1OnClickListener = listener;
+            mShowAction1Divider = showDivider;
+            return this;
+        }
+
+        /**
+         * Sets {@code Supplemental Action} to be represented by two {@code Action Button}s.
+         *
+         * <p>These two action buttons will be aligned towards item end.
+         *
+         * @param action1Text button text to display - this button will be closer to item end.
+         * @param action2Text button text to display.
+         */
+        public Builder withActions(String action1Text, boolean showAction1Divider,
+                View.OnClickListener action1OnClickListener,
+                String action2Text, boolean showAction2Divider,
+                View.OnClickListener action2OnClickListener) {
+            if (TextUtils.isEmpty(action1Text)) {
+                throw new IllegalArgumentException("Action1 text cannot be empty.");
+            }
+            if (TextUtils.isEmpty(action2Text)) {
+                throw new IllegalArgumentException("Action2 text cannot be empty.");
+            }
+            mSupplementalActionType = SUPPLEMENTAL_ACTION_TWO_ACTIONS;
+
+            mAction1Text = action1Text;
+            mAction1OnClickListener = action1OnClickListener;
+            mShowAction1Divider = showAction1Divider;
+            mAction2Text = action2Text;
+            mAction2OnClickListener = action2OnClickListener;
+            mShowAction2Divider = showAction2Divider;
+            return this;
+        }
+
+        /**
+         * Adds {@link ViewBinder} to interact with sub-views in
+         * {@link ListItemAdapter.ViewHolder}. These ViewBinders will always bind after
+         * other {@link Builder} methods have bond.
+         *
+         * <p>Make sure to call with...() method on the intended sub-view first.
+         *
+         * <p>Example:
+         * <pre>
+         * {@code
+         * new Builder()
+         *     .withTitle("title")
+         *     .withViewBinder((viewHolder) -> {
+         *         viewHolder.getTitle().doMoreStuff();
+         *     })
+         *     .build();
+         * }
+         * </pre>
+         */
+        public Builder withViewBinder(ViewBinder binder) {
+            mCustomBinders.add(binder);
+            return this;
+        }
+    }
+}
diff --git a/car/src/main/java/androidx/car/widget/ListItemAdapter.java b/car/src/main/java/androidx/car/widget/ListItemAdapter.java
new file mode 100644
index 0000000..2bdfae6
--- /dev/null
+++ b/car/src/main/java/androidx/car/widget/ListItemAdapter.java
@@ -0,0 +1,182 @@
+/*
+ * 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 androidx.car.widget;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.content.Context;
+import android.support.annotation.IntDef;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import java.lang.annotation.Retention;
+
+import androidx.car.R;
+
+/**
+ * Adapter for {@link PagedListView} to display {@link ListItem}.
+ *
+ * Implements {@link PagedListView.ItemCap} - defaults to unlimited item count.
+ */
+public class ListItemAdapter extends
+        RecyclerView.Adapter<ListItemAdapter.ViewHolder> implements PagedListView.ItemCap {
+    @Retention(SOURCE)
+    @IntDef({CAR_PAGED_LIST_ITEM, CAR_PAGED_LIST_CARD})
+    public @interface PagedListItemType {}
+    public static final int CAR_PAGED_LIST_ITEM = 0;
+    public static final int CAR_PAGED_LIST_CARD = 1;
+
+    private final Context mContext;
+    private final ListItemProvider mItemProvider;
+
+    private int mMaxItems = PagedListView.ItemCap.UNLIMITED;
+
+    public ListItemAdapter(Context context, ListItemProvider itemProvider) {
+        mContext = context;
+        mItemProvider = itemProvider;
+    }
+
+    @Override
+    public int getItemViewType(int position) {
+        return mItemProvider.get(position).getViewType();
+    }
+
+    @Override
+    public ViewHolder onCreateViewHolder(ViewGroup parent, @PagedListItemType int viewType) {
+        LayoutInflater inflater = LayoutInflater.from(mContext);
+        int layoutId;
+        switch (viewType) {
+            case CAR_PAGED_LIST_ITEM:
+                layoutId = R.layout.car_paged_list_item;
+                break;
+            case CAR_PAGED_LIST_CARD:
+                layoutId = R.layout.car_paged_list_card;
+                break;
+            default:
+                throw new IllegalArgumentException("Unrecognizable view type: " + viewType);
+        }
+        View itemView = inflater.inflate(layoutId, parent, false);
+        return new ViewHolder(itemView);
+    }
+
+    @Override
+    public void onBindViewHolder(ViewHolder holder, int position) {
+        ListItem item = mItemProvider.get(position);
+        item.bind(holder);
+    }
+
+    @Override
+    public int getItemCount() {
+        return mMaxItems == PagedListView.ItemCap.UNLIMITED
+                ? mItemProvider.size()
+                : Math.min(mItemProvider.size(), mMaxItems);
+    }
+
+    @Override
+    public void setMaxItems(int maxItems) {
+        mMaxItems = maxItems;
+    }
+
+    /**
+     * Holds views of an item in PagedListView.
+     *
+     * <p>This ViewHolder maps to views in layout car_paged_list_item_content.xml.
+     */
+    public static class ViewHolder extends RecyclerView.ViewHolder {
+
+        private RelativeLayout mContainerLayout;
+
+        private ImageView mPrimaryIcon;
+
+        private TextView mTitle;
+        private TextView mBody;
+
+        private View mSupplementalIconDivider;
+        private ImageView mSupplementalIcon;
+
+        private Button mAction1;
+        private View mAction1Divider;
+
+        private Button mAction2;
+        private View mAction2Divider;
+
+        public ViewHolder(View itemView) {
+            super(itemView);
+
+            mContainerLayout = itemView.findViewById(R.id.container);
+
+            mPrimaryIcon = itemView.findViewById(R.id.primary_icon);
+
+            mTitle = itemView.findViewById(R.id.title);
+            mBody = itemView.findViewById(R.id.body);
+
+            mSupplementalIcon = itemView.findViewById(R.id.supplemental_icon);
+            mSupplementalIconDivider = itemView.findViewById(R.id.supplemental_icon_divider);
+
+            mAction1 = itemView.findViewById(R.id.action1);
+            mAction1Divider = itemView.findViewById(R.id.action1_divider);
+            mAction2 = itemView.findViewById(R.id.action2);
+            mAction2Divider = itemView.findViewById(R.id.action2_divider);
+        }
+
+        public RelativeLayout getContainerLayout() {
+            return mContainerLayout;
+        }
+
+        public ImageView getPrimaryIcon() {
+            return mPrimaryIcon;
+        }
+
+        public TextView getTitle() {
+            return mTitle;
+        }
+
+        public TextView getBody() {
+            return mBody;
+        }
+
+        public ImageView getSupplementalIcon() {
+            return mSupplementalIcon;
+        }
+
+        public View getSupplementalIconDivider() {
+            return mSupplementalIconDivider;
+        }
+
+        public Button getAction1() {
+            return mAction1;
+        }
+
+        public View getAction1Divider() {
+            return mAction1Divider;
+        }
+
+        public Button getAction2() {
+            return mAction2;
+        }
+
+        public View getAction2Divider() {
+            return mAction2Divider;
+        }
+    }
+}
diff --git a/car/src/main/java/androidx/car/widget/ListItemProvider.java b/car/src/main/java/androidx/car/widget/ListItemProvider.java
new file mode 100644
index 0000000..5a3edbd
--- /dev/null
+++ b/car/src/main/java/androidx/car/widget/ListItemProvider.java
@@ -0,0 +1,56 @@
+/*
+ * 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 androidx.car.widget;
+
+import java.util.List;
+
+/**
+ * Supplies data as {@link ListItem}.
+ */
+public abstract class ListItemProvider {
+
+    /**
+     * Returns {@link ListItem} at requested position.
+     */
+    public abstract ListItem get(int position);
+
+    /**
+     * @return number of total items.
+     */
+    public abstract int size();
+
+    /**
+     * A simple provider that wraps around a list.
+     */
+    public static class ListProvider extends ListItemProvider {
+        private final List<ListItem> mItems;
+
+        public ListProvider(List<ListItem> items) {
+            mItems = items;
+        }
+
+        @Override
+        public ListItem get(int position) {
+            return mItems.get(position);
+        }
+
+        @Override
+        public int size() {
+            return mItems.size();
+        }
+    }
+}
diff --git a/car/src/main/java/androidx/car/widget/PagedLayoutManager.java b/car/src/main/java/androidx/car/widget/PagedLayoutManager.java
new file mode 100644
index 0000000..cf3b75d
--- /dev/null
+++ b/car/src/main/java/androidx/car/widget/PagedLayoutManager.java
@@ -0,0 +1,1688 @@
+/*
+ * 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 androidx.car.widget;
+
+import android.content.Context;
+import android.graphics.PointF;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.annotation.IntDef;
+import android.support.annotation.NonNull;
+import android.support.annotation.VisibleForTesting;
+import android.support.v7.widget.LinearSmoothScroller;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerView.Recycler;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.LruCache;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.Animation;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.view.animation.Transformation;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+
+import androidx.car.R;
+
+/**
+ * Custom {@link RecyclerView.LayoutManager} that behaves similar to LinearLayoutManager except that
+ * it has a few tricks up its sleeve.
+ *
+ * <ol>
+ *   <li>In a normal ListView, when views reach the top of the list, they are clipped. In
+ *       PagedLayoutManager, views have the option of flying off of the top of the screen as the
+ *       next row settles in to place. This functionality can be enabled or disabled with
+ *       {@link #setOffsetRows(boolean)}.
+ *   <li>Standard list physics is disabled. Instead, when the user scrolls, it will settle on the
+ *       next page.
+ *   <li>Items can scroll past the bottom edge of the screen. This helps with pagination so that the
+ *       last page can be properly aligned.
+ * </ol>
+ *
+ * This LayoutManger should be used with {@link CarRecyclerView}.
+ */
+public class PagedLayoutManager extends RecyclerView.LayoutManager {
+    private static final String TAG = "PagedLayoutManager";
+
+    /**
+     * Any fling below the threshold will just scroll to the top fully visible row. The units is
+     * whatever {@link android.widget.Scroller} would return.
+     *
+     * <p>A reasonable value is ~200
+     *
+     * <p>This can be disabled by setting the threshold to -1.
+     */
+    private static final int FLING_THRESHOLD_TO_PAGINATE = -1;
+
+    /**
+     * Any fling shorter than this threshold (in px) will just scroll to the top fully visible row.
+     *
+     * <p>A reasonable value is 15.
+     *
+     * <p>This can be disabled by setting the distance to -1.
+     */
+    private static final int DRAG_DISTANCE_TO_PAGINATE = -1;
+
+    /**
+     * If you scroll really quickly, you can hit the end of the laid out rows before Android has a
+     * chance to layout more. To help counter this, we can layout a number of extra rows past
+     * wherever the focus is if necessary.
+     */
+    private static final int NUM_EXTRA_ROWS_TO_LAYOUT_PAST_FOCUS = 2;
+
+    /**
+     * Scroll bar calculation is a bit complicated. This basically defines the granularity we want
+     * our scroll bar to move. Set this to 1 means our scrollbar will have really jerky movement.
+     * Setting it too big will risk an overflow (although there is no performance impact). Ideally
+     * we want to set this higher than the height of our list view. We can't use our list view
+     * height directly though because we might run into situations where getHeight() returns 0,
+     * for example, when the view is not yet measured.
+     */
+    private static final int SCROLL_RANGE = 1000;
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({BEFORE, AFTER})
+    private @interface LayoutDirection {}
+
+    private static final int BEFORE = 0;
+    private static final int AFTER = 1;
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({ROW_OFFSET_MODE_INDIVIDUAL, ROW_OFFSET_MODE_PAGE})
+    public @interface RowOffsetMode {}
+
+    public static final int ROW_OFFSET_MODE_INDIVIDUAL = 0;
+    public static final int ROW_OFFSET_MODE_PAGE = 1;
+
+    private final AccelerateInterpolator mDanglingRowInterpolator = new AccelerateInterpolator(2);
+    private final Context mContext;
+
+    /** Determines whether or not rows will be offset as they slide off screen * */
+    private boolean mOffsetRows;
+
+    /** Determines whether rows will be offset individually or a page at a time * */
+    @RowOffsetMode private int mRowOffsetMode = ROW_OFFSET_MODE_PAGE;
+
+    /**
+     * The LayoutManager only gets {@link #onScrollStateChanged(int)} updates. This enables the
+     * scroll state to be used anywhere.
+     */
+    private int mScrollState = RecyclerView.SCROLL_STATE_IDLE;
+
+    /** Used to inspect the current scroll state to help with the various calculations. */
+    private CarSmoothScroller mSmoothScroller;
+
+    private PagedListView.OnScrollListener mOnScrollListener;
+
+    /** The distance that the list has actually scrolled in the most recent drag gesture. */
+    private int mLastDragDistance = 0;
+
+    /** {@code True} if the current drag was limited/capped because it was at some boundary. */
+    private boolean mReachedLimitOfDrag;
+
+    /** The index of the first item on the current page. */
+    private int mAnchorPageBreakPosition = 0;
+
+    /** The index of the first item on the previous page. */
+    private int mUpperPageBreakPosition = -1;
+
+    /** The index of the first item on the next page. */
+    private int mLowerPageBreakPosition = -1;
+
+    /** Used in the bookkeeping of mario style scrolling to prevent extra calculations. */
+    private int mLastChildPositionToRequestFocus = -1;
+
+    private int mSampleViewHeight = -1;
+
+    /** Used for onPageUp and onPageDown */
+    private int mViewsPerPage = 1;
+
+    private int mCurrentPage = 0;
+
+    private static final int MAX_ANIMATIONS_IN_CACHE = 30;
+    /**
+     * Cache of TranslateAnimation per child view. These are needed since using a single animation
+     * for all children doesn't apply the animation effect multiple times. Key = the view the
+     * animation will transform.
+     */
+    private LruCache<View, TranslateAnimation> mFlyOffscreenAnimations;
+
+    /** Set the anchor to the following position on the next layout pass. */
+    private int mPendingScrollPosition = -1;
+
+    public PagedLayoutManager(Context context) {
+        mContext = context;
+    }
+
+    @Override
+    public RecyclerView.LayoutParams generateDefaultLayoutParams() {
+        return new RecyclerView.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+    }
+
+    @Override
+    public boolean canScrollVertically() {
+        return true;
+    }
+
+    /**
+     * onLayoutChildren is sort of like a "reset" for the layout state. At a high level, it should:
+     *
+     * <ol>
+     *   <li>Check the current views to get the current state of affairs
+     *   <li>Detach all views from the window (a lightweight operation) so that rows not re-added
+     *       will be removed after onLayoutChildren.
+     *   <li>Re-add rows as necessary.
+     * </ol>
+     *
+     * @see super#onLayoutChildren(RecyclerView.Recycler, RecyclerView.State)
+     */
+    @Override
+    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+        /*
+         * The anchor view is the first fully visible view on screen at the beginning of
+         * onLayoutChildren (or 0 if there is none). This row will be laid out first. After that,
+         * layoutNextRow will layout rows above and below it until the boundaries of what should be
+         * laid out have been reached. See shouldLayoutNextRow(View, int) for more info.
+         */
+        int anchorPosition = 0;
+        int anchorTop = -1;
+        if (mPendingScrollPosition == -1) {
+            View anchor = getFirstFullyVisibleChild();
+            if (anchor != null) {
+                anchorPosition = getPosition(anchor);
+                anchorTop = getDecoratedTop(anchor);
+            }
+        } else {
+            anchorPosition = mPendingScrollPosition;
+            mPendingScrollPosition = -1;
+            mAnchorPageBreakPosition = anchorPosition;
+            mUpperPageBreakPosition = -1;
+            mLowerPageBreakPosition = -1;
+        }
+
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(
+                    TAG,
+                    String.format(
+                            ":: onLayoutChildren anchorPosition:%s, anchorTop:%s,"
+                                    + " mPendingScrollPosition: %s, mAnchorPageBreakPosition:%s,"
+                                    + " mUpperPageBreakPosition:%s, mLowerPageBreakPosition:%s",
+                            anchorPosition,
+                            anchorTop,
+                            mPendingScrollPosition,
+                            mAnchorPageBreakPosition,
+                            mUpperPageBreakPosition,
+                            mLowerPageBreakPosition));
+        }
+
+        /*
+         * Detach all attached view for 2 reasons:
+         *
+         * 1) So that views are put in the scrap heap. This enables us to call {@link
+         *    RecyclerView.Recycler#getViewForPosition(int)} which will either return one of these
+         *    detached views if it is in the scrap heap, one from the recycled pool (will only call
+         *    onBind in the adapter), or create an entirely new row if needed (will call onCreate
+         *    and onBind in the adapter).
+         * 2) So that views are automatically removed if they are not manually re-added.
+         */
+        detachAndScrapAttachedViews(recycler);
+
+        /*
+         * Layout the views recursively.
+         *
+         * It's possible that this re-layout is triggered because an item gets removed. If the
+         * anchor view is at the end of the list, the anchor view position will be bigger than the
+         * number of available items. Correct that, and only start the layout if the anchor
+         * position is valid.
+         */
+        anchorPosition = Math.min(anchorPosition, getItemCount() - 1);
+        if (anchorPosition >= 0) {
+            View anchor = layoutAnchor(recycler, anchorPosition, anchorTop);
+            View adjacentRow = anchor;
+            while (shouldLayoutNextRow(state, adjacentRow, BEFORE)) {
+                adjacentRow = layoutNextRow(recycler, adjacentRow, BEFORE);
+            }
+            adjacentRow = anchor;
+            while (shouldLayoutNextRow(state, adjacentRow, AFTER)) {
+                adjacentRow = layoutNextRow(recycler, adjacentRow, AFTER);
+            }
+        }
+
+        updatePageBreakPositions();
+        offsetRows();
+
+        if (Log.isLoggable(TAG, Log.VERBOSE) && getChildCount() > 1) {
+            Log.v(TAG, "Currently showing "
+                    + getChildCount()
+                    + " views "
+                    + getPosition(getChildAt(0))
+                    + " to "
+                    + getPosition(getChildAt(getChildCount() - 1))
+                    + " anchor "
+                    + anchorPosition);
+        }
+        // Should be at least 1
+        mViewsPerPage =
+                Math.max(getLastFullyVisibleChildIndex() + 1 - getFirstFullyVisibleChildIndex(), 1);
+        mCurrentPage = getFirstFullyVisibleChildPosition() / mViewsPerPage;
+        Log.v(TAG, "viewsPerPage " + mViewsPerPage);
+    }
+
+    /**
+     * scrollVerticallyBy does the work of what should happen when the list scrolls in addition to
+     * handling cases where the list hits the end. It should be lighter weight than
+     * onLayoutChildren. It doesn't have to detach all views. It only looks at the end of the list
+     * and removes views that have gone out of bounds and lays out new ones that scroll in.
+     *
+     * @param dy The amount that the list is supposed to scroll. > 0 means the list is scrolling
+     *     down. < 0 means the list is scrolling up.
+     * @param recycler The recycler that enables views to be reused or created as they scroll in.
+     * @param state Various information about the current state of affairs.
+     * @return The amount the list actually scrolled.
+     * @see super#scrollVerticallyBy(int, RecyclerView.Recycler, RecyclerView.State)
+     */
+    @Override
+    public int scrollVerticallyBy(
+            int dy, @NonNull RecyclerView.Recycler recycler, @NonNull RecyclerView.State state) {
+        // If the list is empty, we can prevent the overscroll glow from showing by just
+        // telling RecycerView that we scrolled.
+        if (getItemCount() == 0) {
+            return dy;
+        }
+
+        // Prevent redundant computations if there is definitely nowhere to scroll to.
+        if (getChildCount() <= 1 || dy == 0) {
+            mReachedLimitOfDrag = true;
+            return 0;
+        }
+
+        View firstChild = getChildAt(0);
+        if (firstChild == null) {
+            mReachedLimitOfDrag = true;
+            return 0;
+        }
+        int firstChildPosition = getPosition(firstChild);
+        RecyclerView.LayoutParams firstChildParams = getParams(firstChild);
+        int firstChildTopWithMargin = getDecoratedTop(firstChild) - firstChildParams.topMargin;
+
+        View lastFullyVisibleView = getChildAt(getLastFullyVisibleChildIndex());
+        if (lastFullyVisibleView == null) {
+            mReachedLimitOfDrag = true;
+            return 0;
+        }
+        boolean isLastViewVisible = getPosition(lastFullyVisibleView) == getItemCount() - 1;
+
+        View firstFullyVisibleChild = getFirstFullyVisibleChild();
+        if (firstFullyVisibleChild == null) {
+            mReachedLimitOfDrag = true;
+            return 0;
+        }
+        int firstFullyVisiblePosition = getPosition(firstFullyVisibleChild);
+        RecyclerView.LayoutParams firstFullyVisibleChildParams = getParams(firstFullyVisibleChild);
+        int topRemainingSpace =
+                getDecoratedTop(firstFullyVisibleChild)
+                        - firstFullyVisibleChildParams.topMargin
+                        - getPaddingTop();
+
+        if (isLastViewVisible
+                && firstFullyVisiblePosition == mAnchorPageBreakPosition
+                && dy > topRemainingSpace
+                && dy > 0) {
+            // Prevent dragging down more than 1 page. As a side effect, this also prevents you
+            // from dragging past the bottom because if you are on the second to last page, it
+            // prevents you from dragging past the last page.
+            dy = topRemainingSpace;
+            mReachedLimitOfDrag = true;
+        } else if (dy < 0
+                && firstChildPosition == 0
+                && firstChildTopWithMargin + Math.abs(dy) > getPaddingTop()) {
+            // Prevent scrolling past the beginning
+            dy = firstChildTopWithMargin - getPaddingTop();
+            mReachedLimitOfDrag = true;
+        } else {
+            mReachedLimitOfDrag = false;
+        }
+
+        boolean isDragging = mScrollState == RecyclerView.SCROLL_STATE_DRAGGING;
+        if (isDragging) {
+            mLastDragDistance += dy;
+        }
+        // We offset by -dy because the views translate in the opposite direction that the
+        // list scrolls (think about it.)
+        offsetChildrenVertical(-dy);
+
+        // The last item in the layout should never scroll above the viewport
+        View view = getChildAt(getChildCount() - 1);
+        if (view.getTop() < 0) {
+            view.setTop(0);
+        }
+
+        // This is the meat of this function. We remove views on the trailing edge of the scroll
+        // and add views at the leading edge as necessary.
+        View adjacentRow;
+        if (dy > 0) {
+            recycleChildrenFromStart(recycler);
+            adjacentRow = getChildAt(getChildCount() - 1);
+            while (shouldLayoutNextRow(state, adjacentRow, AFTER)) {
+                adjacentRow = layoutNextRow(recycler, adjacentRow, AFTER);
+            }
+        } else {
+            recycleChildrenFromEnd(recycler);
+            adjacentRow = getChildAt(0);
+            while (shouldLayoutNextRow(state, adjacentRow, BEFORE)) {
+                adjacentRow = layoutNextRow(recycler, adjacentRow, BEFORE);
+            }
+        }
+        // Now that the correct views are laid out, offset rows as necessary so we can do whatever
+        // fancy animation we want such as having the top view fly off the screen as the next one
+        // settles in to place.
+        updatePageBreakPositions();
+        offsetRows();
+
+        if (getChildCount() > 1) {
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(
+                        TAG,
+                        String.format(
+                                "Currently showing  %d views (%d to %d)",
+                                getChildCount(),
+                                getPosition(getChildAt(0)),
+                                getPosition(getChildAt(getChildCount() - 1))));
+            }
+        }
+        updatePagedState();
+        return dy;
+    }
+
+    private void updatePagedState() {
+        int page = getFirstFullyVisibleChildPosition() / mViewsPerPage;
+        if (mOnScrollListener != null) {
+            if (page > mCurrentPage) {
+                mOnScrollListener.onPageDown();
+            } else if (page < mCurrentPage) {
+                mOnScrollListener.onPageUp();
+            }
+        }
+        mCurrentPage = page;
+    }
+
+    @Override
+    public void scrollToPosition(int position) {
+        mPendingScrollPosition = position;
+        requestLayout();
+    }
+
+    @Override
+    public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state,
+            int position) {
+        /*
+         * startSmoothScroll will handle stopping the old one if there is one. We only keep a copy
+         * of it to handle the translation of rows as they slide off the screen in
+         * offsetRowsWithPageBreak().
+         */
+        mSmoothScroller = new CarSmoothScroller(mContext, position);
+        mSmoothScroller.setTargetPosition(position);
+        startSmoothScroll(mSmoothScroller);
+    }
+
+    /** Miscellaneous bookkeeping. */
+    @Override
+    public void onScrollStateChanged(int state) {
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, ":: onScrollStateChanged " + state);
+        }
+        if (state == RecyclerView.SCROLL_STATE_IDLE) {
+            // If the focused view is off screen, give focus to one that is.
+            // If the first fully visible view is first in the list, focus the first item.
+            // Otherwise, focus the second so that you have the first item as scrolling context.
+            View focusedChild = getFocusedChild();
+            if (focusedChild != null
+                    && (getDecoratedTop(focusedChild) >= getHeight() - getPaddingBottom()
+                    || getDecoratedBottom(focusedChild) <= getPaddingTop())) {
+                focusedChild.clearFocus();
+                requestLayout();
+            }
+
+        } else if (state == RecyclerView.SCROLL_STATE_DRAGGING) {
+            mLastDragDistance = 0;
+        }
+
+        if (state != RecyclerView.SCROLL_STATE_SETTLING) {
+            mSmoothScroller = null;
+        }
+
+        mScrollState = state;
+        updatePageBreakPositions();
+    }
+
+    @Override
+    public void onItemsChanged(RecyclerView recyclerView) {
+        super.onItemsChanged(recyclerView);
+        // When item changed, our sample view height is no longer accurate, and need to be
+        // recomputed.
+        mSampleViewHeight = -1;
+    }
+
+    /**
+     * Gives us the opportunity to override the order of the focused views. By default, it will just
+     * go from top to bottom. However, if there is no focused views, we take over the logic and
+     * start the focused views from the middle of what is visible and move from there until the
+     * end of the laid out views in the specified direction.
+     */
+    @Override
+    public boolean onAddFocusables(
+            RecyclerView recyclerView, ArrayList<View> views, int direction, int focusableMode) {
+        View focusedChild = getFocusedChild();
+        if (focusedChild != null) {
+            // If there is a view that already has focus, we can just return false and the normal
+            // Android addFocusables will work fine.
+            return false;
+        }
+
+        // Now we know that there isn't a focused view. We need to set up focusables such that
+        // instead of just focusing the first item that has been laid out, it focuses starting
+        // from a visible item.
+
+        int firstFullyVisibleChildIndex = getFirstFullyVisibleChildIndex();
+        if (firstFullyVisibleChildIndex == -1) {
+            // Somehow there is a focused view but there is no fully visible view. There shouldn't
+            // be a way for this to happen but we'd better stop here and return instead of
+            // continuing on with -1.
+            Log.w(TAG, "There is a focused child but no first fully visible child.");
+            return false;
+        }
+        View firstFullyVisibleChild = getChildAt(firstFullyVisibleChildIndex);
+        int firstFullyVisibleChildPosition = getPosition(firstFullyVisibleChild);
+
+        int firstFocusableChildIndex = firstFullyVisibleChildIndex;
+        if (firstFullyVisibleChildPosition > 0 && firstFocusableChildIndex + 1 < getItemCount()) {
+            // We are somewhere in the middle of the list. Instead of starting focus on the first
+            // item, start focus on the second item to give some context that we aren't at
+            // the beginning.
+            firstFocusableChildIndex++;
+        }
+
+        if (direction == View.FOCUS_FORWARD) {
+            // Iterate from the first focusable view to the end.
+            for (int i = firstFocusableChildIndex; i < getChildCount(); i++) {
+                views.add(getChildAt(i));
+            }
+            return true;
+        } else if (direction == View.FOCUS_BACKWARD) {
+            // Iterate from the first focusable view to the beginning.
+            for (int i = firstFocusableChildIndex; i >= 0; i--) {
+                views.add(getChildAt(i));
+            }
+            return true;
+        } else if (direction == View.FOCUS_DOWN) {
+            // Framework calls onAddFocusables with FOCUS_DOWN direction when the focus is first
+            // gained. Thereafter, it calls onAddFocusables with FOCUS_FORWARD or FOCUS_BACKWARD.
+            // First we try to put the focus back on the last focused item, if it is visible
+            int lastFocusedVisibleChildIndex = getLastFocusedChildIndexIfVisible();
+            if (lastFocusedVisibleChildIndex != -1) {
+                views.add(getChildAt(lastFocusedVisibleChildIndex));
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public View onFocusSearchFailed(
+            View focused, int direction, RecyclerView.Recycler recycler, RecyclerView.State state) {
+        // This doesn't seem to get called the way focus is handled in gearhead...
+        return null;
+    }
+
+    /**
+     * This is the function that decides where to scroll to when a new view is focused. You can get
+     * the position of the currently focused child through the child parameter. Once you have that,
+     * determine where to smooth scroll to and scroll there.
+     *
+     * @param parent The RecyclerView hosting this LayoutManager
+     * @param state Current state of RecyclerView
+     * @param child Direct child of the RecyclerView containing the newly focused view
+     * @param focused The newly focused view. This may be the same view as child or it may be null
+     * @return {@code true} if the default scroll behavior should be suppressed
+     */
+    @Override
+    public boolean onRequestChildFocus(
+            RecyclerView parent, RecyclerView.State state, View child, View focused) {
+        if (child == null) {
+            Log.w(TAG, "onRequestChildFocus with a null child!");
+            return true;
+        }
+
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, String.format(":: onRequestChildFocus child: %s, focused: %s", child,
+                    focused));
+        }
+
+        return onRequestChildFocusMarioStyle(parent, child);
+    }
+
+    /**
+     * Goal: the scrollbar maintains the same size throughout scrolling and that the scrollbar
+     * reaches the bottom of the screen when the last item is fully visible. This is because there
+     * are multiple points that could be considered the bottom since the last item can scroll past
+     * the bottom edge of the screen.
+     *
+     * <p>To find the extent, we divide the number of items that can fit on screen by the number of
+     * items in total.
+     */
+    @Override
+    public int computeVerticalScrollExtent(RecyclerView.State state) {
+        if (getChildCount() <= 1) {
+            return 0;
+        }
+
+        int sampleViewHeight = getSampleViewHeight();
+        int availableHeight = getAvailableHeight();
+        int sampleViewsThatCanFitOnScreen = availableHeight / sampleViewHeight;
+
+        if (state.getItemCount() <= sampleViewsThatCanFitOnScreen) {
+            return SCROLL_RANGE;
+        } else {
+            return SCROLL_RANGE * sampleViewsThatCanFitOnScreen / state.getItemCount();
+        }
+    }
+
+    /**
+     * The scrolling offset is calculated by determining what position is at the top of the list.
+     * However, instead of using fixed integer positions for each row, the scroll position is
+     * factored in and the position is recalculated as a float that takes in to account the
+     * current scroll state. This results in a smooth animation for the scrollbar when the user
+     * scrolls the list.
+     */
+    @Override
+    public int computeVerticalScrollOffset(RecyclerView.State state) {
+        View firstChild = getFirstFullyVisibleChild();
+        if (firstChild == null) {
+            return 0;
+        }
+
+        RecyclerView.LayoutParams params = getParams(firstChild);
+        int firstChildPosition = getPosition(firstChild);
+        float previousChildHieght = (float) (getDecoratedMeasuredHeight(firstChild)
+                + params.topMargin + params.bottomMargin);
+
+        // Assume the previous view is the same height as the current one.
+        float percentOfPreviousViewShowing = (getDecoratedTop(firstChild) - params.topMargin)
+                / previousChildHieght;
+        // If the previous view is actually larger than the current one then this the percent
+        // can be greater than 1.
+        percentOfPreviousViewShowing = Math.min(percentOfPreviousViewShowing, 1);
+
+        float currentPosition = (float) firstChildPosition - percentOfPreviousViewShowing;
+
+        int sampleViewHeight = getSampleViewHeight();
+        int availableHeight = getAvailableHeight();
+        int numberOfSampleViewsThatCanFitOnScreen = availableHeight / sampleViewHeight;
+        int positionWhenLastItemIsVisible =
+                state.getItemCount() - numberOfSampleViewsThatCanFitOnScreen;
+
+        if (positionWhenLastItemIsVisible <= 0) {
+            return 0;
+        }
+
+        if (currentPosition >= positionWhenLastItemIsVisible) {
+            return SCROLL_RANGE;
+        }
+
+        return (int) (SCROLL_RANGE * currentPosition / positionWhenLastItemIsVisible);
+    }
+
+    /**
+     * The range of the scrollbar can be understood as the granularity of how we want the scrollbar
+     * to scroll.
+     */
+    @Override
+    public int computeVerticalScrollRange(RecyclerView.State state) {
+        return SCROLL_RANGE;
+    }
+
+    @Override
+    public void onAttachedToWindow(RecyclerView view) {
+        super.onAttachedToWindow(view);
+        // The purpose of calling this is so that any animation offsets are re-applied. These are
+        // cleared in View.onDetachedFromWindow().
+        // This fixes b/27672379
+        updatePageBreakPositions();
+        offsetRows();
+    }
+
+    @Override
+    public void onDetachedFromWindow(RecyclerView recyclerView, Recycler recycler) {
+        super.onDetachedFromWindow(recyclerView, recycler);
+    }
+
+    /**
+     * @return The first view that starts on screen. It assumes that it fully fits on the screen
+     *     though. If the first fully visible child is also taller than the screen then it will
+     *     still be returned. However, since the LayoutManager snaps to view starts, having a row
+     *     that tall would lead to a broken experience anyways.
+     */
+    public int getFirstFullyVisibleChildIndex() {
+        for (int i = 0; i < getChildCount(); i++) {
+            View child = getChildAt(i);
+            RecyclerView.LayoutParams params = getParams(child);
+            if (getDecoratedTop(child) - params.topMargin >= getPaddingTop()) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * @return The position of first visible child in the list. -1 will be returned if there is no
+     *     child.
+     */
+    public int getFirstFullyVisibleChildPosition() {
+        View child = getFirstFullyVisibleChild();
+        if (child == null) {
+            return -1;
+        }
+        return getPosition(child);
+    }
+
+    /**
+     * @return The position of last visible child in the list. -1 will be returned if there is no
+     *     child.
+     */
+    public int getLastFullyVisibleChildPosition() {
+        View child = getLastFullyVisibleChild();
+        if (child == null) {
+            return -1;
+        }
+        return getPosition(child);
+    }
+
+    /** @return The first View that is completely visible on-screen. */
+    public View getFirstFullyVisibleChild() {
+        int firstFullyVisibleChildIndex = getFirstFullyVisibleChildIndex();
+        View firstChild = null;
+        if (firstFullyVisibleChildIndex != -1) {
+            firstChild = getChildAt(firstFullyVisibleChildIndex);
+        }
+        return firstChild;
+    }
+
+    /** @return The last View that is completely visible on-screen. */
+    public View getLastFullyVisibleChild() {
+        int lastFullyVisibleChildIndex = getLastFullyVisibleChildIndex();
+        View lastChild = null;
+        if (lastFullyVisibleChildIndex != -1) {
+            lastChild = getChildAt(lastFullyVisibleChildIndex);
+        }
+        return lastChild;
+    }
+
+    /**
+     * @return The last view that ends on screen. It assumes that the start is also on screen
+     *     though. If the last fully visible child is also taller than the screen then it will
+     *     still be returned. However, since the LayoutManager snaps to view starts, having a row
+     *     that tall would lead to a broken experience anyways.
+     */
+    public int getLastFullyVisibleChildIndex() {
+        for (int i = getChildCount() - 1; i >= 0; i--) {
+            View child = getChildAt(i);
+            RecyclerView.LayoutParams params = getParams(child);
+            int childBottom = getDecoratedBottom(child) + params.bottomMargin;
+            int listBottom = getHeight() - getPaddingBottom();
+            if (childBottom <= listBottom) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * Returns the index of the child in the list that was last focused and is currently visible to
+     * the user. If no child is found, returns -1.
+     */
+    public int getLastFocusedChildIndexIfVisible() {
+        if (mLastChildPositionToRequestFocus == -1) {
+            return -1;
+        }
+        for (int i = 0; i < getChildCount(); i++) {
+            View child = getChildAt(i);
+            if (getPosition(child) == mLastChildPositionToRequestFocus) {
+                RecyclerView.LayoutParams params = getParams(child);
+                int childBottom = getDecoratedBottom(child) + params.bottomMargin;
+                int listBottom = getHeight() - getPaddingBottom();
+                if (childBottom <= listBottom) {
+                    return i;
+                }
+                break;
+            }
+        }
+        return -1;
+    }
+
+    /** @return Whether or not the first view is fully visible. */
+    public boolean isAtTop() {
+        // getFirstFullyVisibleChildIndex() can return -1 which indicates that there are no views
+        // and also means that the list is at the top.
+        return getFirstFullyVisibleChildIndex() <= 0;
+    }
+
+    /** @return Whether or not the last view is fully visible. */
+    public boolean isAtBottom() {
+        int lastFullyVisibleChildIndex = getLastFullyVisibleChildIndex();
+        if (lastFullyVisibleChildIndex == -1) {
+            return true;
+        }
+        View lastFullyVisibleChild = getChildAt(lastFullyVisibleChildIndex);
+        return getPosition(lastFullyVisibleChild) == getItemCount() - 1;
+    }
+
+    /**
+     * Sets whether or not the rows have an offset animation when it scrolls off-screen. The type
+     * of offset is determined by {@link #setRowOffsetMode(int)}.
+     *
+     * <p>A row being offset means that when they reach the top of the screen, the row is flung off
+     * respectively to the rest of the list. This creates a gap between the offset row(s) and the
+     * list.
+     *
+     * @param offsetRows {@code true} if the rows should be offset.
+     */
+    public void setOffsetRows(boolean offsetRows) {
+        mOffsetRows = offsetRows;
+        if (offsetRows) {
+            // Card animation offsets are only needed when we use the flying off the screen effect
+            if (mFlyOffscreenAnimations == null) {
+                mFlyOffscreenAnimations = new LruCache<>(MAX_ANIMATIONS_IN_CACHE);
+            }
+            offsetRows();
+        } else {
+            int childCount = getChildCount();
+            for (int i = 0; i < childCount; i++) {
+                setCardFlyingEffectOffset(getChildAt(i), 0);
+            }
+            mFlyOffscreenAnimations = null;
+        }
+    }
+
+    /**
+     * Sets the manner of offsetting the rows when they are scrolled off-screen. The rows are either
+     * offset individually or the entire page being scrolled off is offset.
+     *
+     * @param mode One of {@link #ROW_OFFSET_MODE_INDIVIDUAL} or {@link #ROW_OFFSET_MODE_PAGE}.
+     */
+    public void setRowOffsetMode(@RowOffsetMode int mode) {
+        if (mode == mRowOffsetMode) {
+            return;
+        }
+
+        mRowOffsetMode = mode;
+        offsetRows();
+    }
+
+    /**
+     * Sets the listener that will be notified of various scroll events in the list.
+     *
+     * @param listener The on-scroll listener.
+     */
+    public void setOnScrollListener(PagedListView.OnScrollListener listener) {
+        mOnScrollListener = listener;
+    }
+
+    /**
+     * Finish the pagination taking into account where the gesture started (not where we are now).
+     *
+     * @return Whether the list was scrolled as a result of the fling.
+     */
+    public boolean settleScrollForFling(RecyclerView parent, int flingVelocity) {
+        if (getChildCount() == 0) {
+            return false;
+        }
+
+        if (mReachedLimitOfDrag) {
+            return false;
+        }
+
+        // If the fling was too slow or too short, settle on the first fully visible row instead.
+        if (Math.abs(flingVelocity) <= FLING_THRESHOLD_TO_PAGINATE
+                || Math.abs(mLastDragDistance) <= DRAG_DISTANCE_TO_PAGINATE) {
+            int firstFullyVisibleChildIndex = getFirstFullyVisibleChildIndex();
+            if (firstFullyVisibleChildIndex != -1) {
+                int scrollPosition = getPosition(getChildAt(firstFullyVisibleChildIndex));
+                parent.smoothScrollToPosition(scrollPosition);
+                return true;
+            }
+            return false;
+        }
+
+        // Finish the pagination taking into account where the gesture
+        // started (not where we are now).
+        boolean isDownGesture = flingVelocity > 0 || (flingVelocity == 0 && mLastDragDistance >= 0);
+        boolean isUpGesture = flingVelocity < 0 || (flingVelocity == 0 && mLastDragDistance < 0);
+        if (isDownGesture && mLowerPageBreakPosition != -1) {
+            // If the last view is fully visible then only settle on the first fully visible view
+            // instead of the original page down position. However, don't page down if the last
+            // item has come fully into view.
+            parent.smoothScrollToPosition(mAnchorPageBreakPosition);
+            if (mOnScrollListener != null) {
+                mOnScrollListener.onGestureDown();
+            }
+            return true;
+        } else if (isUpGesture && mUpperPageBreakPosition != -1) {
+            parent.smoothScrollToPosition(mUpperPageBreakPosition);
+            if (mOnScrollListener != null) {
+                mOnScrollListener.onGestureUp();
+            }
+            return true;
+        } else {
+            Log.e(
+                    TAG,
+                    "Error setting scroll for fling! flingVelocity: \t"
+                            + flingVelocity
+                            + "\tlastDragDistance: "
+                            + mLastDragDistance
+                            + "\tpageUpAtStartOfDrag: "
+                            + mUpperPageBreakPosition
+                            + "\tpageDownAtStartOfDrag: "
+                            + mLowerPageBreakPosition);
+            // As a last resort, at the last smooth scroller target position if there is one.
+            if (mSmoothScroller != null) {
+                parent.smoothScrollToPosition(mSmoothScroller.getTargetPosition());
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /** @return The position that paging up from the current position would settle at. */
+    public int getPageUpPosition() {
+        return mUpperPageBreakPosition;
+    }
+
+    /** @return The position that paging down from the current position would settle at. */
+    public int getPageDownPosition() {
+        return mLowerPageBreakPosition;
+    }
+
+    @Override
+    public Parcelable onSaveInstanceState() {
+        SavedState savedState = new SavedState();
+        savedState.mFirstChildPosition = getFirstFullyVisibleChildPosition();
+        return savedState;
+    }
+
+    @Override
+    public void onRestoreInstanceState(Parcelable state) {
+        if (state instanceof SavedState) {
+            scrollToPosition(((SavedState) state).mFirstChildPosition);
+        }
+    }
+
+    /** The state that will be saved across configuration changes. */
+    static class SavedState implements Parcelable {
+        /** The position of the first visible child view in the list. */
+        int mFirstChildPosition;
+
+        SavedState() {}
+
+        private SavedState(Parcel in) {
+            mFirstChildPosition = in.readInt();
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(mFirstChildPosition);
+        }
+
+        public static final Parcelable.Creator<SavedState> CREATOR =
+                new Parcelable.Creator<SavedState>() {
+                    @Override
+                    public SavedState createFromParcel(Parcel in) {
+                        return new SavedState(in);
+                    }
+
+                    @Override
+                    public SavedState[] newArray(int size) {
+                        return new SavedState[size];
+                    }
+                };
+    }
+
+    /**
+     * Layout the anchor row. The anchor row is the first fully visible row.
+     *
+     * @param anchorTop The decorated top of the anchor. If it is not known or should be reset to
+     *     the top, pass -1.
+     */
+    private View layoutAnchor(RecyclerView.Recycler recycler, int anchorPosition, int anchorTop) {
+        View anchor = recycler.getViewForPosition(anchorPosition);
+        RecyclerView.LayoutParams params = getParams(anchor);
+        measureChildWithMargins(anchor, 0, 0);
+        int left = getPaddingLeft() + params.leftMargin;
+        int top = (anchorTop == -1) ? params.topMargin : anchorTop;
+        int right = left + getDecoratedMeasuredWidth(anchor);
+        int bottom = top + getDecoratedMeasuredHeight(anchor);
+        layoutDecorated(anchor, left, top, right, bottom);
+        addView(anchor);
+        return anchor;
+    }
+
+    /**
+     * Lays out the next row in the specified direction next to the specified adjacent row.
+     *
+     * @param recycler The recycler from which a new view can be created.
+     * @param adjacentRow The View of the adjacent row which will be used to position the new one.
+     * @param layoutDirection The side of the adjacent row that the new row will be laid out on.
+     * @return The new row that was laid out.
+     */
+    private View layoutNextRow(RecyclerView.Recycler recycler, View adjacentRow,
+            @LayoutDirection int layoutDirection) {
+        int adjacentRowPosition = getPosition(adjacentRow);
+        int newRowPosition = adjacentRowPosition;
+        if (layoutDirection == BEFORE) {
+            newRowPosition = adjacentRowPosition - 1;
+        } else if (layoutDirection == AFTER) {
+            newRowPosition = adjacentRowPosition + 1;
+        }
+
+        // Because we detach all rows in onLayoutChildren, this will often just return a view from
+        // the scrap heap.
+        View newRow = recycler.getViewForPosition(newRowPosition);
+
+        measureChildWithMargins(newRow, 0, 0);
+        RecyclerView.LayoutParams newRowParams =
+                (RecyclerView.LayoutParams) newRow.getLayoutParams();
+        RecyclerView.LayoutParams adjacentRowParams =
+                (RecyclerView.LayoutParams) adjacentRow.getLayoutParams();
+        int left = getPaddingLeft() + newRowParams.leftMargin;
+        int right = left + getDecoratedMeasuredWidth(newRow);
+        int top;
+        int bottom;
+        if (layoutDirection == BEFORE) {
+            bottom = adjacentRow.getTop() - adjacentRowParams.topMargin - newRowParams.bottomMargin;
+            top = bottom - getDecoratedMeasuredHeight(newRow);
+        } else {
+            top = getDecoratedBottom(adjacentRow) + adjacentRowParams.bottomMargin
+                    + newRowParams.topMargin;
+            bottom = top + getDecoratedMeasuredHeight(newRow);
+        }
+        layoutDecorated(newRow, left, top, right, bottom);
+
+        if (layoutDirection == BEFORE) {
+            addView(newRow, 0);
+        } else {
+            addView(newRow);
+        }
+
+        return newRow;
+    }
+
+    /** @return Whether another row should be laid out in the specified direction. */
+    private boolean shouldLayoutNextRow(
+            RecyclerView.State state, View adjacentRow, @LayoutDirection int layoutDirection) {
+        int adjacentRowPosition = getPosition(adjacentRow);
+
+        if (layoutDirection == BEFORE) {
+            if (adjacentRowPosition == 0) {
+                // We already laid out the first row.
+                return false;
+            }
+        } else if (layoutDirection == AFTER) {
+            if (adjacentRowPosition >= state.getItemCount() - 1) {
+                // We already laid out the last row.
+                return false;
+            }
+        }
+
+        // If we are scrolling layout views until the target position.
+        if (mSmoothScroller != null) {
+            if (layoutDirection == BEFORE
+                    && adjacentRowPosition >= mSmoothScroller.getTargetPosition()) {
+                return true;
+            } else if (layoutDirection == AFTER
+                    && adjacentRowPosition <= mSmoothScroller.getTargetPosition()) {
+                return true;
+            }
+        }
+
+        View focusedRow = getFocusedChild();
+        if (focusedRow != null) {
+            int focusedRowPosition = getPosition(focusedRow);
+            if (layoutDirection == BEFORE && adjacentRowPosition
+                    >= focusedRowPosition - NUM_EXTRA_ROWS_TO_LAYOUT_PAST_FOCUS) {
+                return true;
+            } else if (layoutDirection == AFTER && adjacentRowPosition
+                    <= focusedRowPosition + NUM_EXTRA_ROWS_TO_LAYOUT_PAST_FOCUS) {
+                return true;
+            }
+        }
+
+        RecyclerView.LayoutParams params = getParams(adjacentRow);
+        int adjacentRowTop = getDecoratedTop(adjacentRow) - params.topMargin;
+        int adjacentRowBottom = getDecoratedBottom(adjacentRow) - params.bottomMargin;
+        if (layoutDirection == BEFORE && adjacentRowTop < getPaddingTop() - getHeight()) {
+            // View is more than 1 page past the top of the screen and also past where the user has
+            // scrolled to. We want to keep one page past the top to make the scroll up calculation
+            // easier and scrolling smoother.
+            return false;
+        } else if (layoutDirection == AFTER
+                && adjacentRowBottom > getHeight() - getPaddingBottom()) {
+            // View is off of the bottom and also past where the user has scrolled to.
+            return false;
+        }
+
+        return true;
+    }
+
+    /** Remove and recycle views that are no longer needed. */
+    private void recycleChildrenFromStart(RecyclerView.Recycler recycler) {
+        // Start laying out children one page before the top of the viewport.
+        int childrenStart = getPaddingTop() - getHeight();
+
+        int focusedChildPosition = Integer.MAX_VALUE;
+        View focusedChild = getFocusedChild();
+        if (focusedChild != null) {
+            focusedChildPosition = getPosition(focusedChild);
+        }
+
+        // Count the number of views that should be removed.
+        int detachedCount = 0;
+        int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            final View child = getChildAt(i);
+            int childEnd = getDecoratedBottom(child);
+            int childPosition = getPosition(child);
+
+            if (childEnd >= childrenStart || childPosition >= focusedChildPosition - 1) {
+                break;
+            }
+
+            detachedCount++;
+        }
+
+        // Remove the number of views counted above. Done by removing the first child n times.
+        while (--detachedCount >= 0) {
+            final View child = getChildAt(0);
+            removeAndRecycleView(child, recycler);
+        }
+    }
+
+    /** Remove and recycle views that are no longer needed. */
+    private void recycleChildrenFromEnd(RecyclerView.Recycler recycler) {
+        // Layout views until the end of the viewport.
+        int childrenEnd = getHeight();
+
+        int focusedChildPosition = Integer.MIN_VALUE + 1;
+        View focusedChild = getFocusedChild();
+        if (focusedChild != null) {
+            focusedChildPosition = getPosition(focusedChild);
+        }
+
+        // Count the number of views that should be removed.
+        int firstDetachedPos = 0;
+        int detachedCount = 0;
+        int childCount = getChildCount();
+        for (int i = childCount - 1; i >= 0; i--) {
+            final View child = getChildAt(i);
+            int childStart = getDecoratedTop(child);
+            int childPosition = getPosition(child);
+
+            if (childStart <= childrenEnd || childPosition <= focusedChildPosition - 1) {
+                break;
+            }
+
+            firstDetachedPos = i;
+            detachedCount++;
+        }
+
+        while (--detachedCount >= 0) {
+            final View child = getChildAt(firstDetachedPos);
+            removeAndRecycleView(child, recycler);
+        }
+    }
+
+    /**
+     * Offset rows to do fancy animations. If offset rows was not enabled with
+     * {@link #setOffsetRows}, this will do nothing.
+     *
+     * @see #offsetRowsIndividually
+     * @see #offsetRowsByPage
+     * @see #setOffsetRows
+     */
+    public void offsetRows() {
+        if (!mOffsetRows) {
+            return;
+        }
+
+        if (mRowOffsetMode == ROW_OFFSET_MODE_PAGE) {
+            offsetRowsByPage();
+        } else if (mRowOffsetMode == ROW_OFFSET_MODE_INDIVIDUAL) {
+            offsetRowsIndividually();
+        }
+    }
+
+    /**
+     * Offset the single row that is scrolling off the screen such that by the time the next row
+     * reaches the top, it will have accelerated completely off of the screen.
+     */
+    private void offsetRowsIndividually() {
+        if (getChildCount() == 0) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, ":: offsetRowsIndividually getChildCount=0");
+            }
+            return;
+        }
+
+        // Identify the dangling row. It will be the first row that is at the top of the
+        // list or above.
+        int danglingChildIndex = -1;
+        for (int i = getChildCount() - 1; i >= 0; i--) {
+            View child = getChildAt(i);
+            if (getDecoratedTop(child) - getParams(child).topMargin <= getPaddingTop()) {
+                danglingChildIndex = i;
+                break;
+            }
+        }
+
+        mAnchorPageBreakPosition = danglingChildIndex;
+
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, ":: offsetRowsIndividually danglingChildIndex: " + danglingChildIndex);
+        }
+
+        // Calculate the total amount that the view will need to scroll in order to go completely
+        // off screen.
+        RecyclerView rv = (RecyclerView) getChildAt(0).getParent();
+        int[] locs = new int[2];
+        rv.getLocationInWindow(locs);
+        int listTopInWindow = locs[1] + rv.getPaddingTop();
+        int maxDanglingViewTranslation;
+
+        int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            View child = getChildAt(i);
+            RecyclerView.LayoutParams params = getParams(child);
+
+            maxDanglingViewTranslation = listTopInWindow;
+            // If the child has a negative margin, we'll actually need to translate the view a
+            // little but further to get it completely off screen.
+            if (params.topMargin < 0) {
+                maxDanglingViewTranslation -= params.topMargin;
+            }
+            if (params.bottomMargin < 0) {
+                maxDanglingViewTranslation -= params.bottomMargin;
+            }
+
+            if (i < danglingChildIndex) {
+                child.setAlpha(0f);
+            } else if (i > danglingChildIndex) {
+                child.setAlpha(1f);
+                setCardFlyingEffectOffset(child, 0);
+            } else {
+                int totalScrollDistance =
+                        getDecoratedMeasuredHeight(child) + params.topMargin + params.bottomMargin;
+
+                int distanceLeftInScroll =
+                        getDecoratedBottom(child) + params.bottomMargin - getPaddingTop();
+                float percentageIntoScroll = 1 - distanceLeftInScroll / (float) totalScrollDistance;
+                float interpolatedPercentage =
+                        mDanglingRowInterpolator.getInterpolation(percentageIntoScroll);
+
+                child.setAlpha(1f);
+                setCardFlyingEffectOffset(child, -(maxDanglingViewTranslation
+                        * interpolatedPercentage));
+            }
+        }
+    }
+
+    /**
+     * When the list scrolls, the entire page of rows will offset in one contiguous block. This
+     * significantly reduces the amount of extra motion at the top of the screen.
+     */
+    private void offsetRowsByPage() {
+        View anchorView = findViewByPosition(mAnchorPageBreakPosition);
+        if (anchorView == null) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, ":: offsetRowsByPage anchorView null");
+            }
+            return;
+        }
+        int anchorViewTop = getDecoratedTop(anchorView) - getParams(anchorView).topMargin;
+
+        View upperPageBreakView = findViewByPosition(mUpperPageBreakPosition);
+        int upperViewTop =
+                getDecoratedTop(upperPageBreakView) - getParams(upperPageBreakView).topMargin;
+
+        int scrollDistance = upperViewTop - anchorViewTop;
+
+        int distanceLeft = anchorViewTop - getPaddingTop();
+        float scrollPercentage =
+                (Math.abs(scrollDistance) - distanceLeft) / (float) Math.abs(scrollDistance);
+
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, String.format(":: offsetRowsByPage scrollDistance:%s, distanceLeft:%s, "
+                            + "scrollPercentage:%s",
+                    scrollDistance, distanceLeft, scrollPercentage));
+        }
+
+        // Calculate the total amount that the view will need to scroll in order to go completely
+        // off screen.
+        RecyclerView rv = (RecyclerView) getChildAt(0).getParent();
+        int[] locs = new int[2];
+        rv.getLocationInWindow(locs);
+        int listTopInWindow = locs[1] + rv.getPaddingTop();
+
+        int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            View child = getChildAt(i);
+            int position = getPosition(child);
+            if (position < mUpperPageBreakPosition) {
+                child.setAlpha(0f);
+                setCardFlyingEffectOffset(child, -listTopInWindow);
+            } else if (position < mAnchorPageBreakPosition) {
+                // If the child has a negative margin, we need to offset the row by a little bit
+                // extra so that it moves completely off screen.
+                RecyclerView.LayoutParams params = getParams(child);
+                int extraTranslation = 0;
+                if (params.topMargin < 0) {
+                    extraTranslation -= params.topMargin;
+                }
+                if (params.bottomMargin < 0) {
+                    extraTranslation -= params.bottomMargin;
+                }
+                int translation = (int) ((listTopInWindow + extraTranslation)
+                        * mDanglingRowInterpolator.getInterpolation(scrollPercentage));
+                child.setAlpha(1f);
+                setCardFlyingEffectOffset(child, -translation);
+            } else {
+                child.setAlpha(1f);
+                setCardFlyingEffectOffset(child, 0);
+            }
+        }
+    }
+
+    /**
+     * Apply an offset to this view. This offset is applied post-layout so it doesn't affect when
+     * views are recycled
+     *
+     * @param child The view to apply this to
+     * @param verticalOffset The offset for this child.
+     */
+    private void setCardFlyingEffectOffset(View child, float verticalOffset) {
+        // Ideally instead of doing all this, we could use View.setTranslationY(). However, the
+        // default RecyclerView.ItemAnimator also uses this method which causes layout issues.
+        // See: http://b/25977087
+        TranslateAnimation anim = mFlyOffscreenAnimations.get(child);
+        if (anim == null) {
+            anim = new TranslateAnimation();
+            anim.setFillEnabled(true);
+            anim.setFillAfter(true);
+            anim.setDuration(0);
+            mFlyOffscreenAnimations.put(child, anim);
+        } else if (anim.verticalOffset == verticalOffset) {
+            return;
+        }
+
+        anim.reset();
+        anim.verticalOffset = verticalOffset;
+        anim.setStartTime(Animation.START_ON_FIRST_FRAME);
+        child.setAnimation(anim);
+        anim.startNow();
+    }
+
+    /**
+     * Update the page break positions based on the position of the views on screen. This should be
+     * called whenever view move or change such as during a scroll or layout.
+     */
+    private void updatePageBreakPositions() {
+        if (getChildCount() == 0) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, ":: updatePageBreakPosition getChildCount: 0");
+            }
+            return;
+        }
+
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, String.format(":: #BEFORE updatePageBreakPositions "
+                            + "mAnchorPageBreakPosition:%s, mUpperPageBreakPosition:%s, "
+                            + "mLowerPageBreakPosition:%s",
+                            mAnchorPageBreakPosition, mUpperPageBreakPosition,
+                            mLowerPageBreakPosition));
+        }
+
+        mAnchorPageBreakPosition = getPosition(getFirstFullyVisibleChild());
+
+        if (mAnchorPageBreakPosition == -1) {
+            Log.w(TAG, "Unable to update anchor positions. There is no anchor position.");
+            return;
+        }
+
+        View anchorPageBreakView = findViewByPosition(mAnchorPageBreakPosition);
+        if (anchorPageBreakView == null) {
+            return;
+        }
+        int topMargin = getParams(anchorPageBreakView).topMargin;
+        int anchorTop = getDecoratedTop(anchorPageBreakView) - topMargin;
+        View upperPageBreakView = findViewByPosition(mUpperPageBreakPosition);
+        int upperPageBreakTop = upperPageBreakView == null
+                ? Integer.MIN_VALUE
+                : getDecoratedTop(upperPageBreakView) - getParams(upperPageBreakView).topMargin;
+
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, String.format(":: #MID updatePageBreakPositions topMargin:%s, anchorTop:%s"
+                            + " mAnchorPageBreakPosition:%s, mUpperPageBreakPosition:%s,"
+                            + " mLowerPageBreakPosition:%s",
+                            topMargin,
+                            anchorTop,
+                            mAnchorPageBreakPosition,
+                            mUpperPageBreakPosition,
+                            mLowerPageBreakPosition));
+        }
+
+        if (anchorTop < getPaddingTop()) {
+            // The anchor has moved above the viewport. We are now on the next page. Shift the page
+            // break positions and calculate a new lower one.
+            mUpperPageBreakPosition = mAnchorPageBreakPosition;
+            mAnchorPageBreakPosition = mLowerPageBreakPosition;
+            mLowerPageBreakPosition = calculateNextPageBreakPosition(mAnchorPageBreakPosition);
+        } else if (mAnchorPageBreakPosition > 0 && upperPageBreakTop >= getPaddingTop()) {
+            // The anchor has moved below the viewport. We are now on the previous page. Shift
+            // the page break positions and calculate a new upper one.
+            mLowerPageBreakPosition = mAnchorPageBreakPosition;
+            mAnchorPageBreakPosition = mUpperPageBreakPosition;
+            mUpperPageBreakPosition = calculatePreviousPageBreakPosition(mAnchorPageBreakPosition);
+        } else {
+            mUpperPageBreakPosition = calculatePreviousPageBreakPosition(mAnchorPageBreakPosition);
+            mLowerPageBreakPosition = calculateNextPageBreakPosition(mAnchorPageBreakPosition);
+        }
+
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, String.format(":: #AFTER updatePageBreakPositions"
+                            + " mAnchorPageBreakPosition:%s, mUpperPageBreakPosition:%s,"
+                            + " mLowerPageBreakPosition:%s",
+                            mAnchorPageBreakPosition, mUpperPageBreakPosition,
+                    mLowerPageBreakPosition));
+        }
+    }
+
+    /**
+     * @return The page break position of the page before the anchor page break position. However,
+     *     if it reaches the end of the laid out children or position 0, it will just return that.
+     */
+    @VisibleForTesting
+    int calculatePreviousPageBreakPosition(int position) {
+        if (position == -1) {
+            return -1;
+        }
+        View referenceView = findViewByPosition(position);
+        int referenceViewTop = getDecoratedTop(referenceView) - getParams(referenceView).topMargin;
+
+        int previousPagePosition = position;
+        while (previousPagePosition > 0) {
+            previousPagePosition--;
+            View child = findViewByPosition(previousPagePosition);
+            if (child == null) {
+                // View has not been laid out yet.
+                return previousPagePosition + 1;
+            }
+
+            int childTop = getDecoratedTop(child) - getParams(child).topMargin;
+            if (childTop < referenceViewTop - getHeight()) {
+                return previousPagePosition + 1;
+            }
+        }
+        // Beginning of the list.
+        return 0;
+    }
+
+    /**
+     * @return The page break position of the next page after the anchor page break position.
+     *     However, if it reaches the end of the laid out children or end of the list, it will just
+     *     return that.
+     */
+    @VisibleForTesting
+    int calculateNextPageBreakPosition(int position) {
+        if (position == -1) {
+            return -1;
+        }
+
+        View referenceView = findViewByPosition(position);
+        if (referenceView == null) {
+            return position;
+        }
+        int referenceViewTop = getDecoratedTop(referenceView) - getParams(referenceView).topMargin;
+
+        int nextPagePosition = position;
+
+        // Search for the first child item after the referenceView that didn't fully fit on to the
+        // screen. The next page should start from the item before this child, so that users have
+        // a visual anchoring point of the page change.
+        while (nextPagePosition < getItemCount() - 1) {
+            nextPagePosition++;
+            View child = findViewByPosition(nextPagePosition);
+            if (child == null) {
+                // The next view has not been laid out yet.
+                return nextPagePosition - 1;
+            }
+
+            int childTop = getDecoratedTop(child) - getParams(child).topMargin;
+            if (childTop > referenceViewTop + getHeight()) {
+                // If choosing the previous child causes the view to snap back to the referenceView
+                // position, then skip that and go directly to the child. This avoids the case
+                // where a tall card in the layout causes the view to constantly snap back to
+                // the top when scrolled.
+                return nextPagePosition - 1 == position ? nextPagePosition : nextPagePosition - 1;
+            }
+        }
+        // End of the list.
+        return nextPagePosition;
+    }
+
+    /**
+     * In this style, the focus will scroll down to the middle of the screen and lock there so that
+     * moving in either direction will move the entire list by 1.
+     */
+    private boolean onRequestChildFocusMarioStyle(RecyclerView parent, View child) {
+        int focusedPosition = getPosition(child);
+        if (focusedPosition == mLastChildPositionToRequestFocus) {
+            return true;
+        }
+        mLastChildPositionToRequestFocus = focusedPosition;
+
+        int availableHeight = getAvailableHeight();
+        int focusedChildTop = getDecoratedTop(child);
+        int focusedChildBottom = getDecoratedBottom(child);
+
+        int childIndex = parent.indexOfChild(child);
+        // Iterate through children starting at the focused child to find the child above it to
+        // smooth scroll to such that the focused child will be as close to the middle of the screen
+        // as possible.
+        for (int i = childIndex; i >= 0; i--) {
+            View childAtI = getChildAt(i);
+            if (childAtI == null) {
+                Log.e(TAG, "Child is null at index " + i);
+                continue;
+            }
+            // We haven't found a view that is more than half of the recycler view height above it
+            // but we've reached the top so we can't go any further.
+            if (i == 0) {
+                parent.smoothScrollToPosition(getPosition(childAtI));
+                break;
+            }
+
+            // Because we want to scroll to the first view that is less than half of the screen
+            // away from the focused view, we "look ahead" one view. When the look ahead view
+            // is more than availableHeight / 2 away, the current child at i is the one we want to
+            // scroll to. However, sometimes, that view can be null (ie, if the view is in
+            // transition). In that case, just skip that view.
+
+            View childBefore = getChildAt(i - 1);
+            if (childBefore == null) {
+                continue;
+            }
+            int distanceToChildBeforeFromTop = focusedChildTop - getDecoratedTop(childBefore);
+            int distanceToChildBeforeFromBottom = focusedChildBottom - getDecoratedTop(childBefore);
+
+            if (distanceToChildBeforeFromTop > availableHeight / 2
+                    || distanceToChildBeforeFromBottom > availableHeight) {
+                parent.smoothScrollToPosition(getPosition(childAtI));
+                break;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * We don't actually know the size of every single view, only what is currently laid out. This
+     * makes it difficult to do accurate scrollbar calculations. However, lists in the car often
+     * consist of views with identical heights. Because of that, we can use a single sample view to
+     * do our calculations for. The main exceptions are in the first items of a list (hero card,
+     * last call card, etc) so if the first view is at position 0, we pick the next one.
+     *
+     * @return The decorated measured height of the sample view plus its margins.
+     */
+    private int getSampleViewHeight() {
+        if (mSampleViewHeight != -1) {
+            return mSampleViewHeight;
+        }
+        int sampleViewIndex = getFirstFullyVisibleChildIndex();
+        View sampleView = getChildAt(sampleViewIndex);
+        if (getPosition(sampleView) == 0 && sampleViewIndex < getChildCount() - 1) {
+            sampleView = getChildAt(++sampleViewIndex);
+        }
+        RecyclerView.LayoutParams params = getParams(sampleView);
+        int height = getDecoratedMeasuredHeight(sampleView) + params.topMargin
+                + params.bottomMargin;
+        if (height == 0) {
+            // This can happen if the view isn't measured yet.
+            Log.w(
+                    TAG,
+                    "The sample view has a height of 0. Returning a dummy value for now "
+                            + "that won't be cached.");
+            height = mContext.getResources().getDimensionPixelSize(R.dimen.car_sample_row_height);
+        } else {
+            mSampleViewHeight = height;
+        }
+        return height;
+    }
+
+    /** @return The height of the RecyclerView excluding padding. */
+    private int getAvailableHeight() {
+        return getHeight() - getPaddingTop() - getPaddingBottom();
+    }
+
+    /**
+     * @return {@link RecyclerView.LayoutParams} for the given view or null if it isn't a child of
+     *     {@link RecyclerView}.
+     */
+    private static RecyclerView.LayoutParams getParams(View view) {
+        return (RecyclerView.LayoutParams) view.getLayoutParams();
+    }
+
+    /**
+     * Custom {@link LinearSmoothScroller} that has: a) Custom control over the speed of scrolls. b)
+     * Scrolling snaps to start. All of our scrolling logic depends on that. c) Keeps track of some
+     * state of the current scroll so that can aid in things like the scrollbar calculations.
+     */
+    private final class CarSmoothScroller extends LinearSmoothScroller {
+        /** This value (150) was hand tuned by UX for what felt right. * */
+        private static final float MILLISECONDS_PER_INCH = 150f;
+        /** This value (0.45) was hand tuned by UX for what felt right. * */
+        private static final float DECELERATION_TIME_DIVISOR = 0.45f;
+
+        /** This value (1.8) was hand tuned by UX for what felt right. * */
+        private final Interpolator mInterpolator = new DecelerateInterpolator(1.8f);
+
+        private final int mTargetPosition;
+
+        CarSmoothScroller(Context context, int targetPosition) {
+            super(context);
+            mTargetPosition = targetPosition;
+        }
+
+        @Override
+        public PointF computeScrollVectorForPosition(int i) {
+            if (getChildCount() == 0) {
+                return null;
+            }
+            final int firstChildPos = getPosition(getChildAt(getFirstFullyVisibleChildIndex()));
+            final int direction = (mTargetPosition < firstChildPos) ? -1 : 1;
+            return new PointF(0, direction);
+        }
+
+        @Override
+        protected int getVerticalSnapPreference() {
+            // This is key for most of the scrolling logic that guarantees that scrolling
+            // will settle with a view aligned to the top.
+            return LinearSmoothScroller.SNAP_TO_START;
+        }
+
+        @Override
+        protected void onTargetFound(View targetView, RecyclerView.State state, Action action) {
+            int dy = calculateDyToMakeVisible(targetView, SNAP_TO_START);
+            if (dy == 0) {
+                if (Log.isLoggable(TAG, Log.DEBUG)) {
+                    Log.d(TAG, "Scroll distance is 0");
+                }
+                return;
+            }
+
+            final int time = calculateTimeForDeceleration(dy);
+            if (time > 0) {
+                action.update(0, -dy, time, mInterpolator);
+            }
+        }
+
+        @Override
+        protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
+            return MILLISECONDS_PER_INCH / displayMetrics.densityDpi;
+        }
+
+        @Override
+        protected int calculateTimeForDeceleration(int dx) {
+            return (int) Math.ceil(calculateTimeForScrolling(dx) / DECELERATION_TIME_DIVISOR);
+        }
+
+        @Override
+        public int getTargetPosition() {
+            return mTargetPosition;
+        }
+    }
+
+    /**
+     * Animation that translates a view by the specified amount. Used for card flying off the screen
+     * effect.
+     */
+    private static class TranslateAnimation extends Animation {
+        public float verticalOffset;
+
+        @Override
+        protected void applyTransformation(float interpolatedTime, Transformation t) {
+            super.applyTransformation(interpolatedTime, t);
+            t.getMatrix().setTranslate(0, verticalOffset);
+        }
+    }
+}
diff --git a/car/src/main/java/androidx/car/widget/PagedListView.java b/car/src/main/java/androidx/car/widget/PagedListView.java
new file mode 100644
index 0000000..16593b9
--- /dev/null
+++ b/car/src/main/java/androidx/car/widget/PagedListView.java
@@ -0,0 +1,1059 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.car.widget;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.content.Context;
+import android.content.res.Resources;
+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.os.Parcel;
+import android.os.Parcelable;
+import android.support.annotation.IdRes;
+import android.support.annotation.IntDef;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+import android.support.annotation.UiThread;
+import android.support.v7.widget.RecyclerView;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import androidx.car.R;
+
+/**
+ * Custom {@link android.support.v7.widget.RecyclerView} that displays a list of items that
+ * resembles a {@link android.widget.ListView} but also has page up and page down arrows on the
+ * left side.
+ */
+public class PagedListView extends FrameLayout {
+    /** Default maximum number of clicks allowed on a list */
+    public static final int DEFAULT_MAX_CLICKS = 6;
+
+    /**
+     * Value to pass to {@link #setMaxPages(int)} to indicate there is no restriction on the
+     * maximum number of pages to show.
+     */
+    public static final int UNLIMITED_PAGES = -1;
+
+    /**
+     * The amount of time after settling to wait before autoscrolling to the next page when the user
+     * holds down a pagination button.
+     */
+    protected static final int PAGINATION_HOLD_DELAY_MS = 400;
+
+    private static final String TAG = "PagedListView";
+    private static final int INVALID_RESOURCE_ID = -1;
+
+    protected final CarRecyclerView mRecyclerView;
+    protected final PagedLayoutManager mLayoutManager;
+    protected final Handler mHandler = new Handler();
+    private final boolean mScrollBarEnabled;
+    private final PagedScrollBarView mScrollBarView;
+
+    private int mRowsPerPage = -1;
+    protected RecyclerView.Adapter<? extends RecyclerView.ViewHolder> mAdapter;
+
+    /** Maximum number of pages to show. */
+    private int mMaxPages;
+
+    protected OnScrollListener mOnScrollListener;
+
+    /** Number of visible rows per page */
+    private int mDefaultMaxPages = DEFAULT_MAX_CLICKS;
+
+    /** Used to check if there are more items added to the list. */
+    private int mLastItemCount = 0;
+
+    private boolean mNeedsFocus;
+
+    /**
+     * Interface for a {@link android.support.v7.widget.RecyclerView.Adapter} to cap the number of
+     * items.
+     *
+     * <p>NOTE: it is still up to the adapter to use maxItems in {@link
+     * android.support.v7.widget.RecyclerView.Adapter#getItemCount()}.
+     *
+     * <p>the recommended way would be with:
+     *
+     * <pre>{@code
+     * {@literal@}Override
+     * public int getItemCount() {
+     *   return Math.min(super.getItemCount(), mMaxItems);
+     * }
+     * }</pre>
+     */
+    public interface ItemCap {
+        /**
+         * A value to pass to {@link #setMaxItems(int)} that indicates there should be no limit.
+         */
+        int UNLIMITED = -1;
+
+        /**
+         * Sets the maximum number of items available in the adapter. A value less than '0' means
+         * the list should not be capped.
+         */
+        void setMaxItems(int maxItems);
+    }
+
+    /**
+     * The possible values for @{link #setGutter}. The default value is actually
+     * {@link Gutter#BOTH}.
+     */
+    @IntDef({
+            Gutter.NONE,
+            Gutter.START,
+            Gutter.END,
+            Gutter.BOTH,
+    })
+    public @interface Gutter {
+        /**
+         * No gutter on either side of the list items. The items will span the full width of the
+         * {@link PagedListView}.
+         */
+        int NONE = 0;
+
+        /**
+         * Include a gutter only on the start side (that is, the same side as the scroll bar).
+         */
+        int START = 1;
+
+        /**
+         * Include a gutter only on the end side (that is, the opposite side of the scroll bar).
+         */
+        int END = 2;
+
+        /**
+         * Include a gutter on both sides of the list items. This is the default behaviour.
+         */
+        int BOTH = 3;
+    }
+
+    /**
+     * Interface for a {@link android.support.v7.widget.RecyclerView.Adapter} to set the position
+     * offset for the adapter to load the data.
+     *
+     * <p>For example in the adapter, if the positionOffset is 20, then for position 0 it will show
+     * the item in position 20 instead, for position 1 it will show the item in position 21 instead
+     * and so on.
+     */
+    public interface ItemPositionOffset {
+        /** Sets the position offset for the adapter. */
+        void setPositionOffset(int positionOffset);
+    }
+
+    public PagedListView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0 /*defStyleAttrs*/, 0 /*defStyleRes*/);
+    }
+
+    public PagedListView(Context context, AttributeSet attrs, int defStyleAttrs) {
+        this(context, attrs, defStyleAttrs, 0 /*defStyleRes*/);
+    }
+
+    public PagedListView(Context context, AttributeSet attrs, int defStyleAttrs, int defStyleRes) {
+        this(context, attrs, defStyleAttrs, defStyleRes, 0);
+    }
+
+    public PagedListView(
+            Context context, AttributeSet attrs, int defStyleAttrs, int defStyleRes, int layoutId) {
+        super(context, attrs, defStyleAttrs, defStyleRes);
+        if (layoutId == 0) {
+            layoutId = R.layout.car_paged_recycler_view;
+        }
+        LayoutInflater.from(context).inflate(layoutId, this /*root*/, true /*attachToRoot*/);
+
+        TypedArray a = context.obtainStyledAttributes(
+                attrs, R.styleable.PagedListView, defStyleAttrs, defStyleRes);
+        mRecyclerView = (CarRecyclerView) findViewById(R.id.recycler_view);
+        boolean fadeLastItem = a.getBoolean(R.styleable.PagedListView_fadeLastItem, false);
+        mRecyclerView.setFadeLastItem(fadeLastItem);
+        boolean offsetRows = a.getBoolean(R.styleable.PagedListView_offsetRows, false);
+
+        mMaxPages = getDefaultMaxPages();
+
+        mLayoutManager = new PagedLayoutManager(context);
+        mLayoutManager.setOffsetRows(offsetRows);
+        mRecyclerView.setLayoutManager(mLayoutManager);
+        mRecyclerView.setOnScrollListener(mRecyclerViewOnScrollListener);
+        mRecyclerView.getRecycledViewPool().setMaxRecycledViews(0, 12);
+        mRecyclerView.setItemAnimator(new CarItemAnimator(mLayoutManager));
+
+        if (a.hasValue(R.styleable.PagedListView_gutter)) {
+            int gutter = a.getInt(R.styleable.PagedListView_gutter, Gutter.BOTH);
+            setGutter(gutter);
+        } else if (a.hasValue(R.styleable.PagedListView_offsetScrollBar)) {
+            boolean offsetScrollBar =
+                    a.getBoolean(R.styleable.PagedListView_offsetScrollBar, false);
+            if (offsetScrollBar) {
+                setGutter(Gutter.START);
+            }
+        } else {
+            setGutter(Gutter.BOTH);
+        }
+
+        if (a.getBoolean(R.styleable.PagedListView_showPagedListViewDivider, true)) {
+            int dividerStartMargin = a.getDimensionPixelSize(
+                    R.styleable.PagedListView_dividerStartMargin, 0);
+            int dividerStartId = a.getResourceId(
+                    R.styleable.PagedListView_alignDividerStartTo, INVALID_RESOURCE_ID);
+            int dividerEndId = a.getResourceId(
+                    R.styleable.PagedListView_alignDividerEndTo, INVALID_RESOURCE_ID);
+
+            mRecyclerView.addItemDecoration(new DividerDecoration(context, dividerStartMargin,
+                    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
+        // the event.
+        setClickable(true);
+
+        // Set focusable false explicitly to handle the behavior change in Android O where
+        // clickable view becomes focusable by default.
+        setFocusable(false);
+
+        mScrollBarEnabled = a.getBoolean(R.styleable.PagedListView_scrollBarEnabled, true);
+        mScrollBarView = (PagedScrollBarView) findViewById(R.id.paged_scroll_view);
+        mScrollBarView.setPaginationListener(
+                new PagedScrollBarView.PaginationListener() {
+                    @Override
+                    public void onPaginate(int direction) {
+                        if (direction == PagedScrollBarView.PaginationListener.PAGE_UP) {
+                            mRecyclerView.pageUp();
+                            if (mOnScrollListener != null) {
+                                mOnScrollListener.onScrollUpButtonClicked();
+                            }
+                        } else if (direction == PagedScrollBarView.PaginationListener.PAGE_DOWN) {
+                            mRecyclerView.pageDown();
+                            if (mOnScrollListener != null) {
+                                mOnScrollListener.onScrollDownButtonClicked();
+                            }
+                        } else {
+                            Log.e(TAG, "Unknown pagination direction (" + direction + ")");
+                        }
+                    }
+                });
+
+        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.
+        if (!mScrollBarEnabled) {
+            MarginLayoutParams params = (MarginLayoutParams) mRecyclerView.getLayoutParams();
+            params.setMarginStart(0);
+            mRecyclerView.setLayoutParams(params);
+        }
+
+        setDayNightStyle(DayNightStyle.AUTO);
+        a.recycle();
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        mHandler.removeCallbacks(mUpdatePaginationRunnable);
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent e) {
+        if (e.getAction() == MotionEvent.ACTION_DOWN) {
+            // The user has interacted with the list using touch. All movements will now paginate
+            // the list.
+            mLayoutManager.setRowOffsetMode(PagedLayoutManager.ROW_OFFSET_MODE_PAGE);
+        }
+        return super.onInterceptTouchEvent(e);
+    }
+
+    @Override
+    public void requestChildFocus(View child, View focused) {
+        super.requestChildFocus(child, focused);
+        // The user has interacted with the list using the controller. Movements through the list
+        // will now be one row at a time.
+        mLayoutManager.setRowOffsetMode(PagedLayoutManager.ROW_OFFSET_MODE_INDIVIDUAL);
+    }
+
+    /**
+     * Returns the position of the given View in the list.
+     *
+     * @param v The View to check for.
+     * @return The position or -1 if the given View is {@code null} or not in the list.
+     */
+    public int positionOf(@Nullable View v) {
+        if (v == null || v.getParent() != mRecyclerView) {
+            return -1;
+        }
+        return mLayoutManager.getPosition(v);
+    }
+
+    /**
+     * Set the gutter to the specified value.
+     *
+     * The gutter is the space to the start/end of the list view items and will be equal in size
+     * to the scroll bars. By default, there is a gutter to both the left and right of the list
+     * view items, to account for the scroll bar.
+     *
+     * @param gutter A {@link Gutter} value that identifies which sides to apply the gutter to.
+     */
+    public void setGutter(@Gutter int gutter) {
+        int startPadding = 0;
+        int endPadding = 0;
+        if ((gutter & Gutter.START) != 0) {
+            startPadding = getResources().getDimensionPixelSize(R.dimen.car_margin);
+        }
+        if ((gutter & Gutter.END) != 0) {
+            endPadding = getResources().getDimensionPixelSize(R.dimen.car_margin);
+        }
+        mRecyclerView.setPaddingRelative(startPadding, 0, endPadding, 0);
+
+        // If there's a gutter, set ClipToPadding to false so that CardView's shadow will still
+        // appear outside of the padding.
+        mRecyclerView.setClipToPadding(startPadding == 0 && endPadding == 0);
+    }
+
+    @NonNull
+    public CarRecyclerView getRecyclerView() {
+        return mRecyclerView;
+    }
+
+    /**
+     * Scrolls to the given position in the PagedListView.
+     *
+     * @param position The position in the list to scroll to.
+     */
+    public void scrollToPosition(int position) {
+        mLayoutManager.scrollToPosition(position);
+
+        // Sometimes #scrollToPosition doesn't change the scroll state so we need to make sure
+        // the pagination arrows actually get updated. See b/http://b/15801119
+        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.
+     *
+     * <p>The given Adapter can implement {@link ItemCap} if it wishes to control the behavior of
+     * a max number of items. Otherwise, methods in the PagedListView to limit the content, such as
+     * {@link #setMaxPages(int)}, will do nothing.
+     */
+    public void setAdapter(
+            @NonNull RecyclerView.Adapter<? extends RecyclerView.ViewHolder> adapter) {
+        mAdapter = adapter;
+        mRecyclerView.setAdapter(adapter);
+        updateMaxItems();
+    }
+
+    /** @hide */
+    @RestrictTo(LIBRARY_GROUP)
+    @NonNull
+    public PagedLayoutManager getLayoutManager() {
+        return mLayoutManager;
+    }
+
+    @Nullable
+    @SuppressWarnings("unchecked")
+    public RecyclerView.Adapter<? extends RecyclerView.ViewHolder> getAdapter() {
+        return mRecyclerView.getAdapter();
+    }
+
+    /**
+     * Sets the maximum number of the pages that can be shown in the PagedListView. The size of a
+     * page is defined as the number of items that fit completely on the screen at once.
+     *
+     * <p>Passing {@link #UNLIMITED_PAGES} will remove any restrictions on a maximum number
+     * of pages.
+     *
+     * <p>Note that for any restriction on maximum pages to work, the adapter passed to this
+     * PagedListView needs to implement {@link ItemCap}.
+     *
+     * @param maxPages The maximum number of pages that fit on the screen. Should be positive or
+     * {@link #UNLIMITED_PAGES}.
+     */
+    public void setMaxPages(int maxPages) {
+        mMaxPages = Math.max(UNLIMITED_PAGES, maxPages);
+        updateMaxItems();
+    }
+
+    /**
+     * Returns the maximum number of pages allowed in the PagedListView. This number is set by
+     * {@link #setMaxPages(int)}. If that method has not been called, then this value should match
+     * the default value.
+     *
+     * @return The maximum number of pages to be shown or {@link #UNLIMITED_PAGES} if there is
+     * no limit.
+     */
+    public int getMaxPages() {
+        return mMaxPages;
+    }
+
+    /**
+     * Gets the number of rows per page. Default value of mRowsPerPage is -1. If the first child of
+     * PagedLayoutManager is null or the height of the first child is 0, it will return 1.
+     */
+    public int getRowsPerPage() {
+        return mRowsPerPage;
+    }
+
+    /** Resets the maximum number of pages to be shown to be the default. */
+    public void resetMaxPages() {
+        mMaxPages = getDefaultMaxPages();
+        updateMaxItems();
+    }
+
+    /**
+     * @return The position of first visible child in the list. -1 will be returned if there is no
+     *     child.
+     */
+    public int getFirstFullyVisibleChildPosition() {
+        return mLayoutManager.getFirstFullyVisibleChildPosition();
+    }
+
+    /**
+     * @return The position of last visible child in the list. -1 will be returned if there is no
+     *     child.
+     */
+    public int getLastFullyVisibleChildPosition() {
+        return mLayoutManager.getLastFullyVisibleChildPosition();
+    }
+
+    /**
+     * Adds an {@link android.support.v7.widget.RecyclerView.ItemDecoration} to this PagedListView.
+     *
+     * @param decor The decoration to add.
+     * @see RecyclerView#addItemDecoration(RecyclerView.ItemDecoration)
+     */
+    public void addItemDecoration(@NonNull RecyclerView.ItemDecoration decor) {
+        mRecyclerView.addItemDecoration(decor);
+    }
+
+    /**
+     * Removes the given {@link android.support.v7.widget.RecyclerView.ItemDecoration} from this
+     * PagedListView.
+     *
+     * <p>The decoration will function the same as the item decoration for a {@link RecyclerView}.
+     *
+     * @param decor The decoration to remove.
+     * @see RecyclerView#removeItemDecoration(RecyclerView.ItemDecoration)
+     */
+    public void removeItemDecoration(@NonNull RecyclerView.ItemDecoration decor) {
+        mRecyclerView.removeItemDecoration(decor);
+    }
+
+    /**
+     * 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.
+     *
+     * <p>The listener will function the same as the listener for a regular {@link RecyclerView}.
+     *
+     * @param touchListener The touch listener to add.
+     * @see RecyclerView#addOnItemTouchListener(RecyclerView.OnItemTouchListener)
+     */
+    public void addOnItemTouchListener(@NonNull RecyclerView.OnItemTouchListener touchListener) {
+        mRecyclerView.addOnItemTouchListener(touchListener);
+    }
+
+    /**
+     * Removes the given {@link android.support.v7.widget.RecyclerView.OnItemTouchListener} from
+     * the PagedListView.
+     *
+     * @param touchListener The touch listener to remove.
+     * @see RecyclerView#removeOnItemTouchListener(RecyclerView.OnItemTouchListener)
+     */
+    public void removeOnItemTouchListener(@NonNull RecyclerView.OnItemTouchListener touchListener) {
+        mRecyclerView.removeOnItemTouchListener(touchListener);
+    }
+    /**
+     * Sets how this {@link PagedListView} responds to day/night configuration changes. By
+     * default, the PagedListView is darker in the day and lighter at night.
+     *
+     * @param dayNightStyle A value from {@link DayNightStyle}.
+     * @see DayNightStyle
+     */
+    public void setDayNightStyle(@DayNightStyle int dayNightStyle) {
+        // Update the scrollbar
+        mScrollBarView.setDayNightStyle(dayNightStyle);
+
+        int decorCount = mRecyclerView.getItemDecorationCount();
+        for (int i = 0; i < decorCount; i++) {
+            RecyclerView.ItemDecoration decor = mRecyclerView.getItemDecorationAt(i);
+            if (decor instanceof DividerDecoration) {
+                ((DividerDecoration) decor).updateDividerColor();
+            }
+        }
+    }
+
+    /**
+     * Returns the {@link android.support.v7.widget.RecyclerView.ViewHolder} that corresponds to the
+     * last child in the PagedListView that is fully visible.
+     *
+     * @return The corresponding ViewHolder or {@code null} if none exists.
+     */
+    @Nullable
+    public RecyclerView.ViewHolder getLastViewHolder() {
+        View lastFullyVisible = mLayoutManager.getLastFullyVisibleChild();
+        if (lastFullyVisible == null) {
+            return null;
+        }
+        int lastFullyVisibleAdapterPosition = mLayoutManager.getPosition(lastFullyVisible);
+        RecyclerView.ViewHolder lastViewHolder = getRecyclerView()
+                .findViewHolderForAdapterPosition(lastFullyVisibleAdapterPosition + 1);
+        // We want to get the very last ViewHolder in the list, even if it's only fully visible
+        // If it doesn't exist, return the last fully visible ViewHolder.
+        if (lastViewHolder == null) {
+            lastViewHolder = getRecyclerView()
+                    .findViewHolderForAdapterPosition(lastFullyVisibleAdapterPosition);
+        }
+        return lastViewHolder;
+    }
+
+    /**
+     * Sets the {@link OnScrollListener} that will be notified of scroll events within the
+     * PagedListView.
+     *
+     * @param listener The scroll listener to set.
+     */
+    public void setOnScrollListener(OnScrollListener listener) {
+        mOnScrollListener = listener;
+        mLayoutManager.setOnScrollListener(mOnScrollListener);
+    }
+
+    /** Returns the page the given position is on, starting with page 0. */
+    public int getPage(int position) {
+        if (mRowsPerPage == -1) {
+            return -1;
+        }
+        if (mRowsPerPage == 0) {
+            return 0;
+        }
+        return position / mRowsPerPage;
+    }
+
+    /**
+     * Sets the default number of pages that this PagedListView is limited to.
+     *
+     * @param newDefault The default number of pages. Should be positive.
+     */
+    public void setDefaultMaxPages(int newDefault) {
+        if (newDefault < 0) {
+            return;
+        }
+        mDefaultMaxPages = newDefault;
+        resetMaxPages();
+    }
+
+    /** Returns the default number of pages the list should have */
+    protected int getDefaultMaxPages() {
+        // assume list shown in response to a click, so, reduce number of clicks by one
+        return mDefaultMaxPages - 1;
+    }
+
+    @Override
+    public void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        // if a late item is added to the top of the layout after the layout is stabilized, causing
+        // the former top item to be pushed to the 2nd page, the focus will still be on the former
+        // top item. Since our car layout manager tries to scroll the viewport so that the focused
+        // item is visible, the view port will be on the 2nd page. That means the newly added item
+        // will not be visible, on the first page.
+
+        // what we want to do is: if the formerly focused item is the first one in the list, any
+        // item added above it will make the focus to move to the new first item.
+        // if the focus is not on the formerly first item, then we don't need to do anything. Let
+        // the layout manager do the job and scroll the viewport so the currently focused item
+        // is visible.
+
+        // we need to calculate whether we want to request focus here, before the super call,
+        // because after the super call, the first born might be changed.
+        View focusedChild = mLayoutManager.getFocusedChild();
+        View firstBorn = mLayoutManager.getChildAt(0);
+
+        super.onLayout(changed, left, top, right, bottom);
+
+        if (mAdapter != null) {
+            int itemCount = mAdapter.getItemCount();
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, String.format(
+                        "onLayout hasFocus: %s, mLastItemCount: %s, itemCount: %s, "
+                                + "focusedChild: %s, firstBorn: %s, isInTouchMode: %s, "
+                                + "mNeedsFocus: %s",
+                        hasFocus(),
+                        mLastItemCount,
+                        itemCount,
+                        focusedChild,
+                        firstBorn,
+                        isInTouchMode(),
+                        mNeedsFocus));
+            }
+            updateMaxItems();
+            // This is a workaround for missing focus because isInTouchMode() is not always
+            // returning the right value.
+            // This is okay for the Engine release since focus is always showing.
+            // However, in Tala and Fender, we want to show focus only when the user uses
+            // hardware controllers, so we need to revisit this logic. b/22990605.
+            if (mNeedsFocus && itemCount > 0) {
+                if (focusedChild == null) {
+                    requestFocus();
+                }
+                mNeedsFocus = false;
+            }
+            if (itemCount > mLastItemCount && focusedChild == firstBorn) {
+                requestFocus();
+            }
+            mLastItemCount = itemCount;
+        }
+        // We need to update the scroll buttons after layout has happened.
+        // Determining if a scrollbar is necessary requires looking at the layout of the child
+        // views. Therefore, this determination can only be done after layout has happened.
+        // Note: don't animate here to prevent b/26849677
+        updatePaginationButtons(false /*animate*/);
+    }
+
+    /**
+     * Returns the View at the given position within the list.
+     *
+     * @param position A position within the list.
+     * @return The View or {@code null} if no View exists at the given position.
+     */
+    @Nullable
+    public View findViewByPosition(int position) {
+        return mLayoutManager.findViewByPosition(position);
+    }
+
+    /**
+     * Determines if scrollbar should be visible or not and shows/hides it accordingly. If this is
+     * being called as a result of adapter changes, it should be called after the new layout has
+     * been calculated because the method of determining scrollbar visibility uses the current
+     * layout. If this is called after an adapter change but before the new layout, the visibility
+     * determination may not be correct.
+     *
+     * @param animate {@code true} if the scrollbar should animate to its new position.
+     *                {@code false} if no animation is used
+     */
+    protected void updatePaginationButtons(boolean animate) {
+        if (!mScrollBarEnabled) {
+            // Don't change the visibility of the ScrollBar unless it's enabled.
+            return;
+        }
+
+        if ((mLayoutManager.isAtTop() && mLayoutManager.isAtBottom())
+                || mLayoutManager.getItemCount() == 0) {
+            mScrollBarView.setVisibility(View.INVISIBLE);
+        } else {
+            mScrollBarView.setVisibility(View.VISIBLE);
+        }
+        mScrollBarView.setUpEnabled(shouldEnablePageUpButton());
+        mScrollBarView.setDownEnabled(shouldEnablePageDownButton());
+
+        mScrollBarView.setParameters(
+                mRecyclerView.computeVerticalScrollRange(),
+                mRecyclerView.computeVerticalScrollOffset(),
+                mRecyclerView.computeVerticalScrollExtent(),
+                animate);
+        invalidate();
+    }
+
+    protected boolean shouldEnablePageUpButton() {
+        return !mLayoutManager.isAtTop();
+    }
+
+    protected boolean shouldEnablePageDownButton() {
+        return !mLayoutManager.isAtBottom();
+    }
+
+    @UiThread
+    protected void updateMaxItems() {
+        if (mAdapter == null) {
+            return;
+        }
+
+        // Ensure mRowsPerPage regardless of if the adapter implements ItemCap.
+        updateRowsPerPage();
+
+        // If the adapter does not implement ItemCap, then the max items on it cannot be updated.
+        if (!(mAdapter instanceof ItemCap)) {
+            return;
+        }
+
+        final int originalCount = mAdapter.getItemCount();
+        ((ItemCap) mAdapter).setMaxItems(calculateMaxItemCount());
+        final int newCount = mAdapter.getItemCount();
+        if (newCount == originalCount) {
+            return;
+        }
+
+        if (newCount < originalCount) {
+            mAdapter.notifyItemRangeRemoved(newCount, originalCount - newCount);
+        } else {
+            mAdapter.notifyItemRangeInserted(originalCount, newCount - originalCount);
+        }
+    }
+
+    protected int calculateMaxItemCount() {
+        final View firstChild = mLayoutManager.getChildAt(0);
+        if (firstChild == null || firstChild.getHeight() == 0) {
+            return -1;
+        } else {
+            return (mMaxPages < 0) ? -1 : mRowsPerPage * mMaxPages;
+        }
+    }
+
+    /**
+     * Updates the rows number per current page, which is used for calculating how many items we
+     * want to show.
+     */
+    protected void updateRowsPerPage() {
+        final View firstChild = mLayoutManager.getChildAt(0);
+        if (firstChild == null || firstChild.getHeight() == 0) {
+            mRowsPerPage = 1;
+        } else {
+            mRowsPerPage = Math.max(1, (getHeight() - getPaddingTop()) / firstChild.getHeight());
+        }
+    }
+
+    @Override
+    protected Parcelable onSaveInstanceState() {
+        SavedState savedState = new SavedState(super.onSaveInstanceState());
+        savedState.mLayoutManagerState = mLayoutManager.onSaveInstanceState();
+        return savedState;
+    }
+
+    @Override
+    protected void onRestoreInstanceState(Parcelable state) {
+        SavedState savedState = (SavedState) state;
+        mLayoutManager.onRestoreInstanceState(savedState.mLayoutManagerState);
+        super.onRestoreInstanceState(savedState.getSuperState());
+    }
+
+    @Override
+    protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
+        // There is the possibility of multiple PagedListViews on a page. This means that the ids
+        // of the child Views of PagedListView are no longer unique, and onSaveInstanceState()
+        // cannot be used. As a result, PagedListViews needs to manually dispatch the instance
+        // states. Call dispatchFreezeSelfOnly() so that no child views have onSaveInstanceState()
+        // called by the system.
+        dispatchFreezeSelfOnly(container);
+    }
+
+    @Override
+    protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
+        // Prevent onRestoreInstanceState() from being called on child Views. Instead, PagedListView
+        // will manually handle passing the state. See the comment in dispatchSaveInstanceState()
+        // for more information.
+        dispatchThawSelfOnly(container);
+    }
+
+    /** The state that will be saved across configuration changes. */
+    private static class SavedState extends BaseSavedState {
+        /** The state of the {@link #mLayoutManager} of this PagedListView. */
+        Parcelable mLayoutManagerState;
+
+        SavedState(Parcelable superState) {
+            super(superState);
+        }
+
+        private SavedState(Parcel in) {
+            super(in);
+            mLayoutManagerState =
+                    in.readParcelable(PagedLayoutManager.SavedState.class.getClassLoader());
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            super.writeToParcel(out, flags);
+            out.writeParcelable(mLayoutManagerState, flags);
+        }
+
+        public static final ClassLoaderCreator<SavedState> CREATOR =
+                new ClassLoaderCreator<SavedState>() {
+                    @Override
+                    public SavedState createFromParcel(Parcel source, ClassLoader loader) {
+                        return new SavedState(source);
+                    }
+
+                    @Override
+                    public SavedState createFromParcel(Parcel source) {
+                        return createFromParcel(source, null /* loader */);
+                    }
+
+                    @Override
+                    public SavedState[] newArray(int size) {
+                        return new SavedState[size];
+                    }
+                };
+    }
+
+    private final RecyclerView.OnScrollListener mRecyclerViewOnScrollListener =
+            new RecyclerView.OnScrollListener() {
+                @Override
+                public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+                    if (mOnScrollListener != null) {
+                        mOnScrollListener.onScrolled(recyclerView, dx, dy);
+                        if (!mLayoutManager.isAtTop() && mLayoutManager.isAtBottom()) {
+                            mOnScrollListener.onReachBottom();
+                        } else if (mLayoutManager.isAtTop() || !mLayoutManager.isAtBottom()) {
+                            mOnScrollListener.onLeaveBottom();
+                        }
+                    }
+                    updatePaginationButtons(false);
+                }
+
+                @Override
+                public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
+                    if (mOnScrollListener != null) {
+                        mOnScrollListener.onScrollStateChanged(recyclerView, newState);
+                    }
+                    if (newState == RecyclerView.SCROLL_STATE_IDLE) {
+                        mHandler.postDelayed(mPaginationRunnable, PAGINATION_HOLD_DELAY_MS);
+                    }
+                }
+            };
+
+    protected final Runnable mPaginationRunnable =
+            new Runnable() {
+                @Override
+                public void run() {
+                    boolean upPressed = mScrollBarView.isUpPressed();
+                    boolean downPressed = mScrollBarView.isDownPressed();
+                    if (upPressed && downPressed) {
+                        return;
+                    }
+                    if (upPressed) {
+                        mRecyclerView.pageUp();
+                    } else if (downPressed) {
+                        mRecyclerView.pageDown();
+                    }
+                }
+            };
+
+    private final Runnable mUpdatePaginationRunnable =
+            new Runnable() {
+                @Override
+                public void run() {
+                    updatePaginationButtons(true /*animate*/);
+                }
+            };
+
+    /** Used to listen for {@code PagedListView} scroll events. */
+    public abstract static class OnScrollListener {
+        /** Called when menu reaches the bottom */
+        public void onReachBottom() {}
+        /** Called when menu leaves the bottom */
+        public void onLeaveBottom() {}
+        /** Called when scroll up button is clicked */
+        public void onScrollUpButtonClicked() {}
+        /** Called when scroll down button is clicked */
+        public void onScrollDownButtonClicked() {}
+        /** Called when scrolling to the previous page via up gesture */
+        public void onGestureUp() {}
+        /** Called when scrolling to the next page via down gesture */
+        public void onGestureDown() {}
+
+        /**
+         * Called when RecyclerView.OnScrollListener#onScrolled is called. See
+         * RecyclerView.OnScrollListener
+         */
+        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {}
+
+        /** See RecyclerView.OnScrollListener */
+        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {}
+
+        /** Called when the view scrolls up a page */
+        public void onPageUp() {}
+
+        /** Called when the view scrolls down a page */
+        public void onPageDown() {}
+    }
+
+    /**
+     * 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.
+     */
+    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 mDividerEndId;
+
+        /**
+         * @param dividerStartMargin The start offset of the dividing line. This offset will be
+         *     relative to {@code dividerStartId} if that value is given.
+         * @param dividerStartId A child view id whose starting edge will be used as the starting
+         *     edge of the dividing line. If this value is {@link #INVALID_RESOURCE_ID}, the top
+         *     container of each child view will be used.
+         * @param dividerEndId A child view id whose ending edge will be used as the starting edge
+         *     of the dividing lin.e If this value is {@link #INVALID_RESOURCE_ID}, then the top
+         *     container view of each child will be used.
+         */
+        private DividerDecoration(Context context, int dividerStartMargin,
+                @IdRes int dividerStartId, @IdRes int dividerEndId) {
+            mContext = context;
+            mDividerStartMargin = dividerStartMargin;
+            mDividerStartId = dividerStartId;
+            mDividerEndId = dividerEndId;
+
+            Resources res = context.getResources();
+            mPaint = new Paint();
+            mPaint.setColor(res.getColor(R.color.car_list_divider));
+            mDividerHeight = res.getDimensionPixelSize(R.dimen.car_list_divider_height);
+        }
+
+        /** Updates the list divider color which may have changed due to a day night transition. */
+        public void updateDividerColor() {
+            mPaint.setColor(mContext.getResources().getColor(R.color.car_list_divider));
+        }
+
+        @Override
+        public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
+            // 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 =
+                        mDividerEndId != INVALID_RESOURCE_ID
+                                ? container.findViewById(mDividerEndId)
+                                : container;
+
+                if (startChild == null || endChild == null) {
+                    continue;
+                }
+
+                int left = mDividerStartMargin + startChild.getLeft();
+                int right = endChild.getRight();
+                int bottom = container.getBottom() + spacing / 2 + mDividerHeight / 2;
+                int top = bottom - mDividerHeight;
+
+                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/androidx/car/widget/PagedScrollBarView.java b/car/src/main/java/androidx/car/widget/PagedScrollBarView.java
new file mode 100644
index 0000000..6843fb6
--- /dev/null
+++ b/car/src/main/java/androidx/car/widget/PagedScrollBarView.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.car.widget;
+
+import android.content.Context;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
+import android.support.v4.content.ContextCompat;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+
+import androidx.car.R;
+
+/** A custom view to provide list scroll behaviour -- up/down buttons and scroll indicator. */
+public class PagedScrollBarView extends FrameLayout
+        implements View.OnClickListener, View.OnLongClickListener {
+    private static final float BUTTON_DISABLED_ALPHA = 0.2f;
+
+    @DayNightStyle private int mDayNightStyle;
+
+    /** Listener for when the list should paginate. */
+    public interface PaginationListener {
+        int PAGE_UP = 0;
+        int PAGE_DOWN = 1;
+
+        /** Called when the linked view should be paged in the given direction */
+        void onPaginate(int direction);
+    }
+
+    private final ImageView mUpButton;
+    private final ImageView mDownButton;
+    private final ImageView mScrollThumb;
+    /** The "filler" view between the up and down buttons */
+    private final View mFiller;
+
+    private final Interpolator mPaginationInterpolator = new AccelerateDecelerateInterpolator();
+    private final int mMinThumbLength;
+    private final int mMaxThumbLength;
+    private PaginationListener mPaginationListener;
+
+    public PagedScrollBarView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0 /*defStyleAttrs*/, 0 /*defStyleRes*/);
+    }
+
+    public PagedScrollBarView(Context context, AttributeSet attrs, int defStyleAttrs) {
+        this(context, attrs, defStyleAttrs, 0 /*defStyleRes*/);
+    }
+
+    public PagedScrollBarView(
+            Context context, AttributeSet attrs, int defStyleAttrs, int defStyleRes) {
+        super(context, attrs, defStyleAttrs, defStyleRes);
+
+        LayoutInflater inflater =
+                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        inflater.inflate(R.layout.car_paged_scrollbar_buttons, this /* root */,
+                true /* attachToRoot */);
+
+        mUpButton = (ImageView) findViewById(R.id.page_up);
+        mUpButton.setOnClickListener(this);
+        mUpButton.setOnLongClickListener(this);
+        mDownButton = (ImageView) findViewById(R.id.page_down);
+        mDownButton.setOnClickListener(this);
+        mDownButton.setOnLongClickListener(this);
+
+        mScrollThumb = (ImageView) findViewById(R.id.scrollbar_thumb);
+        mFiller = findViewById(R.id.filler);
+
+        mMinThumbLength = getResources()
+                .getDimensionPixelSize(R.dimen.car_min_scroll_bar_thumb_height);
+        mMaxThumbLength = getResources()
+                .getDimensionPixelSize(R.dimen.car_max_scroll_bar_thumb_height);
+    }
+
+    @Override
+    public void onClick(View v) {
+        dispatchPageClick(v);
+    }
+
+    @Override
+    public boolean onLongClick(View v) {
+        dispatchPageClick(v);
+        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.
+     *
+     * @param listener The listener to set.
+     */
+    public void setPaginationListener(PaginationListener listener) {
+        mPaginationListener = listener;
+    }
+
+    /** Returns {@code true} if the "up" button is pressed */
+    public boolean isUpPressed() {
+        return mUpButton.isPressed();
+    }
+
+    /** Returns {@code true} if the "down" button is pressed */
+    public boolean isDownPressed() {
+        return mDownButton.isPressed();
+    }
+
+    /** Sets the range, offset and extent of the scroll bar. See {@link View}. */
+    public void setParameters(int range, int offset, int extent, boolean animate) {
+        // This method is where we take the computed parameters from the PagedLayoutManager and
+        // render it within the specified constraints ({@link #mMaxThumbLength} and
+        // {@link #mMinThumbLength}).
+        final int size = mFiller.getHeight() - mFiller.getPaddingTop() - mFiller.getPaddingBottom();
+
+        int thumbLength = extent * size / range;
+        thumbLength = Math.max(Math.min(thumbLength, mMaxThumbLength), mMinThumbLength);
+
+        int thumbOffset = size - thumbLength;
+        if (isDownEnabled()) {
+            // We need to adjust the offset so that it fits into the possible space inside the
+            // filler with regarding to the constraints set by mMaxThumbLength and mMinThumbLength.
+            thumbOffset = (size - thumbLength) * offset / range;
+        }
+
+        // Sets the size of the thumb and request a redraw if needed.
+        final ViewGroup.LayoutParams lp = mScrollThumb.getLayoutParams();
+        if (lp.height != thumbLength) {
+            lp.height = thumbLength;
+            mScrollThumb.requestLayout();
+        }
+
+        moveY(mScrollThumb, thumbOffset, animate);
+    }
+
+    /**
+     * Sets how this {@link PagedScrollBarView} responds to day/night configuration changes. By
+     * default, the PagedScrollBarView is darker in the day and lighter at night.
+     *
+     * @param dayNightStyle A value from {@link DayNightStyle}.
+     * @see DayNightStyle
+     */
+    public void setDayNightStyle(@DayNightStyle int dayNightStyle) {
+        mDayNightStyle = dayNightStyle;
+        reloadColors();
+    }
+
+    /**
+     * Sets whether or not the up button on the scroll bar is clickable.
+     *
+     * @param enabled {@code true} if the up button is enabled.
+     */
+    public void setUpEnabled(boolean enabled) {
+        mUpButton.setEnabled(enabled);
+        mUpButton.setAlpha(enabled ? 1f : BUTTON_DISABLED_ALPHA);
+    }
+
+    /**
+     * Sets whether or not the down button on the scroll bar is clickable.
+     *
+     * @param enabled {@code true} if the down button is enabled.
+     */
+    public void setDownEnabled(boolean enabled) {
+        mDownButton.setEnabled(enabled);
+        mDownButton.setAlpha(enabled ? 1f : BUTTON_DISABLED_ALPHA);
+    }
+
+    /**
+     * Returns whether or not the down button on the scroll bar is clickable.
+     *
+     * @return {@code true} if the down button is enabled. {@code false} otherwise.
+     */
+    public boolean isDownEnabled() {
+        return mDownButton.isEnabled();
+    }
+
+    /** Reload the colors for the current {@link DayNightStyle}. */
+    private void reloadColors() {
+        int tint;
+        int thumbBackground;
+        int upDownBackgroundResId;
+
+        switch (mDayNightStyle) {
+            case DayNightStyle.AUTO:
+                tint = ContextCompat.getColor(getContext(), R.color.car_tint);
+                thumbBackground = ContextCompat.getColor(getContext(),
+                        R.color.car_scrollbar_thumb);
+                upDownBackgroundResId = R.drawable.car_pagination_background;
+                break;
+            case DayNightStyle.AUTO_INVERSE:
+                tint = ContextCompat.getColor(getContext(), R.color.car_tint_inverse);
+                thumbBackground = ContextCompat.getColor(getContext(),
+                        R.color.car_scrollbar_thumb_inverse);
+                upDownBackgroundResId = R.drawable.car_pagination_background_inverse;
+                break;
+            case DayNightStyle.FORCE_NIGHT:
+                tint = ContextCompat.getColor(getContext(), R.color.car_tint_light);
+                thumbBackground = ContextCompat.getColor(getContext(),
+                        R.color.car_scrollbar_thumb_light);
+                upDownBackgroundResId = R.drawable.car_pagination_background_night;
+                break;
+            case DayNightStyle.FORCE_DAY:
+                tint = ContextCompat.getColor(getContext(), R.color.car_tint_dark);
+                thumbBackground = ContextCompat.getColor(getContext(),
+                        R.color.car_scrollbar_thumb_dark);
+                upDownBackgroundResId = R.drawable.car_pagination_background_day;
+                break;
+            default:
+                throw new IllegalArgumentException("Unknown DayNightStyle: " + mDayNightStyle);
+        }
+
+        mScrollThumb.setBackgroundColor(thumbBackground);
+
+        mUpButton.setColorFilter(tint, PorterDuff.Mode.SRC_IN);
+        mUpButton.setBackgroundResource(upDownBackgroundResId);
+
+        mDownButton.setColorFilter(tint, PorterDuff.Mode.SRC_IN);
+        mDownButton.setBackgroundResource(upDownBackgroundResId);
+    }
+
+    private void dispatchPageClick(View v) {
+        final PaginationListener listener = mPaginationListener;
+        if (listener == null) {
+            return;
+        }
+
+        int direction = v.getId() == R.id.page_up
+                ? PaginationListener.PAGE_UP
+                : PaginationListener.PAGE_DOWN;
+        listener.onPaginate(direction);
+    }
+
+    /** Moves the given view to the specified 'y' position. */
+    private void moveY(final View view, float newPosition, boolean animate) {
+        final int duration = animate ? 200 : 0;
+        view.animate()
+                .y(newPosition)
+                .setDuration(duration)
+                .setInterpolator(mPaginationInterpolator)
+                .start();
+    }
+}
diff --git a/car/tests/AndroidManifest.xml b/car/tests/AndroidManifest.xml
new file mode 100644
index 0000000..8e5422d
--- /dev/null
+++ b/car/tests/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?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.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="androidx.car.widget.test">
+    <uses-sdk android:targetSdkVersion="${target-sdk-version}"/>
+
+    <application android:supportsRtl="true">
+        <activity android:name="androidx.car.widget.ColumnCardViewTestActivity"/>
+        <activity android:name="androidx.car.widget.PagedListViewSavedStateActivity"/>
+        <activity android:name="androidx.car.widget.PagedListViewTestActivity"/>
+    </application>
+</manifest>
diff --git a/car/tests/NO_DOCS b/car/tests/NO_DOCS
new file mode 100644
index 0000000..bd77b1a
--- /dev/null
+++ b/car/tests/NO_DOCS
@@ -0,0 +1,17 @@
+# 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.
+
+Having this file, named NO_DOCS, in a directory will prevent
+Android javadocs from being generated for java files under
+the directory. This is especially useful for test projects.
\ No newline at end of file
diff --git a/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_column_card_view.xml b/car/tests/res/layout/activity_column_card_view.xml
new file mode 100644
index 0000000..a6acc57
--- /dev/null
+++ b/car/tests/res/layout/activity_column_card_view.xml
@@ -0,0 +1,36 @@
+<?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.
+  -->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:car="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <androidx.car.widget.ColumnCardView
+        android:id="@+id/default_width_column_card"
+        android:layout_gravity="center"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+
+    <androidx.car.widget.ColumnCardView
+        car:columnSpan="2"
+        android:id="@+id/span_2_column_card"
+        android:layout_gravity="center"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+</LinearLayout>
\ No newline at end of file
diff --git a/car/tests/res/layout/activity_paged_list_view.xml b/car/tests/res/layout/activity_paged_list_view.xml
new file mode 100644
index 0000000..d7c8946
--- /dev/null
+++ b/car/tests/res/layout/activity_paged_list_view.xml
@@ -0,0 +1,30 @@
+<?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.
+  -->
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/frame_layout"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <androidx.car.widget.PagedListView
+        android:id="@+id/paged_list_view"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        app:showPagedListViewDivider="false"
+        app:offsetScrollBar="true"/>
+</FrameLayout>
diff --git a/car/tests/res/layout/activity_two_paged_list_view.xml b/car/tests/res/layout/activity_two_paged_list_view.xml
new file mode 100644
index 0000000..457cb95
--- /dev/null
+++ b/car/tests/res/layout/activity_two_paged_list_view.xml
@@ -0,0 +1,40 @@
+<?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.
+  -->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="horizontal">
+
+    <androidx.car.widget.PagedListView
+        android:id="@+id/paged_list_view_1"
+        android:layout_weight="1"
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        app:showPagedListViewDivider="false"
+        app:offsetScrollBar="true"/>
+
+    <androidx.car.widget.PagedListView
+        android:id="@+id/paged_list_view_2"
+        android:layout_weight="1"
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        app:showPagedListViewDivider="false"
+        app:offsetScrollBar="true"/>
+
+</LinearLayout>
diff --git a/car/tests/res/layout/paged_list_item_column_card.xml b/car/tests/res/layout/paged_list_item_column_card.xml
new file mode 100644
index 0000000..5028abf
--- /dev/null
+++ b/car/tests/res/layout/paged_list_item_column_card.xml
@@ -0,0 +1,35 @@
+<?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.
+  -->
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical">
+
+    <androidx.car.widget.ColumnCardView
+        android:id="@+id/column_card"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+
+        <TextView
+            android:id="@+id/text_view"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"/>
+
+    </androidx.car.widget.ColumnCardView>
+
+</FrameLayout>
diff --git a/car/tests/res/values/strings.xml b/car/tests/res/values/strings.xml
new file mode 100644
index 0000000..478abc4
--- /dev/null
+++ b/car/tests/res/values/strings.xml
@@ -0,0 +1,18 @@
+<?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>
+    <string name="over_120_chars">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</string>
+</resources>
diff --git a/car/tests/src/androidx/car/widget/ColumnCardViewTest.java b/car/tests/src/androidx/car/widget/ColumnCardViewTest.java
new file mode 100644
index 0000000..1963629
--- /dev/null
+++ b/car/tests/src/androidx/car/widget/ColumnCardViewTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.car.widget;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import android.support.test.annotation.UiThreadTest;
+import android.support.test.filters.SmallTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.ViewTreeObserver;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import androidx.car.test.R;
+import androidx.car.utils.ColumnCalculator;
+
+/** Instrumentation unit tests for {@link ColumnCardView}. */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class ColumnCardViewTest {
+    @Rule
+    public ActivityTestRule<ColumnCardViewTestActivity> mActivityRule =
+            new ActivityTestRule<>(ColumnCardViewTestActivity.class);
+
+    private ColumnCalculator mCalculator;
+    private ColumnCardViewTestActivity mActivity;
+
+    @Before
+    public void setUp() {
+        mActivity = mActivityRule.getActivity();
+        mCalculator = ColumnCalculator.getInstance(mActivity);
+    }
+
+    @Test
+    public void defaultCardWidthMatchesCalculation() {
+        ColumnCardView card = mActivity.findViewById(R.id.default_width_column_card);
+
+        assertEquals(mCalculator.getSizeForColumnSpan(mActivity.getResources().getInteger(
+                R.integer.column_card_default_column_span)),
+                card.getWidth());
+    }
+
+    @Test
+    public void customXmlColumnSpanMatchesCalculation() {
+        ColumnCardView card = mActivity.findViewById(R.id.span_2_column_card);
+
+        assertEquals(mCalculator.getSizeForColumnSpan(2), card.getWidth());
+    }
+
+    @UiThreadTest
+    @Test
+    public void settingColumnSpanMatchesCalculation() {
+        final int columnSpan = 4;
+        final ColumnCardView card = mActivity.findViewById(R.id.span_2_column_card);
+        assertNotEquals(columnSpan, card.getColumnSpan());
+
+        card.setColumnSpan(columnSpan);
+        // When card finishes layout, verify its updated width.
+        card.getViewTreeObserver().addOnGlobalLayoutListener(
+                new ViewTreeObserver.OnGlobalLayoutListener() {
+                    @Override
+                    public void onGlobalLayout() {
+                        assertEquals(mCalculator.getSizeForColumnSpan(columnSpan), card.getWidth());
+                    }
+                });
+    }
+
+    @UiThreadTest
+    @Test
+    public void nonPositiveColumnSpanIsIgnored() {
+        final ColumnCardView card = mActivity.findViewById(R.id.default_width_column_card);
+        final int original = card.getColumnSpan();
+
+        card.setColumnSpan(0);
+        // When card finishes layout, verify its width remains unchanged.
+        card.getViewTreeObserver().addOnGlobalLayoutListener(
+                new ViewTreeObserver.OnGlobalLayoutListener() {
+                    @Override
+                    public void onGlobalLayout() {
+                        assertEquals(mCalculator.getSizeForColumnSpan(original), card.getWidth());
+                    }
+                });
+    }
+}
diff --git a/car/tests/src/androidx/car/widget/ColumnCardViewTestActivity.java b/car/tests/src/androidx/car/widget/ColumnCardViewTestActivity.java
new file mode 100644
index 0000000..3f42d62
--- /dev/null
+++ b/car/tests/src/androidx/car/widget/ColumnCardViewTestActivity.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.car.widget;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import androidx.car.test.R;
+
+public class ColumnCardViewTestActivity extends Activity {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_column_card_view);
+    }
+}
diff --git a/car/tests/src/androidx/car/widget/ListItemTest.java b/car/tests/src/androidx/car/widget/ListItemTest.java
new file mode 100644
index 0000000..2b974c3
--- /dev/null
+++ b/car/tests/src/androidx/car/widget/ListItemTest.java
@@ -0,0 +1,519 @@
+/*
+ * 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 androidx.car.widget;
+
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.action.ViewActions.click;
+import static android.support.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition;
+import static android.support.test.espresso.contrib.RecyclerViewActions.scrollToPosition;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+
+import static junit.framework.TestCase.assertFalse;
+
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.hamcrest.number.IsCloseTo.closeTo;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import android.graphics.drawable.Drawable;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.espresso.UiController;
+import android.support.test.espresso.ViewAction;
+import android.support.test.filters.SmallTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v7.widget.CardView;
+import android.view.View;
+import android.view.ViewGroup;
+
+import org.hamcrest.Matcher;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import androidx.car.test.R;
+
+/**
+* Tests the layout configuration in {@link ListItem}.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ListItemTest {
+
+    @Rule
+    public ActivityTestRule<PagedListViewTestActivity> mActivityRule =
+            new ActivityTestRule<>(PagedListViewTestActivity.class);
+
+    private PagedListViewTestActivity mActivity;
+    private PagedListView mPagedListView;
+
+    @Before
+    public void setUp() {
+        mActivity = mActivityRule.getActivity();
+        mPagedListView = mActivity.findViewById(R.id.paged_list_view);
+    }
+
+    private void setupPagedListView(List<ListItem> items) {
+        ListItemProvider provider = new ListItemProvider.ListProvider(
+                new ArrayList<>(items));
+        try {
+            mActivityRule.runOnUiThread(() -> {
+                mPagedListView.setAdapter(new ListItemAdapter(mActivity, provider));
+            });
+        } catch (Throwable throwable) {
+            throwable.printStackTrace();
+            throw new RuntimeException(throwable);
+        }
+        // Wait for paged list view to layout by using espresso to scroll to a position.
+        onView(withId(R.id.recycler_view)).perform(scrollToPosition(0));
+    }
+
+    private static void verifyViewIsHidden(View view) {
+        if (view instanceof ViewGroup) {
+            ViewGroup viewGroup = (ViewGroup) view;
+            final int childCount = viewGroup.getChildCount();
+            for (int i = 0; i < childCount; i++) {
+                verifyViewIsHidden(viewGroup.getChildAt(i));
+            }
+        } else {
+            assertThat(view.getVisibility(), is(equalTo(View.GONE)));
+        }
+    }
+
+    private ListItemAdapter.ViewHolder getViewHolderAtPosition(int position) {
+        return (ListItemAdapter.ViewHolder) mPagedListView.getRecyclerView()
+                .findViewHolderForAdapterPosition(
+                position);
+    }
+
+    @Test
+    public void testEmptyItemHidesAllViews() {
+        ListItem item = new ListItem.Builder(mActivity).build();
+        setupPagedListView(Arrays.asList(item));
+        verifyViewIsHidden(mPagedListView.findViewByPosition(0));
+    }
+
+    @Test
+    public void testPrimaryActionVisible() {
+        List<ListItem> items = Arrays.asList(
+                new ListItem.Builder(mActivity)
+                        .withPrimaryActionIcon(android.R.drawable.sym_def_app_icon, true)
+                        .build(),
+                new ListItem.Builder(mActivity)
+                        .withPrimaryActionIcon(android.R.drawable.sym_def_app_icon, false)
+                        .build());
+        setupPagedListView(items);
+
+        assertThat(getViewHolderAtPosition(0).getPrimaryIcon().getVisibility(),
+                is(equalTo(View.VISIBLE)));
+        assertThat(getViewHolderAtPosition(1).getPrimaryIcon().getVisibility(),
+                is(equalTo(View.VISIBLE)));
+    }
+
+    @Test
+    public void testTextVisible() {
+        List<ListItem> items = Arrays.asList(
+                new ListItem.Builder(mActivity)
+                        .withTitle("title")
+                        .build(),
+                new ListItem.Builder(mActivity)
+                        .withBody("body")
+                        .build());
+        setupPagedListView(items);
+
+        assertThat(getViewHolderAtPosition(0).getTitle().getVisibility(),
+                is(equalTo(View.VISIBLE)));
+        assertThat(getViewHolderAtPosition(1).getBody().getVisibility(),
+                is(equalTo(View.VISIBLE)));
+    }
+
+    @Test
+    public void testSupplementalActionVisible() {
+        List<ListItem> items = Arrays.asList(
+                new ListItem.Builder(mActivity)
+                        .withSupplementalIcon(android.R.drawable.sym_def_app_icon, true)
+                        .build(),
+                new ListItem.Builder(mActivity)
+                        .withAction("text", true, null)
+                        .build(),
+                new ListItem.Builder(mActivity)
+                        .withActions("text", true, null,
+                                 "text", true, null)
+                        .build());
+        setupPagedListView(items);
+
+        ListItemAdapter.ViewHolder viewHolder = getViewHolderAtPosition(0);
+        assertThat(viewHolder.getSupplementalIcon().getVisibility(), is(equalTo(View.VISIBLE)));
+        assertThat(viewHolder.getSupplementalIconDivider().getVisibility(),
+                is(equalTo(View.VISIBLE)));
+
+        viewHolder = getViewHolderAtPosition(1);
+        assertThat(viewHolder.getAction1().getVisibility(), is(equalTo(View.VISIBLE)));
+        assertThat(viewHolder.getAction1Divider().getVisibility(), is(equalTo(View.VISIBLE)));
+
+        viewHolder = getViewHolderAtPosition(2);
+        assertThat(viewHolder.getAction1().getVisibility(), is(equalTo(View.VISIBLE)));
+        assertThat(viewHolder.getAction1Divider().getVisibility(), is(equalTo(View.VISIBLE)));
+        assertThat(viewHolder.getAction2().getVisibility(), is(equalTo(View.VISIBLE)));
+        assertThat(viewHolder.getAction2Divider().getVisibility(), is(equalTo(View.VISIBLE)));
+    }
+
+    @Test
+    public void testDividersAreOptional() {
+        List<ListItem> items = Arrays.asList(
+                new ListItem.Builder(mActivity)
+                        .withSupplementalIcon(android.R.drawable.sym_def_app_icon, false)
+                        .build(),
+                new ListItem.Builder(mActivity)
+                        .withAction("text", false, null)
+                        .build(),
+                new ListItem.Builder(mActivity)
+                        .withActions("text", false, null,
+                                "text", false, null)
+                        .build());
+        setupPagedListView(items);
+
+        setupPagedListView(items);
+
+        ListItemAdapter.ViewHolder viewHolder = getViewHolderAtPosition(0);
+        assertThat(viewHolder.getSupplementalIcon().getVisibility(), is(equalTo(View.VISIBLE)));
+        assertThat(viewHolder.getSupplementalIconDivider().getVisibility(),
+                is(equalTo(View.GONE)));
+
+        viewHolder = getViewHolderAtPosition(1);
+        assertThat(viewHolder.getAction1().getVisibility(), is(equalTo(View.VISIBLE)));
+        assertThat(viewHolder.getAction1Divider().getVisibility(), is(equalTo(View.GONE)));
+
+        viewHolder = getViewHolderAtPosition(2);
+        assertThat(viewHolder.getAction1().getVisibility(), is(equalTo(View.VISIBLE)));
+        assertThat(viewHolder.getAction1Divider().getVisibility(), is(equalTo(View.GONE)));
+        assertThat(viewHolder.getAction2().getVisibility(), is(equalTo(View.VISIBLE)));
+        assertThat(viewHolder.getAction2Divider().getVisibility(), is(equalTo(View.GONE)));
+    }
+
+    @Test
+    public void testTextStartMarginMatchesPrimaryActionType() {
+        List<ListItem> items = Arrays.asList(
+                new ListItem.Builder(mActivity)
+                        .withPrimaryActionIcon(android.R.drawable.sym_def_app_icon, true)
+                        .build(),
+                new ListItem.Builder(mActivity)
+                        .withPrimaryActionIcon(android.R.drawable.sym_def_app_icon, false)
+                        .build(),
+                new ListItem.Builder(mActivity)
+                        .withPrimaryActionEmptyIcon()
+                        .build(),
+                new ListItem.Builder(mActivity)
+                        .withPrimaryActionNoIcon()
+                        .build());
+        List<Integer> expectedStartMargin = Arrays.asList(R.dimen.car_keyline_4,
+                R.dimen.car_keyline_3, R.dimen.car_keyline_3, R.dimen.car_keyline_1);
+        setupPagedListView(items);
+
+        for (int i = 0; i < items.size(); i++) {
+            ListItemAdapter.ViewHolder viewHolder = getViewHolderAtPosition(i);
+
+            int expected = InstrumentationRegistry.getContext().getResources()
+                    .getDimensionPixelSize(expectedStartMargin.get(i));
+            assertThat(((ViewGroup.MarginLayoutParams) viewHolder.getTitle().getLayoutParams())
+                    .getMarginStart(), is(equalTo(expected)));
+            assertThat(((ViewGroup.MarginLayoutParams) viewHolder.getBody().getLayoutParams())
+                    .getMarginStart(), is(equalTo(expected)));
+        }
+    }
+
+    @Test
+    public void testItemWithOnlyTitleIsSingleLine() {
+        List<ListItem> items = Arrays.asList(
+                // Only space
+                new ListItem.Builder(mActivity)
+                        .withTitle(" ")
+                        .build(),
+                // Underscore
+                new ListItem.Builder(mActivity)
+                        .withTitle("______")
+                        .build(),
+                new ListItem.Builder(mActivity)
+                        .withTitle("ALL UPPER CASE")
+                        .build(),
+                // String wouldn't fit in one line
+                new ListItem.Builder(mActivity)
+                        .withTitle(InstrumentationRegistry.getContext().getResources().getString(
+                                R.string.over_120_chars))
+                        .build());
+        setupPagedListView(items);
+
+        double singleLineHeight = InstrumentationRegistry.getContext().getResources().getDimension(
+                R.dimen.car_single_line_list_item_height);
+
+        for (int i = 0; i < items.size(); i++) {
+            assertThat((double) mPagedListView.findViewByPosition(i).getHeight(),
+                    is(closeTo(singleLineHeight, 1.0d)));
+        }
+    }
+
+    @Test
+    public void testItemWithBodyTextIsAtLeastDoubleLine() {
+        List<ListItem> items = Arrays.asList(
+                // Only space
+                new ListItem.Builder(mActivity)
+                        .withBody(" ")
+                        .build(),
+                // Underscore
+                new ListItem.Builder(mActivity)
+                        .withBody("____")
+                        .build(),
+                // String wouldn't fit in one line
+                new ListItem.Builder(mActivity)
+                        .withBody(InstrumentationRegistry.getContext().getResources().getString(
+                                R.string.over_120_chars))
+                        .build());
+        setupPagedListView(items);
+
+        final int doubleLineHeight =
+                (int) InstrumentationRegistry.getContext().getResources().getDimension(
+                        R.dimen.car_double_line_list_item_height);
+
+        for (int i = 0; i < items.size(); i++) {
+            assertThat(mPagedListView.findViewByPosition(i).getHeight(),
+                    is(greaterThanOrEqualTo(doubleLineHeight)));
+        }
+    }
+
+    @Test
+    public void testBodyTextLengthLimit() {
+        final String longText = InstrumentationRegistry.getContext().getResources().getString(
+                R.string.over_120_chars);
+        final int limit = InstrumentationRegistry.getContext().getResources().getInteger(
+                R.integer.car_list_item_text_length_limit);
+        List<ListItem> items = Arrays.asList(
+                new ListItem.Builder(mActivity)
+                        .withBody(longText)
+                        .build());
+        setupPagedListView(items);
+
+        // + 1 for appended ellipsis.
+        assertThat(getViewHolderAtPosition(0).getBody().getText().length(),
+                is(equalTo(limit + 1)));
+    }
+
+    @Test
+    public void testPrimaryIconDrawable() {
+        Drawable drawable = InstrumentationRegistry.getContext().getResources().getDrawable(
+                android.R.drawable.sym_def_app_icon, null);
+        List<ListItem> items = Arrays.asList(
+                new ListItem.Builder(mActivity)
+                        .withPrimaryActionIcon(drawable, true)
+                        .build());
+        setupPagedListView(items);
+
+        assertTrue(getViewHolderAtPosition(0).getPrimaryIcon().getDrawable().getConstantState()
+                .equals(drawable.getConstantState()));
+    }
+
+    @Test
+    public void testLargePrimaryIconHasNoStartMargin() {
+        List<ListItem> items = Arrays.asList(
+                new ListItem.Builder(mActivity)
+                        .withPrimaryActionIcon(android.R.drawable.sym_def_app_icon, true)
+                        .build());
+        setupPagedListView(items);
+
+        ListItemAdapter.ViewHolder viewHolder = getViewHolderAtPosition(0);
+        assertThat(((ViewGroup.MarginLayoutParams) viewHolder.getPrimaryIcon().getLayoutParams())
+                .getMarginStart(), is(equalTo(0)));
+    }
+
+    @Test
+    public void testSmallPrimaryIconStartMargin() {
+        List<ListItem> items = Arrays.asList(
+                new ListItem.Builder(mActivity)
+                        .withPrimaryActionIcon(android.R.drawable.sym_def_app_icon, false)
+                        .build());
+        setupPagedListView(items);
+
+        int expected = InstrumentationRegistry.getContext().getResources().getDimensionPixelSize(
+                R.dimen.car_keyline_1);
+
+        ListItemAdapter.ViewHolder viewHolder = getViewHolderAtPosition(0);
+        assertThat(((ViewGroup.MarginLayoutParams) viewHolder.getPrimaryIcon().getLayoutParams())
+                .getMarginStart(), is(equalTo(expected)));
+    }
+
+    @Test
+    public void testClickingPrimaryActionIsSeparateFromSupplementalAction() {
+        final boolean[] clicked = {false, false};
+        List<ListItem> items = Arrays.asList(
+                new ListItem.Builder(mActivity)
+                        .withOnClickListener((v) -> clicked[0] = true)
+                        .withSupplementalIcon(android.R.drawable.sym_def_app_icon, true,
+                                (v) -> clicked[1] = true)
+                        .build());
+        setupPagedListView(items);
+
+        onView(withId(R.id.recycler_view)).perform(actionOnItemAtPosition(0, click()));
+        assertTrue(clicked[0]);
+        assertFalse(clicked[1]);
+
+        onView(withId(R.id.recycler_view)).perform(
+                actionOnItemAtPosition(0, clickChildViewWithId(R.id.supplemental_icon)));
+        assertTrue(clicked[1]);
+    }
+
+    @Test
+    public void testClickingSupplementalIcon() {
+        final boolean[] clicked = {false};
+        List<ListItem> items = Arrays.asList(
+                new ListItem.Builder(mActivity)
+                        .withSupplementalIcon(android.R.drawable.sym_def_app_icon, true,
+                                (v) -> clicked[0] = true)
+                        .build());
+        setupPagedListView(items);
+
+        onView(withId(R.id.recycler_view)).perform(
+                actionOnItemAtPosition(0, clickChildViewWithId(R.id.supplemental_icon)));
+        assertTrue(clicked[0]);
+    }
+
+    @Test
+    public void testClickingSupplementalAction() {
+        final boolean[] clicked = {false};
+        List<ListItem> items = Arrays.asList(
+                new ListItem.Builder(mActivity)
+                        .withAction("action", true, (v) -> clicked[0] = true)
+                        .build());
+        setupPagedListView(items);
+
+        onView(withId(R.id.recycler_view)).perform(
+                actionOnItemAtPosition(0, clickChildViewWithId(R.id.action1)));
+        assertTrue(clicked[0]);
+    }
+
+    @Test
+    public void testClickingBothSupplementalActions() {
+        final boolean[] clicked = {false, false};
+        List<ListItem> items = Arrays.asList(
+                new ListItem.Builder(mActivity)
+                        .withActions("action 1", true, (v) -> clicked[0] = true,
+                                "action 2", true, (v) -> clicked[1] = true)
+                        .build());
+        setupPagedListView(items);
+
+        onView(withId(R.id.recycler_view)).perform(
+                actionOnItemAtPosition(0, clickChildViewWithId(R.id.action1)));
+        assertTrue(clicked[0]);
+        assertFalse(clicked[1]);
+
+        onView(withId(R.id.recycler_view)).perform(
+                actionOnItemAtPosition(0, clickChildViewWithId(R.id.action2)));
+        assertTrue(clicked[1]);
+    }
+
+    @Test
+    public void testCustomViewBinderAreCalledLast() {
+        final String updatedTitle = "updated title";
+        List<ListItem> items = Arrays.asList(
+                new ListItem.Builder(mActivity)
+                        .withTitle("original title")
+                        .withViewBinder((viewHolder) -> viewHolder.getTitle().setText(updatedTitle))
+                        .build());
+        setupPagedListView(items);
+
+        ListItemAdapter.ViewHolder viewHolder = getViewHolderAtPosition(0);
+        assertThat(viewHolder.getTitle().getText(), is(equalTo(updatedTitle)));
+    }
+
+    @Test
+    public void testCustomViewBinderOnUnusedViewsHasNoEffect() {
+        List<ListItem> items = Arrays.asList(
+                new ListItem.Builder(mActivity)
+                        .withViewBinder((viewHolder) -> viewHolder.getBody().setText("text"))
+                        .build());
+        setupPagedListView(items);
+
+        ListItemAdapter.ViewHolder viewHolder = getViewHolderAtPosition(0);
+        assertThat(viewHolder.getBody().getVisibility(), is(equalTo(View.GONE)));
+        // Custom binder interacts with body but has no effect.
+        // Expect card height to remain single line.
+        assertThat((double) viewHolder.itemView.getHeight(), is(closeTo(
+                InstrumentationRegistry.getContext().getResources().getDimension(
+                        R.dimen.car_single_line_list_item_height), 1.0d)));
+    }
+
+    @Test
+    public void testCardLookUsesCardView() {
+        List<ListItem> items = Arrays.asList(
+                new ListItem.Builder(mActivity)
+                        .withCardLook()
+                        .build());
+        setupPagedListView(items);
+
+        ListItemAdapter.ViewHolder viewHolder = getViewHolderAtPosition(0);
+        assertThat(viewHolder.itemView, is(instanceOf(CardView.class)));
+    }
+
+    @Test
+    public void testSettingTitleOrBodyAsPrimaryText() {
+        // Create 2 items, one with Title as primary (default) and one with Body.
+        // The primary text, regardless of view, should have consistent look (as primary).
+        List<ListItem> items = Arrays.asList(
+                new ListItem.Builder(mActivity)
+                        .withTitle("title")
+                        .withBody("body")
+                        .build(),
+                new ListItem.Builder(mActivity)
+                        .withTitle("title")
+                        .withBody("body", true)
+                        .build());
+        setupPagedListView(items);
+
+        ListItemAdapter.ViewHolder titlePrimary = getViewHolderAtPosition(0);
+        ListItemAdapter.ViewHolder bodyPrimary = getViewHolderAtPosition(1);
+        assertThat(titlePrimary.getTitle().getTextSize(),
+                is(equalTo(bodyPrimary.getBody().getTextSize())));
+        assertThat(titlePrimary.getTitle().getTextColors(),
+                is(equalTo(bodyPrimary.getBody().getTextColors())));
+    }
+
+    private static ViewAction clickChildViewWithId(final int id) {
+        return new ViewAction() {
+            @Override
+            public Matcher<View> getConstraints() {
+                return null;
+            }
+
+            @Override
+            public String getDescription() {
+                return "Click on a child view with specific id.";
+            }
+
+            @Override
+            public void perform(UiController uiController, View view) {
+                View v = view.findViewById(id);
+                v.performClick();
+            }
+        };
+    }
+}
diff --git a/car/tests/src/androidx/car/widget/PagedListViewSavedStateActivity.java b/car/tests/src/androidx/car/widget/PagedListViewSavedStateActivity.java
new file mode 100644
index 0000000..d9eecd5
--- /dev/null
+++ b/car/tests/src/androidx/car/widget/PagedListViewSavedStateActivity.java
@@ -0,0 +1,34 @@
+/*
+ * 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 androidx.car.widget;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import androidx.car.test.R;
+
+/**
+ * Test Activity for testing the saving of state for the {@link PagedListView}. It will inflate
+ * a layout that has two PagedListViews next to each other.
+ */
+public class PagedListViewSavedStateActivity extends Activity {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_two_paged_list_view);
+    }
+}
diff --git a/car/tests/src/androidx/car/widget/PagedListViewSavedStateTest.java b/car/tests/src/androidx/car/widget/PagedListViewSavedStateTest.java
new file mode 100644
index 0000000..b134e10
--- /dev/null
+++ b/car/tests/src/androidx/car/widget/PagedListViewSavedStateTest.java
@@ -0,0 +1,278 @@
+/*
+ * 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 androidx.car.widget;
+
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.action.ViewActions.click;
+import static android.support.test.espresso.matcher.ViewMatchers.isDescendantOfA;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+
+import static org.hamcrest.Matchers.allOf;
+import static org.junit.Assert.assertEquals;
+
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.support.test.espresso.IdlingRegistry;
+import android.support.test.espresso.IdlingResource;
+import android.support.test.filters.SmallTest;
+import android.support.test.filters.Suppress;
+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.TextView;
+
+import org.hamcrest.Matcher;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+import androidx.car.test.R;
+
+/** Unit tests for the ability of the {@link PagedListView} to save state. */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public final class PagedListViewSavedStateTest {
+    /**
+     * Used by {@link TestAdapter} to calculate ViewHolder height so N items appear in one page of
+     * {@link PagedListView}. If you need to test behavior under multiple pages, set number of items
+     * to ITEMS_PER_PAGE * desired_pages.
+     *
+     * <p>Actual value does not matter.
+     */
+    private static final int ITEMS_PER_PAGE = 5;
+
+    /**
+     * The total number of items to display in a list. This value just needs to be large enough
+     * to ensure the scroll bar shows.
+     */
+    private static final int TOTAL_ITEMS_IN_LIST = 100;
+
+    private static final int NUM_OF_PAGES = TOTAL_ITEMS_IN_LIST / ITEMS_PER_PAGE;
+
+    @Rule
+    public ActivityTestRule<PagedListViewSavedStateActivity> mActivityRule =
+            new ActivityTestRule<>(PagedListViewSavedStateActivity.class);
+
+    private PagedListViewSavedStateActivity mActivity;
+    private PagedListView mPagedListView1;
+    private PagedListView mPagedListView2;
+
+    @Before
+    public void setUp() {
+        mActivity = mActivityRule.getActivity();
+        mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
+
+        mPagedListView1 = mActivity.findViewById(R.id.paged_list_view_1);
+        mPagedListView2 = mActivity.findViewById(R.id.paged_list_view_2);
+
+        setUpPagedListView(mPagedListView1);
+        setUpPagedListView(mPagedListView2);
+    }
+
+    private boolean isAutoDevice() {
+        PackageManager packageManager = mActivityRule.getActivity().getPackageManager();
+        return packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
+    }
+
+    private void setUpPagedListView(PagedListView pagedListView) {
+        try {
+            mActivityRule.runOnUiThread(() -> {
+                pagedListView.setMaxPages(PagedListView.ItemCap.UNLIMITED);
+                pagedListView.setAdapter(new TestAdapter(TOTAL_ITEMS_IN_LIST,
+                        pagedListView.getMeasuredHeight()));
+            });
+        } catch (Throwable throwable) {
+            throwable.printStackTrace();
+            throw new RuntimeException(throwable);
+        }
+    }
+
+    @After
+    public void tearDown() {
+        for (IdlingResource idlingResource : IdlingRegistry.getInstance().getResources()) {
+            IdlingRegistry.getInstance().unregister(idlingResource);
+        }
+    }
+
+    @Suppress
+    @Test
+    public void testPagePositionRememberedOnRotation() {
+        if (!isAutoDevice()) {
+            return;
+        }
+
+        Random random = new Random();
+        IdlingRegistry.getInstance().register(new PagedListViewScrollingIdlingResource(
+                mPagedListView1, mPagedListView2));
+
+        // Add 1 to this random number to ensure it is a value between 1 and NUM_OF_PAGES.
+        int numOfClicks = random.nextInt(NUM_OF_PAGES) + 1;
+        clickPageDownButton(onPagedListView1(), numOfClicks);
+        int topPositionOfPagedListView1 =
+                mPagedListView1.getLayoutManager().getFirstFullyVisibleChildPosition();
+
+        numOfClicks = random.nextInt(NUM_OF_PAGES) + 1;
+        clickPageDownButton(onPagedListView2(), numOfClicks);
+        int topPositionOfPagedListView2 =
+                mPagedListView2.getLayoutManager().getFirstFullyVisibleChildPosition();
+
+        // Perform a configuration change by rotating the screen.
+        mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+        mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
+
+        // Check that the positions are the same after the change.
+        assertEquals(topPositionOfPagedListView1,
+                mPagedListView1.getLayoutManager().getFirstFullyVisibleChildPosition());
+        assertEquals(topPositionOfPagedListView2,
+                mPagedListView2.getLayoutManager().getFirstFullyVisibleChildPosition());
+    }
+
+    /** Clicks the page down button on the given PagedListView for the given number of times. */
+    private void clickPageDownButton(Matcher<View> pagedListView, int times) {
+        for (int i = 0; i < times; i++) {
+            onView(allOf(withId(R.id.page_down), pagedListView)).perform(click());
+        }
+    }
+
+
+    /** Convenience method for checking that a View is on the first PagedListView. */
+    private Matcher<View> onPagedListView1() {
+        return isDescendantOfA(withId(R.id.paged_list_view_1));
+    }
+
+    /** Convenience method for checking that a View is on the second PagedListView. */
+    private Matcher<View> onPagedListView2() {
+        return isDescendantOfA(withId(R.id.paged_list_view_2));
+    }
+
+    private static String getItemText(int index) {
+        return "Data " + index;
+    }
+
+    /** An Adapter that ensures that there is {@link #ITEMS_PER_PAGE} displayed. */
+    private class TestAdapter extends RecyclerView.Adapter<TestViewHolder>
+            implements PagedListView.ItemCap {
+        private List<String> mData;
+        private int mParentHeight;
+
+        TestAdapter(int itemCount, int parentHeight) {
+            mData = new ArrayList<>();
+            for (int i = 0; i < itemCount; i++) {
+                mData.add(getItemText(i));
+            }
+            mParentHeight = parentHeight;
+        }
+
+        @Override
+        public TestViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+            LayoutInflater inflater = LayoutInflater.from(parent.getContext());
+            return new TestViewHolder(inflater, parent);
+        }
+
+        @Override
+        public void onBindViewHolder(TestViewHolder holder, int position) {
+            // Calculate height for an item so one page fits ITEMS_PER_PAGE items.
+            int height = (int) Math.floor(mParentHeight / ITEMS_PER_PAGE);
+            holder.itemView.setMinimumHeight(height);
+            holder.setText(mData.get(position));
+        }
+
+        @Override
+        public int getItemCount() {
+            return mData.size();
+        }
+
+        @Override
+        public void setMaxItems(int maxItems) {
+            // No-op
+        }
+    }
+
+    /** A ViewHolder that holds a View with a TextView. */
+    private class TestViewHolder extends RecyclerView.ViewHolder {
+        private TextView mTextView;
+
+        TestViewHolder(LayoutInflater inflater, ViewGroup parent) {
+            super(inflater.inflate(R.layout.paged_list_item_column_card, parent, false));
+            mTextView = itemView.findViewById(R.id.text_view);
+        }
+
+        public void setText(String text) {
+            mTextView.setText(text);
+        }
+    }
+
+    // Registering IdlingResource in @Before method does not work - espresso doesn't actually wait
+    // for ViewAction to finish. So each method that  clicks on button will need to register their
+    // own IdlingResource.
+    private class PagedListViewScrollingIdlingResource implements IdlingResource {
+        private boolean mIsIdle = true;
+        private ResourceCallback mResourceCallback;
+
+        PagedListViewScrollingIdlingResource(PagedListView pagedListView1,
+                PagedListView pagedListView2) {
+            // Ensure the IdlingResource waits for both RecyclerViews to finish their movement.
+            pagedListView1.getRecyclerView().addOnScrollListener(mOnScrollListener);
+            pagedListView2.getRecyclerView().addOnScrollListener(mOnScrollListener);
+        }
+
+        @Override
+        public String getName() {
+            return PagedListViewScrollingIdlingResource.class.getName();
+        }
+
+        @Override
+        public boolean isIdleNow() {
+            return mIsIdle;
+        }
+
+        @Override
+        public void registerIdleTransitionCallback(ResourceCallback callback) {
+            mResourceCallback = callback;
+        }
+
+        private final RecyclerView.OnScrollListener mOnScrollListener =
+                new RecyclerView.OnScrollListener() {
+                    @Override
+                    public void onScrollStateChanged(
+                            RecyclerView recyclerView, int newState) {
+                        super.onScrollStateChanged(recyclerView, newState);
+
+                        // Treat dragging as idle, or Espresso will block itself when
+                        // swiping.
+                        mIsIdle = (newState == RecyclerView.SCROLL_STATE_IDLE
+                                || newState == RecyclerView.SCROLL_STATE_DRAGGING);
+
+                        if (mIsIdle && mResourceCallback != null) {
+                            mResourceCallback.onTransitionToIdle();
+                        }
+                    }
+
+                    @Override
+                    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {}
+                };
+    }
+}
diff --git a/car/tests/src/androidx/car/widget/PagedListViewTest.java b/car/tests/src/androidx/car/widget/PagedListViewTest.java
new file mode 100644
index 0000000..e924f4b
--- /dev/null
+++ b/car/tests/src/androidx/car/widget/PagedListViewTest.java
@@ -0,0 +1,498 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.car.widget;
+
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.action.ViewActions.click;
+import static android.support.test.espresso.action.ViewActions.swipeDown;
+import static android.support.test.espresso.action.ViewActions.swipeUp;
+import static android.support.test.espresso.assertion.ViewAssertions.matches;
+import static android.support.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition;
+import static android.support.test.espresso.contrib.RecyclerViewActions.scrollToPosition;
+import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static android.support.test.espresso.matcher.ViewMatchers.isEnabled;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+import static android.support.test.espresso.matcher.ViewMatchers.withText;
+
+import static org.hamcrest.Matchers.allOf;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertThat;
+
+import android.content.pm.PackageManager;
+import android.graphics.drawable.Drawable;
+import android.support.test.annotation.UiThreadTest;
+import android.support.test.espresso.Espresso;
+import android.support.test.espresso.IdlingResource;
+import android.support.test.espresso.matcher.ViewMatchers;
+import android.support.test.filters.SmallTest;
+import android.support.test.filters.Suppress;
+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;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import androidx.car.test.R;
+
+/** Unit tests for {@link PagedListView}. */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public final class PagedListViewTest {
+
+    /**
+     * Used by {@link TestAdapter} to calculate ViewHolder height so N items appear in one page of
+     * {@link PagedListView}. If you need to test behavior under multiple pages, set number of items
+     * to ITEMS_PER_PAGE * desired_pages.
+     * Actual value does not matter.
+     */
+    private static final int ITEMS_PER_PAGE = 5;
+
+    @Rule
+    public ActivityTestRule<PagedListViewTestActivity> mActivityRule =
+            new ActivityTestRule<>(PagedListViewTestActivity.class);
+
+    private PagedListViewTestActivity mActivity;
+    private PagedListView mPagedListView;
+
+    @Before
+    public void setUp() {
+        mActivity = mActivityRule.getActivity();
+        mPagedListView = mActivity.findViewById(R.id.paged_list_view);
+
+        // Using deprecated Espresso methods instead of calling it on the IdlingRegistry because
+        // the latter does not seem to work as reliably. Specifically, on the latter, it does
+        // not always register and unregister.
+        Espresso.registerIdlingResources(new PagedListViewScrollingIdlingResource(mPagedListView));
+    }
+
+    @After
+    public void tearDown() {
+        for (IdlingResource idlingResource : Espresso.getIdlingResources()) {
+            Espresso.unregisterIdlingResources(idlingResource);
+        }
+    }
+
+    private boolean isAutoDevice() {
+        PackageManager packageManager = mActivityRule.getActivity().getPackageManager();
+        return packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
+    }
+
+    private void setUpPagedListView(int itemCount) {
+        setUpPagedListView(itemCount, PagedListView.ItemCap.UNLIMITED);
+    }
+
+    private void setUpPagedListView(int itemCount, int maxPages) {
+        try {
+            mActivityRule.runOnUiThread(() -> {
+                mPagedListView.setMaxPages(maxPages);
+                mPagedListView.setAdapter(
+                        new TestAdapter(itemCount, mPagedListView.getMeasuredHeight()));
+            });
+        } catch (Throwable throwable) {
+            throwable.printStackTrace();
+            throw new RuntimeException(throwable);
+        }
+    }
+
+    /** Initializes {@link #mPagedListView} with an adapter that does not implement ItemCap. */
+    public void setUpNonItemCapPagedListView(int itemCount, int maxPages) {
+        try {
+            mActivityRule.runOnUiThread(() -> {
+                mPagedListView.setMaxPages(maxPages);
+                mPagedListView.setAdapter(
+                        new NoItemCapAdapter(itemCount, mPagedListView.getMeasuredHeight()));
+            });
+        } catch (Throwable throwable) {
+            throwable.printStackTrace();
+            throw new RuntimeException(throwable);
+        }
+    }
+
+    @Test
+    public void scrollBarIsInvisibleIfItemsDoNotFillOnePage() {
+        setUpPagedListView(1 /* itemCount */);
+
+        onView(withId(R.id.paged_scroll_view)).check(matches(not(isDisplayed())));
+    }
+
+    @Test
+    public void pageUpDownButtonIsDisabledOnListEnds() throws Throwable {
+        final int itemCount = ITEMS_PER_PAGE * 3;
+        setUpPagedListView(itemCount);
+        // Initially page_up button is disabled.
+        onView(withId(R.id.page_up)).check(matches(not(isEnabled())));
+
+        // Moving to middle of list enables page_up button.
+        onView(withId(R.id.recycler_view)).perform(scrollToPosition(itemCount / 2));
+        onView(withId(R.id.page_up)).check(matches(isEnabled()));
+
+        // Moving to page end, page_down button is disabled.
+        onView(withId(R.id.recycler_view)).perform(scrollToPosition(itemCount));
+        onView(withId(R.id.page_down)).check(matches(not(isEnabled())));
+    }
+
+    @Test
+    public void testMaxPageGetterSetterDefaultValue() {
+        final int maxPages = 2;
+        final int defaultMaxPages = 3;
+
+        // setMaxPages
+        setUpPagedListView(ITEMS_PER_PAGE, maxPages);
+        assertThat(mPagedListView.getMaxPages(), is(equalTo(maxPages)));
+
+        // resetMaxPages
+        mPagedListView.resetMaxPages();
+        // Max pages is equal to max clicks - 1
+        assertThat(mPagedListView.getMaxPages(), is(equalTo(PagedListView.DEFAULT_MAX_CLICKS - 1)));
+
+        // setDefaultMaxPages
+        mPagedListView.setDefaultMaxPages(defaultMaxPages);
+        mPagedListView.resetMaxPages();
+        assertThat(mPagedListView.getMaxPages(), is(equalTo(defaultMaxPages - 1)));
+    }
+
+    @Test
+    public void setMaxPagesLimitsNumberOfClicks() {
+        if (!isAutoDevice()) {
+            return;
+        }
+
+        setUpPagedListView(ITEMS_PER_PAGE * 3 /* itemCount */, 2 /* maxPages */);
+
+        onView(withId(R.id.page_down)).perform(click());
+        onView(withId(R.id.page_down)).check(matches(not(isEnabled())));
+    }
+
+    @Test
+    public void testMaxPagesDoesNothingIfAdapterDoesNotImplementItemCap() {
+        if (!isAutoDevice()) {
+            return;
+        }
+
+        int numOfPages = 20;
+        int maxPages = 2;
+
+        setUpNonItemCapPagedListView(ITEMS_PER_PAGE * numOfPages, maxPages);
+
+        // There should be no limit on the scroll even though a max number of pages was set.
+        for (int i = 0; i < maxPages; i++) {
+            onView(withId(R.id.page_down)).perform(click());
+        }
+        onView(withId(R.id.page_down)).check(matches(isEnabled()));
+
+        // Next scroll all the way to bottom and check this is possible.
+        for (int i = 0; i < numOfPages - maxPages; i++) {
+            onView(withId(R.id.page_down)).perform(click());
+        }
+        onView(withId(R.id.page_down)).check(matches(not(isEnabled())));
+    }
+
+    @Suppress
+    @Test
+    public void resetMaxPagesToDefaultUnlimitedExtendsList() throws Throwable {
+        if (!isAutoDevice()) {
+            return;
+        }
+
+        final int itemCount = ITEMS_PER_PAGE * 4;
+        setUpPagedListView(itemCount, 2 /* maxPages */);
+
+        // Move to next page - should reach end of list.
+        onView(withId(R.id.page_down)).perform(click()).check(matches(not(isEnabled())));
+
+        // After resetting max pages (default unlimited), we scroll to the known total number of
+        // items.
+        mActivityRule.runOnUiThread(() -> mPagedListView.resetMaxPages());
+        onView(withId(R.id.recycler_view)).perform(scrollToPosition(itemCount - 1));
+
+        // Verify the last item that would've been hidden due to max pages is now shown.
+        onView(allOf(withId(R.id.text_view), withText(itemText(itemCount - 1))))
+                .check(matches(isDisplayed()));
+    }
+
+    @Test
+    public void scrollbarKeepsItemSnappedToTopOfList() {
+        if (!isAutoDevice()) {
+            return;
+        }
+
+        // 2.5 so last page is not full
+        setUpPagedListView((int) (ITEMS_PER_PAGE * 2.5 /* itemCount */));
+
+        // Going down one page and first item is snapped to top
+        onView(withId(R.id.page_down)).perform(click());
+        verifyItemSnappedToListTop();
+
+        // Go down another page and we reach the last page.
+        onView(withId(R.id.page_down)).perform(click()).check(matches(not(isEnabled())));
+        verifyItemSnappedToListTop();
+    }
+
+    @Suppress
+    @Test
+    public void swipeUpKeepsItemSnappedToTopOfList() {
+        setUpPagedListView(ITEMS_PER_PAGE * 2 /* itemCount */);
+
+        onView(withId(R.id.recycler_view)).perform(actionOnItemAtPosition(1, swipeUp()));
+
+        verifyItemSnappedToListTop();
+    }
+
+    @Suppress
+    @Test
+    public void swipeDownKeepsItemSnappedToTopOfList() throws Throwable {
+        setUpPagedListView(ITEMS_PER_PAGE * 2 /* itemCount */);
+
+        // Go down one page, then swipe down (going up).
+        onView(withId(R.id.recycler_view)).perform(scrollToPosition(ITEMS_PER_PAGE));
+        onView(withId(R.id.recycler_view))
+                .perform(actionOnItemAtPosition(ITEMS_PER_PAGE, swipeDown()));
+
+        verifyItemSnappedToListTop();
+    }
+
+    @Test
+    public void pageUpAndDownMoveSameDistance() {
+        if (!isAutoDevice()) {
+            return;
+        }
+
+        setUpPagedListView(ITEMS_PER_PAGE * 10);
+
+        // Move down one page so there will be sufficient pages for up and downs.
+        onView(withId(R.id.page_down)).perform(click());
+        final int topPosition = mPagedListView.getFirstFullyVisibleChildPosition();
+
+        for (int i = 0; i < 3; i++) {
+            onView(withId(R.id.page_down)).perform(click());
+            onView(withId(R.id.page_up)).perform(click());
+        }
+
+        assertThat(mPagedListView.getFirstFullyVisibleChildPosition(), is(equalTo(topPosition)));
+    }
+
+    @Suppress
+    @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;
+    }
+
+    private void verifyItemSnappedToListTop() {
+        int firstVisiblePosition = mPagedListView.getFirstFullyVisibleChildPosition();
+        if (firstVisiblePosition > 1) {
+            int lastInPreviousPagePosition = firstVisiblePosition - 1;
+            onView(withText(itemText(lastInPreviousPagePosition)))
+                    .check(matches(not(isDisplayed())));
+        }
+    }
+
+    /** A base adapter that will handle inflating the test view and binding data to it. */
+    private abstract class BaseTestAdapter extends RecyclerView.Adapter<TestViewHolder> {
+        protected List<String> mData;
+        protected int mParentHeight;
+
+        BaseTestAdapter(int itemCount, int parentHeight) {
+            mData = new ArrayList<>();
+            for (int i = 0; i < itemCount; i++) {
+                mData.add(itemText(i));
+            }
+            mParentHeight = parentHeight;
+        }
+
+        @Override
+        public TestViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+            LayoutInflater inflater = LayoutInflater.from(parent.getContext());
+            return new TestViewHolder(inflater, parent);
+        }
+
+        @Override
+        public void onBindViewHolder(TestViewHolder holder, int position) {
+            // Calculate height for an item so one page fits ITEMS_PER_PAGE items.
+            int height = (int) Math.floor(mParentHeight / ITEMS_PER_PAGE);
+            holder.itemView.setMinimumHeight(height);
+            holder.bind(mData.get(position));
+        }
+    }
+
+    private class TestAdapter extends BaseTestAdapter implements PagedListView.ItemCap {
+        private int mMaxItems;
+
+        TestAdapter(int itemCount, int parentHeight) {
+            super(itemCount, parentHeight);
+        }
+
+        @Override
+        public void setMaxItems(int maxItems) {
+            mMaxItems = maxItems;
+        }
+
+        @Override
+        public int getItemCount() {
+            return mMaxItems > 0 ? Math.min(mData.size(), mMaxItems) : mData.size();
+        }
+    }
+
+    /**
+     * A variant of a {@link BaseTestAdapter} that does not implement {@link PagedListView.ItemCap}.
+     */
+    private class NoItemCapAdapter extends BaseTestAdapter {
+        NoItemCapAdapter(int itemCount, int parentHeight) {
+            super(itemCount, parentHeight);
+        }
+
+        @Override
+        public int getItemCount() {
+            return mData.size();
+        }
+    }
+
+    private class TestViewHolder extends RecyclerView.ViewHolder {
+        private TextView mTextView;
+
+        TestViewHolder(LayoutInflater inflater, ViewGroup parent) {
+            super(inflater.inflate(R.layout.paged_list_item_column_card, parent, false));
+            mTextView = itemView.findViewById(R.id.text_view);
+        }
+
+        public void bind(String text) {
+            mTextView.setText(text);
+        }
+    }
+
+    private class PagedListViewScrollingIdlingResource implements IdlingResource {
+
+        private boolean mIdle = true;
+        private ResourceCallback mResourceCallback;
+
+        PagedListViewScrollingIdlingResource(PagedListView pagedListView) {
+            pagedListView.getRecyclerView().addOnScrollListener(
+                    new RecyclerView.OnScrollListener() {
+                        @Override
+                        public void onScrollStateChanged(
+                                RecyclerView recyclerView, int newState) {
+                            super.onScrollStateChanged(recyclerView, newState);
+                            mIdle = (newState == RecyclerView.SCROLL_STATE_IDLE
+                                    // Treat dragging as idle, or Espresso will block itself when
+                                    // swiping.
+                                    || newState == RecyclerView.SCROLL_STATE_DRAGGING);
+                            if (mIdle && mResourceCallback != null) {
+                                mResourceCallback.onTransitionToIdle();
+                            }
+                        }
+
+                        @Override
+                        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+                        }
+                    });
+        }
+
+        @Override
+        public String getName() {
+            return PagedListViewScrollingIdlingResource.class.getName();
+        }
+
+        @Override
+        public boolean isIdleNow() {
+            return mIdle;
+        }
+
+        @Override
+        public void registerIdleTransitionCallback(ResourceCallback callback) {
+            mResourceCallback = callback;
+        }
+    }
+}
diff --git a/car/tests/src/androidx/car/widget/PagedListViewTestActivity.java b/car/tests/src/androidx/car/widget/PagedListViewTestActivity.java
new file mode 100644
index 0000000..741cadd
--- /dev/null
+++ b/car/tests/src/androidx/car/widget/PagedListViewTestActivity.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.car.widget;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import androidx.car.test.R;
+
+/**
+ * Simple test activity for {@link PagedListView} class.
+ *
+ */
+public class PagedListViewTestActivity extends Activity {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_paged_list_view);
+    }
+}
diff --git a/compat/api/27.0.0.ignore b/compat/api/27.0.0.ignore
new file mode 100644
index 0000000..b49088f
--- /dev/null
+++ b/compat/api/27.0.0.ignore
@@ -0,0 +1 @@
+8227953
diff --git a/compat/api/current.txt b/compat/api/current.txt
index 96a94cb..66525e2 100644
--- a/compat/api/current.txt
+++ b/compat/api/current.txt
@@ -495,7 +495,7 @@
     field public static final int IMPORTANCE_UNSPECIFIED = -1000; // 0xfffffc18
   }
 
-  public final class RemoteInput extends android.support.v4.app.RemoteInputCompatBase.RemoteInput {
+  public final class RemoteInput {
     method public static void addDataResultToIntent(android.support.v4.app.RemoteInput, android.content.Intent, java.util.Map<java.lang.String, android.net.Uri>);
     method public static void addResultsToIntent(android.support.v4.app.RemoteInput[], android.content.Intent, android.os.Bundle);
     method public boolean getAllowFreeFormInput();
@@ -522,19 +522,6 @@
     method public android.support.v4.app.RemoteInput.Builder setLabel(java.lang.CharSequence);
   }
 
-   deprecated class RemoteInputCompatBase {
-  }
-
-  public static abstract deprecated class RemoteInputCompatBase.RemoteInput {
-    ctor public deprecated RemoteInputCompatBase.RemoteInput();
-    method protected abstract deprecated boolean getAllowFreeFormInput();
-    method protected abstract deprecated java.util.Set<java.lang.String> getAllowedDataTypes();
-    method protected abstract deprecated java.lang.CharSequence[] getChoices();
-    method protected abstract deprecated android.os.Bundle getExtras();
-    method protected abstract deprecated java.lang.CharSequence getLabel();
-    method protected abstract deprecated java.lang.String getResultKey();
-  }
-
   public final class ServiceCompat {
     method public static void stopForeground(android.app.Service, int);
     field public static final int START_STICKY = 1; // 0x1
@@ -678,6 +665,7 @@
     ctor public ShortcutInfoCompat.Builder(android.content.Context, java.lang.String);
     method public android.support.v4.content.pm.ShortcutInfoCompat build();
     method public android.support.v4.content.pm.ShortcutInfoCompat.Builder setActivity(android.content.ComponentName);
+    method public android.support.v4.content.pm.ShortcutInfoCompat.Builder setAlwaysBadged();
     method public android.support.v4.content.pm.ShortcutInfoCompat.Builder setDisabledMessage(java.lang.CharSequence);
     method public android.support.v4.content.pm.ShortcutInfoCompat.Builder setIcon(android.support.v4.graphics.drawable.IconCompat);
     method public android.support.v4.content.pm.ShortcutInfoCompat.Builder setIntent(android.content.Intent);
@@ -936,7 +924,7 @@
     method public static void requestFont(android.content.Context, android.support.v4.provider.FontRequest, android.support.v4.provider.FontsContractCompat.FontRequestCallback, android.os.Handler);
   }
 
-  public static final class FontsContractCompat.Columns {
+  public static final class FontsContractCompat.Columns implements android.provider.BaseColumns {
     ctor public FontsContractCompat.Columns();
     field public static final java.lang.String FILE_ID = "file_id";
     field public static final java.lang.String ITALIC = "font_italic";
@@ -1135,7 +1123,7 @@
     method public int size();
   }
 
-  public class LongSparseArray<E> {
+  public class LongSparseArray<E> implements java.lang.Cloneable {
     ctor public LongSparseArray();
     ctor public LongSparseArray(int);
     method public void append(long, E);
@@ -1234,7 +1222,7 @@
     method public V valueAt(int);
   }
 
-  public class SparseArrayCompat<E> {
+  public class SparseArrayCompat<E> implements java.lang.Cloneable {
     ctor public SparseArrayCompat();
     ctor public SparseArrayCompat(int);
     method public void append(int, E);
diff --git a/compat/build.gradle b/compat/build.gradle
index 56d0a98..a26eac8 100644
--- a/compat/build.gradle
+++ b/compat/build.gradle
@@ -13,11 +13,13 @@
         transitive = true
     }
 
-    androidTestImplementation(TEST_RUNNER, libs.exclude_annotations)
-    androidTestImplementation(ESPRESSO_CORE, libs.exclude_annotations)
+    androidTestImplementation(TEST_RUNNER)
+    androidTestImplementation(ESPRESSO_CORE)
     androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
     androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
-    androidTestImplementation project(':support-testutils')
+    androidTestImplementation project(':support-testutils'), {
+        exclude group: 'com.android.support', module: 'support-compat'
+    }
 }
 
 android {
diff --git a/compat/src/main/java/android/support/v4/accessibilityservice/package.html b/compat/src/main/java/android/support/v4/accessibilityservice/package.html
deleted file mode 100755
index 3d017b0..0000000
--- a/compat/src/main/java/android/support/v4/accessibilityservice/package.html
+++ /dev/null
@@ -1,6 +0,0 @@
-<body>
-
-Support android.accessibilityservice classes to assist with development of applications for
-android API level 4 or later.
-
-</body>
diff --git a/compat/src/main/java/android/support/v4/app/NotificationCompat.java b/compat/src/main/java/android/support/v4/app/NotificationCompat.java
index 1077b1f..80c7757 100644
--- a/compat/src/main/java/android/support/v4/app/NotificationCompat.java
+++ b/compat/src/main/java/android/support/v4/app/NotificationCompat.java
@@ -32,6 +32,7 @@
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffColorFilter;
 import android.graphics.drawable.Drawable;
+import android.media.AudioAttributes;
 import android.media.AudioManager;
 import android.net.Uri;
 import android.os.Build;
@@ -116,7 +117,6 @@
      * default stream type is {@link AudioManager#STREAM_NOTIFICATION}.
      */
     public static final int STREAM_DEFAULT = -1;
-
     /**
      * Bit set in the Notification flags field when LEDs should be turned on
      * for this notification.
@@ -439,6 +439,14 @@
     public static final int COLOR_DEFAULT = Color.TRANSPARENT;
 
     /** @hide */
+    @RestrictTo(LIBRARY_GROUP)
+    @IntDef({AudioManager.STREAM_VOICE_CALL, AudioManager.STREAM_SYSTEM, AudioManager.STREAM_RING,
+            AudioManager.STREAM_MUSIC, AudioManager.STREAM_ALARM, AudioManager.STREAM_NOTIFICATION,
+            AudioManager.STREAM_DTMF, AudioManager.STREAM_ACCESSIBILITY})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface StreamType {}
+
+    /** @hide */
     @Retention(SOURCE)
     @IntDef({VISIBILITY_PUBLIC, VISIBILITY_PRIVATE, VISIBILITY_SECRET})
     public @interface NotificationVisibility {}
@@ -957,6 +965,12 @@
         public Builder setSound(Uri sound) {
             mNotification.sound = sound;
             mNotification.audioStreamType = Notification.STREAM_DEFAULT;
+            if (Build.VERSION.SDK_INT >= 21) {
+                mNotification.audioAttributes = new AudioAttributes.Builder()
+                        .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+                        .setUsage(AudioAttributes.USAGE_NOTIFICATION)
+                        .build();
+            }
             return this;
         }
 
@@ -971,9 +985,15 @@
          * @see Notification#STREAM_DEFAULT
          * @see AudioManager for the <code>STREAM_</code> constants.
          */
-        public Builder setSound(Uri sound, int streamType) {
+        public Builder setSound(Uri sound, @StreamType int streamType) {
             mNotification.sound = sound;
             mNotification.audioStreamType = streamType;
+            if (Build.VERSION.SDK_INT >= 21) {
+                mNotification.audioAttributes = new AudioAttributes.Builder()
+                        .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+                        .setLegacyStreamType(streamType)
+                        .build();
+            }
             return this;
         }
 
diff --git a/compat/src/main/java/android/support/v4/app/NotificationCompatBuilder.java b/compat/src/main/java/android/support/v4/app/NotificationCompatBuilder.java
index 71f4160..db775a5 100644
--- a/compat/src/main/java/android/support/v4/app/NotificationCompatBuilder.java
+++ b/compat/src/main/java/android/support/v4/app/NotificationCompatBuilder.java
@@ -28,6 +28,7 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.support.annotation.RestrictTo;
+import android.text.TextUtils;
 import android.util.SparseArray;
 import android.widget.RemoteViews;
 
@@ -69,7 +70,6 @@
                 .setSmallIcon(n.icon, n.iconLevel)
                 .setContent(n.contentView)
                 .setTicker(n.tickerText, b.mTickerView)
-                .setSound(n.sound, n.audioStreamType)
                 .setVibrate(n.vibrate)
                 .setLights(n.ledARGB, n.ledOnMS, n.ledOffMS)
                 .setOngoing((n.flags & Notification.FLAG_ONGOING_EVENT) != 0)
@@ -86,6 +86,9 @@
                 .setLargeIcon(b.mLargeIcon)
                 .setNumber(b.mNumber)
                 .setProgress(b.mProgressMax, b.mProgress, b.mProgressIndeterminate);
+        if (Build.VERSION.SDK_INT < 21) {
+            mBuilder.setSound(n.sound, n.audioStreamType);
+        }
         if (Build.VERSION.SDK_INT >= 16) {
             mBuilder.setSubText(b.mSubText)
                     .setUsesChronometer(b.mUseChronometer)
@@ -141,7 +144,8 @@
             mBuilder.setCategory(b.mCategory)
                     .setColor(b.mColor)
                     .setVisibility(b.mVisibility)
-                    .setPublicVersion(b.mPublicVersion);
+                    .setPublicVersion(b.mPublicVersion)
+                    .setSound(n.sound, n.audioAttributes);
 
             for (String person: b.mPeople) {
                 mBuilder.addPerson(person);
@@ -169,6 +173,13 @@
             if (b.mColorizedSet) {
                 mBuilder.setColorized(b.mColorized);
             }
+
+            if (!TextUtils.isEmpty(b.mChannelId)) {
+                mBuilder.setSound(null)
+                        .setDefaults(0)
+                        .setLights(0, 0, 0)
+                        .setVibrate(null);
+            }
         }
     }
 
diff --git a/compat/src/main/java/android/support/v4/app/NotificationManagerCompat.java b/compat/src/main/java/android/support/v4/app/NotificationManagerCompat.java
index 1a0f1bc..07fcb6c 100644
--- a/compat/src/main/java/android/support/v4/app/NotificationManagerCompat.java
+++ b/compat/src/main/java/android/support/v4/app/NotificationManagerCompat.java
@@ -43,10 +43,10 @@
 import java.lang.reflect.Field;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
+import java.util.ArrayDeque;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -560,7 +560,7 @@
             /** The service stub provided by onServiceConnected */
             public INotificationSideChannel service;
             /** Queue of pending tasks to send to this listener service */
-            public LinkedList<Task> taskQueue = new LinkedList<Task>();
+            public ArrayDeque<Task> taskQueue = new ArrayDeque<>();
             /** Number of retries attempted while connecting to this listener service */
             public int retryCount = 0;
 
diff --git a/compat/src/main/java/android/support/v4/app/package.html b/compat/src/main/java/android/support/v4/app/package.html
deleted file mode 100755
index 02d1b79..0000000
--- a/compat/src/main/java/android/support/v4/app/package.html
+++ /dev/null
@@ -1,8 +0,0 @@
-<body>
-
-Support android.app classes to assist with development of applications for
-android API level 4 or later.  The main features here are backwards-compatible
-versions of {@link android.support.v4.app.FragmentManager} and
-{@link android.support.v4.app.LoaderManager}.
-
-</body>
diff --git a/compat/src/main/java/android/support/v4/content/package.html b/compat/src/main/java/android/support/v4/content/package.html
deleted file mode 100755
index 33bf4b5..0000000
--- a/compat/src/main/java/android/support/v4/content/package.html
+++ /dev/null
@@ -1,10 +0,0 @@
-<body>
-
-Support android.content classes to assist with development of applications for
-android API level 4 or later.  The main features here are
-{@link android.support.v4.content.Loader} and related classes and
-{@link android.support.v4.content.LocalBroadcastManager} to
-provide a cleaner implementation of broadcasts that don't need to go outside
-of an app.
-
-</body>
diff --git a/compat/src/main/java/android/support/v4/content/pm/ShortcutInfoCompat.java b/compat/src/main/java/android/support/v4/content/pm/ShortcutInfoCompat.java
index 3ae7470..63585e1 100644
--- a/compat/src/main/java/android/support/v4/content/pm/ShortcutInfoCompat.java
+++ b/compat/src/main/java/android/support/v4/content/pm/ShortcutInfoCompat.java
@@ -18,17 +18,20 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.content.pm.ShortcutInfo;
+import android.graphics.drawable.Drawable;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.annotation.RequiresApi;
+import android.support.annotation.VisibleForTesting;
 import android.support.v4.graphics.drawable.IconCompat;
 import android.text.TextUtils;
 
 import java.util.Arrays;
 
 /**
- * Helper for accessing features in {@link android.content.pm.ShortcutInfo}.
+ * Helper for accessing features in {@link ShortcutInfo}.
  */
 public class ShortcutInfoCompat {
 
@@ -43,6 +46,7 @@
     private CharSequence mDisabledMessage;
 
     private IconCompat mIcon;
+    private boolean mIsAlwaysBadged;
 
     private ShortcutInfoCompat() { }
 
@@ -69,11 +73,26 @@
         return builder.build();
     }
 
+    @VisibleForTesting
     Intent addToIntent(Intent outIntent) {
         outIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, mIntents[mIntents.length - 1])
                 .putExtra(Intent.EXTRA_SHORTCUT_NAME, mLabel.toString());
         if (mIcon != null) {
-            mIcon.addToShortcutIntent(outIntent);
+            Drawable badge = null;
+            if (mIsAlwaysBadged) {
+                PackageManager pm = mContext.getPackageManager();
+                if (mActivity != null) {
+                    try {
+                        badge = pm.getActivityIcon(mActivity);
+                    } catch (PackageManager.NameNotFoundException e) {
+                        // Ignore
+                    }
+                }
+                if (badge == null) {
+                    badge = mContext.getApplicationInfo().loadIcon(pm);
+                }
+            }
+            mIcon.addToShortcutIntent(outIntent, badge);
         }
         return outIntent;
     }
@@ -250,7 +269,7 @@
          * on the launcher.
          *
          * @see ShortcutInfo#getActivity()
-         * @see android.content.pm.ShortcutInfo.Builder#setActivity(ComponentName)
+         * @see ShortcutInfo.Builder#setActivity(ComponentName)
          */
         @NonNull
         public Builder setActivity(@NonNull ComponentName activity) {
@@ -259,6 +278,23 @@
         }
 
         /**
+         * Badges the icon before passing it over to the Launcher.
+         * <p>
+         * Launcher automatically badges {@link ShortcutInfo}, so only the legacy shortcut icon,
+         * {@link Intent.ShortcutIconResource} is badged. This field is ignored when using
+         * {@link ShortcutInfo} on API 25 and above.
+         * <p>
+         * If the shortcut is associated with an activity, the activity icon is used as the badge,
+         * otherwise application icon is used.
+         *
+         * @see #setActivity(ComponentName)
+         */
+        public Builder setAlwaysBadged() {
+            mInfo.mIsAlwaysBadged = true;
+            return this;
+        }
+
+        /**
          * Creates a {@link ShortcutInfoCompat} instance.
          */
         @NonNull
diff --git a/compat/src/main/java/android/support/v4/content/pm/package.html b/compat/src/main/java/android/support/v4/content/pm/package.html
deleted file mode 100755
index da850bd..0000000
--- a/compat/src/main/java/android/support/v4/content/pm/package.html
+++ /dev/null
@@ -1,6 +0,0 @@
-<body>
-
-Support android.content.pm classes to assist with development of applications for
-android API level 4 or later.
-
-</body>
diff --git a/compat/src/main/java/android/support/v4/database/package.html b/compat/src/main/java/android/support/v4/database/package.html
deleted file mode 100755
index 25ac59a..0000000
--- a/compat/src/main/java/android/support/v4/database/package.html
+++ /dev/null
@@ -1,6 +0,0 @@
-<body>
-
-Support android.database classes to assist with development of applications for
-android API level 4 or later.
-
-</body>
diff --git a/compat/src/main/java/android/support/v4/graphics/TypefaceCompatApi26Impl.java b/compat/src/main/java/android/support/v4/graphics/TypefaceCompatApi26Impl.java
index a608403..28ab3ed 100644
--- a/compat/src/main/java/android/support/v4/graphics/TypefaceCompatApi26Impl.java
+++ b/compat/src/main/java/android/support/v4/graphics/TypefaceCompatApi26Impl.java
@@ -240,6 +240,9 @@
             final ContentResolver resolver = context.getContentResolver();
             try (ParcelFileDescriptor pfd =
                     resolver.openFileDescriptor(bestFont.getUri(), "r", cancellationSignal)) {
+                if (pfd == null) {
+                    return null;
+                }
                 return new Typeface.Builder(pfd.getFileDescriptor())
                         .setWeight(bestFont.getWeight())
                         .setItalic(bestFont.isItalic())
diff --git a/compat/src/main/java/android/support/v4/graphics/drawable/IconCompat.java b/compat/src/main/java/android/support/v4/graphics/drawable/IconCompat.java
index a2ad67f..359c96b 100644
--- a/compat/src/main/java/android/support/v4/graphics/drawable/IconCompat.java
+++ b/compat/src/main/java/android/support/v4/graphics/drawable/IconCompat.java
@@ -18,6 +18,7 @@
 
 import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
 
+import android.app.ActivityManager;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Bitmap;
@@ -27,13 +28,17 @@
 import android.graphics.Matrix;
 import android.graphics.Paint;
 import android.graphics.Shader;
+import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
 import android.net.Uri;
 import android.os.Build;
 import android.support.annotation.DrawableRes;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.annotation.RequiresApi;
 import android.support.annotation.RestrictTo;
 import android.support.annotation.VisibleForTesting;
+import android.support.v4.content.ContextCompat;
 
 /**
  * Helper for accessing features in {@link android.graphics.drawable.Icon}.
@@ -187,7 +192,8 @@
                 if (Build.VERSION.SDK_INT >= 26) {
                     return Icon.createWithAdaptiveBitmap((Bitmap) mObj1);
                 } else {
-                    return Icon.createWithBitmap(createLegacyIconFromAdaptiveIcon((Bitmap) mObj1));
+                    return Icon.createWithBitmap(
+                            createLegacyIconFromAdaptiveIcon((Bitmap) mObj1, false));
                 }
             case TYPE_RESOURCE:
                 return Icon.createWithResource((Context) mObj1, mInt1);
@@ -201,34 +207,74 @@
     }
 
     /**
+     * Use {@link #addToShortcutIntent(Intent, Drawable)} instead
      * @hide
      */
     @RestrictTo(LIBRARY_GROUP)
-    public void addToShortcutIntent(Intent outIntent) {
+    @Deprecated
+    public void addToShortcutIntent(@NonNull Intent outIntent) {
+        addToShortcutIntent(outIntent, null);
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    public void addToShortcutIntent(@NonNull Intent outIntent, @Nullable Drawable badge) {
+        Bitmap icon;
         switch (mType) {
             case TYPE_BITMAP:
-                outIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON, (Bitmap) mObj1);
+                icon = (Bitmap) mObj1;
+                if (badge != null) {
+                    // Do not modify the original icon when applying a badge
+                    icon = icon.copy(icon.getConfig(), true);
+                }
                 break;
             case TYPE_ADAPTIVE_BITMAP:
-                outIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON,
-                        createLegacyIconFromAdaptiveIcon((Bitmap) mObj1));
+                icon = createLegacyIconFromAdaptiveIcon((Bitmap) mObj1, true);
                 break;
             case TYPE_RESOURCE:
-                outIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
-                        Intent.ShortcutIconResource.fromContext((Context) mObj1, mInt1));
+                if (badge == null) {
+                    outIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
+                            Intent.ShortcutIconResource.fromContext((Context) mObj1, mInt1));
+                    return;
+                } else {
+                    Context context = (Context) mObj1;
+                    Drawable dr = ContextCompat.getDrawable(context, mInt1);
+                    if (dr.getIntrinsicWidth() <= 0 || dr.getIntrinsicHeight() <= 0) {
+                        int size = ((ActivityManager) context.getSystemService(
+                                Context.ACTIVITY_SERVICE)).getLauncherLargeIconSize();
+                        icon = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
+                    } else {
+                        icon = Bitmap.createBitmap(dr.getIntrinsicWidth(), dr.getIntrinsicHeight(),
+                                Bitmap.Config.ARGB_8888);
+                    }
+                    dr.setBounds(0, 0, icon.getWidth(), icon.getHeight());
+                    dr.draw(new Canvas(icon));
+                }
                 break;
             default:
                 throw new IllegalArgumentException("Icon type not supported for intent shortcuts");
         }
+        if (badge != null) {
+            // Badge the icon
+            int w = icon.getWidth();
+            int h = icon.getHeight();
+            badge.setBounds(w / 2, h / 2, w, h);
+            badge.draw(new Canvas(icon));
+        }
+        outIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON, icon);
     }
 
     /**
      * Converts a bitmap following the adaptive icon guide lines, into a bitmap following the
      * shortcut icon guide lines.
      * The returned bitmap will always have same width and height and clipped to a circle.
+     *
+     * @param addShadow set to {@code true} only for legacy shortcuts and {@code false} otherwise
      */
     @VisibleForTesting
-    static Bitmap createLegacyIconFromAdaptiveIcon(Bitmap adaptiveIconBitmap) {
+    static Bitmap createLegacyIconFromAdaptiveIcon(Bitmap adaptiveIconBitmap, boolean addShadow) {
         int size = (int) (DEFAULT_VIEW_PORT_SCALE * Math.min(adaptiveIconBitmap.getWidth(),
                 adaptiveIconBitmap.getHeight()));
 
@@ -239,16 +285,18 @@
         float center = size * 0.5f;
         float radius = center * ICON_DIAMETER_FACTOR;
 
-        // Draw key shadow
-        float blur = BLUR_FACTOR * size;
-        paint.setColor(Color.TRANSPARENT);
-        paint.setShadowLayer(blur, 0, KEY_SHADOW_OFFSET_FACTOR * size, KEY_SHADOW_ALPHA << 24);
-        canvas.drawCircle(center, center, radius, paint);
+        if (addShadow) {
+            // Draw key shadow
+            float blur = BLUR_FACTOR * size;
+            paint.setColor(Color.TRANSPARENT);
+            paint.setShadowLayer(blur, 0, KEY_SHADOW_OFFSET_FACTOR * size, KEY_SHADOW_ALPHA << 24);
+            canvas.drawCircle(center, center, radius, paint);
 
-        // Draw ambient shadow
-        paint.setShadowLayer(blur, 0, 0, AMBIENT_SHADOW_ALPHA << 24);
-        canvas.drawCircle(center, center, radius, paint);
-        paint.clearShadowLayer();
+            // Draw ambient shadow
+            paint.setShadowLayer(blur, 0, 0, AMBIENT_SHADOW_ALPHA << 24);
+            canvas.drawCircle(center, center, radius, paint);
+            paint.clearShadowLayer();
+        }
 
         // Draw the clipped icon
         paint.setColor(Color.BLACK);
diff --git a/compat/src/main/java/android/support/v4/os/package.html b/compat/src/main/java/android/support/v4/os/package.html
deleted file mode 100755
index 929c967..0000000
--- a/compat/src/main/java/android/support/v4/os/package.html
+++ /dev/null
@@ -1,6 +0,0 @@
-<body>
-
-Support android.os classes to assist with development of applications for
-android API level 4 or later.
-
-</body>
diff --git a/compat/src/main/java/android/support/v4/provider/FontsContractCompat.java b/compat/src/main/java/android/support/v4/provider/FontsContractCompat.java
index 0926186..39acf68 100644
--- a/compat/src/main/java/android/support/v4/provider/FontsContractCompat.java
+++ b/compat/src/main/java/android/support/v4/provider/FontsContractCompat.java
@@ -274,7 +274,10 @@
                     : new ReplyCallback<TypefaceResult>() {
                         @Override
                         public void onReply(final TypefaceResult typeface) {
-                            if (typeface.mResult == FontFamilyResult.STATUS_OK) {
+                            if (typeface == null) {
+                                fontCallback.callbackFailAsync(
+                                        FontRequestCallback.FAIL_REASON_FONT_NOT_FOUND, handler);
+                            } else if (typeface.mResult == FontFamilyResult.STATUS_OK) {
                                 fontCallback.callbackSuccessAsync(typeface.mTypeface, handler);
                             } else {
                                 fontCallback.callbackFailAsync(typeface.mResult, handler);
diff --git a/compat/src/main/java/android/support/v4/util/ArraySet.java b/compat/src/main/java/android/support/v4/util/ArraySet.java
index ab080fa..a9a8806 100644
--- a/compat/src/main/java/android/support/v4/util/ArraySet.java
+++ b/compat/src/main/java/android/support/v4/util/ArraySet.java
@@ -16,9 +16,6 @@
 
 package android.support.v4.util;
 
-import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
-
-import android.support.annotation.RestrictTo;
 import android.util.Log;
 
 import java.lang.reflect.Array;
@@ -74,7 +71,6 @@
     static Object[] sTwiceBaseCache;
     static int sTwiceBaseCacheSize;
 
-    final boolean mIdentityHashCode;
     int[] mHashes;
     Object[] mArray;
     int mSize;
@@ -238,19 +234,13 @@
      * will grow once items are added to it.
      */
     public ArraySet() {
-        this(0, false);
+        this(0);
     }
 
     /**
      * Create a new ArraySet with a given initial capacity.
      */
     public ArraySet(int capacity) {
-        this(capacity, false);
-    }
-
-    /** {@hide} */
-    public ArraySet(int capacity, boolean identityHashCode) {
-        mIdentityHashCode = identityHashCode;
         if (capacity == 0) {
             mHashes = INT;
             mArray = OBJECT;
@@ -270,14 +260,6 @@
         }
     }
 
-    /** {@hide} */
-    public ArraySet(Collection<E> set) {
-        this();
-        if (set != null) {
-            addAll(set);
-        }
-    }
-
     /**
      * Make the array map empty.  All storage is released.
      */
@@ -326,8 +308,7 @@
      * @return Returns the index of the value if it exists, else a negative integer.
      */
     public int indexOf(Object key) {
-        return key == null ? indexOfNull()
-                : indexOf(key, mIdentityHashCode ? System.identityHashCode(key) : key.hashCode());
+        return key == null ? indexOfNull() : indexOf(key, key.hashCode());
     }
 
     /**
@@ -364,7 +345,7 @@
             hash = 0;
             index = indexOfNull();
         } else {
-            hash = mIdentityHashCode ? System.identityHashCode(value) : value.hashCode();
+            hash = value.hashCode();
             index = indexOf(value, hash);
         }
         if (index >= 0) {
@@ -406,36 +387,6 @@
     }
 
     /**
-     * Special fast path for appending items to the end of the array without validation.
-     * The array must already be large enough to contain the item.
-     * @hide
-     */
-    @RestrictTo(LIBRARY_GROUP)
-    public void append(E value) {
-        final int index = mSize;
-        final int hash = value == null ? 0
-                : (mIdentityHashCode ? System.identityHashCode(value) : value.hashCode());
-        if (index >= mHashes.length) {
-            throw new IllegalStateException("Array is full");
-        }
-        if (index > 0 && mHashes[index - 1] > hash) {
-            // Cannot optimize since it would break the sorted order - fallback to add()
-            if (DEBUG) {
-                RuntimeException e = new RuntimeException("here");
-                e.fillInStackTrace();
-                Log.w(TAG, "New hash " + hash
-                        + " is before end of array hash " + mHashes[index - 1]
-                        + " at index " + index, e);
-            }
-            add(value);
-            return;
-        }
-        mSize = index + 1;
-        mHashes[index] = hash;
-        mArray[index] = value;
-    }
-
-    /**
      * Perform a {@link #add(Object)} of all values in <var>array</var>
      * @param array The array whose contents are to be retrieved.
      */
diff --git a/compat/src/main/java/android/support/v4/util/package.html b/compat/src/main/java/android/support/v4/util/package.html
deleted file mode 100644
index afde9b7..0000000
--- a/compat/src/main/java/android/support/v4/util/package.html
+++ /dev/null
@@ -1,6 +0,0 @@
-<body>
-
-Support android.util classes to assist with development of applications for
-android API level 4 or later.
-
-</body>
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/compat/src/main/java/android/support/v4/view/accessibility/package.html b/compat/src/main/java/android/support/v4/view/accessibility/package.html
deleted file mode 100755
index 57b084f..0000000
--- a/compat/src/main/java/android/support/v4/view/accessibility/package.html
+++ /dev/null
@@ -1,5 +0,0 @@
-<body>
-
-Support classes to access some of the android.view.accessibility package features introduced after API level 4 in a backwards compatible fashion.
-
-</body>
diff --git a/compat/src/main/java/android/support/v4/view/package.html b/compat/src/main/java/android/support/v4/view/package.html
deleted file mode 100755
index d80ef70..0000000
--- a/compat/src/main/java/android/support/v4/view/package.html
+++ /dev/null
@@ -1,11 +0,0 @@
-<body>
-
-Support android.util classes to assist with development of applications for
-android API level 4 or later.  The main features here are a variety of classes
-for handling backwards compatibility with views (for example
-{@link android.support.v4.view.MotionEventCompat} allows retrieving multi-touch
-data if available), and a new
-{@link android.support.v4.view.ViewPager} widget (which at some point should be moved over
-to the widget package).
-
-</body>
diff --git a/compat/src/main/java/android/support/v4/widget/package.html b/compat/src/main/java/android/support/v4/widget/package.html
deleted file mode 100755
index e2c636d..0000000
--- a/compat/src/main/java/android/support/v4/widget/package.html
+++ /dev/null
@@ -1,8 +0,0 @@
-<body>
-
-Support android.widget classes to assist with development of applications for
-android API level 4 or later.  This includes a complete modern implementation
-of {@link android.support.v4.widget.CursorAdapter} and related classes, which
-is needed for use with {@link android.support.v4.content.CursorLoader}.
-
-</body>
diff --git a/compat/tests/AndroidManifest.xml b/compat/tests/AndroidManifest.xml
index 4988845..ed6727f 100644
--- a/compat/tests/AndroidManifest.xml
+++ b/compat/tests/AndroidManifest.xml
@@ -37,7 +37,8 @@
 
         <activity android:name="android.support.v4.view.ViewCompatActivity"/>
 
-        <activity android:name="android.support.v4.app.TestSupportActivity"/>
+        <activity android:name="android.support.v4.app.TestSupportActivity"
+                  android:icon="@drawable/test_drawable_blue"/>
 
         <provider android:name="android.support.v4.provider.MockFontProvider"
                   android:authorities="android.support.provider.fonts.font"
diff --git a/compat/tests/java/android/support/v4/app/ActivityCompatTest.java b/compat/tests/java/android/support/v4/app/ActivityCompatTest.java
index 3b4f1b5..35889fb 100644
--- a/compat/tests/java/android/support/v4/app/ActivityCompatTest.java
+++ b/compat/tests/java/android/support/v4/app/ActivityCompatTest.java
@@ -25,6 +25,7 @@
 
 import android.Manifest;
 import android.app.Activity;
+import android.support.test.filters.SdkSuppress;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.support.v4.BaseInstrumentationTestCase;
@@ -40,6 +41,7 @@
         super(TestSupportActivity.class);
     }
 
+    @SdkSuppress(minSdkVersion = 24)
     @SmallTest
     @Test
     public void testPermissionDelegate() {
diff --git a/compat/tests/java/android/support/v4/app/NotificationCompatTest.java b/compat/tests/java/android/support/v4/app/NotificationCompatTest.java
index dd870dd..7a5a57f 100644
--- a/compat/tests/java/android/support/v4/app/NotificationCompatTest.java
+++ b/compat/tests/java/android/support/v4/app/NotificationCompatTest.java
@@ -34,6 +34,9 @@
 import android.annotation.TargetApi;
 import android.app.Notification;
 import android.content.Context;
+import android.graphics.Color;
+import android.media.AudioAttributes;
+import android.media.AudioManager;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
@@ -424,6 +427,71 @@
         }
     }
 
+    @Test
+    @SdkSuppress(minSdkVersion = 21)
+    public void testHasAudioAttributesFrom21() throws Throwable {
+        Notification n = new NotificationCompat.Builder(mActivityTestRule.getActivity())
+                .setSound(Uri.EMPTY)
+                .build();
+        assertNotNull(n.audioAttributes);
+        assertEquals(-1, n.audioStreamType);
+        assertEquals(Uri.EMPTY, n.sound);
+
+        n = new NotificationCompat.Builder(mActivityTestRule.getActivity())
+                .setSound(Uri.EMPTY, AudioManager.STREAM_RING)
+                .build();
+        assertNotNull(n.audioAttributes);
+        assertEquals(AudioAttributes.CONTENT_TYPE_SONIFICATION,
+                n.audioAttributes.getContentType());
+        assertEquals(-1, n.audioStreamType);
+        assertEquals(Uri.EMPTY, n.sound);
+    }
+
+    @Test
+    @SdkSuppress(maxSdkVersion = 20)
+    public void testHasStreamTypePre21() throws Throwable {
+        Notification n = new NotificationCompat.Builder(mActivityTestRule.getActivity())
+                .setSound(Uri.EMPTY, 34)
+                .build();
+        assertEquals(34, n.audioStreamType);
+        assertEquals(Uri.EMPTY, n.sound);
+    }
+
+    @SdkSuppress(minSdkVersion = 26)
+    @Test
+    public void testClearAlertingFieldsIfUsingChannels() throws Throwable {
+        long[] vibration = new long[]{100};
+
+        // stripped if using channels
+        Notification n = new NotificationCompat.Builder(mActivityTestRule.getActivity(), "test")
+                .setSound(Uri.EMPTY)
+                .setDefaults(Notification.DEFAULT_ALL)
+                .setVibrate(vibration)
+                .setLights(Color.BLUE, 100, 100)
+                .build();
+        assertNull(n.sound);
+        assertEquals(0, n.defaults);
+        assertNull(n.vibrate);
+        assertEquals(0, n.ledARGB);
+        assertEquals(0, n.ledOnMS);
+        assertEquals(0, n.ledOffMS);
+
+        // left intact if not using channels
+        n = new NotificationCompat.Builder(mActivityTestRule.getActivity())
+                .setSound(Uri.EMPTY)
+                .setDefaults(Notification.DEFAULT_ALL)
+                .setVibrate(vibration)
+                .setLights(Color.BLUE, 100, 100)
+                .build();
+        assertEquals(Uri.EMPTY, n.sound);
+        assertNotNull(n.audioAttributes);
+        assertEquals(Notification.DEFAULT_ALL, n.defaults);
+        assertEquals(vibration, n.vibrate);
+        assertEquals(Color.BLUE, n.ledARGB);
+        assertEquals(100, n.ledOnMS);
+        assertEquals(100, n.ledOffMS);
+    }
+
     private static RemoteInput newDataOnlyRemoteInput() {
         return new RemoteInput.Builder(DATA_RESULT_KEY)
             .setAllowFreeFormInput(false)
diff --git a/compat/tests/java/android/support/v4/content/pm/ShortcutInfoCompatTest.java b/compat/tests/java/android/support/v4/content/pm/ShortcutInfoCompatTest.java
new file mode 100644
index 0000000..c1a5832
--- /dev/null
+++ b/compat/tests/java/android/support/v4/content/pm/ShortcutInfoCompatTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.content.pm;
+
+import static android.support.v4.graphics.drawable.IconCompatTest.verifyBadgeBitmap;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.support.compat.test.R;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v4.app.TestSupportActivity;
+import android.support.v4.content.ContextCompat;
+import android.support.v4.graphics.drawable.IconCompat;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ShortcutInfoCompatTest {
+
+    private Intent mAction;
+
+    private Context mContext;
+    private ShortcutInfoCompat.Builder mBuilder;
+
+    @Before
+    public void setup() {
+        mContext = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
+        mAction = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName());
+
+        mBuilder = new ShortcutInfoCompat.Builder(mContext, "test-shortcut")
+                .setIntent(mAction)
+                .setShortLabel("Test shortcut")
+                .setIcon(IconCompat.createWithResource(mContext, R.drawable.test_drawable_red));
+    }
+
+    @Test
+    public void testAddToIntent_noBadge() {
+        Intent intent = new Intent();
+        mBuilder.setActivity(new ComponentName(mContext, TestSupportActivity.class))
+                .build()
+                .addToIntent(intent);
+
+        assertEquals(mAction, intent.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT));
+        assertNotNull(intent.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE));
+        assertNull(intent.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON));
+    }
+
+    @Test
+    public void testAddToIntent_badgeActivity() {
+        Intent intent = new Intent();
+        mBuilder.setActivity(new ComponentName(mContext, TestSupportActivity.class))
+                .setAlwaysBadged()
+                .build()
+                .addToIntent(intent);
+
+        assertEquals(mAction, intent.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT));
+        assertNull(intent.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE));
+
+        verifyBadgeBitmap(intent, ContextCompat.getColor(mContext, R.color.test_red),
+                ContextCompat.getColor(mContext, R.color.test_blue));
+    }
+
+    @Test
+    public void testAddToIntent_badgeApplication() {
+        ApplicationInfo appInfo = spy(mContext.getApplicationInfo());
+        doReturn(ContextCompat.getDrawable(mContext, R.drawable.test_drawable_green))
+                .when(appInfo).loadIcon(any(PackageManager.class));
+        doReturn(appInfo).when(mContext).getApplicationInfo();
+
+        Intent intent = new Intent();
+        mBuilder.setAlwaysBadged()
+                .build()
+                .addToIntent(intent);
+
+        assertEquals(mAction, intent.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT));
+        assertNull(intent.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE));
+
+        verifyBadgeBitmap(intent, ContextCompat.getColor(mContext, R.color.test_red),
+                ContextCompat.getColor(mContext, R.color.test_green));
+    }
+}
diff --git a/compat/tests/java/android/support/v4/content/pm/ShortcutManagerCompatTest.java b/compat/tests/java/android/support/v4/content/pm/ShortcutManagerCompatTest.java
index 3a48a6bd..7853f02 100644
--- a/compat/tests/java/android/support/v4/content/pm/ShortcutManagerCompatTest.java
+++ b/compat/tests/java/android/support/v4/content/pm/ShortcutManagerCompatTest.java
@@ -20,6 +20,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.doReturn;
@@ -112,7 +113,7 @@
         ShortcutManager mockShortcutManager = mock(ShortcutManager.class);
         doReturn(mockShortcutManager).when(mContext).getSystemService(eq(Context.SHORTCUT_SERVICE));
         when(mockShortcutManager.requestPinShortcut(
-                any(ShortcutInfo.class), any(IntentSender.class))).thenReturn(true);
+                any(ShortcutInfo.class), nullable(IntentSender.class))).thenReturn(true);
 
         assertTrue(ShortcutManagerCompat.requestPinShortcut(mContext, mInfoCompat, null));
         ArgumentCaptor<ShortcutInfo> captor = ArgumentCaptor.forClass(ShortcutInfo.class);
diff --git a/compat/tests/java/android/support/v4/graphics/drawable/IconCompatTest.java b/compat/tests/java/android/support/v4/graphics/drawable/IconCompatTest.java
index d87ddac..c83ba7e 100644
--- a/compat/tests/java/android/support/v4/graphics/drawable/IconCompatTest.java
+++ b/compat/tests/java/android/support/v4/graphics/drawable/IconCompatTest.java
@@ -17,6 +17,9 @@
 package android.support.v4.graphics.drawable;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
 import android.annotation.TargetApi;
@@ -34,6 +37,7 @@
 import android.support.test.filters.SdkSuppress;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
+import android.support.v4.content.ContextCompat;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -45,7 +49,7 @@
 @SmallTest
 public class IconCompatTest {
 
-    private void verifyClippedCircle(Bitmap bitmap, int fillColor, int size) {
+    private static void verifyClippedCircle(Bitmap bitmap, int fillColor, int size) {
         assertEquals(size, bitmap.getHeight());
         assertEquals(bitmap.getWidth(), bitmap.getHeight());
         assertEquals(fillColor, bitmap.getPixel(size / 2, size / 2));
@@ -53,14 +57,28 @@
         assertEquals(Color.TRANSPARENT, bitmap.getPixel(0, 0));
         assertEquals(Color.TRANSPARENT, bitmap.getPixel(0, size - 1));
         assertEquals(Color.TRANSPARENT, bitmap.getPixel(size - 1, 0));
+
+        // The badge is a full rectangle located at the bottom right corner. Check a single pixel
+        // in that region to verify that badging was properly applied.
         assertEquals(Color.TRANSPARENT, bitmap.getPixel(size - 1, size - 1));
     }
 
+    public static void verifyBadgeBitmap(Intent intent, int bgColor, int badgeColor) {
+        Bitmap bitmap = intent.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
+        int w = bitmap.getWidth();
+        int h = bitmap.getHeight();
+
+        assertEquals(bgColor, bitmap.getPixel(2, 2));
+        assertEquals(bgColor, bitmap.getPixel(w - 2, 2));
+        assertEquals(bgColor, bitmap.getPixel(2, h - 2));
+        assertEquals(badgeColor, bitmap.getPixel(w - 2, h - 2));
+    }
+
     @Test
     public void testClipAdaptiveIcon() throws Throwable {
         Bitmap source = Bitmap.createBitmap(200, 150, Bitmap.Config.ARGB_8888);
         source.eraseColor(Color.RED);
-        Bitmap result = IconCompat.createLegacyIconFromAdaptiveIcon(source);
+        Bitmap result = IconCompat.createLegacyIconFromAdaptiveIcon(source, false);
         verifyClippedCircle(result, Color.RED, 100);
     }
 
@@ -69,11 +87,46 @@
         Bitmap bitmap = Bitmap.createBitmap(200, 200, Bitmap.Config.ARGB_8888);
         bitmap.eraseColor(Color.RED);
         Intent intent = new Intent();
-        IconCompat.createWithBitmap(bitmap).addToShortcutIntent(intent);
+        IconCompat.createWithBitmap(bitmap).addToShortcutIntent(intent, null);
         assertEquals(bitmap, intent.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON));
     }
 
     @Test
+    public void testAddBitmapToShortcutIntent_badged() {
+        Context context = InstrumentationRegistry.getContext();
+        Bitmap bitmap = Bitmap.createBitmap(200, 200, Bitmap.Config.ARGB_8888);
+        bitmap.eraseColor(Color.RED);
+        Intent intent = new Intent();
+
+        Drawable badge = ContextCompat.getDrawable(context, R.drawable.test_drawable_blue);
+        IconCompat.createWithBitmap(bitmap).addToShortcutIntent(intent, badge);
+        assertNotSame(bitmap, intent.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON));
+
+        verifyBadgeBitmap(intent, Color.RED, ContextCompat.getColor(context, R.color.test_blue));
+    }
+
+    @Test
+    public void testAddResourceToShortcutIntent_badged() {
+        Context context = InstrumentationRegistry.getContext();
+        Intent intent = new Intent();
+
+        // No badge
+        IconCompat.createWithResource(context, R.drawable.test_drawable_green)
+                .addToShortcutIntent(intent, null);
+        assertNotNull(intent.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE));
+        assertNull(intent.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON));
+
+        intent = new Intent();
+        Drawable badge = ContextCompat.getDrawable(context, R.drawable.test_drawable_red);
+        IconCompat.createWithResource(context, R.drawable.test_drawable_blue)
+                .addToShortcutIntent(intent, badge);
+
+        assertNull(intent.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE));
+        verifyBadgeBitmap(intent, ContextCompat.getColor(context, R.color.test_blue),
+                ContextCompat.getColor(context, R.color.test_red));
+    }
+
+    @Test
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.M)
     @TargetApi(Build.VERSION_CODES.M)
     public void testCreateWithBitmap() {
@@ -90,7 +143,7 @@
         Bitmap bitmap = Bitmap.createBitmap(200, 200, Bitmap.Config.ARGB_8888);
         bitmap.eraseColor(Color.GREEN);
         Intent intent = new Intent();
-        IconCompat.createWithAdaptiveBitmap(bitmap).addToShortcutIntent(intent);
+        IconCompat.createWithAdaptiveBitmap(bitmap).addToShortcutIntent(intent, null);
 
         Bitmap clipped = intent.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
         verifyClippedCircle(clipped, Color.GREEN, clipped.getWidth());
diff --git a/compat/tests/java/android/support/v4/provider/FontsContractCompatTest.java b/compat/tests/java/android/support/v4/provider/FontsContractCompatTest.java
index 66bdd50..4edab02 100644
--- a/compat/tests/java/android/support/v4/provider/FontsContractCompatTest.java
+++ b/compat/tests/java/android/support/v4/provider/FontsContractCompatTest.java
@@ -40,9 +40,11 @@
 import android.content.pm.ProviderInfo;
 import android.content.pm.Signature;
 import android.graphics.Typeface;
+import android.support.annotation.NonNull;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
+import android.support.v4.content.res.ResourcesCompat;
 import android.support.v4.provider.FontsContractCompat.FontFamilyResult;
 import android.support.v4.provider.FontsContractCompat.FontInfo;
 import android.util.Base64;
@@ -56,6 +58,8 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Unit tests for {@link FontsContractCompat}.
@@ -111,34 +115,6 @@
                 InstrumentationRegistry.getInstrumentation().getTargetContext());
     }
 
-    private static class TestCallback extends FontsContractCompat.FontRequestCallback {
-        private Typeface mTypeface;
-
-        private int mSuccessCallCount;
-        private int mFailedCallCount;
-
-        public void onTypefaceRetrieved(Typeface typeface) {
-            mTypeface = typeface;
-            mSuccessCallCount++;
-        }
-
-        public void onTypefaceRequestFailed(int reason) {
-            mFailedCallCount++;
-        }
-
-        public Typeface getTypeface() {
-            return mTypeface;
-        }
-
-        public int getSuccessCallCount() {
-            return mSuccessCallCount;
-        }
-
-        public int getFailedCallCount() {
-            return mFailedCallCount;
-        }
-    }
-
     @Test
     public void typefaceNotCacheTest() throws NameNotFoundException {
         FontRequest request = new FontRequest(
@@ -413,4 +389,43 @@
         when(packageManager.getPackageInfo(anyString(), anyInt())).thenReturn(packageInfo);
         return info;
     }
+
+    @Test
+    public void testGetFontSync_invalidUri() throws InterruptedException {
+        final Instrumentation inst = InstrumentationRegistry.getInstrumentation();
+        final FontRequest request = new FontRequest(
+                AUTHORITY, PACKAGE, MockFontProvider.INVALID_URI, SIGNATURE);
+        final CountDownLatch latch = new CountDownLatch(1);
+        final FontCallback callback = new FontCallback(latch);
+
+        inst.runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                FontsContractCompat.getFontSync(mContext, request, callback, null,
+                        false /* isBlockingFetch */, 300 /* timeout */, Typeface.NORMAL);
+            }
+        });
+        assertTrue(latch.await(5L, TimeUnit.SECONDS));
+        assertNull(callback.mTypeface);
+    }
+
+    public static class FontCallback extends ResourcesCompat.FontCallback {
+        private final CountDownLatch mLatch;
+        Typeface mTypeface;
+
+        FontCallback(CountDownLatch latch) {
+            mLatch = latch;
+        }
+
+        @Override
+        public void onFontRetrieved(@NonNull Typeface typeface) {
+            mTypeface = typeface;
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onFontRetrievalFailed(int reason) {
+            mLatch.countDown();
+        }
+    }
 }
diff --git a/compat/tests/java/android/support/v4/provider/MockFontProvider.java b/compat/tests/java/android/support/v4/provider/MockFontProvider.java
index f07d92d..f584d68 100644
--- a/compat/tests/java/android/support/v4/provider/MockFontProvider.java
+++ b/compat/tests/java/android/support/v4/provider/MockFontProvider.java
@@ -43,6 +43,7 @@
     static final String[] FONT_FILES = {
             "samplefont.ttf", "large_a.ttf", "large_b.ttf", "large_c.ttf", "large_d.ttf"
     };
+    private static final int INVALID_FONT_FILE_ID = -1;
     private static final int SAMPLE_FONT_FILE_0_ID = 0;
     private static final int LARGE_A_FILE_ID = 1;
     private static final int LARGE_B_FILE_ID = 2;
@@ -59,6 +60,7 @@
     static final String NEGATIVE_ERROR_CODE_QUERY = "negativeCode";
     static final String MANDATORY_FIELDS_ONLY_QUERY = "mandatoryFields";
     static final String STYLE_TEST_QUERY = "styleTest";
+    static final String INVALID_URI = "invalidURI";
 
     static class Font {
         Font(int id, int fileId, int ttcIndex, String varSettings, int weight, int italic,
@@ -176,6 +178,11 @@
                         Columns.RESULT_CODE_OK, true),
         });
 
+        map.put(INVALID_URI, new Font[] {
+                new Font(id++, INVALID_FONT_FILE_ID, 0, null, 400, 0,
+                        Columns.RESULT_CODE_OK, true),
+        });
+
         QUERY_MAP = Collections.unmodifiableMap(map);
     }
 
@@ -260,6 +267,9 @@
     @Override
     public ParcelFileDescriptor openFile(Uri uri, String mode) {
         final int id = (int) ContentUris.parseId(uri);
+        if (id < 0) {
+            return null;
+        }
         final File targetFile = getCopiedFile(getContext(), FONT_FILES[id]);
         try {
             return ParcelFileDescriptor.open(targetFile, ParcelFileDescriptor.MODE_READ_ONLY);
diff --git a/content/OWNERS b/content/OWNERS
new file mode 100644
index 0000000..779e918
--- /dev/null
+++ b/content/OWNERS
@@ -0,0 +1 @@
+smckay@google.com
\ No newline at end of file
diff --git a/content/build.gradle b/content/build.gradle
index 15f96c4..9091053 100644
--- a/content/build.gradle
+++ b/content/build.gradle
@@ -27,8 +27,8 @@
     api(project(":support-compat"))
 
     androidTestImplementation(JUNIT)
-    androidTestImplementation(TEST_RUNNER, libs.exclude_annotations)
-    androidTestImplementation(ESPRESSO_CORE, libs.exclude_annotations)
+    androidTestImplementation(TEST_RUNNER)
+    androidTestImplementation(ESPRESSO_CORE)
 }
 
 android {
diff --git a/core-ui/Android.mk b/core-ui/Android.mk
index 184d7be..47846a9 100644
--- a/core-ui/Android.mk
+++ b/core-ui/Android.mk
@@ -30,6 +30,7 @@
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 LOCAL_SHARED_ANDROID_LIBRARIES := \
     android-support-compat \
+    android-support-core-utils \
     android-support-annotations
 LOCAL_JAR_EXCLUDE_FILES := none
 LOCAL_JAVA_LANGUAGE_VERSION := 1.7
diff --git a/core-ui/api/27.0.0.ignore b/core-ui/api/27.0.0.ignore
new file mode 100644
index 0000000..8dbf04a
--- /dev/null
+++ b/core-ui/api/27.0.0.ignore
@@ -0,0 +1,9 @@
+46690b4
+1670c35
+cde6951
+21047f0
+1416521
+4f63fb9
+2337166
+9e34008
+7546756
diff --git a/core-ui/api/current.txt b/core-ui/api/current.txt
index 6ae4b1a..77e805d 100644
--- a/core-ui/api/current.txt
+++ b/core-ui/api/current.txt
@@ -1,3 +1,96 @@
+package android.support.design.widget {
+
+  public class CoordinatorLayout extends android.view.ViewGroup implements android.support.v4.view.NestedScrollingParent2 {
+    ctor public CoordinatorLayout(android.content.Context);
+    ctor public CoordinatorLayout(android.content.Context, android.util.AttributeSet);
+    ctor public CoordinatorLayout(android.content.Context, android.util.AttributeSet, int);
+    method public void dispatchDependentViewsChanged(android.view.View);
+    method public boolean doViewsOverlap(android.view.View, android.view.View);
+    method protected android.support.design.widget.CoordinatorLayout.LayoutParams generateDefaultLayoutParams();
+    method public android.support.design.widget.CoordinatorLayout.LayoutParams generateLayoutParams(android.util.AttributeSet);
+    method protected android.support.design.widget.CoordinatorLayout.LayoutParams generateLayoutParams(android.view.ViewGroup.LayoutParams);
+    method public java.util.List<android.view.View> getDependencies(android.view.View);
+    method public java.util.List<android.view.View> getDependents(android.view.View);
+    method public android.graphics.drawable.Drawable getStatusBarBackground();
+    method public boolean isPointInChildBounds(android.view.View, int, int);
+    method public void onAttachedToWindow();
+    method public void onDetachedFromWindow();
+    method public void onDraw(android.graphics.Canvas);
+    method public void onLayoutChild(android.view.View, int);
+    method public void onMeasureChild(android.view.View, int, int, int, int);
+    method public void onNestedPreScroll(android.view.View, int, int, int[], int);
+    method public void onNestedScroll(android.view.View, int, int, int, int, int);
+    method public void onNestedScrollAccepted(android.view.View, android.view.View, int, int);
+    method public boolean onStartNestedScroll(android.view.View, android.view.View, int, int);
+    method public void onStopNestedScroll(android.view.View, int);
+    method public void setStatusBarBackground(android.graphics.drawable.Drawable);
+    method public void setStatusBarBackgroundColor(int);
+    method public void setStatusBarBackgroundResource(int);
+  }
+
+  public static abstract class CoordinatorLayout.Behavior<V extends android.view.View> {
+    ctor public CoordinatorLayout.Behavior();
+    ctor public CoordinatorLayout.Behavior(android.content.Context, android.util.AttributeSet);
+    method public boolean blocksInteractionBelow(android.support.design.widget.CoordinatorLayout, V);
+    method public boolean getInsetDodgeRect(android.support.design.widget.CoordinatorLayout, V, android.graphics.Rect);
+    method public int getScrimColor(android.support.design.widget.CoordinatorLayout, V);
+    method public float getScrimOpacity(android.support.design.widget.CoordinatorLayout, V);
+    method public static java.lang.Object getTag(android.view.View);
+    method public boolean layoutDependsOn(android.support.design.widget.CoordinatorLayout, V, android.view.View);
+    method public android.support.v4.view.WindowInsetsCompat onApplyWindowInsets(android.support.design.widget.CoordinatorLayout, V, android.support.v4.view.WindowInsetsCompat);
+    method public void onAttachedToLayoutParams(android.support.design.widget.CoordinatorLayout.LayoutParams);
+    method public boolean onDependentViewChanged(android.support.design.widget.CoordinatorLayout, V, android.view.View);
+    method public void onDependentViewRemoved(android.support.design.widget.CoordinatorLayout, V, android.view.View);
+    method public void onDetachedFromLayoutParams();
+    method public boolean onInterceptTouchEvent(android.support.design.widget.CoordinatorLayout, V, android.view.MotionEvent);
+    method public boolean onLayoutChild(android.support.design.widget.CoordinatorLayout, V, int);
+    method public boolean onMeasureChild(android.support.design.widget.CoordinatorLayout, V, int, int, int, int);
+    method public boolean onNestedFling(android.support.design.widget.CoordinatorLayout, V, android.view.View, float, float, boolean);
+    method public boolean onNestedPreFling(android.support.design.widget.CoordinatorLayout, V, android.view.View, float, float);
+    method public deprecated void onNestedPreScroll(android.support.design.widget.CoordinatorLayout, V, android.view.View, int, int, int[]);
+    method public void onNestedPreScroll(android.support.design.widget.CoordinatorLayout, V, android.view.View, int, int, int[], int);
+    method public deprecated void onNestedScroll(android.support.design.widget.CoordinatorLayout, V, android.view.View, int, int, int, int);
+    method public void onNestedScroll(android.support.design.widget.CoordinatorLayout, V, android.view.View, int, int, int, int, int);
+    method public deprecated void onNestedScrollAccepted(android.support.design.widget.CoordinatorLayout, V, android.view.View, android.view.View, int);
+    method public void onNestedScrollAccepted(android.support.design.widget.CoordinatorLayout, V, android.view.View, android.view.View, int, int);
+    method public boolean onRequestChildRectangleOnScreen(android.support.design.widget.CoordinatorLayout, V, android.graphics.Rect, boolean);
+    method public void onRestoreInstanceState(android.support.design.widget.CoordinatorLayout, V, android.os.Parcelable);
+    method public android.os.Parcelable onSaveInstanceState(android.support.design.widget.CoordinatorLayout, V);
+    method public deprecated boolean onStartNestedScroll(android.support.design.widget.CoordinatorLayout, V, android.view.View, android.view.View, int);
+    method public boolean onStartNestedScroll(android.support.design.widget.CoordinatorLayout, V, android.view.View, android.view.View, int, int);
+    method public deprecated void onStopNestedScroll(android.support.design.widget.CoordinatorLayout, V, android.view.View);
+    method public void onStopNestedScroll(android.support.design.widget.CoordinatorLayout, V, android.view.View, int);
+    method public boolean onTouchEvent(android.support.design.widget.CoordinatorLayout, V, android.view.MotionEvent);
+    method public static void setTag(android.view.View, java.lang.Object);
+  }
+
+  public static abstract class CoordinatorLayout.DefaultBehavior implements java.lang.annotation.Annotation {
+  }
+
+  public static class CoordinatorLayout.LayoutParams extends android.view.ViewGroup.MarginLayoutParams {
+    ctor public CoordinatorLayout.LayoutParams(int, int);
+    ctor public CoordinatorLayout.LayoutParams(android.support.design.widget.CoordinatorLayout.LayoutParams);
+    ctor public CoordinatorLayout.LayoutParams(android.view.ViewGroup.MarginLayoutParams);
+    ctor public CoordinatorLayout.LayoutParams(android.view.ViewGroup.LayoutParams);
+    method public int getAnchorId();
+    method public android.support.design.widget.CoordinatorLayout.Behavior getBehavior();
+    method public void setAnchorId(int);
+    method public void setBehavior(android.support.design.widget.CoordinatorLayout.Behavior);
+    field public int anchorGravity;
+    field public int dodgeInsetEdges;
+    field public int gravity;
+    field public int insetEdge;
+    field public int keyline;
+  }
+
+  protected static class CoordinatorLayout.SavedState extends android.support.v4.view.AbsSavedState {
+    ctor public CoordinatorLayout.SavedState(android.os.Parcel, java.lang.ClassLoader);
+    ctor public CoordinatorLayout.SavedState(android.os.Parcelable);
+    field public static final android.os.Parcelable.Creator<android.support.design.widget.CoordinatorLayout.SavedState> CREATOR;
+  }
+
+}
+
 package android.support.v4.app {
 
   public deprecated class ActionBarDrawerToggle implements android.support.v4.widget.DrawerLayout.DrawerListener {
@@ -120,7 +213,6 @@
     ctor public PagerTitleStrip(android.content.Context);
     ctor public PagerTitleStrip(android.content.Context, android.util.AttributeSet);
     method public int getTextSpacing();
-    method protected void onLayout(boolean, int, int, int, int);
     method public void setGravity(int);
     method public void setNonPrimaryAlpha(float);
     method public void setTextColor(int);
@@ -145,7 +237,6 @@
     method public int getOffscreenPageLimit();
     method public int getPageMargin();
     method public boolean isFakeDragging();
-    method protected void onLayout(boolean, int, int, int, int);
     method protected void onPageScrolled(int, float, int);
     method public void onRestoreInstanceState(android.os.Parcelable);
     method public android.os.Parcelable onSaveInstanceState();
@@ -206,23 +297,18 @@
 
 package android.support.v4.view.animation {
 
-  public class FastOutLinearInInterpolator extends android.support.v4.view.animation.LookupTableInterpolator {
+  public class FastOutLinearInInterpolator implements android.view.animation.Interpolator {
     ctor public FastOutLinearInInterpolator();
   }
 
-  public class FastOutSlowInInterpolator extends android.support.v4.view.animation.LookupTableInterpolator {
+  public class FastOutSlowInInterpolator implements android.view.animation.Interpolator {
     ctor public FastOutSlowInInterpolator();
   }
 
-  public class LinearOutSlowInInterpolator extends android.support.v4.view.animation.LookupTableInterpolator {
+  public class LinearOutSlowInInterpolator implements android.view.animation.Interpolator {
     ctor public LinearOutSlowInInterpolator();
   }
 
-   abstract class LookupTableInterpolator implements android.view.animation.Interpolator {
-    ctor public LookupTableInterpolator(float[]);
-    method public float getInterpolation(float);
-  }
-
 }
 
 package android.support.v4.widget {
@@ -254,7 +340,7 @@
     field public static final float RELATIVE_UNSPECIFIED = 0.0f;
   }
 
-  public class CircularProgressDrawable extends android.graphics.drawable.Drawable {
+  public class CircularProgressDrawable extends android.graphics.drawable.Drawable implements android.graphics.drawable.Animatable {
     ctor public CircularProgressDrawable(android.content.Context);
     method public void draw(android.graphics.Canvas);
     method public boolean getArrowEnabled();
@@ -299,7 +385,7 @@
     method public void show();
   }
 
-  public abstract class CursorAdapter extends android.widget.BaseAdapter {
+  public abstract class CursorAdapter extends android.widget.BaseAdapter implements android.widget.Filterable {
     ctor public deprecated CursorAdapter(android.content.Context, android.database.Cursor);
     ctor public CursorAdapter(android.content.Context, android.database.Cursor, boolean);
     ctor public CursorAdapter(android.content.Context, android.database.Cursor, int);
@@ -344,7 +430,6 @@
     method public boolean isDrawerVisible(android.view.View);
     method public boolean isDrawerVisible(int);
     method public void onDraw(android.graphics.Canvas);
-    method protected void onLayout(boolean, int, int, int, int);
     method public void openDrawer(android.view.View);
     method public void openDrawer(android.view.View, boolean);
     method public void openDrawer(int);
@@ -435,12 +520,18 @@
     method public void scrollTargetBy(int, int);
   }
 
-  public class NestedScrollView extends android.widget.FrameLayout {
+  public class NestedScrollView extends android.widget.FrameLayout implements android.support.v4.view.NestedScrollingChild2 android.support.v4.view.NestedScrollingParent android.support.v4.view.ScrollingView {
     ctor public NestedScrollView(android.content.Context);
     ctor public NestedScrollView(android.content.Context, android.util.AttributeSet);
     ctor public NestedScrollView(android.content.Context, android.util.AttributeSet, int);
     method public boolean arrowScroll(int);
+    method public int computeHorizontalScrollExtent();
+    method public int computeHorizontalScrollOffset();
+    method public int computeHorizontalScrollRange();
     method protected int computeScrollDeltaToGetChildRectOnScreen(android.graphics.Rect);
+    method public int computeVerticalScrollExtent();
+    method public int computeVerticalScrollOffset();
+    method public int computeVerticalScrollRange();
     method public boolean dispatchNestedPreScroll(int, int, int[], int[], int);
     method public boolean dispatchNestedScroll(int, int, int, int, int[], int);
     method public boolean executeKeyEvent(android.view.KeyEvent);
@@ -509,7 +600,6 @@
     method public int getSliderFadeColor();
     method public boolean isOpen();
     method public boolean isSlideable();
-    method protected void onLayout(boolean, int, int, int, int);
     method public boolean openPane();
     method public void setCoveredFadeColor(int);
     method public void setPanelSlideListener(android.support.v4.widget.SlidingPaneLayout.PanelSlideListener);
@@ -554,7 +644,7 @@
     ctor public Space(android.content.Context);
   }
 
-  public class SwipeRefreshLayout extends android.view.ViewGroup {
+  public class SwipeRefreshLayout extends android.view.ViewGroup implements android.support.v4.view.NestedScrollingChild android.support.v4.view.NestedScrollingParent {
     ctor public SwipeRefreshLayout(android.content.Context);
     ctor public SwipeRefreshLayout(android.content.Context, android.util.AttributeSet);
     method public boolean canChildScrollUp();
@@ -562,7 +652,6 @@
     method public int getProgressViewEndOffset();
     method public int getProgressViewStartOffset();
     method public boolean isRefreshing();
-    method protected void onLayout(boolean, int, int, int, int);
     method public void onMeasure(int, int);
     method public deprecated void setColorScheme(int...);
     method public void setColorSchemeColors(int...);
diff --git a/core-ui/build.gradle b/core-ui/build.gradle
index 6c17d40..f7cd2d7 100644
--- a/core-ui/build.gradle
+++ b/core-ui/build.gradle
@@ -9,12 +9,17 @@
 dependencies {
     api(project(":support-annotations"))
     api(project(":support-compat"))
+    api project(':support-core-utils')
 
-    androidTestImplementation(TEST_RUNNER, libs.exclude_annotations)
-    androidTestImplementation(ESPRESSO_CORE, libs.exclude_annotations)
-    androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
+    androidTestImplementation(TEST_RUNNER)
+    androidTestImplementation(ESPRESSO_CORE)
+    androidTestImplementation(ESPRESSO_CONTRIB, libs.exclude_support)
     androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
-    androidTestImplementation project(':support-testutils')
+    androidTestImplementation project(':support-testutils'), {
+        exclude group: 'com.android.support', module: 'support-core-ui'
+    }
+
+    testImplementation(JUNIT)
 }
 
 android {
@@ -22,6 +27,13 @@
         minSdkVersion 14
     }
 
+    sourceSets {
+        main.res.srcDirs = [
+                'res',
+                'res-public'
+        ]
+    }
+
     buildTypes.all {
         consumerProguardFiles 'proguard-rules.pro'
     }
diff --git a/core-ui/proguard-rules.pro b/core-ui/proguard-rules.pro
index 2ec1c65..cbf4e1f 100644
--- a/core-ui/proguard-rules.pro
+++ b/core-ui/proguard-rules.pro
@@ -12,5 +12,11 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# Make sure we keep annotations for ViewPager's DecorView
+# CoordinatorLayout resolves the behaviors of its child components with reflection.
+-keep public class * extends android.support.design.widget.CoordinatorLayout$Behavior {
+    public <init>(android.content.Context, android.util.AttributeSet);
+    public <init>();
+}
+
+# Make sure we keep annotations for CoordinatorLayout's DefaultBehavior and ViewPager's DecorView
 -keepattributes *Annotation*
diff --git a/core-ui/res-public/values/public_attrs.xml b/core-ui/res-public/values/public_attrs.xml
new file mode 100644
index 0000000..505d55b
--- /dev/null
+++ b/core-ui/res-public/values/public_attrs.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.
+-->
+
+<!-- Definitions of attributes to be exposed as public -->
+<resources>
+    <public type="attr" name="keylines"/>
+    <public type="attr" name="layout_anchor"/>
+    <public type="attr" name="layout_anchorGravity"/>
+    <public type="attr" name="layout_behavior"/>
+    <public type="attr" name="layout_dodgeInsetEdges"/>
+    <public type="attr" name="layout_insetEdge"/>
+    <public type="attr" name="layout_keyline"/>
+    <public type="attr" name="statusBarBackground"/>
+</resources>
diff --git a/core-ui/res-public/values/public_styles.xml b/core-ui/res-public/values/public_styles.xml
new file mode 100644
index 0000000..f9b6bab
--- /dev/null
+++ b/core-ui/res-public/values/public_styles.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.
+  -->
+
+<!-- Definitions of styles to be exposed as public -->
+<resources>
+    <public type="style" name="Widget.Support.CoordinatorLayout"/>
+</resources>
diff --git a/core-ui/res/values/attrs.xml b/core-ui/res/values/attrs.xml
new file mode 100644
index 0000000..b535c45
--- /dev/null
+++ b/core-ui/res/values/attrs.xml
@@ -0,0 +1,121 @@
+<?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 to use for coordinator layouts. -->
+    <attr name="coordinatorLayoutStyle" format="reference" />
+
+    <declare-styleable name="CoordinatorLayout">
+        <!-- A reference to an array of integers representing the
+             locations of horizontal keylines in dp from the starting edge.
+             Child views can refer to these keylines for alignment using
+             layout_keyline="index" where index is a 0-based index into
+             this array. -->
+        <attr name="keylines" format="reference"/>
+        <!-- Drawable to display behind the status bar when the view is set to draw behind it. -->
+        <attr name="statusBarBackground" format="color|reference"/>
+    </declare-styleable>
+
+    <declare-styleable name="CoordinatorLayout_Layout">
+        <attr name="android:layout_gravity"/>
+        <!-- The class name of a Behavior class defining special runtime behavior
+             for this child view. -->
+        <attr name="layout_behavior" format="string"/>
+        <!-- The id of an anchor view that this view should position relative to. -->
+        <attr name="layout_anchor" format="reference"/>
+        <!-- The index of a keyline this view should position relative to.
+             android:layout_gravity will affect how the view aligns to the
+             specified keyline. -->
+        <attr name="layout_keyline" format="integer"/>
+
+        <!-- Specifies how an object should position relative to an anchor, on both the X and Y axes,
+             within its parent's bounds.  -->
+        <attr name="layout_anchorGravity">
+            <!-- Push object to the top of its container, not changing its size. -->
+            <flag name="top" value="0x30"/>
+            <!-- Push object to the bottom of its container, not changing its size. -->
+            <flag name="bottom" value="0x50"/>
+            <!-- Push object to the left of its container, not changing its size. -->
+            <flag name="left" value="0x03"/>
+            <!-- Push object to the right of its container, not changing its size. -->
+            <flag name="right" value="0x05"/>
+            <!-- Place object in the vertical center of its container, not changing its size. -->
+            <flag name="center_vertical" value="0x10"/>
+            <!-- Grow the vertical size of the object if needed so it completely fills its container. -->
+            <flag name="fill_vertical" value="0x70"/>
+            <!-- Place object in the horizontal center of its container, not changing its size. -->
+            <flag name="center_horizontal" value="0x01"/>
+            <!-- Grow the horizontal size of the object if needed so it completely fills its container. -->
+            <flag name="fill_horizontal" value="0x07"/>
+            <!-- Place the object in the center of its container in both the vertical and horizontal axis, not changing its size. -->
+            <flag name="center" value="0x11"/>
+            <!-- Grow the horizontal and vertical size of the object if needed so it completely fills its container. -->
+            <flag name="fill" value="0x77"/>
+            <!-- Additional option that can be set to have the top and/or bottom edges of
+                 the child clipped to its container's bounds.
+                 The clip will be based on the vertical gravity: a top gravity will clip the bottom
+                 edge, a bottom gravity will clip the top edge, and neither will clip both edges. -->
+            <flag name="clip_vertical" value="0x80"/>
+            <!-- Additional option that can be set to have the left and/or right edges of
+                 the child clipped to its container's bounds.
+                 The clip will be based on the horizontal gravity: a left gravity will clip the right
+                 edge, a right gravity will clip the left edge, and neither will clip both edges. -->
+            <flag name="clip_horizontal" value="0x08"/>
+            <!-- Push object to the beginning of its container, not changing its size. -->
+            <flag name="start" value="0x00800003"/>
+            <!-- Push object to the end of its container, not changing its size. -->
+            <flag name="end" value="0x00800005"/>
+        </attr>
+
+        <!-- Specifies how this view insets the CoordinatorLayout and make some other views
+             dodge it. -->
+        <attr name="layout_insetEdge" format="enum">
+            <!-- Don't inset. -->
+            <enum name="none" value="0x0"/>
+            <!-- Inset the top edge. -->
+            <enum name="top" value="0x30"/>
+            <!-- Inset the bottom edge. -->
+            <enum name="bottom" value="0x50"/>
+            <!-- Inset the left edge. -->
+            <enum name="left" value="0x03"/>
+            <!-- Inset the right edge. -->
+            <enum name="right" value="0x05"/>
+            <!-- Inset the start edge. -->
+            <enum name="start" value="0x00800003"/>
+            <!-- Inset the end edge. -->
+            <enum name="end" value="0x00800005"/>
+        </attr>
+        <!-- Specifies how this view dodges the inset edges of the CoordinatorLayout. -->
+        <attr name="layout_dodgeInsetEdges">
+            <!-- Don't dodge any edges -->
+            <flag name="none" value="0x0"/>
+            <!-- Dodge the top inset edge. -->
+            <flag name="top" value="0x30"/>
+            <!-- Dodge the bottom inset edge. -->
+            <flag name="bottom" value="0x50"/>
+            <!-- Dodge the left inset edge. -->
+            <flag name="left" value="0x03"/>
+            <!-- Dodge the right inset edge. -->
+            <flag name="right" value="0x05"/>
+            <!-- Dodge the start inset edge. -->
+            <flag name="start" value="0x00800003"/>
+            <!-- Dodge the end inset edge. -->
+            <flag name="end" value="0x00800005"/>
+            <!-- Dodge all the inset edges. -->
+            <flag name="all" value="0x77"/>
+        </attr>
+    </declare-styleable>
+</resources>
diff --git a/core-ui/res/values/styles.xml b/core-ui/res/values/styles.xml
new file mode 100644
index 0000000..07fdbc5
--- /dev/null
+++ b/core-ui/res/values/styles.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.
+  -->
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <style name="Widget.Support.CoordinatorLayout" parent="android:Widget">
+        <item name="statusBarBackground">#000000</item>
+    </style>
+</resources>
diff --git a/core-ui/src/main/java/android/support/design/widget/CoordinatorLayout.java b/core-ui/src/main/java/android/support/design/widget/CoordinatorLayout.java
new file mode 100644
index 0000000..c45810e
--- /dev/null
+++ b/core-ui/src/main/java/android/support/design/widget/CoordinatorLayout.java
@@ -0,0 +1,3249 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.design.widget;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.SystemClock;
+import android.support.annotation.ColorInt;
+import android.support.annotation.DrawableRes;
+import android.support.annotation.FloatRange;
+import android.support.annotation.IdRes;
+import android.support.annotation.IntDef;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+import android.support.annotation.VisibleForTesting;
+import android.support.coreui.R;
+import android.support.v4.content.ContextCompat;
+import android.support.v4.graphics.drawable.DrawableCompat;
+import android.support.v4.math.MathUtils;
+import android.support.v4.util.ObjectsCompat;
+import android.support.v4.util.Pools;
+import android.support.v4.view.AbsSavedState;
+import android.support.v4.view.GravityCompat;
+import android.support.v4.view.NestedScrollingParent;
+import android.support.v4.view.NestedScrollingParent2;
+import android.support.v4.view.NestedScrollingParentHelper;
+import android.support.v4.view.ViewCompat;
+import android.support.v4.view.ViewCompat.NestedScrollType;
+import android.support.v4.view.ViewCompat.ScrollAxis;
+import android.support.v4.view.WindowInsetsCompat;
+import android.support.v4.widget.DirectedAcyclicGraph;
+import android.support.v4.widget.ViewGroupUtils;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.view.ViewTreeObserver;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.reflect.Constructor;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * CoordinatorLayout is a super-powered {@link android.widget.FrameLayout FrameLayout}.
+ *
+ * <p>CoordinatorLayout is intended for two primary use cases:</p>
+ * <ol>
+ *     <li>As a top-level application decor or chrome layout</li>
+ *     <li>As a container for a specific interaction with one or more child views</li>
+ * </ol>
+ *
+ * <p>By specifying {@link Behavior Behaviors} for child views of a
+ * CoordinatorLayout you can provide many different interactions within a single parent and those
+ * views can also interact with one another. View classes can specify a default behavior when
+ * used as a child of a CoordinatorLayout using the
+ * {@link DefaultBehavior} annotation.</p>
+ *
+ * <p>Behaviors may be used to implement a variety of interactions and additional layout
+ * modifications ranging from sliding drawers and panels to swipe-dismissable elements and buttons
+ * that stick to other elements as they move and animate.</p>
+ *
+ * <p>Children of a CoordinatorLayout may have an
+ * {@link LayoutParams#setAnchorId(int) anchor}. This view id must correspond
+ * to an arbitrary descendant of the CoordinatorLayout, but it may not be the anchored child itself
+ * or a descendant of the anchored child. This can be used to place floating views relative to
+ * other arbitrary content panes.</p>
+ *
+ * <p>Children can specify {@link LayoutParams#insetEdge} to describe how the
+ * view insets the CoordinatorLayout. Any child views which are set to dodge the same inset edges by
+ * {@link LayoutParams#dodgeInsetEdges} will be moved appropriately so that the
+ * views do not overlap.</p>
+ */
+public class CoordinatorLayout extends ViewGroup implements NestedScrollingParent2 {
+    static final String TAG = "CoordinatorLayout";
+    static final String WIDGET_PACKAGE_NAME;
+
+    static {
+        final Package pkg = CoordinatorLayout.class.getPackage();
+        WIDGET_PACKAGE_NAME = pkg != null ? pkg.getName() : null;
+    }
+
+    private static final int TYPE_ON_INTERCEPT = 0;
+    private static final int TYPE_ON_TOUCH = 1;
+
+    static {
+        if (Build.VERSION.SDK_INT >= 21) {
+            TOP_SORTED_CHILDREN_COMPARATOR = new ViewElevationComparator();
+        } else {
+            TOP_SORTED_CHILDREN_COMPARATOR = null;
+        }
+    }
+
+    static final Class<?>[] CONSTRUCTOR_PARAMS = new Class<?>[] {
+            Context.class,
+            AttributeSet.class
+    };
+
+    static final ThreadLocal<Map<String, Constructor<Behavior>>> sConstructors =
+            new ThreadLocal<>();
+
+    static final int EVENT_PRE_DRAW = 0;
+    static final int EVENT_NESTED_SCROLL = 1;
+    static final int EVENT_VIEW_REMOVED = 2;
+
+    /** @hide */
+    @RestrictTo(LIBRARY_GROUP)
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({EVENT_PRE_DRAW, EVENT_NESTED_SCROLL, EVENT_VIEW_REMOVED})
+    public @interface DispatchChangeEvent {}
+
+    static final Comparator<View> TOP_SORTED_CHILDREN_COMPARATOR;
+    private static final Pools.Pool<Rect> sRectPool = new Pools.SynchronizedPool<>(12);
+
+    @NonNull
+    private static Rect acquireTempRect() {
+        Rect rect = sRectPool.acquire();
+        if (rect == null) {
+            rect = new Rect();
+        }
+        return rect;
+    }
+
+    private static void releaseTempRect(@NonNull Rect rect) {
+        rect.setEmpty();
+        sRectPool.release(rect);
+    }
+
+    private final List<View> mDependencySortedChildren = new ArrayList<>();
+    private final DirectedAcyclicGraph<View> mChildDag = new DirectedAcyclicGraph<>();
+
+    private final List<View> mTempList1 = new ArrayList<>();
+    private final List<View> mTempDependenciesList = new ArrayList<>();
+    private final int[] mTempIntPair = new int[2];
+    private Paint mScrimPaint;
+
+    private boolean mDisallowInterceptReset;
+
+    private boolean mIsAttachedToWindow;
+
+    private int[] mKeylines;
+
+    private View mBehaviorTouchView;
+    private View mNestedScrollingTarget;
+
+    private OnPreDrawListener mOnPreDrawListener;
+    private boolean mNeedsPreDrawListener;
+
+    private WindowInsetsCompat mLastInsets;
+    private boolean mDrawStatusBarBackground;
+    private Drawable mStatusBarBackground;
+
+    OnHierarchyChangeListener mOnHierarchyChangeListener;
+    private android.support.v4.view.OnApplyWindowInsetsListener mApplyWindowInsetsListener;
+
+    private final NestedScrollingParentHelper mNestedScrollingParentHelper =
+            new NestedScrollingParentHelper(this);
+
+    public CoordinatorLayout(Context context) {
+        this(context, null);
+    }
+
+    public CoordinatorLayout(Context context, AttributeSet attrs) {
+        this(context, attrs, R.attr.coordinatorLayoutStyle);
+    }
+
+    public CoordinatorLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+
+        final TypedArray a = (defStyleAttr == 0)
+                ? context.obtainStyledAttributes(attrs, R.styleable.CoordinatorLayout,
+                    0, R.style.Widget_Support_CoordinatorLayout)
+                : context.obtainStyledAttributes(attrs, R.styleable.CoordinatorLayout,
+                    defStyleAttr, 0);
+        final int keylineArrayRes = a.getResourceId(R.styleable.CoordinatorLayout_keylines, 0);
+        if (keylineArrayRes != 0) {
+            final Resources res = context.getResources();
+            mKeylines = res.getIntArray(keylineArrayRes);
+            final float density = res.getDisplayMetrics().density;
+            final int count = mKeylines.length;
+            for (int i = 0; i < count; i++) {
+                mKeylines[i] = (int) (mKeylines[i] * density);
+            }
+        }
+        mStatusBarBackground = a.getDrawable(R.styleable.CoordinatorLayout_statusBarBackground);
+        a.recycle();
+
+        setupForInsets();
+        super.setOnHierarchyChangeListener(new HierarchyChangeListener());
+    }
+
+    @Override
+    public void setOnHierarchyChangeListener(OnHierarchyChangeListener onHierarchyChangeListener) {
+        mOnHierarchyChangeListener = onHierarchyChangeListener;
+    }
+
+    @Override
+    public void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        resetTouchBehaviors(false);
+        if (mNeedsPreDrawListener) {
+            if (mOnPreDrawListener == null) {
+                mOnPreDrawListener = new OnPreDrawListener();
+            }
+            final ViewTreeObserver vto = getViewTreeObserver();
+            vto.addOnPreDrawListener(mOnPreDrawListener);
+        }
+        if (mLastInsets == null && ViewCompat.getFitsSystemWindows(this)) {
+            // We're set to fitSystemWindows but we haven't had any insets yet...
+            // We should request a new dispatch of window insets
+            ViewCompat.requestApplyInsets(this);
+        }
+        mIsAttachedToWindow = true;
+    }
+
+    @Override
+    public void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        resetTouchBehaviors(false);
+        if (mNeedsPreDrawListener && mOnPreDrawListener != null) {
+            final ViewTreeObserver vto = getViewTreeObserver();
+            vto.removeOnPreDrawListener(mOnPreDrawListener);
+        }
+        if (mNestedScrollingTarget != null) {
+            onStopNestedScroll(mNestedScrollingTarget);
+        }
+        mIsAttachedToWindow = false;
+    }
+
+    /**
+     * Set a drawable to draw in the insets area for the status bar.
+     * Note that this will only be activated if this DrawerLayout fitsSystemWindows.
+     *
+     * @param bg Background drawable to draw behind the status bar
+     */
+    public void setStatusBarBackground(@Nullable final Drawable bg) {
+        if (mStatusBarBackground != bg) {
+            if (mStatusBarBackground != null) {
+                mStatusBarBackground.setCallback(null);
+            }
+            mStatusBarBackground = bg != null ? bg.mutate() : null;
+            if (mStatusBarBackground != null) {
+                if (mStatusBarBackground.isStateful()) {
+                    mStatusBarBackground.setState(getDrawableState());
+                }
+                DrawableCompat.setLayoutDirection(mStatusBarBackground,
+                        ViewCompat.getLayoutDirection(this));
+                mStatusBarBackground.setVisible(getVisibility() == VISIBLE, false);
+                mStatusBarBackground.setCallback(this);
+            }
+            ViewCompat.postInvalidateOnAnimation(this);
+        }
+    }
+
+    /**
+     * Gets the drawable used to draw in the insets area for the status bar.
+     *
+     * @return The status bar background drawable, or null if none set
+     */
+    @Nullable
+    public Drawable getStatusBarBackground() {
+        return mStatusBarBackground;
+    }
+
+    @Override
+    protected void drawableStateChanged() {
+        super.drawableStateChanged();
+
+        final int[] state = getDrawableState();
+        boolean changed = false;
+
+        Drawable d = mStatusBarBackground;
+        if (d != null && d.isStateful()) {
+            changed |= d.setState(state);
+        }
+
+        if (changed) {
+            invalidate();
+        }
+    }
+
+    @Override
+    protected boolean verifyDrawable(Drawable who) {
+        return super.verifyDrawable(who) || who == mStatusBarBackground;
+    }
+
+    @Override
+    public void setVisibility(int visibility) {
+        super.setVisibility(visibility);
+
+        final boolean visible = visibility == VISIBLE;
+        if (mStatusBarBackground != null && mStatusBarBackground.isVisible() != visible) {
+            mStatusBarBackground.setVisible(visible, false);
+        }
+    }
+
+    /**
+     * Set a drawable to draw in the insets area for the status bar.
+     * Note that this will only be activated if this DrawerLayout fitsSystemWindows.
+     *
+     * @param resId Resource id of a background drawable to draw behind the status bar
+     */
+    public void setStatusBarBackgroundResource(@DrawableRes int resId) {
+        setStatusBarBackground(resId != 0 ? ContextCompat.getDrawable(getContext(), resId) : null);
+    }
+
+    /**
+     * Set a drawable to draw in the insets area for the status bar.
+     * Note that this will only be activated if this DrawerLayout fitsSystemWindows.
+     *
+     * @param color Color to use as a background drawable to draw behind the status bar
+     *              in 0xAARRGGBB format.
+     */
+    public void setStatusBarBackgroundColor(@ColorInt int color) {
+        setStatusBarBackground(new ColorDrawable(color));
+    }
+
+    final WindowInsetsCompat setWindowInsets(WindowInsetsCompat insets) {
+        if (!ObjectsCompat.equals(mLastInsets, insets)) {
+            mLastInsets = insets;
+            mDrawStatusBarBackground = insets != null && insets.getSystemWindowInsetTop() > 0;
+            setWillNotDraw(!mDrawStatusBarBackground && getBackground() == null);
+
+            // Now dispatch to the Behaviors
+            insets = dispatchApplyWindowInsetsToBehaviors(insets);
+            requestLayout();
+        }
+        return insets;
+    }
+
+    final WindowInsetsCompat getLastWindowInsets() {
+        return mLastInsets;
+    }
+
+    /**
+     * Reset all Behavior-related tracking records either to clean up or in preparation
+     * for a new event stream. This should be called when attached or detached from a window,
+     * in response to an UP or CANCEL event, when intercept is request-disallowed
+     * and similar cases where an event stream in progress will be aborted.
+     */
+    private void resetTouchBehaviors(boolean notifyOnInterceptTouchEvent) {
+        final int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            final View child = getChildAt(i);
+            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+            final Behavior b = lp.getBehavior();
+            if (b != null) {
+                final long now = SystemClock.uptimeMillis();
+                final MotionEvent cancelEvent = MotionEvent.obtain(now, now,
+                        MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
+                if (notifyOnInterceptTouchEvent) {
+                    b.onInterceptTouchEvent(this, child, cancelEvent);
+                } else {
+                    b.onTouchEvent(this, child, cancelEvent);
+                }
+                cancelEvent.recycle();
+            }
+        }
+
+        for (int i = 0; i < childCount; i++) {
+            final View child = getChildAt(i);
+            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+            lp.resetTouchBehaviorTracking();
+        }
+        mDisallowInterceptReset = false;
+    }
+
+    /**
+     * Populate a list with the current child views, sorted such that the topmost views
+     * in z-order are at the front of the list. Useful for hit testing and event dispatch.
+     */
+    private void getTopSortedChildren(List<View> out) {
+        out.clear();
+
+        final boolean useCustomOrder = isChildrenDrawingOrderEnabled();
+        final int childCount = getChildCount();
+        for (int i = childCount - 1; i >= 0; i--) {
+            final int childIndex = useCustomOrder ? getChildDrawingOrder(childCount, i) : i;
+            final View child = getChildAt(childIndex);
+            out.add(child);
+        }
+
+        if (TOP_SORTED_CHILDREN_COMPARATOR != null) {
+            Collections.sort(out, TOP_SORTED_CHILDREN_COMPARATOR);
+        }
+    }
+
+    private boolean performIntercept(MotionEvent ev, final int type) {
+        boolean intercepted = false;
+        boolean newBlock = false;
+
+        MotionEvent cancelEvent = null;
+
+        final int action = ev.getActionMasked();
+
+        final List<View> topmostChildList = mTempList1;
+        getTopSortedChildren(topmostChildList);
+
+        // Let topmost child views inspect first
+        final int childCount = topmostChildList.size();
+        for (int i = 0; i < childCount; i++) {
+            final View child = topmostChildList.get(i);
+            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+            final Behavior b = lp.getBehavior();
+
+            if ((intercepted || newBlock) && action != MotionEvent.ACTION_DOWN) {
+                // Cancel all behaviors beneath the one that intercepted.
+                // If the event is "down" then we don't have anything to cancel yet.
+                if (b != null) {
+                    if (cancelEvent == null) {
+                        final long now = SystemClock.uptimeMillis();
+                        cancelEvent = MotionEvent.obtain(now, now,
+                                MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
+                    }
+                    switch (type) {
+                        case TYPE_ON_INTERCEPT:
+                            b.onInterceptTouchEvent(this, child, cancelEvent);
+                            break;
+                        case TYPE_ON_TOUCH:
+                            b.onTouchEvent(this, child, cancelEvent);
+                            break;
+                    }
+                }
+                continue;
+            }
+
+            if (!intercepted && b != null) {
+                switch (type) {
+                    case TYPE_ON_INTERCEPT:
+                        intercepted = b.onInterceptTouchEvent(this, child, ev);
+                        break;
+                    case TYPE_ON_TOUCH:
+                        intercepted = b.onTouchEvent(this, child, ev);
+                        break;
+                }
+                if (intercepted) {
+                    mBehaviorTouchView = child;
+                }
+            }
+
+            // Don't keep going if we're not allowing interaction below this.
+            // Setting newBlock will make sure we cancel the rest of the behaviors.
+            final boolean wasBlocking = lp.didBlockInteraction();
+            final boolean isBlocking = lp.isBlockingInteractionBelow(this, child);
+            newBlock = isBlocking && !wasBlocking;
+            if (isBlocking && !newBlock) {
+                // Stop here since we don't have anything more to cancel - we already did
+                // when the behavior first started blocking things below this point.
+                break;
+            }
+        }
+
+        topmostChildList.clear();
+
+        return intercepted;
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        MotionEvent cancelEvent = null;
+
+        final int action = ev.getActionMasked();
+
+        // Make sure we reset in case we had missed a previous important event.
+        if (action == MotionEvent.ACTION_DOWN) {
+            resetTouchBehaviors(true);
+        }
+
+        final boolean intercepted = performIntercept(ev, TYPE_ON_INTERCEPT);
+
+        if (cancelEvent != null) {
+            cancelEvent.recycle();
+        }
+
+        if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
+            resetTouchBehaviors(true);
+        }
+
+        return intercepted;
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        boolean handled = false;
+        boolean cancelSuper = false;
+        MotionEvent cancelEvent = null;
+
+        final int action = ev.getActionMasked();
+
+        if (mBehaviorTouchView != null || (cancelSuper = performIntercept(ev, TYPE_ON_TOUCH))) {
+            // Safe since performIntercept guarantees that
+            // mBehaviorTouchView != null if it returns true
+            final LayoutParams lp = (LayoutParams) mBehaviorTouchView.getLayoutParams();
+            final Behavior b = lp.getBehavior();
+            if (b != null) {
+                handled = b.onTouchEvent(this, mBehaviorTouchView, ev);
+            }
+        }
+
+        // Keep the super implementation correct
+        if (mBehaviorTouchView == null) {
+            handled |= super.onTouchEvent(ev);
+        } else if (cancelSuper) {
+            if (cancelEvent == null) {
+                final long now = SystemClock.uptimeMillis();
+                cancelEvent = MotionEvent.obtain(now, now,
+                        MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
+            }
+            super.onTouchEvent(cancelEvent);
+        }
+
+        if (!handled && action == MotionEvent.ACTION_DOWN) {
+
+        }
+
+        if (cancelEvent != null) {
+            cancelEvent.recycle();
+        }
+
+        if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
+            resetTouchBehaviors(false);
+        }
+
+        return handled;
+    }
+
+    @Override
+    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
+        super.requestDisallowInterceptTouchEvent(disallowIntercept);
+        if (disallowIntercept && !mDisallowInterceptReset) {
+            resetTouchBehaviors(false);
+            mDisallowInterceptReset = true;
+        }
+    }
+
+    private int getKeyline(int index) {
+        if (mKeylines == null) {
+            Log.e(TAG, "No keylines defined for " + this + " - attempted index lookup " + index);
+            return 0;
+        }
+
+        if (index < 0 || index >= mKeylines.length) {
+            Log.e(TAG, "Keyline index " + index + " out of range for " + this);
+            return 0;
+        }
+
+        return mKeylines[index];
+    }
+
+    static Behavior parseBehavior(Context context, AttributeSet attrs, String name) {
+        if (TextUtils.isEmpty(name)) {
+            return null;
+        }
+
+        final String fullName;
+        if (name.startsWith(".")) {
+            // Relative to the app package. Prepend the app package name.
+            fullName = context.getPackageName() + name;
+        } else if (name.indexOf('.') >= 0) {
+            // Fully qualified package name.
+            fullName = name;
+        } else {
+            // Assume stock behavior in this package (if we have one)
+            fullName = !TextUtils.isEmpty(WIDGET_PACKAGE_NAME)
+                    ? (WIDGET_PACKAGE_NAME + '.' + name)
+                    : name;
+        }
+
+        try {
+            Map<String, Constructor<Behavior>> constructors = sConstructors.get();
+            if (constructors == null) {
+                constructors = new HashMap<>();
+                sConstructors.set(constructors);
+            }
+            Constructor<Behavior> c = constructors.get(fullName);
+            if (c == null) {
+                final Class<Behavior> clazz = (Class<Behavior>) context.getClassLoader()
+                        .loadClass(fullName);
+                c = clazz.getConstructor(CONSTRUCTOR_PARAMS);
+                c.setAccessible(true);
+                constructors.put(fullName, c);
+            }
+            return c.newInstance(context, attrs);
+        } catch (Exception e) {
+            throw new RuntimeException("Could not inflate Behavior subclass " + fullName, e);
+        }
+    }
+
+    LayoutParams getResolvedLayoutParams(View child) {
+        final LayoutParams result = (LayoutParams) child.getLayoutParams();
+        if (!result.mBehaviorResolved) {
+            Class<?> childClass = child.getClass();
+            DefaultBehavior defaultBehavior = null;
+            while (childClass != null &&
+                    (defaultBehavior = childClass.getAnnotation(DefaultBehavior.class)) == null) {
+                childClass = childClass.getSuperclass();
+            }
+            if (defaultBehavior != null) {
+                try {
+                    result.setBehavior(
+                            defaultBehavior.value().getDeclaredConstructor().newInstance());
+                } catch (Exception e) {
+                    Log.e(TAG, "Default behavior class " + defaultBehavior.value().getName() +
+                            " could not be instantiated. Did you forget a default constructor?", e);
+                }
+            }
+            result.mBehaviorResolved = true;
+        }
+        return result;
+    }
+
+    private void prepareChildren() {
+        mDependencySortedChildren.clear();
+        mChildDag.clear();
+
+        for (int i = 0, count = getChildCount(); i < count; i++) {
+            final View view = getChildAt(i);
+
+            final LayoutParams lp = getResolvedLayoutParams(view);
+            lp.findAnchorView(this, view);
+
+            mChildDag.addNode(view);
+
+            // Now iterate again over the other children, adding any dependencies to the graph
+            for (int j = 0; j < count; j++) {
+                if (j == i) {
+                    continue;
+                }
+                final View other = getChildAt(j);
+                if (lp.dependsOn(this, view, other)) {
+                    if (!mChildDag.contains(other)) {
+                        // Make sure that the other node is added
+                        mChildDag.addNode(other);
+                    }
+                    // Now add the dependency to the graph
+                    mChildDag.addEdge(other, view);
+                }
+            }
+        }
+
+        // Finally add the sorted graph list to our list
+        mDependencySortedChildren.addAll(mChildDag.getSortedList());
+        // We also need to reverse the result since we want the start of the list to contain
+        // Views which have no dependencies, then dependent views after that
+        Collections.reverse(mDependencySortedChildren);
+    }
+
+    /**
+     * Retrieve the transformed bounding rect of an arbitrary descendant view.
+     * This does not need to be a direct child.
+     *
+     * @param descendant descendant view to reference
+     * @param out rect to set to the bounds of the descendant view
+     */
+    void getDescendantRect(View descendant, Rect out) {
+        ViewGroupUtils.getDescendantRect(this, descendant, out);
+    }
+
+    @Override
+    protected int getSuggestedMinimumWidth() {
+        return Math.max(super.getSuggestedMinimumWidth(), getPaddingLeft() + getPaddingRight());
+    }
+
+    @Override
+    protected int getSuggestedMinimumHeight() {
+        return Math.max(super.getSuggestedMinimumHeight(), getPaddingTop() + getPaddingBottom());
+    }
+
+    /**
+     * Called to measure each individual child view unless a
+     * {@link Behavior Behavior} is present. The Behavior may choose to delegate
+     * child measurement to this method.
+     *
+     * @param child the child to measure
+     * @param parentWidthMeasureSpec the width requirements for this view
+     * @param widthUsed extra space that has been used up by the parent
+     *        horizontally (possibly by other children of the parent)
+     * @param parentHeightMeasureSpec the height requirements for this view
+     * @param heightUsed extra space that has been used up by the parent
+     *        vertically (possibly by other children of the parent)
+     */
+    public void onMeasureChild(View child, int parentWidthMeasureSpec, int widthUsed,
+            int parentHeightMeasureSpec, int heightUsed) {
+        measureChildWithMargins(child, parentWidthMeasureSpec, widthUsed,
+                parentHeightMeasureSpec, heightUsed);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        prepareChildren();
+        ensurePreDrawListener();
+
+        final int paddingLeft = getPaddingLeft();
+        final int paddingTop = getPaddingTop();
+        final int paddingRight = getPaddingRight();
+        final int paddingBottom = getPaddingBottom();
+        final int layoutDirection = ViewCompat.getLayoutDirection(this);
+        final boolean isRtl = layoutDirection == ViewCompat.LAYOUT_DIRECTION_RTL;
+        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+        final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+        final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+
+        final int widthPadding = paddingLeft + paddingRight;
+        final int heightPadding = paddingTop + paddingBottom;
+        int widthUsed = getSuggestedMinimumWidth();
+        int heightUsed = getSuggestedMinimumHeight();
+        int childState = 0;
+
+        final boolean applyInsets = mLastInsets != null && ViewCompat.getFitsSystemWindows(this);
+
+        final int childCount = mDependencySortedChildren.size();
+        for (int i = 0; i < childCount; i++) {
+            final View child = mDependencySortedChildren.get(i);
+            if (child.getVisibility() == GONE) {
+                // If the child is GONE, skip...
+                continue;
+            }
+
+            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+            int keylineWidthUsed = 0;
+            if (lp.keyline >= 0 && widthMode != MeasureSpec.UNSPECIFIED) {
+                final int keylinePos = getKeyline(lp.keyline);
+                final int keylineGravity = GravityCompat.getAbsoluteGravity(
+                        resolveKeylineGravity(lp.gravity), layoutDirection)
+                        & Gravity.HORIZONTAL_GRAVITY_MASK;
+                if ((keylineGravity == Gravity.LEFT && !isRtl)
+                        || (keylineGravity == Gravity.RIGHT && isRtl)) {
+                    keylineWidthUsed = Math.max(0, widthSize - paddingRight - keylinePos);
+                } else if ((keylineGravity == Gravity.RIGHT && !isRtl)
+                        || (keylineGravity == Gravity.LEFT && isRtl)) {
+                    keylineWidthUsed = Math.max(0, keylinePos - paddingLeft);
+                }
+            }
+
+            int childWidthMeasureSpec = widthMeasureSpec;
+            int childHeightMeasureSpec = heightMeasureSpec;
+            if (applyInsets && !ViewCompat.getFitsSystemWindows(child)) {
+                // We're set to handle insets but this child isn't, so we will measure the
+                // child as if there are no insets
+                final int horizInsets = mLastInsets.getSystemWindowInsetLeft()
+                        + mLastInsets.getSystemWindowInsetRight();
+                final int vertInsets = mLastInsets.getSystemWindowInsetTop()
+                        + mLastInsets.getSystemWindowInsetBottom();
+
+                childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
+                        widthSize - horizInsets, widthMode);
+                childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
+                        heightSize - vertInsets, heightMode);
+            }
+
+            final Behavior b = lp.getBehavior();
+            if (b == null || !b.onMeasureChild(this, child, childWidthMeasureSpec, keylineWidthUsed,
+                    childHeightMeasureSpec, 0)) {
+                onMeasureChild(child, childWidthMeasureSpec, keylineWidthUsed,
+                        childHeightMeasureSpec, 0);
+            }
+
+            widthUsed = Math.max(widthUsed, widthPadding + child.getMeasuredWidth() +
+                    lp.leftMargin + lp.rightMargin);
+
+            heightUsed = Math.max(heightUsed, heightPadding + child.getMeasuredHeight() +
+                    lp.topMargin + lp.bottomMargin);
+            childState = View.combineMeasuredStates(childState, child.getMeasuredState());
+        }
+
+        final int width = View.resolveSizeAndState(widthUsed, widthMeasureSpec,
+                childState & View.MEASURED_STATE_MASK);
+        final int height = View.resolveSizeAndState(heightUsed, heightMeasureSpec,
+                childState << View.MEASURED_HEIGHT_STATE_SHIFT);
+        setMeasuredDimension(width, height);
+    }
+
+    private WindowInsetsCompat dispatchApplyWindowInsetsToBehaviors(WindowInsetsCompat insets) {
+        if (insets.isConsumed()) {
+            return insets;
+        }
+
+        for (int i = 0, z = getChildCount(); i < z; i++) {
+            final View child = getChildAt(i);
+            if (ViewCompat.getFitsSystemWindows(child)) {
+                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+                final Behavior b = lp.getBehavior();
+
+                if (b != null) {
+                    // If the view has a behavior, let it try first
+                    insets = b.onApplyWindowInsets(this, child, insets);
+                    if (insets.isConsumed()) {
+                        // If it consumed the insets, break
+                        break;
+                    }
+                }
+            }
+        }
+
+        return insets;
+    }
+
+    /**
+     * Called to lay out each individual child view unless a
+     * {@link Behavior Behavior} is present. The Behavior may choose to
+     * delegate child measurement to this method.
+     *
+     * @param child child view to lay out
+     * @param layoutDirection the resolved layout direction for the CoordinatorLayout, such as
+     *                        {@link ViewCompat#LAYOUT_DIRECTION_LTR} or
+     *                        {@link ViewCompat#LAYOUT_DIRECTION_RTL}.
+     */
+    public void onLayoutChild(View child, int layoutDirection) {
+        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+        if (lp.checkAnchorChanged()) {
+            throw new IllegalStateException("An anchor may not be changed after CoordinatorLayout"
+                    + " measurement begins before layout is complete.");
+        }
+        if (lp.mAnchorView != null) {
+            layoutChildWithAnchor(child, lp.mAnchorView, layoutDirection);
+        } else if (lp.keyline >= 0) {
+            layoutChildWithKeyline(child, lp.keyline, layoutDirection);
+        } else {
+            layoutChild(child, layoutDirection);
+        }
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        final int layoutDirection = ViewCompat.getLayoutDirection(this);
+        final int childCount = mDependencySortedChildren.size();
+        for (int i = 0; i < childCount; i++) {
+            final View child = mDependencySortedChildren.get(i);
+            if (child.getVisibility() == GONE) {
+                // If the child is GONE, skip...
+                continue;
+            }
+
+            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+            final Behavior behavior = lp.getBehavior();
+
+            if (behavior == null || !behavior.onLayoutChild(this, child, layoutDirection)) {
+                onLayoutChild(child, layoutDirection);
+            }
+        }
+    }
+
+    @Override
+    public void onDraw(Canvas c) {
+        super.onDraw(c);
+        if (mDrawStatusBarBackground && mStatusBarBackground != null) {
+            final int inset = mLastInsets != null ? mLastInsets.getSystemWindowInsetTop() : 0;
+            if (inset > 0) {
+                mStatusBarBackground.setBounds(0, 0, getWidth(), inset);
+                mStatusBarBackground.draw(c);
+            }
+        }
+    }
+
+    @Override
+    public void setFitsSystemWindows(boolean fitSystemWindows) {
+        super.setFitsSystemWindows(fitSystemWindows);
+        setupForInsets();
+    }
+
+    /**
+     * Mark the last known child position rect for the given child view.
+     * This will be used when checking if a child view's position has changed between frames.
+     * The rect used here should be one returned by
+     * {@link #getChildRect(View, boolean, Rect)}, with translation
+     * disabled.
+     *
+     * @param child child view to set for
+     * @param r rect to set
+     */
+    void recordLastChildRect(View child, Rect r) {
+        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+        lp.setLastChildRect(r);
+    }
+
+    /**
+     * Get the last known child rect recorded by
+     * {@link #recordLastChildRect(View, Rect)}.
+     *
+     * @param child child view to retrieve from
+     * @param out rect to set to the outpur values
+     */
+    void getLastChildRect(View child, Rect out) {
+        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+        out.set(lp.getLastChildRect());
+    }
+
+    /**
+     * Get the position rect for the given child. If the child has currently requested layout
+     * or has a visibility of GONE.
+     *
+     * @param child child view to check
+     * @param transform true to include transformation in the output rect, false to
+     *                        only account for the base position
+     * @param out rect to set to the output values
+     */
+    void getChildRect(View child, boolean transform, Rect out) {
+        if (child.isLayoutRequested() || child.getVisibility() == View.GONE) {
+            out.setEmpty();
+            return;
+        }
+        if (transform) {
+            getDescendantRect(child, out);
+        } else {
+            out.set(child.getLeft(), child.getTop(), child.getRight(), child.getBottom());
+        }
+    }
+
+    private void getDesiredAnchoredChildRectWithoutConstraints(View child, int layoutDirection,
+            Rect anchorRect, Rect out, LayoutParams lp, int childWidth, int childHeight) {
+        final int absGravity = GravityCompat.getAbsoluteGravity(
+                resolveAnchoredChildGravity(lp.gravity), layoutDirection);
+        final int absAnchorGravity = GravityCompat.getAbsoluteGravity(
+                resolveGravity(lp.anchorGravity),
+                layoutDirection);
+
+        final int hgrav = absGravity & Gravity.HORIZONTAL_GRAVITY_MASK;
+        final int vgrav = absGravity & Gravity.VERTICAL_GRAVITY_MASK;
+        final int anchorHgrav = absAnchorGravity & Gravity.HORIZONTAL_GRAVITY_MASK;
+        final int anchorVgrav = absAnchorGravity & Gravity.VERTICAL_GRAVITY_MASK;
+
+        int left;
+        int top;
+
+        // Align to the anchor. This puts us in an assumed right/bottom child view gravity.
+        // If this is not the case we will subtract out the appropriate portion of
+        // the child size below.
+        switch (anchorHgrav) {
+            default:
+            case Gravity.LEFT:
+                left = anchorRect.left;
+                break;
+            case Gravity.RIGHT:
+                left = anchorRect.right;
+                break;
+            case Gravity.CENTER_HORIZONTAL:
+                left = anchorRect.left + anchorRect.width() / 2;
+                break;
+        }
+
+        switch (anchorVgrav) {
+            default:
+            case Gravity.TOP:
+                top = anchorRect.top;
+                break;
+            case Gravity.BOTTOM:
+                top = anchorRect.bottom;
+                break;
+            case Gravity.CENTER_VERTICAL:
+                top = anchorRect.top + anchorRect.height() / 2;
+                break;
+        }
+
+        // Offset by the child view's gravity itself. The above assumed right/bottom gravity.
+        switch (hgrav) {
+            default:
+            case Gravity.LEFT:
+                left -= childWidth;
+                break;
+            case Gravity.RIGHT:
+                // Do nothing, we're already in position.
+                break;
+            case Gravity.CENTER_HORIZONTAL:
+                left -= childWidth / 2;
+                break;
+        }
+
+        switch (vgrav) {
+            default:
+            case Gravity.TOP:
+                top -= childHeight;
+                break;
+            case Gravity.BOTTOM:
+                // Do nothing, we're already in position.
+                break;
+            case Gravity.CENTER_VERTICAL:
+                top -= childHeight / 2;
+                break;
+        }
+
+        out.set(left, top, left + childWidth, top + childHeight);
+    }
+
+    private void constrainChildRect(LayoutParams lp, Rect out, int childWidth, int childHeight) {
+        final int width = getWidth();
+        final int height = getHeight();
+
+        // Obey margins and padding
+        int left = Math.max(getPaddingLeft() + lp.leftMargin,
+                Math.min(out.left,
+                        width - getPaddingRight() - childWidth - lp.rightMargin));
+        int top = Math.max(getPaddingTop() + lp.topMargin,
+                Math.min(out.top,
+                        height - getPaddingBottom() - childHeight - lp.bottomMargin));
+
+        out.set(left, top, left + childWidth, top + childHeight);
+    }
+
+    /**
+     * Calculate the desired child rect relative to an anchor rect, respecting both
+     * gravity and anchorGravity.
+     *
+     * @param child child view to calculate a rect for
+     * @param layoutDirection the desired layout direction for the CoordinatorLayout
+     * @param anchorRect rect in CoordinatorLayout coordinates of the anchor view area
+     * @param out rect to set to the output values
+     */
+    void getDesiredAnchoredChildRect(View child, int layoutDirection, Rect anchorRect, Rect out) {
+        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+        final int childWidth = child.getMeasuredWidth();
+        final int childHeight = child.getMeasuredHeight();
+        getDesiredAnchoredChildRectWithoutConstraints(child, layoutDirection, anchorRect, out, lp,
+                childWidth, childHeight);
+        constrainChildRect(lp, out, childWidth, childHeight);
+    }
+
+    /**
+     * CORE ASSUMPTION: anchor has been laid out by the time this is called for a given child view.
+     *
+     * @param child child to lay out
+     * @param anchor view to anchor child relative to; already laid out.
+     * @param layoutDirection ViewCompat constant for layout direction
+     */
+    private void layoutChildWithAnchor(View child, View anchor, int layoutDirection) {
+        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+        final Rect anchorRect = acquireTempRect();
+        final Rect childRect = acquireTempRect();
+        try {
+            getDescendantRect(anchor, anchorRect);
+            getDesiredAnchoredChildRect(child, layoutDirection, anchorRect, childRect);
+            child.layout(childRect.left, childRect.top, childRect.right, childRect.bottom);
+        } finally {
+            releaseTempRect(anchorRect);
+            releaseTempRect(childRect);
+        }
+    }
+
+    /**
+     * Lay out a child view with respect to a keyline.
+     *
+     * <p>The keyline represents a horizontal offset from the unpadded starting edge of
+     * the CoordinatorLayout. The child's gravity will affect how it is positioned with
+     * respect to the keyline.</p>
+     *
+     * @param child child to lay out
+     * @param keyline offset from the starting edge in pixels of the keyline to align with
+     * @param layoutDirection ViewCompat constant for layout direction
+     */
+    private void layoutChildWithKeyline(View child, int keyline, int layoutDirection) {
+        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+        final int absGravity = GravityCompat.getAbsoluteGravity(
+                resolveKeylineGravity(lp.gravity), layoutDirection);
+
+        final int hgrav = absGravity & Gravity.HORIZONTAL_GRAVITY_MASK;
+        final int vgrav = absGravity & Gravity.VERTICAL_GRAVITY_MASK;
+        final int width = getWidth();
+        final int height = getHeight();
+        final int childWidth = child.getMeasuredWidth();
+        final int childHeight = child.getMeasuredHeight();
+
+        if (layoutDirection == ViewCompat.LAYOUT_DIRECTION_RTL) {
+            keyline = width - keyline;
+        }
+
+        int left = getKeyline(keyline) - childWidth;
+        int top = 0;
+
+        switch (hgrav) {
+            default:
+            case Gravity.LEFT:
+                // Nothing to do.
+                break;
+            case Gravity.RIGHT:
+                left += childWidth;
+                break;
+            case Gravity.CENTER_HORIZONTAL:
+                left += childWidth / 2;
+                break;
+        }
+
+        switch (vgrav) {
+            default:
+            case Gravity.TOP:
+                // Do nothing, we're already in position.
+                break;
+            case Gravity.BOTTOM:
+                top += childHeight;
+                break;
+            case Gravity.CENTER_VERTICAL:
+                top += childHeight / 2;
+                break;
+        }
+
+        // Obey margins and padding
+        left = Math.max(getPaddingLeft() + lp.leftMargin,
+                Math.min(left,
+                        width - getPaddingRight() - childWidth - lp.rightMargin));
+        top = Math.max(getPaddingTop() + lp.topMargin,
+                Math.min(top,
+                        height - getPaddingBottom() - childHeight - lp.bottomMargin));
+
+        child.layout(left, top, left + childWidth, top + childHeight);
+    }
+
+    /**
+     * Lay out a child view with no special handling. This will position the child as
+     * if it were within a FrameLayout or similar simple frame.
+     *
+     * @param child child view to lay out
+     * @param layoutDirection ViewCompat constant for the desired layout direction
+     */
+    private void layoutChild(View child, int layoutDirection) {
+        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+        final Rect parent = acquireTempRect();
+        parent.set(getPaddingLeft() + lp.leftMargin,
+                getPaddingTop() + lp.topMargin,
+                getWidth() - getPaddingRight() - lp.rightMargin,
+                getHeight() - getPaddingBottom() - lp.bottomMargin);
+
+        if (mLastInsets != null && ViewCompat.getFitsSystemWindows(this)
+                && !ViewCompat.getFitsSystemWindows(child)) {
+            // If we're set to handle insets but this child isn't, then it has been measured as
+            // if there are no insets. We need to lay it out to match.
+            parent.left += mLastInsets.getSystemWindowInsetLeft();
+            parent.top += mLastInsets.getSystemWindowInsetTop();
+            parent.right -= mLastInsets.getSystemWindowInsetRight();
+            parent.bottom -= mLastInsets.getSystemWindowInsetBottom();
+        }
+
+        final Rect out = acquireTempRect();
+        GravityCompat.apply(resolveGravity(lp.gravity), child.getMeasuredWidth(),
+                child.getMeasuredHeight(), parent, out, layoutDirection);
+        child.layout(out.left, out.top, out.right, out.bottom);
+
+        releaseTempRect(parent);
+        releaseTempRect(out);
+    }
+
+    /**
+     * Return the given gravity value, but if either or both of the axes doesn't have any gravity
+     * specified, the default value (start or top) is specified. This should be used for children
+     * that are not anchored to another view or a keyline.
+     */
+    private static int resolveGravity(int gravity) {
+        if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.NO_GRAVITY) {
+            gravity |= GravityCompat.START;
+        }
+        if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.NO_GRAVITY) {
+            gravity |= Gravity.TOP;
+        }
+        return gravity;
+    }
+
+    /**
+     * Return the given gravity value or the default if the passed value is NO_GRAVITY.
+     * This should be used for children that are positioned relative to a keyline.
+     */
+    private static int resolveKeylineGravity(int gravity) {
+        return gravity == Gravity.NO_GRAVITY ? GravityCompat.END | Gravity.TOP : gravity;
+    }
+
+    /**
+     * Return the given gravity value or the default if the passed value is NO_GRAVITY.
+     * This should be used for children that are anchored to another view.
+     */
+    private static int resolveAnchoredChildGravity(int gravity) {
+        return gravity == Gravity.NO_GRAVITY ? Gravity.CENTER : gravity;
+    }
+
+    @Override
+    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
+        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+        if (lp.mBehavior != null) {
+            final float scrimAlpha = lp.mBehavior.getScrimOpacity(this, child);
+            if (scrimAlpha > 0f) {
+                if (mScrimPaint == null) {
+                    mScrimPaint = new Paint();
+                }
+                mScrimPaint.setColor(lp.mBehavior.getScrimColor(this, child));
+                mScrimPaint.setAlpha(MathUtils.clamp(Math.round(255 * scrimAlpha), 0, 255));
+
+                final int saved = canvas.save();
+                if (child.isOpaque()) {
+                    // If the child is opaque, there is no need to draw behind it so we'll inverse
+                    // clip the canvas
+                    canvas.clipRect(child.getLeft(), child.getTop(), child.getRight(),
+                            child.getBottom(), Region.Op.DIFFERENCE);
+                }
+                // Now draw the rectangle for the scrim
+                canvas.drawRect(getPaddingLeft(), getPaddingTop(),
+                        getWidth() - getPaddingRight(), getHeight() - getPaddingBottom(),
+                        mScrimPaint);
+                canvas.restoreToCount(saved);
+            }
+        }
+        return super.drawChild(canvas, child, drawingTime);
+    }
+
+    /**
+     * Dispatch any dependent view changes to the relevant {@link Behavior} instances.
+     *
+     * Usually run as part of the pre-draw step when at least one child view has a reported
+     * dependency on another view. This allows CoordinatorLayout to account for layout
+     * changes and animations that occur outside of the normal layout pass.
+     *
+     * It can also be ran as part of the nested scrolling dispatch to ensure that any offsetting
+     * is completed within the correct coordinate window.
+     *
+     * The offsetting behavior implemented here does not store the computed offset in
+     * the LayoutParams; instead it expects that the layout process will always reconstruct
+     * the proper positioning.
+     *
+     * @param type the type of event which has caused this call
+     */
+    final void onChildViewsChanged(@DispatchChangeEvent final int type) {
+        final int layoutDirection = ViewCompat.getLayoutDirection(this);
+        final int childCount = mDependencySortedChildren.size();
+        final Rect inset = acquireTempRect();
+        final Rect drawRect = acquireTempRect();
+        final Rect lastDrawRect = acquireTempRect();
+
+        for (int i = 0; i < childCount; i++) {
+            final View child = mDependencySortedChildren.get(i);
+            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+            if (type == EVENT_PRE_DRAW && child.getVisibility() == View.GONE) {
+                // Do not try to update GONE child views in pre draw updates.
+                continue;
+            }
+
+            // Check child views before for anchor
+            for (int j = 0; j < i; j++) {
+                final View checkChild = mDependencySortedChildren.get(j);
+
+                if (lp.mAnchorDirectChild == checkChild) {
+                    offsetChildToAnchor(child, layoutDirection);
+                }
+            }
+
+            // Get the current draw rect of the view
+            getChildRect(child, true, drawRect);
+
+            // Accumulate inset sizes
+            if (lp.insetEdge != Gravity.NO_GRAVITY && !drawRect.isEmpty()) {
+                final int absInsetEdge = GravityCompat.getAbsoluteGravity(
+                        lp.insetEdge, layoutDirection);
+                switch (absInsetEdge & Gravity.VERTICAL_GRAVITY_MASK) {
+                    case Gravity.TOP:
+                        inset.top = Math.max(inset.top, drawRect.bottom);
+                        break;
+                    case Gravity.BOTTOM:
+                        inset.bottom = Math.max(inset.bottom, getHeight() - drawRect.top);
+                        break;
+                }
+                switch (absInsetEdge & Gravity.HORIZONTAL_GRAVITY_MASK) {
+                    case Gravity.LEFT:
+                        inset.left = Math.max(inset.left, drawRect.right);
+                        break;
+                    case Gravity.RIGHT:
+                        inset.right = Math.max(inset.right, getWidth() - drawRect.left);
+                        break;
+                }
+            }
+
+            // Dodge inset edges if necessary
+            if (lp.dodgeInsetEdges != Gravity.NO_GRAVITY && child.getVisibility() == View.VISIBLE) {
+                offsetChildByInset(child, inset, layoutDirection);
+            }
+
+            if (type != EVENT_VIEW_REMOVED) {
+                // Did it change? if not continue
+                getLastChildRect(child, lastDrawRect);
+                if (lastDrawRect.equals(drawRect)) {
+                    continue;
+                }
+                recordLastChildRect(child, drawRect);
+            }
+
+            // Update any behavior-dependent views for the change
+            for (int j = i + 1; j < childCount; j++) {
+                final View checkChild = mDependencySortedChildren.get(j);
+                final LayoutParams checkLp = (LayoutParams) checkChild.getLayoutParams();
+                final Behavior b = checkLp.getBehavior();
+
+                if (b != null && b.layoutDependsOn(this, checkChild, child)) {
+                    if (type == EVENT_PRE_DRAW && checkLp.getChangedAfterNestedScroll()) {
+                        // If this is from a pre-draw and we have already been changed
+                        // from a nested scroll, skip the dispatch and reset the flag
+                        checkLp.resetChangedAfterNestedScroll();
+                        continue;
+                    }
+
+                    final boolean handled;
+                    switch (type) {
+                        case EVENT_VIEW_REMOVED:
+                            // EVENT_VIEW_REMOVED means that we need to dispatch
+                            // onDependentViewRemoved() instead
+                            b.onDependentViewRemoved(this, checkChild, child);
+                            handled = true;
+                            break;
+                        default:
+                            // Otherwise we dispatch onDependentViewChanged()
+                            handled = b.onDependentViewChanged(this, checkChild, child);
+                            break;
+                    }
+
+                    if (type == EVENT_NESTED_SCROLL) {
+                        // If this is from a nested scroll, set the flag so that we may skip
+                        // any resulting onPreDraw dispatch (if needed)
+                        checkLp.setChangedAfterNestedScroll(handled);
+                    }
+                }
+            }
+        }
+
+        releaseTempRect(inset);
+        releaseTempRect(drawRect);
+        releaseTempRect(lastDrawRect);
+    }
+
+    private void offsetChildByInset(final View child, final Rect inset, final int layoutDirection) {
+        if (!ViewCompat.isLaidOut(child)) {
+            // The view has not been laid out yet, so we can't obtain its bounds.
+            return;
+        }
+
+        if (child.getWidth() <= 0 || child.getHeight() <= 0) {
+            // Bounds are empty so there is nothing to dodge against, skip...
+            return;
+        }
+
+        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+        final Behavior behavior = lp.getBehavior();
+        final Rect dodgeRect = acquireTempRect();
+        final Rect bounds = acquireTempRect();
+        bounds.set(child.getLeft(), child.getTop(), child.getRight(), child.getBottom());
+
+        if (behavior != null && behavior.getInsetDodgeRect(this, child, dodgeRect)) {
+            // Make sure that the rect is within the view's bounds
+            if (!bounds.contains(dodgeRect)) {
+                throw new IllegalArgumentException("Rect should be within the child's bounds."
+                        + " Rect:" + dodgeRect.toShortString()
+                        + " | Bounds:" + bounds.toShortString());
+            }
+        } else {
+            dodgeRect.set(bounds);
+        }
+
+        // We can release the bounds rect now
+        releaseTempRect(bounds);
+
+        if (dodgeRect.isEmpty()) {
+            // Rect is empty so there is nothing to dodge against, skip...
+            releaseTempRect(dodgeRect);
+            return;
+        }
+
+        final int absDodgeInsetEdges = GravityCompat.getAbsoluteGravity(lp.dodgeInsetEdges,
+                layoutDirection);
+
+        boolean offsetY = false;
+        if ((absDodgeInsetEdges & Gravity.TOP) == Gravity.TOP) {
+            int distance = dodgeRect.top - lp.topMargin - lp.mInsetOffsetY;
+            if (distance < inset.top) {
+                setInsetOffsetY(child, inset.top - distance);
+                offsetY = true;
+            }
+        }
+        if ((absDodgeInsetEdges & Gravity.BOTTOM) == Gravity.BOTTOM) {
+            int distance = getHeight() - dodgeRect.bottom - lp.bottomMargin + lp.mInsetOffsetY;
+            if (distance < inset.bottom) {
+                setInsetOffsetY(child, distance - inset.bottom);
+                offsetY = true;
+            }
+        }
+        if (!offsetY) {
+            setInsetOffsetY(child, 0);
+        }
+
+        boolean offsetX = false;
+        if ((absDodgeInsetEdges & Gravity.LEFT) == Gravity.LEFT) {
+            int distance = dodgeRect.left - lp.leftMargin - lp.mInsetOffsetX;
+            if (distance < inset.left) {
+                setInsetOffsetX(child, inset.left - distance);
+                offsetX = true;
+            }
+        }
+        if ((absDodgeInsetEdges & Gravity.RIGHT) == Gravity.RIGHT) {
+            int distance = getWidth() - dodgeRect.right - lp.rightMargin + lp.mInsetOffsetX;
+            if (distance < inset.right) {
+                setInsetOffsetX(child, distance - inset.right);
+                offsetX = true;
+            }
+        }
+        if (!offsetX) {
+            setInsetOffsetX(child, 0);
+        }
+
+        releaseTempRect(dodgeRect);
+    }
+
+    private void setInsetOffsetX(View child, int offsetX) {
+        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+        if (lp.mInsetOffsetX != offsetX) {
+            final int dx = offsetX - lp.mInsetOffsetX;
+            ViewCompat.offsetLeftAndRight(child, dx);
+            lp.mInsetOffsetX = offsetX;
+        }
+    }
+
+    private void setInsetOffsetY(View child, int offsetY) {
+        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+        if (lp.mInsetOffsetY != offsetY) {
+            final int dy = offsetY - lp.mInsetOffsetY;
+            ViewCompat.offsetTopAndBottom(child, dy);
+            lp.mInsetOffsetY = offsetY;
+        }
+    }
+
+    /**
+     * Allows the caller to manually dispatch
+     * {@link Behavior#onDependentViewChanged(CoordinatorLayout, View, View)} to the associated
+     * {@link Behavior} instances of views which depend on the provided {@link View}.
+     *
+     * <p>You should not normally need to call this method as the it will be automatically done
+     * when the view has changed.
+     *
+     * @param view the View to find dependents of to dispatch the call.
+     */
+    public void dispatchDependentViewsChanged(View view) {
+        final List<View> dependents = mChildDag.getIncomingEdges(view);
+        if (dependents != null && !dependents.isEmpty()) {
+            for (int i = 0; i < dependents.size(); i++) {
+                final View child = dependents.get(i);
+                LayoutParams lp = (LayoutParams)
+                        child.getLayoutParams();
+                Behavior b = lp.getBehavior();
+                if (b != null) {
+                    b.onDependentViewChanged(this, child, view);
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns the list of views which the provided view depends on. Do not store this list as its
+     * contents may not be valid beyond the caller.
+     *
+     * @param child the view to find dependencies for.
+     *
+     * @return the list of views which {@code child} depends on.
+     */
+    @NonNull
+    public List<View> getDependencies(@NonNull View child) {
+        final List<View> dependencies = mChildDag.getOutgoingEdges(child);
+        mTempDependenciesList.clear();
+        if (dependencies != null) {
+            mTempDependenciesList.addAll(dependencies);
+        }
+        return mTempDependenciesList;
+    }
+
+    /**
+     * Returns the list of views which depend on the provided view. Do not store this list as its
+     * contents may not be valid beyond the caller.
+     *
+     * @param child the view to find dependents of.
+     *
+     * @return the list of views which depend on {@code child}.
+     */
+    @NonNull
+    public List<View> getDependents(@NonNull View child) {
+        final List<View> edges = mChildDag.getIncomingEdges(child);
+        mTempDependenciesList.clear();
+        if (edges != null) {
+            mTempDependenciesList.addAll(edges);
+        }
+        return mTempDependenciesList;
+    }
+
+    @VisibleForTesting
+    final List<View> getDependencySortedChildren() {
+        prepareChildren();
+        return Collections.unmodifiableList(mDependencySortedChildren);
+    }
+
+    /**
+     * Add or remove the pre-draw listener as necessary.
+     */
+    void ensurePreDrawListener() {
+        boolean hasDependencies = false;
+        final int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            final View child = getChildAt(i);
+            if (hasDependencies(child)) {
+                hasDependencies = true;
+                break;
+            }
+        }
+
+        if (hasDependencies != mNeedsPreDrawListener) {
+            if (hasDependencies) {
+                addPreDrawListener();
+            } else {
+                removePreDrawListener();
+            }
+        }
+    }
+
+    /**
+     * Check if the given child has any layout dependencies on other child views.
+     */
+    private boolean hasDependencies(View child) {
+        return mChildDag.hasOutgoingEdges(child);
+    }
+
+    /**
+     * Add the pre-draw listener if we're attached to a window and mark that we currently
+     * need it when attached.
+     */
+    void addPreDrawListener() {
+        if (mIsAttachedToWindow) {
+            // Add the listener
+            if (mOnPreDrawListener == null) {
+                mOnPreDrawListener = new OnPreDrawListener();
+            }
+            final ViewTreeObserver vto = getViewTreeObserver();
+            vto.addOnPreDrawListener(mOnPreDrawListener);
+        }
+
+        // Record that we need the listener regardless of whether or not we're attached.
+        // We'll add the real listener when we become attached.
+        mNeedsPreDrawListener = true;
+    }
+
+    /**
+     * Remove the pre-draw listener if we're attached to a window and mark that we currently
+     * do not need it when attached.
+     */
+    void removePreDrawListener() {
+        if (mIsAttachedToWindow) {
+            if (mOnPreDrawListener != null) {
+                final ViewTreeObserver vto = getViewTreeObserver();
+                vto.removeOnPreDrawListener(mOnPreDrawListener);
+            }
+        }
+        mNeedsPreDrawListener = false;
+    }
+
+    /**
+     * Adjust the child left, top, right, bottom rect to the correct anchor view position,
+     * respecting gravity and anchor gravity.
+     *
+     * Note that child translation properties are ignored in this process, allowing children
+     * to be animated away from their anchor. However, if the anchor view is animated,
+     * the child will be offset to match the anchor's translated position.
+     */
+    void offsetChildToAnchor(View child, int layoutDirection) {
+        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+        if (lp.mAnchorView != null) {
+            final Rect anchorRect = acquireTempRect();
+            final Rect childRect = acquireTempRect();
+            final Rect desiredChildRect = acquireTempRect();
+
+            getDescendantRect(lp.mAnchorView, anchorRect);
+            getChildRect(child, false, childRect);
+
+            int childWidth = child.getMeasuredWidth();
+            int childHeight = child.getMeasuredHeight();
+            getDesiredAnchoredChildRectWithoutConstraints(child, layoutDirection, anchorRect,
+                    desiredChildRect, lp, childWidth, childHeight);
+            boolean changed = desiredChildRect.left != childRect.left ||
+                    desiredChildRect.top != childRect.top;
+            constrainChildRect(lp, desiredChildRect, childWidth, childHeight);
+
+            final int dx = desiredChildRect.left - childRect.left;
+            final int dy = desiredChildRect.top - childRect.top;
+
+            if (dx != 0) {
+                ViewCompat.offsetLeftAndRight(child, dx);
+            }
+            if (dy != 0) {
+                ViewCompat.offsetTopAndBottom(child, dy);
+            }
+
+            if (changed) {
+                // If we have needed to move, make sure to notify the child's Behavior
+                final Behavior b = lp.getBehavior();
+                if (b != null) {
+                    b.onDependentViewChanged(this, child, lp.mAnchorView);
+                }
+            }
+
+            releaseTempRect(anchorRect);
+            releaseTempRect(childRect);
+            releaseTempRect(desiredChildRect);
+        }
+    }
+
+    /**
+     * Check if a given point in the CoordinatorLayout's coordinates are within the view bounds
+     * of the given direct child view.
+     *
+     * @param child child view to test
+     * @param x X coordinate to test, in the CoordinatorLayout's coordinate system
+     * @param y Y coordinate to test, in the CoordinatorLayout's coordinate system
+     * @return true if the point is within the child view's bounds, false otherwise
+     */
+    public boolean isPointInChildBounds(View child, int x, int y) {
+        final Rect r = acquireTempRect();
+        getDescendantRect(child, r);
+        try {
+            return r.contains(x, y);
+        } finally {
+            releaseTempRect(r);
+        }
+    }
+
+    /**
+     * Check whether two views overlap each other. The views need to be descendants of this
+     * {@link CoordinatorLayout} in the view hierarchy.
+     *
+     * @param first first child view to test
+     * @param second second child view to test
+     * @return true if both views are visible and overlap each other
+     */
+    public boolean doViewsOverlap(View first, View second) {
+        if (first.getVisibility() == VISIBLE && second.getVisibility() == VISIBLE) {
+            final Rect firstRect = acquireTempRect();
+            getChildRect(first, first.getParent() != this, firstRect);
+            final Rect secondRect = acquireTempRect();
+            getChildRect(second, second.getParent() != this, secondRect);
+            try {
+                return !(firstRect.left > secondRect.right || firstRect.top > secondRect.bottom
+                        || firstRect.right < secondRect.left || firstRect.bottom < secondRect.top);
+            } finally {
+                releaseTempRect(firstRect);
+                releaseTempRect(secondRect);
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public LayoutParams generateLayoutParams(AttributeSet attrs) {
+        return new LayoutParams(getContext(), attrs);
+    }
+
+    @Override
+    protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
+        if (p instanceof LayoutParams) {
+            return new LayoutParams((LayoutParams) p);
+        } else if (p instanceof MarginLayoutParams) {
+            return new LayoutParams((MarginLayoutParams) p);
+        }
+        return new LayoutParams(p);
+    }
+
+    @Override
+    protected LayoutParams generateDefaultLayoutParams() {
+        return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+    }
+
+    @Override
+    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+        return p instanceof LayoutParams && super.checkLayoutParams(p);
+    }
+
+    @Override
+    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
+        return onStartNestedScroll(child, target, nestedScrollAxes, ViewCompat.TYPE_TOUCH);
+    }
+
+    @Override
+    public boolean onStartNestedScroll(View child, View target, int axes, int type) {
+        boolean handled = false;
+
+        final int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            final View view = getChildAt(i);
+            if (view.getVisibility() == View.GONE) {
+                // If it's GONE, don't dispatch
+                continue;
+            }
+            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
+            final Behavior viewBehavior = lp.getBehavior();
+            if (viewBehavior != null) {
+                final boolean accepted = viewBehavior.onStartNestedScroll(this, view, child,
+                        target, axes, type);
+                handled |= accepted;
+                lp.setNestedScrollAccepted(type, accepted);
+            } else {
+                lp.setNestedScrollAccepted(type, false);
+            }
+        }
+        return handled;
+    }
+
+    @Override
+    public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes) {
+        onNestedScrollAccepted(child, target, nestedScrollAxes, ViewCompat.TYPE_TOUCH);
+    }
+
+    @Override
+    public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes, int type) {
+        mNestedScrollingParentHelper.onNestedScrollAccepted(child, target, nestedScrollAxes, type);
+        mNestedScrollingTarget = target;
+
+        final int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            final View view = getChildAt(i);
+            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
+            if (!lp.isNestedScrollAccepted(type)) {
+                continue;
+            }
+
+            final Behavior viewBehavior = lp.getBehavior();
+            if (viewBehavior != null) {
+                viewBehavior.onNestedScrollAccepted(this, view, child, target,
+                        nestedScrollAxes, type);
+            }
+        }
+    }
+
+    @Override
+    public void onStopNestedScroll(View target) {
+        onStopNestedScroll(target, ViewCompat.TYPE_TOUCH);
+    }
+
+    @Override
+    public void onStopNestedScroll(View target, int type) {
+        mNestedScrollingParentHelper.onStopNestedScroll(target, type);
+
+        final int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            final View view = getChildAt(i);
+            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
+            if (!lp.isNestedScrollAccepted(type)) {
+                continue;
+            }
+
+            final Behavior viewBehavior = lp.getBehavior();
+            if (viewBehavior != null) {
+                viewBehavior.onStopNestedScroll(this, view, target, type);
+            }
+            lp.resetNestedScroll(type);
+            lp.resetChangedAfterNestedScroll();
+        }
+        mNestedScrollingTarget = null;
+    }
+
+    @Override
+    public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
+            int dxUnconsumed, int dyUnconsumed) {
+        onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed,
+                ViewCompat.TYPE_TOUCH);
+    }
+
+    @Override
+    public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
+            int dxUnconsumed, int dyUnconsumed, int type) {
+        final int childCount = getChildCount();
+        boolean accepted = false;
+
+        for (int i = 0; i < childCount; i++) {
+            final View view = getChildAt(i);
+            if (view.getVisibility() == GONE) {
+                // If the child is GONE, skip...
+                continue;
+            }
+
+            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
+            if (!lp.isNestedScrollAccepted(type)) {
+                continue;
+            }
+
+            final Behavior viewBehavior = lp.getBehavior();
+            if (viewBehavior != null) {
+                viewBehavior.onNestedScroll(this, view, target, dxConsumed, dyConsumed,
+                        dxUnconsumed, dyUnconsumed, type);
+                accepted = true;
+            }
+        }
+
+        if (accepted) {
+            onChildViewsChanged(EVENT_NESTED_SCROLL);
+        }
+    }
+
+    @Override
+    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
+        onNestedPreScroll(target, dx, dy, consumed, ViewCompat.TYPE_TOUCH);
+    }
+
+    @Override
+    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed, int  type) {
+        int xConsumed = 0;
+        int yConsumed = 0;
+        boolean accepted = false;
+
+        final int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            final View view = getChildAt(i);
+            if (view.getVisibility() == GONE) {
+                // If the child is GONE, skip...
+                continue;
+            }
+
+            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
+            if (!lp.isNestedScrollAccepted(type)) {
+                continue;
+            }
+
+            final Behavior viewBehavior = lp.getBehavior();
+            if (viewBehavior != null) {
+                mTempIntPair[0] = mTempIntPair[1] = 0;
+                viewBehavior.onNestedPreScroll(this, view, target, dx, dy, mTempIntPair, type);
+
+                xConsumed = dx > 0 ? Math.max(xConsumed, mTempIntPair[0])
+                        : Math.min(xConsumed, mTempIntPair[0]);
+                yConsumed = dy > 0 ? Math.max(yConsumed, mTempIntPair[1])
+                        : Math.min(yConsumed, mTempIntPair[1]);
+
+                accepted = true;
+            }
+        }
+
+        consumed[0] = xConsumed;
+        consumed[1] = yConsumed;
+
+        if (accepted) {
+            onChildViewsChanged(EVENT_NESTED_SCROLL);
+        }
+    }
+
+    @Override
+    public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
+        boolean handled = false;
+
+        final int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            final View view = getChildAt(i);
+            if (view.getVisibility() == GONE) {
+                // If the child is GONE, skip...
+                continue;
+            }
+
+            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
+            if (!lp.isNestedScrollAccepted(ViewCompat.TYPE_TOUCH)) {
+                continue;
+            }
+
+            final Behavior viewBehavior = lp.getBehavior();
+            if (viewBehavior != null) {
+                handled |= viewBehavior.onNestedFling(this, view, target, velocityX, velocityY,
+                        consumed);
+            }
+        }
+        if (handled) {
+            onChildViewsChanged(EVENT_NESTED_SCROLL);
+        }
+        return handled;
+    }
+
+    @Override
+    public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
+        boolean handled = false;
+
+        final int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            final View view = getChildAt(i);
+            if (view.getVisibility() == GONE) {
+                // If the child is GONE, skip...
+                continue;
+            }
+
+            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
+            if (!lp.isNestedScrollAccepted(ViewCompat.TYPE_TOUCH)) {
+                continue;
+            }
+
+            final Behavior viewBehavior = lp.getBehavior();
+            if (viewBehavior != null) {
+                handled |= viewBehavior.onNestedPreFling(this, view, target, velocityX, velocityY);
+            }
+        }
+        return handled;
+    }
+
+    @Override
+    public int getNestedScrollAxes() {
+        return mNestedScrollingParentHelper.getNestedScrollAxes();
+    }
+
+    class OnPreDrawListener implements ViewTreeObserver.OnPreDrawListener {
+        @Override
+        public boolean onPreDraw() {
+            onChildViewsChanged(EVENT_PRE_DRAW);
+            return true;
+        }
+    }
+
+    /**
+     * Sorts child views with higher Z values to the beginning of a collection.
+     */
+    static class ViewElevationComparator implements Comparator<View> {
+        @Override
+        public int compare(View lhs, View rhs) {
+            final float lz = ViewCompat.getZ(lhs);
+            final float rz = ViewCompat.getZ(rhs);
+            if (lz > rz) {
+                return -1;
+            } else if (lz < rz) {
+                return 1;
+            }
+            return 0;
+        }
+    }
+
+    /**
+     * Defines the default {@link Behavior} of a {@link View} class.
+     *
+     * <p>When writing a custom view, use this annotation to define the default behavior
+     * when used as a direct child of an {@link CoordinatorLayout}. The default behavior
+     * can be overridden using {@link LayoutParams#setBehavior}.</p>
+     *
+     * <p>Example: <code>@DefaultBehavior(MyBehavior.class)</code></p>
+     */
+    @Retention(RetentionPolicy.RUNTIME)
+    public @interface DefaultBehavior {
+        Class<? extends Behavior> value();
+    }
+
+    /**
+     * Interaction behavior plugin for child views of {@link CoordinatorLayout}.
+     *
+     * <p>A Behavior implements one or more interactions that a user can take on a child view.
+     * These interactions may include drags, swipes, flings, or any other gestures.</p>
+     *
+     * @param <V> The View type that this Behavior operates on
+     */
+    public static abstract class Behavior<V extends View> {
+
+        /**
+         * Default constructor for instantiating Behaviors.
+         */
+        public Behavior() {
+        }
+
+        /**
+         * Default constructor for inflating Behaviors from layout. The Behavior will have
+         * the opportunity to parse specially defined layout parameters. These parameters will
+         * appear on the child view tag.
+         *
+         * @param context
+         * @param attrs
+         */
+        public Behavior(Context context, AttributeSet attrs) {
+        }
+
+        /**
+         * Called when the Behavior has been attached to a LayoutParams instance.
+         *
+         * <p>This will be called after the LayoutParams has been instantiated and can be
+         * modified.</p>
+         *
+         * @param params the LayoutParams instance that this Behavior has been attached to
+         */
+        public void onAttachedToLayoutParams(@NonNull CoordinatorLayout.LayoutParams params) {
+        }
+
+        /**
+         * Called when the Behavior has been detached from its holding LayoutParams instance.
+         *
+         * <p>This will only be called if the Behavior has been explicitly removed from the
+         * LayoutParams instance via {@link LayoutParams#setBehavior(Behavior)}. It will not be
+         * called if the associated view is removed from the CoordinatorLayout or similar.</p>
+         */
+        public void onDetachedFromLayoutParams() {
+        }
+
+        /**
+         * Respond to CoordinatorLayout touch events before they are dispatched to child views.
+         *
+         * <p>Behaviors can use this to monitor inbound touch events until one decides to
+         * intercept the rest of the event stream to take an action on its associated child view.
+         * This method will return false until it detects the proper intercept conditions, then
+         * return true once those conditions have occurred.</p>
+         *
+         * <p>Once a Behavior intercepts touch events, the rest of the event stream will
+         * be sent to the {@link #onTouchEvent} method.</p>
+         *
+         * <p>This method will be called regardless of the visibility of the associated child
+         * of the behavior. If you only wish to handle touch events when the child is visible, you
+         * should add a check to {@link View#isShown()} on the given child.</p>
+         *
+         * <p>The default implementation of this method always returns false.</p>
+         *
+         * @param parent the parent view currently receiving this touch event
+         * @param child the child view associated with this Behavior
+         * @param ev the MotionEvent describing the touch event being processed
+         * @return true if this Behavior would like to intercept and take over the event stream.
+         *         The default always returns false.
+         */
+        public boolean onInterceptTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev) {
+            return false;
+        }
+
+        /**
+         * Respond to CoordinatorLayout touch events after this Behavior has started
+         * {@link #onInterceptTouchEvent intercepting} them.
+         *
+         * <p>Behaviors may intercept touch events in order to help the CoordinatorLayout
+         * manipulate its child views. For example, a Behavior may allow a user to drag a
+         * UI pane open or closed. This method should perform actual mutations of view
+         * layout state.</p>
+         *
+         * <p>This method will be called regardless of the visibility of the associated child
+         * of the behavior. If you only wish to handle touch events when the child is visible, you
+         * should add a check to {@link View#isShown()} on the given child.</p>
+         *
+         * @param parent the parent view currently receiving this touch event
+         * @param child the child view associated with this Behavior
+         * @param ev the MotionEvent describing the touch event being processed
+         * @return true if this Behavior handled this touch event and would like to continue
+         *         receiving events in this stream. The default always returns false.
+         */
+        public boolean onTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev) {
+            return false;
+        }
+
+        /**
+         * Supply a scrim color that will be painted behind the associated child view.
+         *
+         * <p>A scrim may be used to indicate that the other elements beneath it are not currently
+         * interactive or actionable, drawing user focus and attention to the views above the scrim.
+         * </p>
+         *
+         * <p>The default implementation returns {@link Color#BLACK}.</p>
+         *
+         * @param parent the parent view of the given child
+         * @param child the child view above the scrim
+         * @return the desired scrim color in 0xAARRGGBB format. The default return value is
+         *         {@link Color#BLACK}.
+         * @see #getScrimOpacity(CoordinatorLayout, View)
+         */
+        @ColorInt
+        public int getScrimColor(CoordinatorLayout parent, V child) {
+            return Color.BLACK;
+        }
+
+        /**
+         * Determine the current opacity of the scrim behind a given child view
+         *
+         * <p>A scrim may be used to indicate that the other elements beneath it are not currently
+         * interactive or actionable, drawing user focus and attention to the views above the scrim.
+         * </p>
+         *
+         * <p>The default implementation returns 0.0f.</p>
+         *
+         * @param parent the parent view of the given child
+         * @param child the child view above the scrim
+         * @return the desired scrim opacity from 0.0f to 1.0f. The default return value is 0.0f.
+         */
+        @FloatRange(from = 0, to = 1)
+        public float getScrimOpacity(CoordinatorLayout parent, V child) {
+            return 0.f;
+        }
+
+        /**
+         * Determine whether interaction with views behind the given child in the child order
+         * should be blocked.
+         *
+         * <p>The default implementation returns true if
+         * {@link #getScrimOpacity(CoordinatorLayout, View)} would return > 0.0f.</p>
+         *
+         * @param parent the parent view of the given child
+         * @param child the child view to test
+         * @return true if {@link #getScrimOpacity(CoordinatorLayout, View)} would
+         *         return > 0.0f.
+         */
+        public boolean blocksInteractionBelow(CoordinatorLayout parent, V child) {
+            return getScrimOpacity(parent, child) > 0.f;
+        }
+
+        /**
+         * Determine whether the supplied child view has another specific sibling view as a
+         * layout dependency.
+         *
+         * <p>This method will be called at least once in response to a layout request. If it
+         * returns true for a given child and dependency view pair, the parent CoordinatorLayout
+         * will:</p>
+         * <ol>
+         *     <li>Always lay out this child after the dependent child is laid out, regardless
+         *     of child order.</li>
+         *     <li>Call {@link #onDependentViewChanged} when the dependency view's layout or
+         *     position changes.</li>
+         * </ol>
+         *
+         * @param parent the parent view of the given child
+         * @param child the child view to test
+         * @param dependency the proposed dependency of child
+         * @return true if child's layout depends on the proposed dependency's layout,
+         *         false otherwise
+         *
+         * @see #onDependentViewChanged(CoordinatorLayout, View, View)
+         */
+        public boolean layoutDependsOn(CoordinatorLayout parent, V child, View dependency) {
+            return false;
+        }
+
+        /**
+         * Respond to a change in a child's dependent view
+         *
+         * <p>This method is called whenever a dependent view changes in size or position outside
+         * of the standard layout flow. A Behavior may use this method to appropriately update
+         * the child view in response.</p>
+         *
+         * <p>A view's dependency is determined by
+         * {@link #layoutDependsOn(CoordinatorLayout, View, View)} or
+         * if {@code child} has set another view as it's anchor.</p>
+         *
+         * <p>Note that if a Behavior changes the layout of a child via this method, it should
+         * also be able to reconstruct the correct position in
+         * {@link #onLayoutChild(CoordinatorLayout, View, int) onLayoutChild}.
+         * <code>onDependentViewChanged</code> will not be called during normal layout since
+         * the layout of each child view will always happen in dependency order.</p>
+         *
+         * <p>If the Behavior changes the child view's size or position, it should return true.
+         * The default implementation returns false.</p>
+         *
+         * @param parent the parent view of the given child
+         * @param child the child view to manipulate
+         * @param dependency the dependent view that changed
+         * @return true if the Behavior changed the child view's size or position, false otherwise
+         */
+        public boolean onDependentViewChanged(CoordinatorLayout parent, V child, View dependency) {
+            return false;
+        }
+
+        /**
+         * Respond to a child's dependent view being removed.
+         *
+         * <p>This method is called after a dependent view has been removed from the parent.
+         * A Behavior may use this method to appropriately update the child view in response.</p>
+         *
+         * <p>A view's dependency is determined by
+         * {@link #layoutDependsOn(CoordinatorLayout, View, View)} or
+         * if {@code child} has set another view as it's anchor.</p>
+         *
+         * @param parent the parent view of the given child
+         * @param child the child view to manipulate
+         * @param dependency the dependent view that has been removed
+         */
+        public void onDependentViewRemoved(CoordinatorLayout parent, V child, View dependency) {
+        }
+
+        /**
+         * Called when the parent CoordinatorLayout is about to measure the given child view.
+         *
+         * <p>This method can be used to perform custom or modified measurement of a child view
+         * in place of the default child measurement behavior. The Behavior's implementation
+         * can delegate to the standard CoordinatorLayout measurement behavior by calling
+         * {@link CoordinatorLayout#onMeasureChild(View, int, int, int, int)
+         * parent.onMeasureChild}.</p>
+         *
+         * @param parent the parent CoordinatorLayout
+         * @param child the child to measure
+         * @param parentWidthMeasureSpec the width requirements for this view
+         * @param widthUsed extra space that has been used up by the parent
+         *        horizontally (possibly by other children of the parent)
+         * @param parentHeightMeasureSpec the height requirements for this view
+         * @param heightUsed extra space that has been used up by the parent
+         *        vertically (possibly by other children of the parent)
+         * @return true if the Behavior measured the child view, false if the CoordinatorLayout
+         *         should perform its default measurement
+         */
+        public boolean onMeasureChild(CoordinatorLayout parent, V child,
+                int parentWidthMeasureSpec, int widthUsed,
+                int parentHeightMeasureSpec, int heightUsed) {
+            return false;
+        }
+
+        /**
+         * Called when the parent CoordinatorLayout is about the lay out the given child view.
+         *
+         * <p>This method can be used to perform custom or modified layout of a child view
+         * in place of the default child layout behavior. The Behavior's implementation can
+         * delegate to the standard CoordinatorLayout measurement behavior by calling
+         * {@link CoordinatorLayout#onLayoutChild(View, int)
+         * parent.onLayoutChild}.</p>
+         *
+         * <p>If a Behavior implements
+         * {@link #onDependentViewChanged(CoordinatorLayout, View, View)}
+         * to change the position of a view in response to a dependent view changing, it
+         * should also implement <code>onLayoutChild</code> in such a way that respects those
+         * dependent views. <code>onLayoutChild</code> will always be called for a dependent view
+         * <em>after</em> its dependency has been laid out.</p>
+         *
+         * @param parent the parent CoordinatorLayout
+         * @param child child view to lay out
+         * @param layoutDirection the resolved layout direction for the CoordinatorLayout, such as
+         *                        {@link ViewCompat#LAYOUT_DIRECTION_LTR} or
+         *                        {@link ViewCompat#LAYOUT_DIRECTION_RTL}.
+         * @return true if the Behavior performed layout of the child view, false to request
+         *         default layout behavior
+         */
+        public boolean onLayoutChild(CoordinatorLayout parent, V child, int layoutDirection) {
+            return false;
+        }
+
+        // Utility methods for accessing child-specific, behavior-modifiable properties.
+
+        /**
+         * Associate a Behavior-specific tag object with the given child view.
+         * This object will be stored with the child view's LayoutParams.
+         *
+         * @param child child view to set tag with
+         * @param tag tag object to set
+         */
+        public static void setTag(View child, Object tag) {
+            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+            lp.mBehaviorTag = tag;
+        }
+
+        /**
+         * Get the behavior-specific tag object with the given child view.
+         * This object is stored with the child view's LayoutParams.
+         *
+         * @param child child view to get tag with
+         * @return the previously stored tag object
+         */
+        public static Object getTag(View child) {
+            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+            return lp.mBehaviorTag;
+        }
+
+        /**
+         * @deprecated You should now override
+         * {@link #onStartNestedScroll(CoordinatorLayout, View, View, View, int, int)}. This
+         * method will still continue to be called if the type is {@link ViewCompat#TYPE_TOUCH}.
+         */
+        @Deprecated
+        public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout,
+                @NonNull V child, @NonNull View directTargetChild, @NonNull View target,
+                @ScrollAxis int axes) {
+            return false;
+        }
+
+        /**
+         * Called when a descendant of the CoordinatorLayout attempts to initiate a nested scroll.
+         *
+         * <p>Any Behavior associated with any direct child of the CoordinatorLayout may respond
+         * to this event and return true to indicate that the CoordinatorLayout should act as
+         * a nested scrolling parent for this scroll. Only Behaviors that return true from
+         * this method will receive subsequent nested scroll events.</p>
+         *
+         * @param coordinatorLayout the CoordinatorLayout parent of the view this Behavior is
+         *                          associated with
+         * @param child the child view of the CoordinatorLayout this Behavior is associated with
+         * @param directTargetChild the child view of the CoordinatorLayout that either is or
+         *                          contains the target of the nested scroll operation
+         * @param target the descendant view of the CoordinatorLayout initiating the nested scroll
+         * @param axes the axes that this nested scroll applies to. See
+         *                         {@link ViewCompat#SCROLL_AXIS_HORIZONTAL},
+         *                         {@link ViewCompat#SCROLL_AXIS_VERTICAL}
+         * @param type the type of input which cause this scroll event
+         * @return true if the Behavior wishes to accept this nested scroll
+         *
+         * @see NestedScrollingParent2#onStartNestedScroll(View, View, int, int)
+         */
+        public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout,
+                @NonNull V child, @NonNull View directTargetChild, @NonNull View target,
+                @ScrollAxis int axes, @NestedScrollType int type) {
+            if (type == ViewCompat.TYPE_TOUCH) {
+                return onStartNestedScroll(coordinatorLayout, child, directTargetChild,
+                        target, axes);
+            }
+            return false;
+        }
+
+        /**
+         * @deprecated You should now override
+         * {@link #onNestedScrollAccepted(CoordinatorLayout, View, View, View, int, int)}. This
+         * method will still continue to be called if the type is {@link ViewCompat#TYPE_TOUCH}.
+         */
+        @Deprecated
+        public void onNestedScrollAccepted(@NonNull CoordinatorLayout coordinatorLayout,
+                @NonNull V child, @NonNull View directTargetChild, @NonNull View target,
+                @ScrollAxis int axes) {
+            // Do nothing
+        }
+
+        /**
+         * Called when a nested scroll has been accepted by the CoordinatorLayout.
+         *
+         * <p>Any Behavior associated with any direct child of the CoordinatorLayout may elect
+         * to accept the nested scroll as part of {@link #onStartNestedScroll}. Each Behavior
+         * that returned true will receive subsequent nested scroll events for that nested scroll.
+         * </p>
+         *
+         * @param coordinatorLayout the CoordinatorLayout parent of the view this Behavior is
+         *                          associated with
+         * @param child the child view of the CoordinatorLayout this Behavior is associated with
+         * @param directTargetChild the child view of the CoordinatorLayout that either is or
+         *                          contains the target of the nested scroll operation
+         * @param target the descendant view of the CoordinatorLayout initiating the nested scroll
+         * @param axes the axes that this nested scroll applies to. See
+         *                         {@link ViewCompat#SCROLL_AXIS_HORIZONTAL},
+         *                         {@link ViewCompat#SCROLL_AXIS_VERTICAL}
+         * @param type the type of input which cause this scroll event
+         *
+         * @see NestedScrollingParent2#onNestedScrollAccepted(View, View, int, int)
+         */
+        public void onNestedScrollAccepted(@NonNull CoordinatorLayout coordinatorLayout,
+                @NonNull V child, @NonNull View directTargetChild, @NonNull View target,
+                @ScrollAxis int axes, @NestedScrollType int type) {
+            if (type == ViewCompat.TYPE_TOUCH) {
+                onNestedScrollAccepted(coordinatorLayout, child, directTargetChild,
+                        target, axes);
+            }
+        }
+
+        /**
+         * @deprecated You should now override
+         * {@link #onStopNestedScroll(CoordinatorLayout, View, View, int)}. This method will still
+         * continue to be called if the type is {@link ViewCompat#TYPE_TOUCH}.
+         */
+        @Deprecated
+        public void onStopNestedScroll(@NonNull CoordinatorLayout coordinatorLayout,
+                @NonNull V child, @NonNull View target) {
+            // Do nothing
+        }
+
+        /**
+         * Called when a nested scroll has ended.
+         *
+         * <p>Any Behavior associated with any direct child of the CoordinatorLayout may elect
+         * to accept the nested scroll as part of {@link #onStartNestedScroll}. Each Behavior
+         * that returned true will receive subsequent nested scroll events for that nested scroll.
+         * </p>
+         *
+         * <p><code>onStopNestedScroll</code> marks the end of a single nested scroll event
+         * sequence. This is a good place to clean up any state related to the nested scroll.
+         * </p>
+         *
+         * @param coordinatorLayout the CoordinatorLayout parent of the view this Behavior is
+         *                          associated with
+         * @param child the child view of the CoordinatorLayout this Behavior is associated with
+         * @param target the descendant view of the CoordinatorLayout that initiated
+         *               the nested scroll
+         * @param type the type of input which cause this scroll event
+         *
+         * @see NestedScrollingParent2#onStopNestedScroll(View, int)
+         */
+        public void onStopNestedScroll(@NonNull CoordinatorLayout coordinatorLayout,
+                @NonNull V child, @NonNull View target, @NestedScrollType int type) {
+            if (type == ViewCompat.TYPE_TOUCH) {
+                onStopNestedScroll(coordinatorLayout, child, target);
+            }
+        }
+
+        /**
+         * @deprecated You should now override
+         * {@link #onNestedScroll(CoordinatorLayout, View, View, int, int, int, int, int)}.
+         * This method will still continue to be called if the type is
+         * {@link ViewCompat#TYPE_TOUCH}.
+         */
+        @Deprecated
+        public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child,
+                @NonNull View target, int dxConsumed, int dyConsumed,
+                int dxUnconsumed, int dyUnconsumed) {
+            // Do nothing
+        }
+
+        /**
+         * Called when a nested scroll in progress has updated and the target has scrolled or
+         * attempted to scroll.
+         *
+         * <p>Any Behavior associated with the direct child of the CoordinatorLayout may elect
+         * to accept the nested scroll as part of {@link #onStartNestedScroll}. Each Behavior
+         * that returned true will receive subsequent nested scroll events for that nested scroll.
+         * </p>
+         *
+         * <p><code>onNestedScroll</code> is called each time the nested scroll is updated by the
+         * nested scrolling child, with both consumed and unconsumed components of the scroll
+         * supplied in pixels. <em>Each Behavior responding to the nested scroll will receive the
+         * same values.</em>
+         * </p>
+         *
+         * @param coordinatorLayout the CoordinatorLayout parent of the view this Behavior is
+         *                          associated with
+         * @param child the child view of the CoordinatorLayout this Behavior is associated with
+         * @param target the descendant view of the CoordinatorLayout performing the nested scroll
+         * @param dxConsumed horizontal pixels consumed by the target's own scrolling operation
+         * @param dyConsumed vertical pixels consumed by the target's own scrolling operation
+         * @param dxUnconsumed horizontal pixels not consumed by the target's own scrolling
+         *                     operation, but requested by the user
+         * @param dyUnconsumed vertical pixels not consumed by the target's own scrolling operation,
+         *                     but requested by the user
+         * @param type the type of input which cause this scroll event
+         *
+         * @see NestedScrollingParent2#onNestedScroll(View, int, int, int, int, int)
+         */
+        public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child,
+                @NonNull View target, int dxConsumed, int dyConsumed,
+                int dxUnconsumed, int dyUnconsumed, @NestedScrollType int type) {
+            if (type == ViewCompat.TYPE_TOUCH) {
+                onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed,
+                        dxUnconsumed, dyUnconsumed);
+            }
+        }
+
+        /**
+         * @deprecated You should now override
+         * {@link #onNestedPreScroll(CoordinatorLayout, View, View, int, int, int[], int)}.
+         * This method will still continue to be called if the type is
+         * {@link ViewCompat#TYPE_TOUCH}.
+         */
+        @Deprecated
+        public void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout,
+                @NonNull V child, @NonNull View target, int dx, int dy, @NonNull int[] consumed) {
+            // Do nothing
+        }
+
+        /**
+         * Called when a nested scroll in progress is about to update, before the target has
+         * consumed any of the scrolled distance.
+         *
+         * <p>Any Behavior associated with the direct child of the CoordinatorLayout may elect
+         * to accept the nested scroll as part of {@link #onStartNestedScroll}. Each Behavior
+         * that returned true will receive subsequent nested scroll events for that nested scroll.
+         * </p>
+         *
+         * <p><code>onNestedPreScroll</code> is called each time the nested scroll is updated
+         * by the nested scrolling child, before the nested scrolling child has consumed the scroll
+         * distance itself. <em>Each Behavior responding to the nested scroll will receive the
+         * same values.</em> The CoordinatorLayout will report as consumed the maximum number
+         * of pixels in either direction that any Behavior responding to the nested scroll reported
+         * as consumed.</p>
+         *
+         * @param coordinatorLayout the CoordinatorLayout parent of the view this Behavior is
+         *                          associated with
+         * @param child the child view of the CoordinatorLayout this Behavior is associated with
+         * @param target the descendant view of the CoordinatorLayout performing the nested scroll
+         * @param dx the raw horizontal number of pixels that the user attempted to scroll
+         * @param dy the raw vertical number of pixels that the user attempted to scroll
+         * @param consumed out parameter. consumed[0] should be set to the distance of dx that
+         *                 was consumed, consumed[1] should be set to the distance of dy that
+         *                 was consumed
+         * @param type the type of input which cause this scroll event
+         *
+         * @see NestedScrollingParent2#onNestedPreScroll(View, int, int, int[], int)
+         */
+        public void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout,
+                @NonNull V child, @NonNull View target, int dx, int dy, @NonNull int[] consumed,
+                @NestedScrollType int type) {
+            if (type == ViewCompat.TYPE_TOUCH) {
+                onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
+            }
+        }
+
+        /**
+         * Called when a nested scrolling child is starting a fling or an action that would
+         * be a fling.
+         *
+         * <p>Any Behavior associated with the direct child of the CoordinatorLayout may elect
+         * to accept the nested scroll as part of {@link #onStartNestedScroll}. Each Behavior
+         * that returned true will receive subsequent nested scroll events for that nested scroll.
+         * </p>
+         *
+         * <p><code>onNestedFling</code> is called when the current nested scrolling child view
+         * detects the proper conditions for a fling. It reports if the child itself consumed
+         * the fling. If it did not, the child is expected to show some sort of overscroll
+         * indication. This method should return true if it consumes the fling, so that a child
+         * that did not itself take an action in response can choose not to show an overfling
+         * indication.</p>
+         *
+         * @param coordinatorLayout the CoordinatorLayout parent of the view this Behavior is
+         *                          associated with
+         * @param child the child view of the CoordinatorLayout this Behavior is associated with
+         * @param target the descendant view of the CoordinatorLayout performing the nested scroll
+         * @param velocityX horizontal velocity of the attempted fling
+         * @param velocityY vertical velocity of the attempted fling
+         * @param consumed true if the nested child view consumed the fling
+         * @return true if the Behavior consumed the fling
+         *
+         * @see NestedScrollingParent#onNestedFling(View, float, float, boolean)
+         */
+        public boolean onNestedFling(@NonNull CoordinatorLayout coordinatorLayout,
+                @NonNull V child, @NonNull View target, float velocityX, float velocityY,
+                boolean consumed) {
+            return false;
+        }
+
+        /**
+         * Called when a nested scrolling child is about to start a fling.
+         *
+         * <p>Any Behavior associated with the direct child of the CoordinatorLayout may elect
+         * to accept the nested scroll as part of {@link #onStartNestedScroll}. Each Behavior
+         * that returned true will receive subsequent nested scroll events for that nested scroll.
+         * </p>
+         *
+         * <p><code>onNestedPreFling</code> is called when the current nested scrolling child view
+         * detects the proper conditions for a fling, but it has not acted on it yet. A
+         * Behavior can return true to indicate that it consumed the fling. If at least one
+         * Behavior returns true, the fling should not be acted upon by the child.</p>
+         *
+         * @param coordinatorLayout the CoordinatorLayout parent of the view this Behavior is
+         *                          associated with
+         * @param child the child view of the CoordinatorLayout this Behavior is associated with
+         * @param target the descendant view of the CoordinatorLayout performing the nested scroll
+         * @param velocityX horizontal velocity of the attempted fling
+         * @param velocityY vertical velocity of the attempted fling
+         * @return true if the Behavior consumed the fling
+         *
+         * @see NestedScrollingParent#onNestedPreFling(View, float, float)
+         */
+        public boolean onNestedPreFling(@NonNull CoordinatorLayout coordinatorLayout,
+                @NonNull V child, @NonNull View target, float velocityX, float velocityY) {
+            return false;
+        }
+
+        /**
+         * Called when the window insets have changed.
+         *
+         * <p>Any Behavior associated with the direct child of the CoordinatorLayout may elect
+         * to handle the window inset change on behalf of it's associated view.
+         * </p>
+         *
+         * @param coordinatorLayout the CoordinatorLayout parent of the view this Behavior is
+         *                          associated with
+         * @param child the child view of the CoordinatorLayout this Behavior is associated with
+         * @param insets the new window insets.
+         *
+         * @return The insets supplied, minus any insets that were consumed
+         */
+        @NonNull
+        public WindowInsetsCompat onApplyWindowInsets(CoordinatorLayout coordinatorLayout,
+                V child, WindowInsetsCompat insets) {
+            return insets;
+        }
+
+        /**
+         * Called when a child of the view associated with this behavior wants a particular
+         * rectangle to be positioned onto the screen.
+         *
+         * <p>The contract for this method is the same as
+         * {@link ViewParent#requestChildRectangleOnScreen(View, Rect, boolean)}.</p>
+         *
+         * @param coordinatorLayout the CoordinatorLayout parent of the view this Behavior is
+         *                          associated with
+         * @param child             the child view of the CoordinatorLayout this Behavior is
+         *                          associated with
+         * @param rectangle         The rectangle which the child wishes to be on the screen
+         *                          in the child's coordinates
+         * @param immediate         true to forbid animated or delayed scrolling, false otherwise
+         * @return true if the Behavior handled the request
+         * @see ViewParent#requestChildRectangleOnScreen(View, Rect, boolean)
+         */
+        public boolean onRequestChildRectangleOnScreen(CoordinatorLayout coordinatorLayout,
+                V child, Rect rectangle, boolean immediate) {
+            return false;
+        }
+
+        /**
+         * Hook allowing a behavior to re-apply a representation of its internal state that had
+         * previously been generated by {@link #onSaveInstanceState}. This function will never
+         * be called with a null state.
+         *
+         * @param parent the parent CoordinatorLayout
+         * @param child child view to restore from
+         * @param state The frozen state that had previously been returned by
+         *        {@link #onSaveInstanceState}.
+         *
+         * @see #onSaveInstanceState()
+         */
+        public void onRestoreInstanceState(CoordinatorLayout parent, V child, Parcelable state) {
+            // no-op
+        }
+
+        /**
+         * Hook allowing a behavior to generate a representation of its internal state
+         * that can later be used to create a new instance with that same state.
+         * This state should only contain information that is not persistent or can
+         * not be reconstructed later.
+         *
+         * <p>Behavior state is only saved when both the parent {@link CoordinatorLayout} and
+         * a view using this behavior have valid IDs set.</p>
+         *
+         * @param parent the parent CoordinatorLayout
+         * @param child child view to restore from
+         *
+         * @return Returns a Parcelable object containing the behavior's current dynamic
+         *         state.
+         *
+         * @see #onRestoreInstanceState(Parcelable)
+         * @see View#onSaveInstanceState()
+         */
+        public Parcelable onSaveInstanceState(CoordinatorLayout parent, V child) {
+            return BaseSavedState.EMPTY_STATE;
+        }
+
+        /**
+         * Called when a view is set to dodge view insets.
+         *
+         * <p>This method allows a behavior to update the rectangle that should be dodged.
+         * The rectangle should be in the parent's coordinate system and within the child's
+         * bounds. If not, a {@link IllegalArgumentException} is thrown.</p>
+         *
+         * @param parent the CoordinatorLayout parent of the view this Behavior is
+         *               associated with
+         * @param child  the child view of the CoordinatorLayout this Behavior is associated with
+         * @param rect   the rect to update with the dodge rectangle
+         * @return true the rect was updated, false if we should use the child's bounds
+         */
+        public boolean getInsetDodgeRect(@NonNull CoordinatorLayout parent, @NonNull V child,
+                @NonNull Rect rect) {
+            return false;
+        }
+    }
+
+    /**
+     * Parameters describing the desired layout for a child of a {@link CoordinatorLayout}.
+     */
+    public static class LayoutParams extends MarginLayoutParams {
+        /**
+         * A {@link Behavior} that the child view should obey.
+         */
+        Behavior mBehavior;
+
+        boolean mBehaviorResolved = false;
+
+        /**
+         * A {@link Gravity} value describing how this child view should lay out.
+         * If either or both of the axes are not specified, they are treated by CoordinatorLayout
+         * as {@link Gravity#TOP} or {@link GravityCompat#START}. If an
+         * {@link #setAnchorId(int) anchor} is also specified, the gravity describes how this child
+         * view should be positioned relative to its anchored position.
+         */
+        public int gravity = Gravity.NO_GRAVITY;
+
+        /**
+         * A {@link Gravity} value describing which edge of a child view's
+         * {@link #getAnchorId() anchor} view the child should position itself relative to.
+         */
+        public int anchorGravity = Gravity.NO_GRAVITY;
+
+        /**
+         * The index of the horizontal keyline specified to the parent CoordinatorLayout that this
+         * child should align relative to. If an {@link #setAnchorId(int) anchor} is present the
+         * keyline will be ignored.
+         */
+        public int keyline = -1;
+
+        /**
+         * A {@link View#getId() view id} of a descendant view of the CoordinatorLayout that
+         * this child should position relative to.
+         */
+        int mAnchorId = View.NO_ID;
+
+        /**
+         * A {@link Gravity} value describing how this child view insets the CoordinatorLayout.
+         * Other child views which are set to dodge the same inset edges will be moved appropriately
+         * so that the views do not overlap.
+         */
+        public int insetEdge = Gravity.NO_GRAVITY;
+
+        /**
+         * A {@link Gravity} value describing how this child view dodges any inset child views in
+         * the CoordinatorLayout. Any views which are inset on the same edge as this view is set to
+         * dodge will result in this view being moved so that the views do not overlap.
+         */
+        public int dodgeInsetEdges = Gravity.NO_GRAVITY;
+
+        int mInsetOffsetX;
+        int mInsetOffsetY;
+
+        View mAnchorView;
+        View mAnchorDirectChild;
+
+        private boolean mDidBlockInteraction;
+        private boolean mDidAcceptNestedScrollTouch;
+        private boolean mDidAcceptNestedScrollNonTouch;
+        private boolean mDidChangeAfterNestedScroll;
+
+        final Rect mLastChildRect = new Rect();
+
+        Object mBehaviorTag;
+
+        public LayoutParams(int width, int height) {
+            super(width, height);
+        }
+
+        LayoutParams(Context context, AttributeSet attrs) {
+            super(context, attrs);
+
+            final TypedArray a = context.obtainStyledAttributes(attrs,
+                    R.styleable.CoordinatorLayout_Layout);
+
+            this.gravity = a.getInteger(
+                    R.styleable.CoordinatorLayout_Layout_android_layout_gravity,
+                    Gravity.NO_GRAVITY);
+            mAnchorId = a.getResourceId(R.styleable.CoordinatorLayout_Layout_layout_anchor,
+                    View.NO_ID);
+            this.anchorGravity = a.getInteger(
+                    R.styleable.CoordinatorLayout_Layout_layout_anchorGravity,
+                    Gravity.NO_GRAVITY);
+
+            this.keyline = a.getInteger(R.styleable.CoordinatorLayout_Layout_layout_keyline,
+                    -1);
+
+            insetEdge = a.getInt(R.styleable.CoordinatorLayout_Layout_layout_insetEdge, 0);
+            dodgeInsetEdges = a.getInt(
+                    R.styleable.CoordinatorLayout_Layout_layout_dodgeInsetEdges, 0);
+            mBehaviorResolved = a.hasValue(
+                    R.styleable.CoordinatorLayout_Layout_layout_behavior);
+            if (mBehaviorResolved) {
+                mBehavior = parseBehavior(context, attrs, a.getString(
+                        R.styleable.CoordinatorLayout_Layout_layout_behavior));
+            }
+            a.recycle();
+
+            if (mBehavior != null) {
+                // If we have a Behavior, dispatch that it has been attached
+                mBehavior.onAttachedToLayoutParams(this);
+            }
+        }
+
+        public LayoutParams(LayoutParams p) {
+            super(p);
+        }
+
+        public LayoutParams(MarginLayoutParams p) {
+            super(p);
+        }
+
+        public LayoutParams(ViewGroup.LayoutParams p) {
+            super(p);
+        }
+
+        /**
+         * Get the id of this view's anchor.
+         *
+         * @return A {@link View#getId() view id} or {@link View#NO_ID} if there is no anchor
+         */
+        @IdRes
+        public int getAnchorId() {
+            return mAnchorId;
+        }
+
+        /**
+         * Set the id of this view's anchor.
+         *
+         * <p>The view with this id must be a descendant of the CoordinatorLayout containing
+         * the child view this LayoutParams belongs to. It may not be the child view with
+         * this LayoutParams or a descendant of it.</p>
+         *
+         * @param id The {@link View#getId() view id} of the anchor or
+         *           {@link View#NO_ID} if there is no anchor
+         */
+        public void setAnchorId(@IdRes int id) {
+            invalidateAnchor();
+            mAnchorId = id;
+        }
+
+        /**
+         * Get the behavior governing the layout and interaction of the child view within
+         * a parent CoordinatorLayout.
+         *
+         * @return The current behavior or null if no behavior is specified
+         */
+        @Nullable
+        public Behavior getBehavior() {
+            return mBehavior;
+        }
+
+        /**
+         * Set the behavior governing the layout and interaction of the child view within
+         * a parent CoordinatorLayout.
+         *
+         * <p>Setting a new behavior will remove any currently associated
+         * {@link Behavior#setTag(View, Object) Behavior tag}.</p>
+         *
+         * @param behavior The behavior to set or null for no special behavior
+         */
+        public void setBehavior(@Nullable Behavior behavior) {
+            if (mBehavior != behavior) {
+                if (mBehavior != null) {
+                    // First detach any old behavior
+                    mBehavior.onDetachedFromLayoutParams();
+                }
+
+                mBehavior = behavior;
+                mBehaviorTag = null;
+                mBehaviorResolved = true;
+
+                if (behavior != null) {
+                    // Now dispatch that the Behavior has been attached
+                    behavior.onAttachedToLayoutParams(this);
+                }
+            }
+        }
+
+        /**
+         * Set the last known position rect for this child view
+         * @param r the rect to set
+         */
+        void setLastChildRect(Rect r) {
+            mLastChildRect.set(r);
+        }
+
+        /**
+         * Get the last known position rect for this child view.
+         * Note: do not mutate the result of this call.
+         */
+        Rect getLastChildRect() {
+            return mLastChildRect;
+        }
+
+        /**
+         * Returns true if the anchor id changed to another valid view id since the anchor view
+         * was resolved.
+         */
+        boolean checkAnchorChanged() {
+            return mAnchorView == null && mAnchorId != View.NO_ID;
+        }
+
+        /**
+         * Returns true if the associated Behavior previously blocked interaction with other views
+         * below the associated child since the touch behavior tracking was last
+         * {@link #resetTouchBehaviorTracking() reset}.
+         *
+         * @see #isBlockingInteractionBelow(CoordinatorLayout, View)
+         */
+        boolean didBlockInteraction() {
+            if (mBehavior == null) {
+                mDidBlockInteraction = false;
+            }
+            return mDidBlockInteraction;
+        }
+
+        /**
+         * Check if the associated Behavior wants to block interaction below the given child
+         * view. The given child view should be the child this LayoutParams is associated with.
+         *
+         * <p>Once interaction is blocked, it will remain blocked until touch interaction tracking
+         * is {@link #resetTouchBehaviorTracking() reset}.</p>
+         *
+         * @param parent the parent CoordinatorLayout
+         * @param child the child view this LayoutParams is associated with
+         * @return true to block interaction below the given child
+         */
+        boolean isBlockingInteractionBelow(CoordinatorLayout parent, View child) {
+            if (mDidBlockInteraction) {
+                return true;
+            }
+
+            return mDidBlockInteraction |= mBehavior != null
+                    ? mBehavior.blocksInteractionBelow(parent, child)
+                    : false;
+        }
+
+        /**
+         * Reset tracking of Behavior-specific touch interactions. This includes
+         * interaction blocking.
+         *
+         * @see #isBlockingInteractionBelow(CoordinatorLayout, View)
+         * @see #didBlockInteraction()
+         */
+        void resetTouchBehaviorTracking() {
+            mDidBlockInteraction = false;
+        }
+
+        void resetNestedScroll(int type) {
+            setNestedScrollAccepted(type, false);
+        }
+
+        void setNestedScrollAccepted(int type, boolean accept) {
+            switch (type) {
+                case ViewCompat.TYPE_TOUCH:
+                    mDidAcceptNestedScrollTouch = accept;
+                    break;
+                case ViewCompat.TYPE_NON_TOUCH:
+                    mDidAcceptNestedScrollNonTouch = accept;
+                    break;
+            }
+        }
+
+        boolean isNestedScrollAccepted(int type) {
+            switch (type) {
+                case ViewCompat.TYPE_TOUCH:
+                    return mDidAcceptNestedScrollTouch;
+                case ViewCompat.TYPE_NON_TOUCH:
+                    return mDidAcceptNestedScrollNonTouch;
+            }
+            return false;
+        }
+
+        boolean getChangedAfterNestedScroll() {
+            return mDidChangeAfterNestedScroll;
+        }
+
+        void setChangedAfterNestedScroll(boolean changed) {
+            mDidChangeAfterNestedScroll = changed;
+        }
+
+        void resetChangedAfterNestedScroll() {
+            mDidChangeAfterNestedScroll = false;
+        }
+
+        /**
+         * Check if an associated child view depends on another child view of the CoordinatorLayout.
+         *
+         * @param parent the parent CoordinatorLayout
+         * @param child the child to check
+         * @param dependency the proposed dependency to check
+         * @return true if child depends on dependency
+         */
+        boolean dependsOn(CoordinatorLayout parent, View child, View dependency) {
+            return dependency == mAnchorDirectChild
+                    || shouldDodge(dependency, ViewCompat.getLayoutDirection(parent))
+                    || (mBehavior != null && mBehavior.layoutDependsOn(parent, child, dependency));
+        }
+
+        /**
+         * Invalidate the cached anchor view and direct child ancestor of that anchor.
+         * The anchor will need to be
+         * {@link #findAnchorView(CoordinatorLayout, View) found} before
+         * being used again.
+         */
+        void invalidateAnchor() {
+            mAnchorView = mAnchorDirectChild = null;
+        }
+
+        /**
+         * Locate the appropriate anchor view by the current {@link #setAnchorId(int) anchor id}
+         * or return the cached anchor view if already known.
+         *
+         * @param parent the parent CoordinatorLayout
+         * @param forChild the child this LayoutParams is associated with
+         * @return the located descendant anchor view, or null if the anchor id is
+         *         {@link View#NO_ID}.
+         */
+        View findAnchorView(CoordinatorLayout parent, View forChild) {
+            if (mAnchorId == View.NO_ID) {
+                mAnchorView = mAnchorDirectChild = null;
+                return null;
+            }
+
+            if (mAnchorView == null || !verifyAnchorView(forChild, parent)) {
+                resolveAnchorView(forChild, parent);
+            }
+            return mAnchorView;
+        }
+
+        /**
+         * Determine the anchor view for the child view this LayoutParams is assigned to.
+         * Assumes mAnchorId is valid.
+         */
+        private void resolveAnchorView(final View forChild, final CoordinatorLayout parent) {
+            mAnchorView = parent.findViewById(mAnchorId);
+            if (mAnchorView != null) {
+                if (mAnchorView == parent) {
+                    if (parent.isInEditMode()) {
+                        mAnchorView = mAnchorDirectChild = null;
+                        return;
+                    }
+                    throw new IllegalStateException(
+                            "View can not be anchored to the the parent CoordinatorLayout");
+                }
+
+                View directChild = mAnchorView;
+                for (ViewParent p = mAnchorView.getParent();
+                        p != parent && p != null;
+                        p = p.getParent()) {
+                    if (p == forChild) {
+                        if (parent.isInEditMode()) {
+                            mAnchorView = mAnchorDirectChild = null;
+                            return;
+                        }
+                        throw new IllegalStateException(
+                                "Anchor must not be a descendant of the anchored view");
+                    }
+                    if (p instanceof View) {
+                        directChild = (View) p;
+                    }
+                }
+                mAnchorDirectChild = directChild;
+            } else {
+                if (parent.isInEditMode()) {
+                    mAnchorView = mAnchorDirectChild = null;
+                    return;
+                }
+                throw new IllegalStateException("Could not find CoordinatorLayout descendant view"
+                        + " with id " + parent.getResources().getResourceName(mAnchorId)
+                        + " to anchor view " + forChild);
+            }
+        }
+
+        /**
+         * Verify that the previously resolved anchor view is still valid - that it is still
+         * a descendant of the expected parent view, it is not the child this LayoutParams
+         * is assigned to or a descendant of it, and it has the expected id.
+         */
+        private boolean verifyAnchorView(View forChild, CoordinatorLayout parent) {
+            if (mAnchorView.getId() != mAnchorId) {
+                return false;
+            }
+
+            View directChild = mAnchorView;
+            for (ViewParent p = mAnchorView.getParent();
+                    p != parent;
+                    p = p.getParent()) {
+                if (p == null || p == forChild) {
+                    mAnchorView = mAnchorDirectChild = null;
+                    return false;
+                }
+                if (p instanceof View) {
+                    directChild = (View) p;
+                }
+            }
+            mAnchorDirectChild = directChild;
+            return true;
+        }
+
+        /**
+         * Checks whether the view with this LayoutParams should dodge the specified view.
+         */
+        private boolean shouldDodge(View other, int layoutDirection) {
+            LayoutParams lp = (LayoutParams) other.getLayoutParams();
+            final int absInset = GravityCompat.getAbsoluteGravity(lp.insetEdge, layoutDirection);
+            return absInset != Gravity.NO_GRAVITY && (absInset &
+                    GravityCompat.getAbsoluteGravity(dodgeInsetEdges, layoutDirection)) == absInset;
+        }
+    }
+
+    private class HierarchyChangeListener implements OnHierarchyChangeListener {
+        HierarchyChangeListener() {
+        }
+
+        @Override
+        public void onChildViewAdded(View parent, View child) {
+            if (mOnHierarchyChangeListener != null) {
+                mOnHierarchyChangeListener.onChildViewAdded(parent, child);
+            }
+        }
+
+        @Override
+        public void onChildViewRemoved(View parent, View child) {
+            onChildViewsChanged(EVENT_VIEW_REMOVED);
+
+            if (mOnHierarchyChangeListener != null) {
+                mOnHierarchyChangeListener.onChildViewRemoved(parent, child);
+            }
+        }
+    }
+
+    @Override
+    protected void onRestoreInstanceState(Parcelable state) {
+        if (!(state instanceof SavedState)) {
+            super.onRestoreInstanceState(state);
+            return;
+        }
+
+        final SavedState ss = (SavedState) state;
+        super.onRestoreInstanceState(ss.getSuperState());
+
+        final SparseArray<Parcelable> behaviorStates = ss.behaviorStates;
+
+        for (int i = 0, count = getChildCount(); i < count; i++) {
+            final View child = getChildAt(i);
+            final int childId = child.getId();
+            final LayoutParams lp = getResolvedLayoutParams(child);
+            final Behavior b = lp.getBehavior();
+
+            if (childId != NO_ID && b != null) {
+                Parcelable savedState = behaviorStates.get(childId);
+                if (savedState != null) {
+                    b.onRestoreInstanceState(this, child, savedState);
+                }
+            }
+        }
+    }
+
+    @Override
+    protected Parcelable onSaveInstanceState() {
+        final SavedState ss = new SavedState(super.onSaveInstanceState());
+
+        final SparseArray<Parcelable> behaviorStates = new SparseArray<>();
+        for (int i = 0, count = getChildCount(); i < count; i++) {
+            final View child = getChildAt(i);
+            final int childId = child.getId();
+            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+            final Behavior b = lp.getBehavior();
+
+            if (childId != NO_ID && b != null) {
+                // If the child has an ID and a Behavior, let it save some state...
+                Parcelable state = b.onSaveInstanceState(this, child);
+                if (state != null) {
+                    behaviorStates.append(childId, state);
+                }
+            }
+        }
+        ss.behaviorStates = behaviorStates;
+        return ss;
+    }
+
+    @Override
+    public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
+        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+        final Behavior behavior = lp.getBehavior();
+
+        if (behavior != null
+                && behavior.onRequestChildRectangleOnScreen(this, child, rectangle, immediate)) {
+            return true;
+        }
+
+        return super.requestChildRectangleOnScreen(child, rectangle, immediate);
+    }
+
+    private void setupForInsets() {
+        if (Build.VERSION.SDK_INT < 21) {
+            return;
+        }
+
+        if (ViewCompat.getFitsSystemWindows(this)) {
+            if (mApplyWindowInsetsListener == null) {
+                mApplyWindowInsetsListener =
+                        new android.support.v4.view.OnApplyWindowInsetsListener() {
+                            @Override
+                            public WindowInsetsCompat onApplyWindowInsets(View v,
+                                    WindowInsetsCompat insets) {
+                                return setWindowInsets(insets);
+                            }
+                        };
+            }
+            // First apply the insets listener
+            ViewCompat.setOnApplyWindowInsetsListener(this, mApplyWindowInsetsListener);
+
+            // Now set the sys ui flags to enable us to lay out in the window insets
+            setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+                    | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
+        } else {
+            ViewCompat.setOnApplyWindowInsetsListener(this, null);
+        }
+    }
+
+    protected static class SavedState extends AbsSavedState {
+        SparseArray<Parcelable> behaviorStates;
+
+        public SavedState(Parcel source, ClassLoader loader) {
+            super(source, loader);
+
+            final int size = source.readInt();
+
+            final int[] ids = new int[size];
+            source.readIntArray(ids);
+
+            final Parcelable[] states = source.readParcelableArray(loader);
+
+            behaviorStates = new SparseArray<>(size);
+            for (int i = 0; i < size; i++) {
+                behaviorStates.append(ids[i], states[i]);
+            }
+        }
+
+        public SavedState(Parcelable superState) {
+            super(superState);
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            super.writeToParcel(dest, flags);
+
+            final int size = behaviorStates != null ? behaviorStates.size() : 0;
+            dest.writeInt(size);
+
+            final int[] ids = new int[size];
+            final Parcelable[] states = new Parcelable[size];
+
+            for (int i = 0; i < size; i++) {
+                ids[i] = behaviorStates.keyAt(i);
+                states[i] = behaviorStates.valueAt(i);
+            }
+            dest.writeIntArray(ids);
+            dest.writeParcelableArray(states, flags);
+
+        }
+
+        public static final Creator<SavedState> CREATOR =
+                new ClassLoaderCreator<SavedState>() {
+                    @Override
+                    public SavedState createFromParcel(Parcel in, ClassLoader loader) {
+                        return new SavedState(in, loader);
+                    }
+
+                    @Override
+                    public SavedState createFromParcel(Parcel in) {
+                        return new SavedState(in, null);
+                    }
+
+                    @Override
+                    public SavedState[] newArray(int size) {
+                        return new SavedState[size];
+                    }
+                };
+    }
+}
diff --git a/core-ui/src/main/java/android/support/v4/app/package.html b/core-ui/src/main/java/android/support/v4/app/package.html
deleted file mode 100755
index 02d1b79..0000000
--- a/core-ui/src/main/java/android/support/v4/app/package.html
+++ /dev/null
@@ -1,8 +0,0 @@
-<body>
-
-Support android.app classes to assist with development of applications for
-android API level 4 or later.  The main features here are backwards-compatible
-versions of {@link android.support.v4.app.FragmentManager} and
-{@link android.support.v4.app.LoaderManager}.
-
-</body>
diff --git a/core-ui/src/main/java/android/support/v4/view/package.html b/core-ui/src/main/java/android/support/v4/view/package.html
deleted file mode 100755
index d80ef70..0000000
--- a/core-ui/src/main/java/android/support/v4/view/package.html
+++ /dev/null
@@ -1,11 +0,0 @@
-<body>
-
-Support android.util classes to assist with development of applications for
-android API level 4 or later.  The main features here are a variety of classes
-for handling backwards compatibility with views (for example
-{@link android.support.v4.view.MotionEventCompat} allows retrieving multi-touch
-data if available), and a new
-{@link android.support.v4.view.ViewPager} widget (which at some point should be moved over
-to the widget package).
-
-</body>
diff --git a/core-ui/src/main/java/android/support/v4/widget/DirectedAcyclicGraph.java b/core-ui/src/main/java/android/support/v4/widget/DirectedAcyclicGraph.java
new file mode 100644
index 0000000..83c62c0
--- /dev/null
+++ b/core-ui/src/main/java/android/support/v4/widget/DirectedAcyclicGraph.java
@@ -0,0 +1,216 @@
+/*
+ * 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.v4.widget;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+import android.support.v4.util.Pools;
+import android.support.v4.util.SimpleArrayMap;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+
+/**
+ * A class which represents a simple directed acyclic graph.
+ *
+ * @param <T> Class for the data objects of this graph.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY)
+public final class DirectedAcyclicGraph<T> {
+    private final Pools.Pool<ArrayList<T>> mListPool = new Pools.SimplePool<>(10);
+    private final SimpleArrayMap<T, ArrayList<T>> mGraph = new SimpleArrayMap<>();
+
+    private final ArrayList<T> mSortResult = new ArrayList<>();
+    private final HashSet<T> mSortTmpMarked = new HashSet<>();
+
+    /**
+     * Add a node to the graph.
+     *
+     * <p>If the node already exists in the graph then this method is a no-op.</p>
+     *
+     * @param node the node to add
+     */
+    public void addNode(@NonNull T node) {
+        if (!mGraph.containsKey(node)) {
+            mGraph.put(node, null);
+        }
+    }
+
+    /**
+     * Returns true if the node is already present in the graph, false otherwise.
+     */
+    public boolean contains(@NonNull T node) {
+        return mGraph.containsKey(node);
+    }
+
+    /**
+     * Add an edge to the graph.
+     *
+     * <p>Both the given nodes should already have been added to the graph through
+     * {@link #addNode(Object)}.</p>
+     *
+     * @param node the parent node
+     * @param incomingEdge the node which has is an incoming edge to {@code node}
+     */
+    public void addEdge(@NonNull T node, @NonNull T incomingEdge) {
+        if (!mGraph.containsKey(node) || !mGraph.containsKey(incomingEdge)) {
+            throw new IllegalArgumentException("All nodes must be present in the graph before"
+                    + " being added as an edge");
+        }
+
+        ArrayList<T> edges = mGraph.get(node);
+        if (edges == null) {
+            // If edges is null, we should try and get one from the pool and add it to the graph
+            edges = getEmptyList();
+            mGraph.put(node, edges);
+        }
+        // Finally add the edge to the list
+        edges.add(incomingEdge);
+    }
+
+    /**
+     * Get any incoming edges from the given node.
+     *
+     * @return a list containing any incoming edges, or null if there are none.
+     */
+    @Nullable
+    public List getIncomingEdges(@NonNull T node) {
+        return mGraph.get(node);
+    }
+
+    /**
+     * Get any outgoing edges for the given node (i.e. nodes which have an incoming edge
+     * from the given node).
+     *
+     * @return a list containing any outgoing edges, or null if there are none.
+     */
+    @Nullable
+    public List<T> getOutgoingEdges(@NonNull T node) {
+        ArrayList<T> result = null;
+        for (int i = 0, size = mGraph.size(); i < size; i++) {
+            ArrayList<T> edges = mGraph.valueAt(i);
+            if (edges != null && edges.contains(node)) {
+                if (result == null) {
+                    result = new ArrayList<>();
+                }
+                result.add(mGraph.keyAt(i));
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Checks whether we have any outgoing edges for the given node (i.e. nodes which have
+     * an incoming edge from the given node).
+     *
+     * @return <code>true</code> if the node has any outgoing edges, <code>false</code>
+     * otherwise.
+     */
+    public boolean hasOutgoingEdges(@NonNull T node) {
+        for (int i = 0, size = mGraph.size(); i < size; i++) {
+            ArrayList<T> edges = mGraph.valueAt(i);
+            if (edges != null && edges.contains(node)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Clears the internal graph, and releases resources to pools.
+     */
+    public void clear() {
+        for (int i = 0, size = mGraph.size(); i < size; i++) {
+            ArrayList<T> edges = mGraph.valueAt(i);
+            if (edges != null) {
+                poolList(edges);
+            }
+        }
+        mGraph.clear();
+    }
+
+    /**
+     * Returns a topologically sorted list of the nodes in this graph. This uses the DFS algorithm
+     * as described by Cormen et al. (2001). If this graph contains cyclic dependencies then this
+     * method will throw a {@link RuntimeException}.
+     *
+     * <p>The resulting list will be ordered such that index 0 will contain the node at the bottom
+     * of the graph. The node at the end of the list will have no dependencies on other nodes.</p>
+     */
+    @NonNull
+    public ArrayList<T> getSortedList() {
+        mSortResult.clear();
+        mSortTmpMarked.clear();
+
+        // Start a DFS from each node in the graph
+        for (int i = 0, size = mGraph.size(); i < size; i++) {
+            dfs(mGraph.keyAt(i), mSortResult, mSortTmpMarked);
+        }
+
+        return mSortResult;
+    }
+
+    private void dfs(final T node, final ArrayList<T> result, final HashSet<T> tmpMarked) {
+        if (result.contains(node)) {
+            // We've already seen and added the node to the result list, skip...
+            return;
+        }
+        if (tmpMarked.contains(node)) {
+            throw new RuntimeException("This graph contains cyclic dependencies");
+        }
+        // Temporarily mark the node
+        tmpMarked.add(node);
+        // Recursively dfs all of the node's edges
+        final ArrayList<T> edges = mGraph.get(node);
+        if (edges != null) {
+            for (int i = 0, size = edges.size(); i < size; i++) {
+                dfs(edges.get(i), result, tmpMarked);
+            }
+        }
+        // Unmark the node from the temporary list
+        tmpMarked.remove(node);
+        // Finally add it to the result list
+        result.add(node);
+    }
+
+    /**
+     * Returns the size of the graph
+     */
+    int size() {
+        return mGraph.size();
+    }
+
+    @NonNull
+    private ArrayList<T> getEmptyList() {
+        ArrayList<T> list = mListPool.acquire();
+        if (list == null) {
+            list = new ArrayList<>();
+        }
+        return list;
+    }
+
+    private void poolList(@NonNull ArrayList<T> list) {
+        list.clear();
+        mListPool.release(list);
+    }
+}
diff --git a/core-ui/src/main/java/android/support/v4/widget/ViewGroupUtils.java b/core-ui/src/main/java/android/support/v4/widget/ViewGroupUtils.java
new file mode 100644
index 0000000..986b4c2
--- /dev/null
+++ b/core-ui/src/main/java/android/support/v4/widget/ViewGroupUtils.java
@@ -0,0 +1,94 @@
+/*
+ * 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.v4.widget;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY;
+
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.support.annotation.RestrictTo;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+
+/**
+ * @hide
+ */
+@RestrictTo(LIBRARY)
+public class ViewGroupUtils {
+    private static final ThreadLocal<Matrix> sMatrix = new ThreadLocal<>();
+    private static final ThreadLocal<RectF> sRectF = new ThreadLocal<>();
+
+    /**
+     * This is a port of the common
+     * {@link ViewGroup#offsetDescendantRectToMyCoords(View, Rect)}
+     * from the framework, but adapted to take transformations into account. The result
+     * will be the bounding rect of the real transformed rect.
+     *
+     * @param descendant view defining the original coordinate system of rect
+     * @param rect (in/out) the rect to offset from descendant to this view's coordinate system
+     */
+    static void offsetDescendantRect(ViewGroup parent, View descendant, Rect rect) {
+        Matrix m = sMatrix.get();
+        if (m == null) {
+            m = new Matrix();
+            sMatrix.set(m);
+        } else {
+            m.reset();
+        }
+
+        offsetDescendantMatrix(parent, descendant, m);
+
+        RectF rectF = sRectF.get();
+        if (rectF == null) {
+            rectF = new RectF();
+            sRectF.set(rectF);
+        }
+        rectF.set(rect);
+        m.mapRect(rectF);
+        rect.set((int) (rectF.left + 0.5f), (int) (rectF.top + 0.5f),
+                (int) (rectF.right + 0.5f), (int) (rectF.bottom + 0.5f));
+    }
+
+    /**
+     * Retrieve the transformed bounding rect of an arbitrary descendant view.
+     * This does not need to be a direct child.
+     *
+     * @param descendant descendant view to reference
+     * @param out rect to set to the bounds of the descendant view
+     */
+    public static void getDescendantRect(ViewGroup parent, View descendant, Rect out) {
+        out.set(0, 0, descendant.getWidth(), descendant.getHeight());
+        offsetDescendantRect(parent, descendant, out);
+    }
+
+    private static void offsetDescendantMatrix(ViewParent target, View view, Matrix m) {
+        final ViewParent parent = view.getParent();
+        if (parent instanceof View && parent != target) {
+            final View vp = (View) parent;
+            offsetDescendantMatrix(target, vp, m);
+            m.preTranslate(-vp.getScrollX(), -vp.getScrollY());
+        }
+
+        m.preTranslate(view.getLeft(), view.getTop());
+
+        if (!view.getMatrix().isIdentity()) {
+            m.preConcat(view.getMatrix());
+        }
+    }
+}
diff --git a/core-ui/src/main/java/android/support/v4/widget/package.html b/core-ui/src/main/java/android/support/v4/widget/package.html
deleted file mode 100755
index e2c636d..0000000
--- a/core-ui/src/main/java/android/support/v4/widget/package.html
+++ /dev/null
@@ -1,8 +0,0 @@
-<body>
-
-Support android.widget classes to assist with development of applications for
-android API level 4 or later.  This includes a complete modern implementation
-of {@link android.support.v4.widget.CursorAdapter} and related classes, which
-is needed for use with {@link android.support.v4.content.CursorLoader}.
-
-</body>
diff --git a/core-ui/tests/AndroidManifest.xml b/core-ui/tests/AndroidManifest.xml
index 0185f72..904111e 100644
--- a/core-ui/tests/AndroidManifest.xml
+++ b/core-ui/tests/AndroidManifest.xml
@@ -38,6 +38,8 @@
 
         <activity android:name="android.support.v4.view.ViewPagerTest$ViewPagerActivity"/>
 
+        <activity android:name="android.support.design.widget.CoordinatorLayoutActivity"/>
+
     </application>
 
 </manifest>
diff --git a/design/tests/src/android/support/design/testutils/CoordinatorLayoutUtils.java b/core-ui/tests/java/android/support/design/testutils/CoordinatorLayoutUtils.java
similarity index 100%
rename from design/tests/src/android/support/design/testutils/CoordinatorLayoutUtils.java
rename to core-ui/tests/java/android/support/design/testutils/CoordinatorLayoutUtils.java
diff --git a/core-ui/tests/java/android/support/design/widget/CoordinatorLayoutActivity.java b/core-ui/tests/java/android/support/design/widget/CoordinatorLayoutActivity.java
new file mode 100644
index 0000000..b7fe740
--- /dev/null
+++ b/core-ui/tests/java/android/support/design/widget/CoordinatorLayoutActivity.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.design.widget;
+
+import android.support.coreui.test.R;
+import android.support.v4.BaseTestActivity;
+import android.widget.FrameLayout;
+
+public class CoordinatorLayoutActivity extends BaseTestActivity {
+
+    FrameLayout mContainer;
+    CoordinatorLayout mCoordinatorLayout;
+
+    @Override
+    protected int getContentViewLayoutResId() {
+        return R.layout.activity_coordinator_layout;
+    }
+
+    @Override
+    protected void onContentViewSet() {
+        mContainer = findViewById(R.id.container);
+        mCoordinatorLayout = findViewById(R.id.coordinator);
+    }
+
+}
diff --git a/core-ui/tests/java/android/support/design/widget/CoordinatorLayoutSortTest.java b/core-ui/tests/java/android/support/design/widget/CoordinatorLayoutSortTest.java
new file mode 100644
index 0000000..4e0ccfc
--- /dev/null
+++ b/core-ui/tests/java/android/support/design/widget/CoordinatorLayoutSortTest.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.design.widget;
+
+import static org.junit.Assert.assertEquals;
+
+import android.app.Instrumentation;
+import android.support.design.testutils.CoordinatorLayoutUtils;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.rule.ActivityTestRule;
+import android.view.View;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+@RunWith(Parameterized.class)
+@MediumTest
+public class CoordinatorLayoutSortTest {
+    @Rule
+    public final ActivityTestRule<CoordinatorLayoutActivity> mActivityTestRule;
+
+    private static final int NUMBER_VIEWS_DEPENDENCY_SORT = 4;
+
+    /**
+     * All 27 permutations of a quad-tuple containing unique values in the range 0-3
+     */
+    @Parameterized.Parameters
+    public static Collection<Object[]> data() {
+        return Arrays.asList(new Object[][] {
+                {0, 1, 2, 3}, {0, 1, 3, 2}, {0, 2, 1, 3}, {0, 2, 3, 1}, {0, 3, 1, 2}, {0, 3, 2, 1},
+                {1, 0, 2, 3}, {1, 0, 3, 2}, {1, 2, 0, 3}, {1, 2, 3, 0}, {1, 3, 0, 2}, {1, 3, 2, 0},
+                {2, 0, 1, 3}, {2, 0, 3, 1}, {2, 1, 0, 3}, {2, 1, 3, 0}, {2, 3, 0, 1}, {2, 3, 1, 0},
+                {3, 0, 1, 2}, {3, 0, 2, 1}, {3, 1, 0, 2}, {3, 1, 2, 0}, {3, 2, 0, 1}, {3, 2, 1, 0}
+        });
+    }
+
+    private int mFirstAddIndex;
+    private int mSecondAddIndex;
+    private int mThirdAddIndex;
+    private int mFourthAddIndex;
+
+    public CoordinatorLayoutSortTest(int firstIndex, int secondIndex, int thirdIndex,
+            int fourthIndex) {
+        mActivityTestRule = new ActivityTestRule<>(CoordinatorLayoutActivity.class);
+        mFirstAddIndex = firstIndex;
+        mSecondAddIndex = secondIndex;
+        mThirdAddIndex = thirdIndex;
+        mFourthAddIndex = fourthIndex;
+    }
+
+    @Test
+    public void testDependencySortingOrder() throws Throwable {
+        final CoordinatorLayout col = mActivityTestRule.getActivity().mCoordinatorLayout;
+
+        // Let's create some views where each view depends on the previous view.
+        // i.e C depends on B, B depends on A, A doesn't depend on anything.
+        final List<View> views = new ArrayList<>();
+        for (int i = 0; i < NUMBER_VIEWS_DEPENDENCY_SORT; i++) {
+            // 65 == A in ASCII
+            final String label = Character.toString((char) (65 + i));
+            final View view = new View(col.getContext()) {
+                @Override
+                public String toString() {
+                    return label;
+                }
+            };
+
+            // Create a Behavior which depends on the previously added view
+            View dependency = i > 0 ? views.get(i - 1) : null;
+            final CoordinatorLayout.Behavior<View> behavior =
+                    new CoordinatorLayoutUtils.DependentBehavior(dependency);
+
+            // And set its LayoutParams to use the Behavior
+            CoordinatorLayout.LayoutParams lp = col.generateDefaultLayoutParams();
+            lp.setBehavior(behavior);
+            view.setLayoutParams(lp);
+
+            views.add(view);
+        }
+
+        // Now the add the views in the given order and assert that they still end up in
+        // the expected order A, B, C, D
+        final List<View> testOrder = new ArrayList<>();
+        testOrder.add(views.get(mFirstAddIndex));
+        testOrder.add(views.get(mSecondAddIndex));
+        testOrder.add(views.get(mThirdAddIndex));
+        testOrder.add(views.get(mFourthAddIndex));
+        addViewsAndAssertOrdering(col, views, testOrder);
+    }
+
+    private void addViewsAndAssertOrdering(final CoordinatorLayout col,
+            final List<View> expectedOrder, final List<View> addOrder) throws Throwable {
+        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+
+        // Add the Views in the given order
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                for (int i = 0; i < addOrder.size(); i++) {
+                    col.addView(addOrder.get(i));
+                }
+            }
+        });
+        instrumentation.waitForIdleSync();
+
+        // Now assert that the dependency sorted order is correct
+        assertEquals(expectedOrder, col.getDependencySortedChildren());
+
+        // Finally remove all of the views
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                col.removeAllViews();
+            }
+        });
+    }
+}
diff --git a/core-ui/tests/java/android/support/design/widget/CoordinatorLayoutTest.java b/core-ui/tests/java/android/support/design/widget/CoordinatorLayoutTest.java
new file mode 100644
index 0000000..4062efb
--- /dev/null
+++ b/core-ui/tests/java/android/support/design/widget/CoordinatorLayoutTest.java
@@ -0,0 +1,783 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.design.widget;
+
+import static android.support.test.InstrumentationRegistry.getInstrumentation;
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.action.ViewActions.swipeUp;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.same;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doCallRealMethod;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.annotation.TargetApi;
+import android.app.Instrumentation;
+import android.content.Context;
+import android.graphics.Rect;
+import android.support.annotation.NonNull;
+import android.support.coreui.test.R;
+import android.support.design.testutils.CoordinatorLayoutUtils;
+import android.support.design.testutils.CoordinatorLayoutUtils.DependentBehavior;
+import android.support.design.widget.CoordinatorLayout.Behavior;
+import android.support.test.annotation.UiThreadTest;
+import android.support.test.filters.MediumTest;
+import android.support.test.filters.SdkSuppress;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v4.view.GravityCompat;
+import android.support.v4.view.ViewCompat;
+import android.support.v4.view.WindowInsetsCompat;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.MeasureSpec;
+import android.widget.ImageView;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class CoordinatorLayoutTest {
+    @Rule
+    public final ActivityTestRule<CoordinatorLayoutActivity> mActivityTestRule;
+
+    private Instrumentation mInstrumentation;
+
+    public CoordinatorLayoutTest() {
+        mActivityTestRule = new ActivityTestRule<>(CoordinatorLayoutActivity.class);
+    }
+
+    @Before
+    public void setup() {
+        mInstrumentation = getInstrumentation();
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 21)
+    @TargetApi(21)
+    public void testSetFitSystemWindows() throws Throwable {
+        final Instrumentation instrumentation = getInstrumentation();
+        final CoordinatorLayout col = mActivityTestRule.getActivity().mCoordinatorLayout;
+        final View view = new View(col.getContext());
+
+        // Create a mock which calls the default impl of onApplyWindowInsets()
+        final CoordinatorLayout.Behavior<View> mockBehavior =
+                mock(CoordinatorLayout.Behavior.class);
+        doCallRealMethod().when(mockBehavior)
+                .onApplyWindowInsets(same(col), same(view), any(WindowInsetsCompat.class));
+
+        // Assert that the CoL is currently not set to fitSystemWindows
+        assertFalse(col.getFitsSystemWindows());
+
+        // Now add a view with our mocked behavior to the CoordinatorLayout
+        view.setFitsSystemWindows(true);
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                final CoordinatorLayout.LayoutParams lp = col.generateDefaultLayoutParams();
+                lp.setBehavior(mockBehavior);
+                col.addView(view, lp);
+            }
+        });
+        instrumentation.waitForIdleSync();
+
+        // Now request some insets and wait for the pass to happen
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                col.requestApplyInsets();
+            }
+        });
+        instrumentation.waitForIdleSync();
+
+        // Verify that onApplyWindowInsets() has not been called
+        verify(mockBehavior, never())
+                .onApplyWindowInsets(same(col), same(view), any(WindowInsetsCompat.class));
+
+        // Now enable fits system windows and wait for a pass to happen
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                col.setFitsSystemWindows(true);
+            }
+        });
+        instrumentation.waitForIdleSync();
+
+        // Verify that onApplyWindowInsets() has been called with some insets
+        verify(mockBehavior, atLeastOnce())
+                .onApplyWindowInsets(same(col), same(view), any(WindowInsetsCompat.class));
+    }
+
+    @Test
+    public void testLayoutChildren() throws Throwable {
+        final Instrumentation instrumentation = getInstrumentation();
+        final CoordinatorLayout col = mActivityTestRule.getActivity().mCoordinatorLayout;
+        final View view = new View(col.getContext());
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                col.addView(view, 100, 100);
+            }
+        });
+        instrumentation.waitForIdleSync();
+        int horizontallyCentered = (col.getWidth() - view.getWidth()) / 2;
+        int end = col.getWidth() - view.getWidth();
+        int verticallyCentered = (col.getHeight() - view.getHeight()) / 2;
+        int bottom = col.getHeight() - view.getHeight();
+        final int[][] testCases = {
+                // gravity, expected left, expected top
+                {Gravity.NO_GRAVITY, 0, 0},
+                {Gravity.LEFT, 0, 0},
+                {GravityCompat.START, 0, 0},
+                {Gravity.TOP, 0, 0},
+                {Gravity.CENTER, horizontallyCentered, verticallyCentered},
+                {Gravity.CENTER_HORIZONTAL, horizontallyCentered, 0},
+                {Gravity.CENTER_VERTICAL, 0, verticallyCentered},
+                {Gravity.RIGHT, end, 0},
+                {GravityCompat.END, end, 0},
+                {Gravity.BOTTOM, 0, bottom},
+                {Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM, horizontallyCentered, bottom},
+                {Gravity.RIGHT | Gravity.CENTER_VERTICAL, end, verticallyCentered},
+        };
+        for (final int[] testCase : testCases) {
+            mActivityTestRule.runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    final CoordinatorLayout.LayoutParams lp =
+                            (CoordinatorLayout.LayoutParams) view.getLayoutParams();
+                    lp.gravity = testCase[0];
+                    view.setLayoutParams(lp);
+                }
+            });
+            instrumentation.waitForIdleSync();
+            mActivityTestRule.runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    assertThat("Gravity: " + testCase[0], view.getLeft(), is(testCase[1]));
+                    assertThat("Gravity: " + testCase[0], view.getTop(), is(testCase[2]));
+                }
+            });
+        }
+    }
+
+    @Test
+    public void testInsetDependency() {
+        final CoordinatorLayout col = mActivityTestRule.getActivity().mCoordinatorLayout;
+
+        final CoordinatorLayout.LayoutParams lpInsetLeft = col.generateDefaultLayoutParams();
+        lpInsetLeft.insetEdge = Gravity.LEFT;
+
+        final CoordinatorLayout.LayoutParams lpInsetRight = col.generateDefaultLayoutParams();
+        lpInsetRight.insetEdge = Gravity.RIGHT;
+
+        final CoordinatorLayout.LayoutParams lpInsetTop = col.generateDefaultLayoutParams();
+        lpInsetTop.insetEdge = Gravity.TOP;
+
+        final CoordinatorLayout.LayoutParams lpInsetBottom = col.generateDefaultLayoutParams();
+        lpInsetBottom.insetEdge = Gravity.BOTTOM;
+
+        final CoordinatorLayout.LayoutParams lpDodgeLeft = col.generateDefaultLayoutParams();
+        lpDodgeLeft.dodgeInsetEdges = Gravity.LEFT;
+
+        final CoordinatorLayout.LayoutParams lpDodgeLeftAndTop = col.generateDefaultLayoutParams();
+        lpDodgeLeftAndTop.dodgeInsetEdges = Gravity.LEFT | Gravity.TOP;
+
+        final CoordinatorLayout.LayoutParams lpDodgeAll = col.generateDefaultLayoutParams();
+        lpDodgeAll.dodgeInsetEdges = Gravity.FILL;
+
+        final View a = new View(col.getContext());
+        final View b = new View(col.getContext());
+
+        assertThat(dependsOn(lpDodgeLeft, lpInsetLeft, col, a, b), is(true));
+        assertThat(dependsOn(lpDodgeLeft, lpInsetRight, col, a, b), is(false));
+        assertThat(dependsOn(lpDodgeLeft, lpInsetTop, col, a, b), is(false));
+        assertThat(dependsOn(lpDodgeLeft, lpInsetBottom, col, a, b), is(false));
+
+        assertThat(dependsOn(lpDodgeLeftAndTop, lpInsetLeft, col, a, b), is(true));
+        assertThat(dependsOn(lpDodgeLeftAndTop, lpInsetRight, col, a, b), is(false));
+        assertThat(dependsOn(lpDodgeLeftAndTop, lpInsetTop, col, a, b), is(true));
+        assertThat(dependsOn(lpDodgeLeftAndTop, lpInsetBottom, col, a, b), is(false));
+
+        assertThat(dependsOn(lpDodgeAll, lpInsetLeft, col, a, b), is(true));
+        assertThat(dependsOn(lpDodgeAll, lpInsetRight, col, a, b), is(true));
+        assertThat(dependsOn(lpDodgeAll, lpInsetTop, col, a, b), is(true));
+        assertThat(dependsOn(lpDodgeAll, lpInsetBottom, col, a, b), is(true));
+
+        assertThat(dependsOn(lpInsetLeft, lpDodgeLeft, col, a, b), is(false));
+    }
+
+    private static boolean dependsOn(CoordinatorLayout.LayoutParams lpChild,
+            CoordinatorLayout.LayoutParams lpDependency, CoordinatorLayout col,
+            View child, View dependency) {
+        child.setLayoutParams(lpChild);
+        dependency.setLayoutParams(lpDependency);
+        return lpChild.dependsOn(col, child, dependency);
+    }
+
+    @Test
+    public void testInsetEdge() throws Throwable {
+        final Instrumentation instrumentation = getInstrumentation();
+        final CoordinatorLayout col = mActivityTestRule.getActivity().mCoordinatorLayout;
+
+        final View insetView = new View(col.getContext());
+        final View dodgeInsetView = new View(col.getContext());
+        final AtomicInteger originalTop = new AtomicInteger();
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                CoordinatorLayout.LayoutParams lpInsetView = col.generateDefaultLayoutParams();
+                lpInsetView.width = CoordinatorLayout.LayoutParams.MATCH_PARENT;
+                lpInsetView.height = 100;
+                lpInsetView.gravity = Gravity.TOP | Gravity.LEFT;
+                lpInsetView.insetEdge = Gravity.TOP;
+                col.addView(insetView, lpInsetView);
+                insetView.setBackgroundColor(0xFF0000FF);
+
+                CoordinatorLayout.LayoutParams lpDodgeInsetView = col.generateDefaultLayoutParams();
+                lpDodgeInsetView.width = 100;
+                lpDodgeInsetView.height = 100;
+                lpDodgeInsetView.gravity = Gravity.TOP | Gravity.LEFT;
+                lpDodgeInsetView.dodgeInsetEdges = Gravity.TOP;
+                col.addView(dodgeInsetView, lpDodgeInsetView);
+                dodgeInsetView.setBackgroundColor(0xFFFF0000);
+            }
+        });
+        instrumentation.waitForIdleSync();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                List<View> dependencies = col.getDependencies(dodgeInsetView);
+                assertThat(dependencies.size(), is(1));
+                assertThat(dependencies.get(0), is(insetView));
+
+                // Move the insetting view
+                originalTop.set(dodgeInsetView.getTop());
+                assertThat(originalTop.get(), is(insetView.getBottom()));
+                ViewCompat.offsetTopAndBottom(insetView, 123);
+            }
+        });
+        instrumentation.waitForIdleSync();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                // Confirm that the dodging view was moved by the same size
+                assertThat(dodgeInsetView.getTop() - originalTop.get(), is(123));
+            }
+        });
+    }
+
+    @Test
+    public void testDependentViewChanged() throws Throwable {
+        final Instrumentation instrumentation = getInstrumentation();
+        final CoordinatorLayout col = mActivityTestRule.getActivity().mCoordinatorLayout;
+
+        // Add two views, A & B, where B depends on A
+        final View viewA = new View(col.getContext());
+        final CoordinatorLayout.LayoutParams lpA = col.generateDefaultLayoutParams();
+        lpA.width = 100;
+        lpA.height = 100;
+
+        final View viewB = new View(col.getContext());
+        final CoordinatorLayout.LayoutParams lpB = col.generateDefaultLayoutParams();
+        lpB.width = 100;
+        lpB.height = 100;
+        final CoordinatorLayout.Behavior behavior =
+                spy(new DependentBehavior(viewA));
+        lpB.setBehavior(behavior);
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                col.addView(viewA, lpA);
+                col.addView(viewB, lpB);
+            }
+        });
+        instrumentation.waitForIdleSync();
+
+        // Reset the Behavior since onDependentViewChanged may have already been called as part of
+        // any layout/draw passes already
+        reset(behavior);
+
+        // Now offset view A
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                ViewCompat.offsetLeftAndRight(viewA, 20);
+                ViewCompat.offsetTopAndBottom(viewA, 20);
+            }
+        });
+        instrumentation.waitForIdleSync();
+
+        // And assert that view B's Behavior was called appropriately
+        verify(behavior, times(1)).onDependentViewChanged(col, viewB, viewA);
+    }
+
+    @Test
+    public void testDependentViewRemoved() throws Throwable {
+        final Instrumentation instrumentation = getInstrumentation();
+        final CoordinatorLayout col = mActivityTestRule.getActivity().mCoordinatorLayout;
+
+        // Add two views, A & B, where B depends on A
+        final View viewA = new View(col.getContext());
+        final View viewB = new View(col.getContext());
+        final CoordinatorLayout.LayoutParams lpB = col.generateDefaultLayoutParams();
+        final CoordinatorLayout.Behavior behavior =
+                spy(new DependentBehavior(viewA));
+        lpB.setBehavior(behavior);
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                col.addView(viewA);
+                col.addView(viewB, lpB);
+            }
+        });
+        instrumentation.waitForIdleSync();
+
+        // Now remove view A
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                col.removeView(viewA);
+            }
+        });
+
+        // And assert that View B's Behavior was called appropriately
+        verify(behavior, times(1)).onDependentViewRemoved(col, viewB, viewA);
+    }
+
+    @Test
+    public void testGetDependenciesAfterDependentViewRemoved() throws Throwable {
+        final Instrumentation instrumentation = getInstrumentation();
+        final CoordinatorLayout col = mActivityTestRule.getActivity().mCoordinatorLayout;
+
+        // Add two views, A & B, where B depends on A
+        final View viewA = new View(col.getContext());
+        final View viewB = new View(col.getContext());
+        final CoordinatorLayout.LayoutParams lpB = col.generateDefaultLayoutParams();
+        final CoordinatorLayout.Behavior behavior =
+                new CoordinatorLayoutUtils.DependentBehavior(viewA) {
+                    @Override
+                    public void onDependentViewRemoved(
+                            CoordinatorLayout parent, View child, View dependency) {
+                        parent.getDependencies(child);
+                    }
+                };
+        lpB.setBehavior(behavior);
+
+        // Now add views
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                col.addView(viewA);
+                col.addView(viewB, lpB);
+            }
+        });
+
+        // Wait for a layout
+        instrumentation.waitForIdleSync();
+
+        // Now remove view A, which will trigger onDependentViewRemoved() on view B's behavior
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                col.removeView(viewA);
+            }
+        });
+    }
+
+    @Test
+    public void testDodgeInsetBeforeLayout() throws Throwable {
+        final CoordinatorLayout col = mActivityTestRule.getActivity().mCoordinatorLayout;
+
+        // Add a dummy view, which will be used to trigger a hierarchy change.
+        final View dummy = new View(col.getContext());
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                col.addView(dummy);
+            }
+        });
+
+        // Wait for a layout.
+        mInstrumentation.waitForIdleSync();
+
+        final View dodge = new View(col.getContext());
+        final CoordinatorLayout.LayoutParams lpDodge = col.generateDefaultLayoutParams();
+        lpDodge.dodgeInsetEdges = Gravity.BOTTOM;
+        lpDodge.setBehavior(new Behavior() {
+            @Override
+            public boolean getInsetDodgeRect(CoordinatorLayout parent, View child, Rect rect) {
+                // Any non-empty rect is fine here.
+                rect.set(0, 0, 10, 10);
+                return true;
+            }
+        });
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                col.addView(dodge, lpDodge);
+
+                // Ensure the new view is in the list of children.
+                int heightSpec = MeasureSpec.makeMeasureSpec(col.getHeight(), MeasureSpec.EXACTLY);
+                int widthSpec = MeasureSpec.makeMeasureSpec(col.getWidth(), MeasureSpec.EXACTLY);
+                col.measure(widthSpec, heightSpec);
+
+                // Force a hierarchy change.
+                col.removeView(dummy);
+            }
+        });
+
+        // Wait for a layout.
+        mInstrumentation.waitForIdleSync();
+    }
+
+    @Test
+    public void testGoneViewsNotMeasuredLaidOut() throws Throwable {
+        final CoordinatorLayoutActivity activity = mActivityTestRule.getActivity();
+        final CoordinatorLayout col = activity.mCoordinatorLayout;
+
+        // Now create a GONE view and add it to the CoordinatorLayout
+        final View imageView = new View(activity);
+        imageView.setVisibility(View.GONE);
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                col.addView(imageView, 200, 200);
+            }
+        });
+        // Wait for a layout and measure pass
+        mInstrumentation.waitForIdleSync();
+
+        // And assert that it has not been laid out
+        assertFalse(imageView.getMeasuredWidth() > 0);
+        assertFalse(imageView.getMeasuredHeight() > 0);
+        assertFalse(ViewCompat.isLaidOut(imageView));
+
+        // Now set the view to INVISIBLE
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                imageView.setVisibility(View.INVISIBLE);
+            }
+        });
+        // Wait for a layout and measure pass
+        mInstrumentation.waitForIdleSync();
+
+        // And assert that it has been laid out
+        assertTrue(imageView.getMeasuredWidth() > 0);
+        assertTrue(imageView.getMeasuredHeight() > 0);
+        assertTrue(ViewCompat.isLaidOut(imageView));
+    }
+
+    @Test
+    public void testNestedScrollingDispatchesToBehavior() throws Throwable {
+        final CoordinatorLayoutActivity activity = mActivityTestRule.getActivity();
+        final CoordinatorLayout col = activity.mCoordinatorLayout;
+
+        // Now create a view and add it to the CoordinatorLayout with the spy behavior,
+        // along with a NestedScrollView
+        final ImageView imageView = new ImageView(activity);
+        final CoordinatorLayout.Behavior behavior = spy(new NestedScrollingBehavior());
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                LayoutInflater.from(activity).inflate(R.layout.include_nestedscrollview, col, true);
+
+                CoordinatorLayout.LayoutParams clp = new CoordinatorLayout.LayoutParams(200, 200);
+                clp.setBehavior(behavior);
+                col.addView(imageView, clp);
+            }
+        });
+
+        // Now vertically swipe up on the NSV, causing nested scrolling to occur
+        onView(withId(R.id.nested_scrollview)).perform(swipeUp());
+
+        // Verify that the Behavior's onStartNestedScroll was called once
+        verify(behavior, times(1)).onStartNestedScroll(
+                eq(col), // parent
+                eq(imageView), // child
+                any(View.class), // target
+                any(View.class), // direct child target
+                any(int.class)); // axes
+
+        // Verify that the Behavior's onNestedScrollAccepted was called once
+        verify(behavior, times(1)).onNestedScrollAccepted(
+                eq(col), // parent
+                eq(imageView), // child
+                any(View.class), // target
+                any(View.class), // direct child target
+                any(int.class)); // axes
+
+        // Verify that the Behavior's onNestedPreScroll was called at least once
+        verify(behavior, atLeastOnce()).onNestedPreScroll(
+                eq(col), // parent
+                eq(imageView), // child
+                any(View.class), // target
+                any(int.class), // dx
+                any(int.class), // dy
+                any(int[].class)); // consumed
+
+        // Verify that the Behavior's onNestedScroll was called at least once
+        verify(behavior, atLeastOnce()).onNestedScroll(
+                eq(col), // parent
+                eq(imageView), // child
+                any(View.class), // target
+                any(int.class), // dx consumed
+                any(int.class), // dy consumed
+                any(int.class), // dx unconsumed
+                any(int.class)); // dy unconsumed
+
+        // Verify that the Behavior's onStopNestedScroll was called once
+        verify(behavior, times(1)).onStopNestedScroll(
+                eq(col), // parent
+                eq(imageView), // child
+                any(View.class)); // target
+    }
+
+    @Test
+    public void testNestedScrollingDispatchingToBehaviorWithGoneView() throws Throwable {
+        final CoordinatorLayoutActivity activity = mActivityTestRule.getActivity();
+        final CoordinatorLayout col = activity.mCoordinatorLayout;
+
+        // Now create a GONE view and add it to the CoordinatorLayout with the spy behavior,
+        // along with a NestedScrollView
+        final ImageView imageView = new ImageView(activity);
+        imageView.setVisibility(View.GONE);
+        final CoordinatorLayout.Behavior behavior = spy(new NestedScrollingBehavior());
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                LayoutInflater.from(activity).inflate(R.layout.include_nestedscrollview, col, true);
+
+                CoordinatorLayout.LayoutParams clp = new CoordinatorLayout.LayoutParams(200, 200);
+                clp.setBehavior(behavior);
+                col.addView(imageView, clp);
+            }
+        });
+
+        // Now vertically swipe up on the NSV, causing nested scrolling to occur
+        onView(withId(R.id.nested_scrollview)).perform(swipeUp());
+
+        // Verify that the Behavior's onStartNestedScroll was not called
+        verify(behavior, never()).onStartNestedScroll(
+                eq(col), // parent
+                eq(imageView), // child
+                any(View.class), // target
+                any(View.class), // direct child target
+                any(int.class)); // axes
+
+        // Verify that the Behavior's onNestedScrollAccepted was not called
+        verify(behavior, never()).onNestedScrollAccepted(
+                eq(col), // parent
+                eq(imageView), // child
+                any(View.class), // target
+                any(View.class), // direct child target
+                any(int.class)); // axes
+
+        // Verify that the Behavior's onNestedPreScroll was not called
+        verify(behavior, never()).onNestedPreScroll(
+                eq(col), // parent
+                eq(imageView), // child
+                any(View.class), // target
+                any(int.class), // dx
+                any(int.class), // dy
+                any(int[].class)); // consumed
+
+        // Verify that the Behavior's onNestedScroll was not called
+        verify(behavior, never()).onNestedScroll(
+                eq(col), // parent
+                eq(imageView), // child
+                any(View.class), // target
+                any(int.class), // dx consumed
+                any(int.class), // dy consumed
+                any(int.class), // dx unconsumed
+                any(int.class)); // dy unconsumed
+
+        // Verify that the Behavior's onStopNestedScroll was not called
+        verify(behavior, never()).onStopNestedScroll(
+                eq(col), // parent
+                eq(imageView), // child
+                any(View.class)); // target
+    }
+
+    @Test
+    public void testNestedScrollingTriggeringDependentViewChanged() throws Throwable {
+        final CoordinatorLayoutActivity activity = mActivityTestRule.getActivity();
+        final CoordinatorLayout col = activity.mCoordinatorLayout;
+
+        // First a NestedScrollView to trigger nested scrolling
+        final View scrollView = LayoutInflater.from(activity).inflate(
+                R.layout.include_nestedscrollview, col, false);
+
+        // Now create a View and Behavior which depend on the scrollview
+        final ImageView dependentView = new ImageView(activity);
+        final CoordinatorLayout.Behavior dependentBehavior = spy(new DependentBehavior(scrollView));
+
+        // Finally a view which accepts nested scrolling in the CoordinatorLayout
+        final ImageView nestedScrollAwareView = new ImageView(activity);
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                // First add the ScrollView
+                col.addView(scrollView);
+
+                // Now add the view which depends on the scrollview
+                CoordinatorLayout.LayoutParams clp = new CoordinatorLayout.LayoutParams(200, 200);
+                clp.setBehavior(dependentBehavior);
+                col.addView(dependentView, clp);
+
+                // Now add the nested scrolling aware view
+                clp = new CoordinatorLayout.LayoutParams(200, 200);
+                clp.setBehavior(new NestedScrollingBehavior());
+                col.addView(nestedScrollAwareView, clp);
+            }
+        });
+
+        // Wait for any layouts, and reset the Behavior so that the call counts are 0
+        getInstrumentation().waitForIdleSync();
+        reset(dependentBehavior);
+
+        // Now vertically swipe up on the NSV, causing nested scrolling to occur
+        onView(withId(R.id.nested_scrollview)).perform(swipeUp());
+
+        // Verify that the Behavior's onDependentViewChanged is not called due to the
+        // nested scroll
+        verify(dependentBehavior, never()).onDependentViewChanged(
+                eq(col), // parent
+                eq(dependentView), // child
+                eq(scrollView)); // axes
+    }
+
+    @Test
+    public void testDodgeInsetViewWithEmptyBounds() throws Throwable {
+        final CoordinatorLayout col = mActivityTestRule.getActivity().mCoordinatorLayout;
+
+        // Add a view with zero height/width which is set to dodge its bounds
+        final View view = new View(col.getContext());
+        final Behavior spyBehavior = spy(new DodgeBoundsBehavior());
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                final CoordinatorLayout.LayoutParams lp = col.generateDefaultLayoutParams();
+                lp.dodgeInsetEdges = Gravity.BOTTOM;
+                lp.gravity = Gravity.BOTTOM;
+                lp.height = 0;
+                lp.width = 0;
+                lp.setBehavior(spyBehavior);
+                col.addView(view, lp);
+            }
+        });
+
+        // Wait for a layout
+        mInstrumentation.waitForIdleSync();
+
+        // Now add an non-empty bounds inset view to the bottom of the CoordinatorLayout
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                final View dodge = new View(col.getContext());
+                final CoordinatorLayout.LayoutParams lp = col.generateDefaultLayoutParams();
+                lp.insetEdge = Gravity.BOTTOM;
+                lp.gravity = Gravity.BOTTOM;
+                lp.height = 60;
+                lp.width = CoordinatorLayout.LayoutParams.MATCH_PARENT;
+                col.addView(dodge, lp);
+            }
+        });
+
+        // Verify that the Behavior of the view with empty bounds does not have its
+        // getInsetDodgeRect() called
+        verify(spyBehavior, never())
+                .getInsetDodgeRect(same(col), same(view), any(Rect.class));
+    }
+
+    public static class NestedScrollingBehavior extends CoordinatorLayout.Behavior<View> {
+        @Override
+        public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child,
+                View directTargetChild, View target, int nestedScrollAxes) {
+            // Return true so that we always accept nested scroll events
+            return true;
+        }
+    }
+
+    public static class DodgeBoundsBehavior extends Behavior<View> {
+        @Override
+        public boolean getInsetDodgeRect(CoordinatorLayout parent, View child, Rect rect) {
+            rect.set(child.getLeft(), child.getTop(), child.getRight(), child.getBottom());
+            return true;
+        }
+    }
+
+    @UiThreadTest
+    @Test
+    public void testAnchorDependencyGraph() throws Throwable {
+        final CoordinatorLayout col = mActivityTestRule.getActivity().mCoordinatorLayout;
+
+        // Override hashcode because of implementation of SimpleArrayMap used in
+        // DirectedAcyclicGraph used for sorting dependencies. Hashcode of anchored view has to be
+        // greater than of the one it is anchored to in order to reproduce the error.
+        final View anchor = createViewWithHashCode(col.getContext(), 2);
+        anchor.setId(R.id.anchor);
+
+        final View ship = createViewWithHashCode(col.getContext(), 3);
+        final CoordinatorLayout.LayoutParams lp = col.generateDefaultLayoutParams();
+        lp.setAnchorId(R.id.anchor);
+
+        col.addView(anchor);
+        col.addView(ship, lp);
+
+        // Get dependencies immediately to avoid possible call to onMeasure(), since error
+        // only happens on first computing of sorted dependencies.
+        List<View> dependencySortedChildren = col.getDependencySortedChildren();
+        assertThat(dependencySortedChildren, is(Arrays.asList(anchor, ship)));
+    }
+
+    @NonNull
+    private View createViewWithHashCode(final Context context, final int hashCode) {
+        return new View(context) {
+            @Override
+            public int hashCode() {
+                return hashCode;
+            }
+        };
+    }
+}
diff --git a/core-ui/tests/java/android/support/v4/widget/DirectedAcyclicGraphTest.java b/core-ui/tests/java/android/support/v4/widget/DirectedAcyclicGraphTest.java
new file mode 100644
index 0000000..8355fcc
--- /dev/null
+++ b/core-ui/tests/java/android/support/v4/widget/DirectedAcyclicGraphTest.java
@@ -0,0 +1,207 @@
+/*
+ * 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.v4.widget;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.support.annotation.NonNull;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.List;
+
+@RunWith(JUnit4.class)
+public class DirectedAcyclicGraphTest {
+
+    private DirectedAcyclicGraph<TestNode> mGraph;
+
+    @Before
+    public void setup() {
+        mGraph = new DirectedAcyclicGraph<>();
+    }
+
+    @Test
+    public void test_addNode() {
+        final TestNode node = new TestNode("node");
+        mGraph.addNode(node);
+        assertEquals(1, mGraph.size());
+        assertTrue(mGraph.contains(node));
+    }
+
+    @Test
+    public void test_addNodeAgain() {
+        final TestNode node = new TestNode("node");
+        mGraph.addNode(node);
+        mGraph.addNode(node);
+
+        assertEquals(1, mGraph.size());
+        assertTrue(mGraph.contains(node));
+    }
+
+    @Test
+    public void test_addEdge() {
+        final TestNode node = new TestNode("node");
+        final TestNode edge = new TestNode("edge");
+
+        mGraph.addNode(node);
+        mGraph.addNode(edge);
+        mGraph.addEdge(node, edge);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void test_addEdgeWithNotAddedEdgeNode() {
+        final TestNode node = new TestNode("node");
+        final TestNode edge = new TestNode("edge");
+
+        // Add the node, but not the edge node
+        mGraph.addNode(node);
+
+        // Now add the link
+        mGraph.addEdge(node, edge);
+    }
+
+    @Test
+    public void test_getIncomingEdges() {
+        final TestNode node = new TestNode("node");
+        final TestNode edge = new TestNode("edge");
+        mGraph.addNode(node);
+        mGraph.addNode(edge);
+        mGraph.addEdge(node, edge);
+
+        final List<TestNode> incomingEdges = mGraph.getIncomingEdges(node);
+        assertNotNull(incomingEdges);
+        assertEquals(1, incomingEdges.size());
+        assertEquals(edge, incomingEdges.get(0));
+    }
+
+    @Test
+    public void test_getOutgoingEdges() {
+        final TestNode node = new TestNode("node");
+        final TestNode edge = new TestNode("edge");
+        mGraph.addNode(node);
+        mGraph.addNode(edge);
+        mGraph.addEdge(node, edge);
+
+        // Now assert the getOutgoingEdges returns a list which has one element (node)
+        final List<TestNode> outgoingEdges = mGraph.getOutgoingEdges(edge);
+        assertNotNull(outgoingEdges);
+        assertEquals(1, outgoingEdges.size());
+        assertTrue(outgoingEdges.contains(node));
+    }
+
+    @Test
+    public void test_getOutgoingEdgesMultiple() {
+        final TestNode node1 = new TestNode("1");
+        final TestNode node2 = new TestNode("2");
+        final TestNode edge = new TestNode("edge");
+        mGraph.addNode(node1);
+        mGraph.addNode(node2);
+        mGraph.addNode(edge);
+
+        mGraph.addEdge(node1, edge);
+        mGraph.addEdge(node2, edge);
+
+        // Now assert the getOutgoingEdges returns a list which has 2 elements (node1 & node2)
+        final List<TestNode> outgoingEdges = mGraph.getOutgoingEdges(edge);
+        assertNotNull(outgoingEdges);
+        assertEquals(2, outgoingEdges.size());
+        assertTrue(outgoingEdges.contains(node1));
+        assertTrue(outgoingEdges.contains(node2));
+    }
+
+    @Test
+    public void test_hasOutgoingEdges() {
+        final TestNode node = new TestNode("node");
+        final TestNode edge = new TestNode("edge");
+        mGraph.addNode(node);
+        mGraph.addNode(edge);
+
+        // There is no edge currently and assert that fact
+        assertFalse(mGraph.hasOutgoingEdges(edge));
+        // Now add the edge
+        mGraph.addEdge(node, edge);
+        // and assert that the methods returns true;
+        assertTrue(mGraph.hasOutgoingEdges(edge));
+    }
+
+    @Test
+    public void test_clear() {
+        final TestNode node1 = new TestNode("1");
+        final TestNode node2 = new TestNode("2");
+        final TestNode edge = new TestNode("edge");
+        mGraph.addNode(node1);
+        mGraph.addNode(node2);
+        mGraph.addNode(edge);
+
+        // Now clear the graph
+        mGraph.clear();
+
+        // Now assert the graph is empty and that contains returns false
+        assertEquals(0, mGraph.size());
+        assertFalse(mGraph.contains(node1));
+        assertFalse(mGraph.contains(node2));
+        assertFalse(mGraph.contains(edge));
+    }
+
+    @Test
+    public void test_getSortedList() {
+        final TestNode node1 = new TestNode("A");
+        final TestNode node2 = new TestNode("B");
+        final TestNode node3 = new TestNode("C");
+        final TestNode node4 = new TestNode("D");
+
+        // Now we'll add the nodes
+        mGraph.addNode(node1);
+        mGraph.addNode(node2);
+        mGraph.addNode(node3);
+        mGraph.addNode(node4);
+
+        // Now we'll add edges so that 4 <- 2, 2 <- 3, 3 <- 1  (where <- denotes a dependency)
+        mGraph.addEdge(node4, node2);
+        mGraph.addEdge(node2, node3);
+        mGraph.addEdge(node3, node1);
+
+        final List<TestNode> sorted = mGraph.getSortedList();
+        // Assert that it is the correct size
+        assertEquals(4, sorted.size());
+        // Assert that all of the nodes are present and in their sorted order
+        assertEquals(node1, sorted.get(0));
+        assertEquals(node3, sorted.get(1));
+        assertEquals(node2, sorted.get(2));
+        assertEquals(node4, sorted.get(3));
+    }
+
+    private static class TestNode {
+        private final String mLabel;
+
+        TestNode(@NonNull String label) {
+            mLabel = label;
+        }
+
+        @Override
+        public String toString() {
+            return "TestNode: " + mLabel;
+        }
+    }
+
+}
diff --git a/core-ui/tests/res/layout/activity_coordinator_layout.xml b/core-ui/tests/res/layout/activity_coordinator_layout.xml
new file mode 100644
index 0000000..93bf947
--- /dev/null
+++ b/core-ui/tests/res/layout/activity_coordinator_layout.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+             android:id="@+id/container"
+             android:layout_width="match_parent"
+             android:layout_height="match_parent">
+
+    <android.support.design.widget.CoordinatorLayout
+        android:id="@+id/coordinator"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
+</FrameLayout>
\ No newline at end of file
diff --git a/design/tests/res/layout/include_nestedscrollview.xml b/core-ui/tests/res/layout/include_nestedscrollview.xml
similarity index 100%
rename from design/tests/res/layout/include_nestedscrollview.xml
rename to core-ui/tests/res/layout/include_nestedscrollview.xml
diff --git a/core-ui/tests/res/values/ids.xml b/core-ui/tests/res/values/ids.xml
index e5fcf63..f3ac9b4 100644
--- a/core-ui/tests/res/values/ids.xml
+++ b/core-ui/tests/res/values/ids.xml
@@ -24,4 +24,5 @@
     <item name="page_7" type="id"/>
     <item name="page_8" type="id"/>
     <item name="page_9" type="id"/>
+    <item name="anchor" type="id"/>
 </resources>
\ No newline at end of file
diff --git a/core-utils/Android.mk b/core-utils/Android.mk
index a6855fc..6dda862 100644
--- a/core-utils/Android.mk
+++ b/core-utils/Android.mk
@@ -27,7 +27,9 @@
 LOCAL_MODULE := android-support-core-utils
 LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
 LOCAL_SRC_FILES := \
-    $(call all-java-files-under,src/main/java)
+    $(call all-java-files-under,kitkat) \
+    $(call all-java-files-under,api21) \
+    $(call all-java-files-under,java)
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 LOCAL_SHARED_ANDROID_LIBRARIES := \
     android-support-compat \
diff --git a/core-utils/src/main/java/android/support/v4/graphics/drawable/RoundedBitmapDrawable21.java b/core-utils/api21/android/support/v4/graphics/drawable/RoundedBitmapDrawable21.java
similarity index 100%
rename from core-utils/src/main/java/android/support/v4/graphics/drawable/RoundedBitmapDrawable21.java
rename to core-utils/api21/android/support/v4/graphics/drawable/RoundedBitmapDrawable21.java
diff --git a/core-utils/build.gradle b/core-utils/build.gradle
index c0482d0..3f1efa1 100644
--- a/core-utils/build.gradle
+++ b/core-utils/build.gradle
@@ -10,8 +10,8 @@
     api(project(":support-annotations"))
     api(project(":support-compat"))
 
-    androidTestImplementation(TEST_RUNNER, libs.exclude_annotations)
-    androidTestImplementation(ESPRESSO_CORE, libs.exclude_annotations)
+    androidTestImplementation(TEST_RUNNER)
+    androidTestImplementation(ESPRESSO_CORE)
     androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
     androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
 }
@@ -20,6 +20,14 @@
     defaultConfig {
         minSdkVersion 14
     }
+
+    sourceSets {
+        main.java.srcDirs = [
+                'kitkat',
+                'api21',
+                'java'
+        ]
+    }
 }
 
 supportLibrary {
diff --git a/core-utils/src/main/java/android/support/v4/app/AppLaunchChecker.java b/core-utils/java/android/support/v4/app/AppLaunchChecker.java
similarity index 100%
rename from core-utils/src/main/java/android/support/v4/app/AppLaunchChecker.java
rename to core-utils/java/android/support/v4/app/AppLaunchChecker.java
diff --git a/core-utils/src/main/java/android/support/v4/app/FrameMetricsAggregator.java b/core-utils/java/android/support/v4/app/FrameMetricsAggregator.java
similarity index 100%
rename from core-utils/src/main/java/android/support/v4/app/FrameMetricsAggregator.java
rename to core-utils/java/android/support/v4/app/FrameMetricsAggregator.java
diff --git a/core-utils/src/main/java/android/support/v4/app/NavUtils.java b/core-utils/java/android/support/v4/app/NavUtils.java
similarity index 100%
rename from core-utils/src/main/java/android/support/v4/app/NavUtils.java
rename to core-utils/java/android/support/v4/app/NavUtils.java
diff --git a/core-utils/src/main/java/android/support/v4/app/TaskStackBuilder.java b/core-utils/java/android/support/v4/app/TaskStackBuilder.java
similarity index 100%
rename from core-utils/src/main/java/android/support/v4/app/TaskStackBuilder.java
rename to core-utils/java/android/support/v4/app/TaskStackBuilder.java
diff --git a/core-utils/src/main/java/android/support/v4/content/AsyncTaskLoader.java b/core-utils/java/android/support/v4/content/AsyncTaskLoader.java
similarity index 100%
rename from core-utils/src/main/java/android/support/v4/content/AsyncTaskLoader.java
rename to core-utils/java/android/support/v4/content/AsyncTaskLoader.java
diff --git a/core-utils/src/main/java/android/support/v4/content/CursorLoader.java b/core-utils/java/android/support/v4/content/CursorLoader.java
similarity index 100%
rename from core-utils/src/main/java/android/support/v4/content/CursorLoader.java
rename to core-utils/java/android/support/v4/content/CursorLoader.java
diff --git a/core-utils/src/main/java/android/support/v4/content/FileProvider.java b/core-utils/java/android/support/v4/content/FileProvider.java
similarity index 100%
rename from core-utils/src/main/java/android/support/v4/content/FileProvider.java
rename to core-utils/java/android/support/v4/content/FileProvider.java
diff --git a/core-utils/src/main/java/android/support/v4/content/Loader.java b/core-utils/java/android/support/v4/content/Loader.java
similarity index 100%
rename from core-utils/src/main/java/android/support/v4/content/Loader.java
rename to core-utils/java/android/support/v4/content/Loader.java
diff --git a/core-utils/src/main/java/android/support/v4/content/LocalBroadcastManager.java b/core-utils/java/android/support/v4/content/LocalBroadcastManager.java
similarity index 100%
rename from core-utils/src/main/java/android/support/v4/content/LocalBroadcastManager.java
rename to core-utils/java/android/support/v4/content/LocalBroadcastManager.java
diff --git a/core-utils/src/main/java/android/support/v4/content/MimeTypeFilter.java b/core-utils/java/android/support/v4/content/MimeTypeFilter.java
similarity index 100%
rename from core-utils/src/main/java/android/support/v4/content/MimeTypeFilter.java
rename to core-utils/java/android/support/v4/content/MimeTypeFilter.java
diff --git a/core-utils/src/main/java/android/support/v4/content/ModernAsyncTask.java b/core-utils/java/android/support/v4/content/ModernAsyncTask.java
similarity index 100%
rename from core-utils/src/main/java/android/support/v4/content/ModernAsyncTask.java
rename to core-utils/java/android/support/v4/content/ModernAsyncTask.java
diff --git a/core-utils/src/main/java/android/support/v4/content/PermissionChecker.java b/core-utils/java/android/support/v4/content/PermissionChecker.java
similarity index 100%
rename from core-utils/src/main/java/android/support/v4/content/PermissionChecker.java
rename to core-utils/java/android/support/v4/content/PermissionChecker.java
diff --git a/core-utils/src/main/java/android/support/v4/content/WakefulBroadcastReceiver.java b/core-utils/java/android/support/v4/content/WakefulBroadcastReceiver.java
similarity index 100%
rename from core-utils/src/main/java/android/support/v4/content/WakefulBroadcastReceiver.java
rename to core-utils/java/android/support/v4/content/WakefulBroadcastReceiver.java
diff --git a/core-utils/src/main/java/android/support/v4/graphics/ColorUtils.java b/core-utils/java/android/support/v4/graphics/ColorUtils.java
similarity index 100%
rename from core-utils/src/main/java/android/support/v4/graphics/ColorUtils.java
rename to core-utils/java/android/support/v4/graphics/ColorUtils.java
diff --git a/core-utils/src/main/java/android/support/v4/graphics/drawable/RoundedBitmapDrawable.java b/core-utils/java/android/support/v4/graphics/drawable/RoundedBitmapDrawable.java
similarity index 100%
rename from core-utils/src/main/java/android/support/v4/graphics/drawable/RoundedBitmapDrawable.java
rename to core-utils/java/android/support/v4/graphics/drawable/RoundedBitmapDrawable.java
diff --git a/core-utils/src/main/java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java b/core-utils/java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java
similarity index 100%
rename from core-utils/src/main/java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java
rename to core-utils/java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java
diff --git a/core-utils/src/main/java/android/support/v4/math/MathUtils.java b/core-utils/java/android/support/v4/math/MathUtils.java
similarity index 100%
rename from core-utils/src/main/java/android/support/v4/math/MathUtils.java
rename to core-utils/java/android/support/v4/math/MathUtils.java
diff --git a/core-utils/src/main/java/android/support/v4/print/PrintHelper.java b/core-utils/java/android/support/v4/print/PrintHelper.java
similarity index 100%
rename from core-utils/src/main/java/android/support/v4/print/PrintHelper.java
rename to core-utils/java/android/support/v4/print/PrintHelper.java
diff --git a/core-utils/src/main/java/android/support/v4/provider/DocumentFile.java b/core-utils/java/android/support/v4/provider/DocumentFile.java
similarity index 100%
rename from core-utils/src/main/java/android/support/v4/provider/DocumentFile.java
rename to core-utils/java/android/support/v4/provider/DocumentFile.java
diff --git a/core-utils/src/main/java/android/support/v4/provider/RawDocumentFile.java b/core-utils/java/android/support/v4/provider/RawDocumentFile.java
similarity index 100%
rename from core-utils/src/main/java/android/support/v4/provider/RawDocumentFile.java
rename to core-utils/java/android/support/v4/provider/RawDocumentFile.java
diff --git a/core-utils/src/main/java/android/support/v4/provider/SingleDocumentFile.java b/core-utils/java/android/support/v4/provider/SingleDocumentFile.java
similarity index 100%
rename from core-utils/src/main/java/android/support/v4/provider/SingleDocumentFile.java
rename to core-utils/java/android/support/v4/provider/SingleDocumentFile.java
diff --git a/core-utils/src/main/java/android/support/v4/provider/TreeDocumentFile.java b/core-utils/java/android/support/v4/provider/TreeDocumentFile.java
similarity index 100%
rename from core-utils/src/main/java/android/support/v4/provider/TreeDocumentFile.java
rename to core-utils/java/android/support/v4/provider/TreeDocumentFile.java
diff --git a/core-utils/src/main/java/android/support/v4/provider/DocumentsContractApi19.java b/core-utils/kitkat/android/support/v4/provider/DocumentsContractApi19.java
similarity index 100%
rename from core-utils/src/main/java/android/support/v4/provider/DocumentsContractApi19.java
rename to core-utils/kitkat/android/support/v4/provider/DocumentsContractApi19.java
diff --git a/core-utils/src/main/java/android/support/v4/app/package.html b/core-utils/src/main/java/android/support/v4/app/package.html
deleted file mode 100755
index 02d1b79..0000000
--- a/core-utils/src/main/java/android/support/v4/app/package.html
+++ /dev/null
@@ -1,8 +0,0 @@
-<body>
-
-Support android.app classes to assist with development of applications for
-android API level 4 or later.  The main features here are backwards-compatible
-versions of {@link android.support.v4.app.FragmentManager} and
-{@link android.support.v4.app.LoaderManager}.
-
-</body>
diff --git a/core-utils/src/main/java/android/support/v4/content/package.html b/core-utils/src/main/java/android/support/v4/content/package.html
deleted file mode 100755
index 33bf4b5..0000000
--- a/core-utils/src/main/java/android/support/v4/content/package.html
+++ /dev/null
@@ -1,10 +0,0 @@
-<body>
-
-Support android.content classes to assist with development of applications for
-android API level 4 or later.  The main features here are
-{@link android.support.v4.content.Loader} and related classes and
-{@link android.support.v4.content.LocalBroadcastManager} to
-provide a cleaner implementation of broadcasts that don't need to go outside
-of an app.
-
-</body>
diff --git a/customtabs/build.gradle b/customtabs/build.gradle
index 0bbd03d..75e28f7 100644
--- a/customtabs/build.gradle
+++ b/customtabs/build.gradle
@@ -1,5 +1,4 @@
-import static android.support.dependencies.DependenciesKt.ESPRESSO_CORE
-import static android.support.dependencies.DependenciesKt.TEST_RUNNER
+import static android.support.dependencies.DependenciesKt.*
 import android.support.LibraryGroups
 import android.support.LibraryVersions
 
@@ -11,8 +10,8 @@
     api(project(":support-compat"))
     api(project(":support-annotations"))
 
-    androidTestImplementation(TEST_RUNNER, libs.exclude_annotations)
-    androidTestImplementation(ESPRESSO_CORE, libs.exclude_annotations)
+    androidTestImplementation(TEST_RUNNER)
+    androidTestImplementation(ESPRESSO_CORE)
     androidTestImplementation(project(":support-testutils"))
 }
 
diff --git a/design/Android.mk b/design/Android.mk
index 08f8815..4a51f77 100644
--- a/design/Android.mk
+++ b/design/Android.mk
@@ -38,7 +38,11 @@
     android-support-transition \
     android-support-v7-appcompat \
     android-support-v7-recyclerview \
-    android-support-v4 \
+    android-support-compat \
+    android-support-media-compat \
+    android-support-core-utils \
+    android-support-core-ui \
+    android-support-fragment \
     android-support-annotations
 LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 LOCAL_AAPT_FLAGS := \
diff --git a/design/api/27.0.0.ignore b/design/api/27.0.0.ignore
new file mode 100644
index 0000000..d982dc1
--- /dev/null
+++ b/design/api/27.0.0.ignore
@@ -0,0 +1,14 @@
+a1fc27e
+197ce1d
+88bc57e
+9761c3e
+86b38bf
+c6abd5e
+cea5c29
+8bac7c2
+f02de9f
+1d50e39
+fb2d5b2
+4c9b1f0
+7da565d
+33c90df
diff --git a/design/api/current.txt b/design/api/current.txt
index 602ee48..71de20a 100644
--- a/design/api/current.txt
+++ b/design/api/current.txt
@@ -15,7 +15,7 @@
     method public deprecated void setTargetElevation(float);
   }
 
-  public static class AppBarLayout.Behavior extends android.support.design.widget.HeaderBehavior {
+  public static class AppBarLayout.Behavior extends android.support.design.widget.CoordinatorLayout.Behavior {
     ctor public AppBarLayout.Behavior();
     ctor public AppBarLayout.Behavior(android.content.Context, android.util.AttributeSet);
     method public boolean onLayoutChild(android.support.design.widget.CoordinatorLayout, android.support.design.widget.AppBarLayout, int);
@@ -63,7 +63,7 @@
     method public abstract void onOffsetChanged(android.support.design.widget.AppBarLayout, int);
   }
 
-  public static class AppBarLayout.ScrollingViewBehavior extends android.support.design.widget.HeaderScrollingViewBehavior {
+  public static class AppBarLayout.ScrollingViewBehavior extends android.support.design.widget.CoordinatorLayout.Behavior {
     ctor public AppBarLayout.ScrollingViewBehavior();
     ctor public AppBarLayout.ScrollingViewBehavior(android.content.Context, android.util.AttributeSet);
     method public boolean layoutDependsOn(android.support.design.widget.CoordinatorLayout, android.view.View, android.view.View);
@@ -183,7 +183,6 @@
     ctor public CollapsingToolbarLayout(android.content.Context, android.util.AttributeSet);
     ctor public CollapsingToolbarLayout(android.content.Context, android.util.AttributeSet, int);
     method protected android.support.design.widget.CollapsingToolbarLayout.LayoutParams generateDefaultLayoutParams();
-    method public android.widget.FrameLayout.LayoutParams generateLayoutParams(android.util.AttributeSet);
     method protected android.widget.FrameLayout.LayoutParams generateLayoutParams(android.view.ViewGroup.LayoutParams);
     method public int getCollapsedTitleGravity();
     method public android.graphics.Typeface getCollapsedTitleTypeface();
@@ -244,103 +243,14 @@
     field public static final int COLLAPSE_MODE_PIN = 1; // 0x1
   }
 
-  public class CoordinatorLayout extends android.view.ViewGroup {
-    ctor public CoordinatorLayout(android.content.Context);
-    ctor public CoordinatorLayout(android.content.Context, android.util.AttributeSet);
-    ctor public CoordinatorLayout(android.content.Context, android.util.AttributeSet, int);
-    method public void dispatchDependentViewsChanged(android.view.View);
-    method public boolean doViewsOverlap(android.view.View, android.view.View);
-    method protected android.support.design.widget.CoordinatorLayout.LayoutParams generateDefaultLayoutParams();
-    method public android.support.design.widget.CoordinatorLayout.LayoutParams generateLayoutParams(android.util.AttributeSet);
-    method protected android.support.design.widget.CoordinatorLayout.LayoutParams generateLayoutParams(android.view.ViewGroup.LayoutParams);
-    method public java.util.List<android.view.View> getDependencies(android.view.View);
-    method public java.util.List<android.view.View> getDependents(android.view.View);
-    method public android.graphics.drawable.Drawable getStatusBarBackground();
-    method public boolean isPointInChildBounds(android.view.View, int, int);
-    method public void onAttachedToWindow();
-    method public void onDetachedFromWindow();
-    method public void onDraw(android.graphics.Canvas);
-    method protected void onLayout(boolean, int, int, int, int);
-    method public void onLayoutChild(android.view.View, int);
-    method public void onMeasureChild(android.view.View, int, int, int, int);
-    method public void onNestedPreScroll(android.view.View, int, int, int[], int);
-    method public void onNestedScroll(android.view.View, int, int, int, int, int);
-    method public void onNestedScrollAccepted(android.view.View, android.view.View, int, int);
-    method public boolean onStartNestedScroll(android.view.View, android.view.View, int, int);
-    method public void onStopNestedScroll(android.view.View, int);
-    method public void setStatusBarBackground(android.graphics.drawable.Drawable);
-    method public void setStatusBarBackgroundColor(int);
-    method public void setStatusBarBackgroundResource(int);
-  }
-
-  public static abstract class CoordinatorLayout.Behavior<V extends android.view.View> {
-    ctor public CoordinatorLayout.Behavior();
-    ctor public CoordinatorLayout.Behavior(android.content.Context, android.util.AttributeSet);
-    method public boolean blocksInteractionBelow(android.support.design.widget.CoordinatorLayout, V);
-    method public boolean getInsetDodgeRect(android.support.design.widget.CoordinatorLayout, V, android.graphics.Rect);
-    method public int getScrimColor(android.support.design.widget.CoordinatorLayout, V);
-    method public float getScrimOpacity(android.support.design.widget.CoordinatorLayout, V);
-    method public static java.lang.Object getTag(android.view.View);
-    method public boolean layoutDependsOn(android.support.design.widget.CoordinatorLayout, V, android.view.View);
-    method public android.support.v4.view.WindowInsetsCompat onApplyWindowInsets(android.support.design.widget.CoordinatorLayout, V, android.support.v4.view.WindowInsetsCompat);
-    method public void onAttachedToLayoutParams(android.support.design.widget.CoordinatorLayout.LayoutParams);
-    method public boolean onDependentViewChanged(android.support.design.widget.CoordinatorLayout, V, android.view.View);
-    method public void onDependentViewRemoved(android.support.design.widget.CoordinatorLayout, V, android.view.View);
-    method public void onDetachedFromLayoutParams();
-    method public boolean onInterceptTouchEvent(android.support.design.widget.CoordinatorLayout, V, android.view.MotionEvent);
-    method public boolean onLayoutChild(android.support.design.widget.CoordinatorLayout, V, int);
-    method public boolean onMeasureChild(android.support.design.widget.CoordinatorLayout, V, int, int, int, int);
-    method public boolean onNestedFling(android.support.design.widget.CoordinatorLayout, V, android.view.View, float, float, boolean);
-    method public boolean onNestedPreFling(android.support.design.widget.CoordinatorLayout, V, android.view.View, float, float);
-    method public deprecated void onNestedPreScroll(android.support.design.widget.CoordinatorLayout, V, android.view.View, int, int, int[]);
-    method public void onNestedPreScroll(android.support.design.widget.CoordinatorLayout, V, android.view.View, int, int, int[], int);
-    method public deprecated void onNestedScroll(android.support.design.widget.CoordinatorLayout, V, android.view.View, int, int, int, int);
-    method public void onNestedScroll(android.support.design.widget.CoordinatorLayout, V, android.view.View, int, int, int, int, int);
-    method public deprecated void onNestedScrollAccepted(android.support.design.widget.CoordinatorLayout, V, android.view.View, android.view.View, int);
-    method public void onNestedScrollAccepted(android.support.design.widget.CoordinatorLayout, V, android.view.View, android.view.View, int, int);
-    method public boolean onRequestChildRectangleOnScreen(android.support.design.widget.CoordinatorLayout, V, android.graphics.Rect, boolean);
-    method public void onRestoreInstanceState(android.support.design.widget.CoordinatorLayout, V, android.os.Parcelable);
-    method public android.os.Parcelable onSaveInstanceState(android.support.design.widget.CoordinatorLayout, V);
-    method public deprecated boolean onStartNestedScroll(android.support.design.widget.CoordinatorLayout, V, android.view.View, android.view.View, int);
-    method public boolean onStartNestedScroll(android.support.design.widget.CoordinatorLayout, V, android.view.View, android.view.View, int, int);
-    method public deprecated void onStopNestedScroll(android.support.design.widget.CoordinatorLayout, V, android.view.View);
-    method public void onStopNestedScroll(android.support.design.widget.CoordinatorLayout, V, android.view.View, int);
-    method public boolean onTouchEvent(android.support.design.widget.CoordinatorLayout, V, android.view.MotionEvent);
-    method public static void setTag(android.view.View, java.lang.Object);
-  }
-
-  public static abstract class CoordinatorLayout.DefaultBehavior implements java.lang.annotation.Annotation {
-  }
-
-  public static class CoordinatorLayout.LayoutParams extends android.view.ViewGroup.MarginLayoutParams {
-    ctor public CoordinatorLayout.LayoutParams(int, int);
-    ctor public CoordinatorLayout.LayoutParams(android.support.design.widget.CoordinatorLayout.LayoutParams);
-    ctor public CoordinatorLayout.LayoutParams(android.view.ViewGroup.MarginLayoutParams);
-    ctor public CoordinatorLayout.LayoutParams(android.view.ViewGroup.LayoutParams);
-    method public int getAnchorId();
-    method public android.support.design.widget.CoordinatorLayout.Behavior getBehavior();
-    method public void setAnchorId(int);
-    method public void setBehavior(android.support.design.widget.CoordinatorLayout.Behavior);
-    field public int anchorGravity;
-    field public int dodgeInsetEdges;
-    field public int gravity;
-    field public int insetEdge;
-    field public int keyline;
-  }
-
-  protected static class CoordinatorLayout.SavedState extends android.support.v4.view.AbsSavedState {
-    ctor public CoordinatorLayout.SavedState(android.os.Parcel, java.lang.ClassLoader);
-    ctor public CoordinatorLayout.SavedState(android.os.Parcelable);
-    field public static final android.os.Parcelable.Creator<android.support.design.widget.CoordinatorLayout.SavedState> CREATOR;
-  }
-
-  public class FloatingActionButton extends android.support.design.widget.VisibilityAwareImageButton {
+  public class FloatingActionButton extends android.widget.ImageButton {
     ctor public FloatingActionButton(android.content.Context);
     ctor public FloatingActionButton(android.content.Context, android.util.AttributeSet);
     ctor public FloatingActionButton(android.content.Context, android.util.AttributeSet, int);
     method public float getCompatElevation();
     method public android.graphics.drawable.Drawable getContentBackground();
     method public boolean getContentRect(android.graphics.Rect);
+    method public int getCustomSize();
     method public int getRippleColor();
     method public int getSize();
     method public boolean getUseCompatPadding();
@@ -348,11 +258,13 @@
     method public void hide(android.support.design.widget.FloatingActionButton.OnVisibilityChangedListener);
     method public void setBackgroundDrawable(android.graphics.drawable.Drawable);
     method public void setCompatElevation(float);
+    method public void setCustomSize(int);
     method public void setRippleColor(int);
     method public void setSize(int);
     method public void setUseCompatPadding(boolean);
     method public void show();
     method public void show(android.support.design.widget.FloatingActionButton.OnVisibilityChangedListener);
+    field public static final int NO_CUSTOM_SIZE = 0; // 0x0
     field public static final int SIZE_AUTO = -1; // 0xffffffff
     field public static final int SIZE_MINI = 1; // 0x1
     field public static final int SIZE_NORMAL = 0; // 0x0
@@ -374,20 +286,6 @@
     method public void onShown(android.support.design.widget.FloatingActionButton);
   }
 
-   abstract class HeaderBehavior<V extends android.view.View> extends android.support.design.widget.ViewOffsetBehavior {
-    ctor public HeaderBehavior();
-    ctor public HeaderBehavior(android.content.Context, android.util.AttributeSet);
-  }
-
-   abstract class HeaderScrollingViewBehavior extends android.support.design.widget.ViewOffsetBehavior {
-    ctor public HeaderScrollingViewBehavior();
-    ctor public HeaderScrollingViewBehavior(android.content.Context, android.util.AttributeSet);
-    method public final int getOverlayTop();
-    method protected void layoutChild(android.support.design.widget.CoordinatorLayout, android.view.View, int);
-    method public boolean onMeasureChild(android.support.design.widget.CoordinatorLayout, android.view.View, int, int, int, int);
-    method public final void setOverlayTop(int);
-  }
-
   public class NavigationView extends android.widget.FrameLayout {
     ctor public NavigationView(android.content.Context);
     ctor public NavigationView(android.content.Context, android.util.AttributeSet);
@@ -486,7 +384,6 @@
     method public void addTab(android.support.design.widget.TabLayout.Tab, boolean);
     method public void addTab(android.support.design.widget.TabLayout.Tab, int, boolean);
     method public void clearOnTabSelectedListeners();
-    method public android.widget.FrameLayout.LayoutParams generateLayoutParams(android.util.AttributeSet);
     method public int getSelectedTabPosition();
     method public android.support.design.widget.TabLayout.Tab getTabAt(int);
     method public int getTabCount();
@@ -562,7 +459,7 @@
     ctor public TextInputEditText(android.content.Context, android.util.AttributeSet, int);
   }
 
-  public class TextInputLayout extends android.widget.LinearLayout {
+  public class TextInputLayout extends android.widget.LinearLayout implements android.support.v7.widget.WithHint {
     ctor public TextInputLayout(android.content.Context);
     ctor public TextInputLayout(android.content.Context, android.util.AttributeSet);
     ctor public TextInputLayout(android.content.Context, android.util.AttributeSet, int);
@@ -598,21 +495,5 @@
     method public void setTypeface(android.graphics.Typeface);
   }
 
-   class ViewOffsetBehavior<V extends android.view.View> extends android.support.design.widget.CoordinatorLayout.Behavior {
-    ctor public ViewOffsetBehavior();
-    ctor public ViewOffsetBehavior(android.content.Context, android.util.AttributeSet);
-    method public int getLeftAndRightOffset();
-    method public int getTopAndBottomOffset();
-    method protected void layoutChild(android.support.design.widget.CoordinatorLayout, V, int);
-    method public boolean setLeftAndRightOffset(int);
-    method public boolean setTopAndBottomOffset(int);
-  }
-
-   class VisibilityAwareImageButton extends android.widget.ImageButton {
-    ctor public VisibilityAwareImageButton(android.content.Context);
-    ctor public VisibilityAwareImageButton(android.content.Context, android.util.AttributeSet);
-    ctor public VisibilityAwareImageButton(android.content.Context, android.util.AttributeSet, int);
-  }
-
 }
 
diff --git a/design/build.gradle b/design/build.gradle
index 30bd21a..e7ebc91 100644
--- a/design/build.gradle
+++ b/design/build.gradle
@@ -12,15 +12,12 @@
     api(project(":recyclerview-v7"))
     api(project(":transition"))
 
-    androidTestImplementation(TEST_RUNNER, libs.exclude_annotations)
-    androidTestImplementation(ESPRESSO_CORE, libs.exclude_annotations)
+    androidTestImplementation(TEST_RUNNER)
+    androidTestImplementation(ESPRESSO_CORE)
     androidTestImplementation(ESPRESSO_CONTRIB, libs.exclude_support)
     androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
     androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
     androidTestImplementation project(':support-testutils')
-
-    testImplementation(JUNIT)
-    testImplementation(TEST_RUNNER, libs.exclude_annotations)
 }
 
 android {
@@ -43,8 +40,6 @@
                 'res-public'
         ]
         main.resources.srcDir 'src'
-
-        test.java.srcDir 'jvm-tests/src'
     }
 
     buildTypes.all {
diff --git a/design/jvm-tests/NO_DOCS b/design/jvm-tests/NO_DOCS
deleted file mode 100644
index 092a39c..0000000
--- a/design/jvm-tests/NO_DOCS
+++ /dev/null
@@ -1,17 +0,0 @@
-# Copyright (C) 2016 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-Having this file, named NO_DOCS, in a directory will prevent
-Android javadocs from being generated for java files under
-the directory. This is especially useful for test projects.
diff --git a/design/jvm-tests/src/android/support/design/widget/DirectedAcyclicGraphTest.java b/design/jvm-tests/src/android/support/design/widget/DirectedAcyclicGraphTest.java
deleted file mode 100644
index 4a5ffc5..0000000
--- a/design/jvm-tests/src/android/support/design/widget/DirectedAcyclicGraphTest.java
+++ /dev/null
@@ -1,209 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.design.widget;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
-import android.support.annotation.NonNull;
-import android.support.test.filters.SmallTest;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.util.List;
-
-@RunWith(JUnit4.class)
-@SmallTest
-public class DirectedAcyclicGraphTest {
-
-    private DirectedAcyclicGraph<TestNode> mGraph;
-
-    @Before
-    public void setup() {
-        mGraph = new DirectedAcyclicGraph<>();
-    }
-
-    @Test
-    public void test_addNode() {
-        final TestNode node = new TestNode("node");
-        mGraph.addNode(node);
-        assertEquals(1, mGraph.size());
-        assertTrue(mGraph.contains(node));
-    }
-
-    @Test
-    public void test_addNodeAgain() {
-        final TestNode node = new TestNode("node");
-        mGraph.addNode(node);
-        mGraph.addNode(node);
-
-        assertEquals(1, mGraph.size());
-        assertTrue(mGraph.contains(node));
-    }
-
-    @Test
-    public void test_addEdge() {
-        final TestNode node = new TestNode("node");
-        final TestNode edge = new TestNode("edge");
-
-        mGraph.addNode(node);
-        mGraph.addNode(edge);
-        mGraph.addEdge(node, edge);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void test_addEdgeWithNotAddedEdgeNode() {
-        final TestNode node = new TestNode("node");
-        final TestNode edge = new TestNode("edge");
-
-        // Add the node, but not the edge node
-        mGraph.addNode(node);
-
-        // Now add the link
-        mGraph.addEdge(node, edge);
-    }
-
-    @Test
-    public void test_getIncomingEdges() {
-        final TestNode node = new TestNode("node");
-        final TestNode edge = new TestNode("edge");
-        mGraph.addNode(node);
-        mGraph.addNode(edge);
-        mGraph.addEdge(node, edge);
-
-        final List<TestNode> incomingEdges = mGraph.getIncomingEdges(node);
-        assertNotNull(incomingEdges);
-        assertEquals(1, incomingEdges.size());
-        assertEquals(edge, incomingEdges.get(0));
-    }
-
-    @Test
-    public void test_getOutgoingEdges() {
-        final TestNode node = new TestNode("node");
-        final TestNode edge = new TestNode("edge");
-        mGraph.addNode(node);
-        mGraph.addNode(edge);
-        mGraph.addEdge(node, edge);
-
-        // Now assert the getOutgoingEdges returns a list which has one element (node)
-        final List<TestNode> outgoingEdges = mGraph.getOutgoingEdges(edge);
-        assertNotNull(outgoingEdges);
-        assertEquals(1, outgoingEdges.size());
-        assertTrue(outgoingEdges.contains(node));
-    }
-
-    @Test
-    public void test_getOutgoingEdgesMultiple() {
-        final TestNode node1 = new TestNode("1");
-        final TestNode node2 = new TestNode("2");
-        final TestNode edge = new TestNode("edge");
-        mGraph.addNode(node1);
-        mGraph.addNode(node2);
-        mGraph.addNode(edge);
-
-        mGraph.addEdge(node1, edge);
-        mGraph.addEdge(node2, edge);
-
-        // Now assert the getOutgoingEdges returns a list which has 2 elements (node1 & node2)
-        final List<TestNode> outgoingEdges = mGraph.getOutgoingEdges(edge);
-        assertNotNull(outgoingEdges);
-        assertEquals(2, outgoingEdges.size());
-        assertTrue(outgoingEdges.contains(node1));
-        assertTrue(outgoingEdges.contains(node2));
-    }
-
-    @Test
-    public void test_hasOutgoingEdges() {
-        final TestNode node = new TestNode("node");
-        final TestNode edge = new TestNode("edge");
-        mGraph.addNode(node);
-        mGraph.addNode(edge);
-
-        // There is no edge currently and assert that fact
-        assertFalse(mGraph.hasOutgoingEdges(edge));
-        // Now add the edge
-        mGraph.addEdge(node, edge);
-        // and assert that the methods returns true;
-        assertTrue(mGraph.hasOutgoingEdges(edge));
-    }
-
-    @Test
-    public void test_clear() {
-        final TestNode node1 = new TestNode("1");
-        final TestNode node2 = new TestNode("2");
-        final TestNode edge = new TestNode("edge");
-        mGraph.addNode(node1);
-        mGraph.addNode(node2);
-        mGraph.addNode(edge);
-
-        // Now clear the graph
-        mGraph.clear();
-
-        // Now assert the graph is empty and that contains returns false
-        assertEquals(0, mGraph.size());
-        assertFalse(mGraph.contains(node1));
-        assertFalse(mGraph.contains(node2));
-        assertFalse(mGraph.contains(edge));
-    }
-
-    @Test
-    public void test_getSortedList() {
-        final TestNode node1 = new TestNode("A");
-        final TestNode node2 = new TestNode("B");
-        final TestNode node3 = new TestNode("C");
-        final TestNode node4 = new TestNode("D");
-
-        // Now we'll add the nodes
-        mGraph.addNode(node1);
-        mGraph.addNode(node2);
-        mGraph.addNode(node3);
-        mGraph.addNode(node4);
-
-        // Now we'll add edges so that 4 <- 2, 2 <- 3, 3 <- 1  (where <- denotes a dependency)
-        mGraph.addEdge(node4, node2);
-        mGraph.addEdge(node2, node3);
-        mGraph.addEdge(node3, node1);
-
-        final List<TestNode> sorted = mGraph.getSortedList();
-        // Assert that it is the correct size
-        assertEquals(4, sorted.size());
-        // Assert that all of the nodes are present and in their sorted order
-        assertEquals(node1, sorted.get(0));
-        assertEquals(node3, sorted.get(1));
-        assertEquals(node2, sorted.get(2));
-        assertEquals(node4, sorted.get(3));
-    }
-
-    private static class TestNode {
-        private final String mLabel;
-
-        TestNode(@NonNull String label) {
-            mLabel = label;
-        }
-
-        @Override
-        public String toString() {
-            return "TestNode: " + mLabel;
-        }
-    }
-
-}
diff --git a/design/res-public/values/public_attrs.xml b/design/res-public/values/public_attrs.xml
index b443778..9afe981 100644
--- a/design/res-public/values/public_attrs.xml
+++ b/design/res-public/values/public_attrs.xml
@@ -51,19 +51,13 @@
     <public type="attr" name="itemIconTint"/>
     <public type="attr" name="itemTextAppearance"/>
     <public type="attr" name="itemTextColor"/>
-    <public type="attr" name="keylines"/>
-    <public type="attr" name="layout_anchor"/>
-    <public type="attr" name="layout_anchorGravity"/>
-    <public type="attr" name="layout_behavior"/>
     <public type="attr" name="layout_collapseMode"/>
     <public type="attr" name="layout_collapseParallaxMultiplier"/>
-    <public type="attr" name="layout_keyline"/>
     <public type="attr" name="layout_scrollFlags"/>
     <public type="attr" name="layout_scrollInterpolator"/>
     <public type="attr" name="menu"/>
     <public type="attr" name="pressedTranslationZ"/>
     <public type="attr" name="rippleColor"/>
-    <public type="attr" name="statusBarBackground"/>
     <public type="attr" name="statusBarScrim"/>
     <public type="attr" name="tabBackground"/>
     <public type="attr" name="tabContentStart"/>
diff --git a/design/res/values/attrs.xml b/design/res/values/attrs.xml
index b378849..8c3536f 100644
--- a/design/res/values/attrs.xml
+++ b/design/res/values/attrs.xml
@@ -23,7 +23,7 @@
 
         <!-- Ripple color for the FAB. -->
         <attr name="rippleColor" format="color"/>
-        <!-- Size for the FAB. -->
+        <!-- Size for the FAB. If fabCustomSize is set, this will be ignored. -->
         <attr name="fabSize">
             <!-- A size which will change based on the window size. -->
             <enum name="auto" value="-1"/>
@@ -32,6 +32,8 @@
             <!-- The mini sized button. -->
             <enum name="mini" value="1"/>
         </attr>
+        <!-- Custom size for the FAB. If this is set, fabSize will be ignored. -->
+        <attr name="fabCustomSize" format="dimension"/>
         <!-- Elevation value for the FAB -->
         <attr name="elevation"/>
         <!-- TranslationZ value for the FAB when pressed-->
@@ -122,107 +124,6 @@
         <attr name="android:layout" />
     </declare-styleable>
 
-    <declare-styleable name="CoordinatorLayout">
-        <!-- A reference to an array of integers representing the
-             locations of horizontal keylines in dp from the starting edge.
-             Child views can refer to these keylines for alignment using
-             layout_keyline="index" where index is a 0-based index into
-             this array. -->
-        <attr name="keylines" format="reference"/>
-        <!-- Drawable to display behind the status bar when the view is set to draw behind it. -->
-        <attr name="statusBarBackground" format="reference"/>
-    </declare-styleable>
-
-    <declare-styleable name="CoordinatorLayout_Layout">
-        <attr name="android:layout_gravity"/>
-        <!-- The class name of a Behavior class defining special runtime behavior
-             for this child view. -->
-        <attr name="layout_behavior" format="string"/>
-        <!-- The id of an anchor view that this view should position relative to. -->
-        <attr name="layout_anchor" format="reference"/>
-        <!-- The index of a keyline this view should position relative to.
-             android:layout_gravity will affect how the view aligns to the
-             specified keyline. -->
-        <attr name="layout_keyline" format="integer"/>
-
-        <!-- Specifies how an object should position relative to an anchor, on both the X and Y axes,
-             within its parent's bounds.  -->
-        <attr name="layout_anchorGravity">
-            <!-- Push object to the top of its container, not changing its size. -->
-            <flag name="top" value="0x30"/>
-            <!-- Push object to the bottom of its container, not changing its size. -->
-            <flag name="bottom" value="0x50"/>
-            <!-- Push object to the left of its container, not changing its size. -->
-            <flag name="left" value="0x03"/>
-            <!-- Push object to the right of its container, not changing its size. -->
-            <flag name="right" value="0x05"/>
-            <!-- Place object in the vertical center of its container, not changing its size. -->
-            <flag name="center_vertical" value="0x10"/>
-            <!-- Grow the vertical size of the object if needed so it completely fills its container. -->
-            <flag name="fill_vertical" value="0x70"/>
-            <!-- Place object in the horizontal center of its container, not changing its size. -->
-            <flag name="center_horizontal" value="0x01"/>
-            <!-- Grow the horizontal size of the object if needed so it completely fills its container. -->
-            <flag name="fill_horizontal" value="0x07"/>
-            <!-- Place the object in the center of its container in both the vertical and horizontal axis, not changing its size. -->
-            <flag name="center" value="0x11"/>
-            <!-- Grow the horizontal and vertical size of the object if needed so it completely fills its container. -->
-            <flag name="fill" value="0x77"/>
-            <!-- Additional option that can be set to have the top and/or bottom edges of
-                 the child clipped to its container's bounds.
-                 The clip will be based on the vertical gravity: a top gravity will clip the bottom
-                 edge, a bottom gravity will clip the top edge, and neither will clip both edges. -->
-            <flag name="clip_vertical" value="0x80"/>
-            <!-- Additional option that can be set to have the left and/or right edges of
-                 the child clipped to its container's bounds.
-                 The clip will be based on the horizontal gravity: a left gravity will clip the right
-                 edge, a right gravity will clip the left edge, and neither will clip both edges. -->
-            <flag name="clip_horizontal" value="0x08"/>
-            <!-- Push object to the beginning of its container, not changing its size. -->
-            <flag name="start" value="0x00800003"/>
-            <!-- Push object to the end of its container, not changing its size. -->
-            <flag name="end" value="0x00800005"/>
-        </attr>
-
-        <!-- Specifies how this view insets the CoordinatorLayout and make some other views
-             dodge it. -->
-        <attr name="layout_insetEdge" format="enum">
-            <!-- Don't inset. -->
-            <enum name="none" value="0x0"/>
-            <!-- Inset the top edge. -->
-            <enum name="top" value="0x30"/>
-            <!-- Inset the bottom edge. -->
-            <enum name="bottom" value="0x50"/>
-            <!-- Inset the left edge. -->
-            <enum name="left" value="0x03"/>
-            <!-- Inset the right edge. -->
-            <enum name="right" value="0x03"/>
-            <!-- Inset the start edge. -->
-            <enum name="start" value="0x00800003"/>
-            <!-- Inset the end edge. -->
-            <enum name="end" value="0x00800005"/>
-        </attr>
-        <!-- Specifies how this view dodges the inset edges of the CoordinatorLayout. -->
-        <attr name="layout_dodgeInsetEdges">
-            <!-- Don't dodge any edges -->
-            <flag name="none" value="0x0"/>
-            <!-- Dodge the top inset edge. -->
-            <flag name="top" value="0x30"/>
-            <!-- Dodge the bottom inset edge. -->
-            <flag name="bottom" value="0x50"/>
-            <!-- Dodge the left inset edge. -->
-            <flag name="left" value="0x03"/>
-            <!-- Dodge the right inset edge. -->
-            <flag name="right" value="0x03"/>
-            <!-- Dodge the start inset edge. -->
-            <flag name="start" value="0x00800003"/>
-            <!-- Dodge the end inset edge. -->
-            <flag name="end" value="0x00800005"/>
-            <!-- Dodge all the inset edges. -->
-            <flag name="all" value="0x77"/>
-        </attr>
-    </declare-styleable>
-
     <declare-styleable name="TextInputLayout">
         <attr name="hintTextAppearance" format="reference"/>
         <!-- The hint to display in the floating label. -->
diff --git a/design/res/values/styles.xml b/design/res/values/styles.xml
index 93fb7eb..bbb200d 100644
--- a/design/res/values/styles.xml
+++ b/design/res/values/styles.xml
@@ -117,7 +117,7 @@
     <style name="Widget.Design.AppBarLayout" parent="Base.Widget.Design.AppBarLayout">
     </style>
 
-    <style name="Widget.Design.CoordinatorLayout" parent="android:Widget">
+    <style name="Widget.Design.CoordinatorLayout" parent="@style/Widget.Support.CoordinatorLayout">
         <item name="statusBarBackground">?attr/colorPrimaryDark</item>
     </style>
 
diff --git a/design/res/values/themes.xml b/design/res/values/themes.xml
index a7bd92d..aa4c876 100644
--- a/design/res/values/themes.xml
+++ b/design/res/values/themes.xml
@@ -30,10 +30,12 @@
 
     <style name="Theme.Design" parent="Theme.AppCompat">
         <item name="textColorError">?attr/colorError</item>
+        <item name="coordinatorLayoutStyle">@style/Widget.Design.CoordinatorLayout</item>
     </style>
 
     <style name="Theme.Design.Light" parent="Theme.AppCompat.Light">
         <item name="textColorError">?attr/colorError</item>
+        <item name="coordinatorLayoutStyle">@style/Widget.Design.CoordinatorLayout</item>
     </style>
 
     <style name="Theme.Design.NoActionBar">
diff --git a/design/src/android/support/design/widget/CollapsingToolbarLayout.java b/design/src/android/support/design/widget/CollapsingToolbarLayout.java
index 0051de9..8c9b7d4 100644
--- a/design/src/android/support/design/widget/CollapsingToolbarLayout.java
+++ b/design/src/android/support/design/widget/CollapsingToolbarLayout.java
@@ -44,6 +44,7 @@
 import android.support.v4.view.GravityCompat;
 import android.support.v4.view.ViewCompat;
 import android.support.v4.view.WindowInsetsCompat;
+import android.support.v4.widget.ViewGroupUtils;
 import android.support.v7.widget.Toolbar;
 import android.text.TextUtils;
 import android.util.AttributeSet;
diff --git a/design/src/android/support/design/widget/CoordinatorLayout.java b/design/src/android/support/design/widget/CoordinatorLayout.java
deleted file mode 100644
index d97d4e6..0000000
--- a/design/src/android/support/design/widget/CoordinatorLayout.java
+++ /dev/null
@@ -1,3246 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.design.widget;
-
-import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.graphics.Region;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
-import android.os.Build;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.os.SystemClock;
-import android.support.annotation.ColorInt;
-import android.support.annotation.DrawableRes;
-import android.support.annotation.FloatRange;
-import android.support.annotation.IdRes;
-import android.support.annotation.IntDef;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.annotation.RestrictTo;
-import android.support.annotation.VisibleForTesting;
-import android.support.design.R;
-import android.support.v4.content.ContextCompat;
-import android.support.v4.graphics.drawable.DrawableCompat;
-import android.support.v4.math.MathUtils;
-import android.support.v4.util.ObjectsCompat;
-import android.support.v4.util.Pools;
-import android.support.v4.view.AbsSavedState;
-import android.support.v4.view.GravityCompat;
-import android.support.v4.view.NestedScrollingParent;
-import android.support.v4.view.NestedScrollingParent2;
-import android.support.v4.view.NestedScrollingParentHelper;
-import android.support.v4.view.ViewCompat;
-import android.support.v4.view.ViewCompat.NestedScrollType;
-import android.support.v4.view.ViewCompat.ScrollAxis;
-import android.support.v4.view.WindowInsetsCompat;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.util.SparseArray;
-import android.view.Gravity;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewParent;
-import android.view.ViewTreeObserver;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.reflect.Constructor;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * CoordinatorLayout is a super-powered {@link android.widget.FrameLayout FrameLayout}.
- *
- * <p>CoordinatorLayout is intended for two primary use cases:</p>
- * <ol>
- *     <li>As a top-level application decor or chrome layout</li>
- *     <li>As a container for a specific interaction with one or more child views</li>
- * </ol>
- *
- * <p>By specifying {@link CoordinatorLayout.Behavior Behaviors} for child views of a
- * CoordinatorLayout you can provide many different interactions within a single parent and those
- * views can also interact with one another. View classes can specify a default behavior when
- * used as a child of a CoordinatorLayout using the
- * {@link CoordinatorLayout.DefaultBehavior DefaultBehavior} annotation.</p>
- *
- * <p>Behaviors may be used to implement a variety of interactions and additional layout
- * modifications ranging from sliding drawers and panels to swipe-dismissable elements and buttons
- * that stick to other elements as they move and animate.</p>
- *
- * <p>Children of a CoordinatorLayout may have an
- * {@link CoordinatorLayout.LayoutParams#setAnchorId(int) anchor}. This view id must correspond
- * to an arbitrary descendant of the CoordinatorLayout, but it may not be the anchored child itself
- * or a descendant of the anchored child. This can be used to place floating views relative to
- * other arbitrary content panes.</p>
- *
- * <p>Children can specify {@link CoordinatorLayout.LayoutParams#insetEdge} to describe how the
- * view insets the CoordinatorLayout. Any child views which are set to dodge the same inset edges by
- * {@link CoordinatorLayout.LayoutParams#dodgeInsetEdges} will be moved appropriately so that the
- * views do not overlap.</p>
- */
-public class CoordinatorLayout extends ViewGroup implements NestedScrollingParent2 {
-    static final String TAG = "CoordinatorLayout";
-    static final String WIDGET_PACKAGE_NAME;
-
-    static {
-        final Package pkg = CoordinatorLayout.class.getPackage();
-        WIDGET_PACKAGE_NAME = pkg != null ? pkg.getName() : null;
-    }
-
-    private static final int TYPE_ON_INTERCEPT = 0;
-    private static final int TYPE_ON_TOUCH = 1;
-
-    static {
-        if (Build.VERSION.SDK_INT >= 21) {
-            TOP_SORTED_CHILDREN_COMPARATOR = new ViewElevationComparator();
-        } else {
-            TOP_SORTED_CHILDREN_COMPARATOR = null;
-        }
-    }
-
-    static final Class<?>[] CONSTRUCTOR_PARAMS = new Class<?>[] {
-            Context.class,
-            AttributeSet.class
-    };
-
-    static final ThreadLocal<Map<String, Constructor<Behavior>>> sConstructors =
-            new ThreadLocal<>();
-
-    static final int EVENT_PRE_DRAW = 0;
-    static final int EVENT_NESTED_SCROLL = 1;
-    static final int EVENT_VIEW_REMOVED = 2;
-
-    /** @hide */
-    @RestrictTo(LIBRARY_GROUP)
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef({EVENT_PRE_DRAW, EVENT_NESTED_SCROLL, EVENT_VIEW_REMOVED})
-    public @interface DispatchChangeEvent {}
-
-    static final Comparator<View> TOP_SORTED_CHILDREN_COMPARATOR;
-    private static final Pools.Pool<Rect> sRectPool = new Pools.SynchronizedPool<>(12);
-
-    @NonNull
-    private static Rect acquireTempRect() {
-        Rect rect = sRectPool.acquire();
-        if (rect == null) {
-            rect = new Rect();
-        }
-        return rect;
-    }
-
-    private static void releaseTempRect(@NonNull Rect rect) {
-        rect.setEmpty();
-        sRectPool.release(rect);
-    }
-
-    private final List<View> mDependencySortedChildren = new ArrayList<>();
-    private final DirectedAcyclicGraph<View> mChildDag = new DirectedAcyclicGraph<>();
-
-    private final List<View> mTempList1 = new ArrayList<>();
-    private final List<View> mTempDependenciesList = new ArrayList<>();
-    private final int[] mTempIntPair = new int[2];
-    private Paint mScrimPaint;
-
-    private boolean mDisallowInterceptReset;
-
-    private boolean mIsAttachedToWindow;
-
-    private int[] mKeylines;
-
-    private View mBehaviorTouchView;
-    private View mNestedScrollingTarget;
-
-    private OnPreDrawListener mOnPreDrawListener;
-    private boolean mNeedsPreDrawListener;
-
-    private WindowInsetsCompat mLastInsets;
-    private boolean mDrawStatusBarBackground;
-    private Drawable mStatusBarBackground;
-
-    OnHierarchyChangeListener mOnHierarchyChangeListener;
-    private android.support.v4.view.OnApplyWindowInsetsListener mApplyWindowInsetsListener;
-
-    private final NestedScrollingParentHelper mNestedScrollingParentHelper =
-            new NestedScrollingParentHelper(this);
-
-    public CoordinatorLayout(Context context) {
-        this(context, null);
-    }
-
-    public CoordinatorLayout(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public CoordinatorLayout(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-
-        ThemeUtils.checkAppCompatTheme(context);
-
-        final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CoordinatorLayout,
-                defStyleAttr, R.style.Widget_Design_CoordinatorLayout);
-        final int keylineArrayRes = a.getResourceId(R.styleable.CoordinatorLayout_keylines, 0);
-        if (keylineArrayRes != 0) {
-            final Resources res = context.getResources();
-            mKeylines = res.getIntArray(keylineArrayRes);
-            final float density = res.getDisplayMetrics().density;
-            final int count = mKeylines.length;
-            for (int i = 0; i < count; i++) {
-                mKeylines[i] = (int) (mKeylines[i] * density);
-            }
-        }
-        mStatusBarBackground = a.getDrawable(R.styleable.CoordinatorLayout_statusBarBackground);
-        a.recycle();
-
-        setupForInsets();
-        super.setOnHierarchyChangeListener(new HierarchyChangeListener());
-    }
-
-    @Override
-    public void setOnHierarchyChangeListener(OnHierarchyChangeListener onHierarchyChangeListener) {
-        mOnHierarchyChangeListener = onHierarchyChangeListener;
-    }
-
-    @Override
-    public void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        resetTouchBehaviors(false);
-        if (mNeedsPreDrawListener) {
-            if (mOnPreDrawListener == null) {
-                mOnPreDrawListener = new OnPreDrawListener();
-            }
-            final ViewTreeObserver vto = getViewTreeObserver();
-            vto.addOnPreDrawListener(mOnPreDrawListener);
-        }
-        if (mLastInsets == null && ViewCompat.getFitsSystemWindows(this)) {
-            // We're set to fitSystemWindows but we haven't had any insets yet...
-            // We should request a new dispatch of window insets
-            ViewCompat.requestApplyInsets(this);
-        }
-        mIsAttachedToWindow = true;
-    }
-
-    @Override
-    public void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-        resetTouchBehaviors(false);
-        if (mNeedsPreDrawListener && mOnPreDrawListener != null) {
-            final ViewTreeObserver vto = getViewTreeObserver();
-            vto.removeOnPreDrawListener(mOnPreDrawListener);
-        }
-        if (mNestedScrollingTarget != null) {
-            onStopNestedScroll(mNestedScrollingTarget);
-        }
-        mIsAttachedToWindow = false;
-    }
-
-    /**
-     * Set a drawable to draw in the insets area for the status bar.
-     * Note that this will only be activated if this DrawerLayout fitsSystemWindows.
-     *
-     * @param bg Background drawable to draw behind the status bar
-     */
-    public void setStatusBarBackground(@Nullable final Drawable bg) {
-        if (mStatusBarBackground != bg) {
-            if (mStatusBarBackground != null) {
-                mStatusBarBackground.setCallback(null);
-            }
-            mStatusBarBackground = bg != null ? bg.mutate() : null;
-            if (mStatusBarBackground != null) {
-                if (mStatusBarBackground.isStateful()) {
-                    mStatusBarBackground.setState(getDrawableState());
-                }
-                DrawableCompat.setLayoutDirection(mStatusBarBackground,
-                        ViewCompat.getLayoutDirection(this));
-                mStatusBarBackground.setVisible(getVisibility() == VISIBLE, false);
-                mStatusBarBackground.setCallback(this);
-            }
-            ViewCompat.postInvalidateOnAnimation(this);
-        }
-    }
-
-    /**
-     * Gets the drawable used to draw in the insets area for the status bar.
-     *
-     * @return The status bar background drawable, or null if none set
-     */
-    @Nullable
-    public Drawable getStatusBarBackground() {
-        return mStatusBarBackground;
-    }
-
-    @Override
-    protected void drawableStateChanged() {
-        super.drawableStateChanged();
-
-        final int[] state = getDrawableState();
-        boolean changed = false;
-
-        Drawable d = mStatusBarBackground;
-        if (d != null && d.isStateful()) {
-            changed |= d.setState(state);
-        }
-
-        if (changed) {
-            invalidate();
-        }
-    }
-
-    @Override
-    protected boolean verifyDrawable(Drawable who) {
-        return super.verifyDrawable(who) || who == mStatusBarBackground;
-    }
-
-    @Override
-    public void setVisibility(int visibility) {
-        super.setVisibility(visibility);
-
-        final boolean visible = visibility == VISIBLE;
-        if (mStatusBarBackground != null && mStatusBarBackground.isVisible() != visible) {
-            mStatusBarBackground.setVisible(visible, false);
-        }
-    }
-
-    /**
-     * Set a drawable to draw in the insets area for the status bar.
-     * Note that this will only be activated if this DrawerLayout fitsSystemWindows.
-     *
-     * @param resId Resource id of a background drawable to draw behind the status bar
-     */
-    public void setStatusBarBackgroundResource(@DrawableRes int resId) {
-        setStatusBarBackground(resId != 0 ? ContextCompat.getDrawable(getContext(), resId) : null);
-    }
-
-    /**
-     * Set a drawable to draw in the insets area for the status bar.
-     * Note that this will only be activated if this DrawerLayout fitsSystemWindows.
-     *
-     * @param color Color to use as a background drawable to draw behind the status bar
-     *              in 0xAARRGGBB format.
-     */
-    public void setStatusBarBackgroundColor(@ColorInt int color) {
-        setStatusBarBackground(new ColorDrawable(color));
-    }
-
-    final WindowInsetsCompat setWindowInsets(WindowInsetsCompat insets) {
-        if (!ObjectsCompat.equals(mLastInsets, insets)) {
-            mLastInsets = insets;
-            mDrawStatusBarBackground = insets != null && insets.getSystemWindowInsetTop() > 0;
-            setWillNotDraw(!mDrawStatusBarBackground && getBackground() == null);
-
-            // Now dispatch to the Behaviors
-            insets = dispatchApplyWindowInsetsToBehaviors(insets);
-            requestLayout();
-        }
-        return insets;
-    }
-
-    final WindowInsetsCompat getLastWindowInsets() {
-        return mLastInsets;
-    }
-
-    /**
-     * Reset all Behavior-related tracking records either to clean up or in preparation
-     * for a new event stream. This should be called when attached or detached from a window,
-     * in response to an UP or CANCEL event, when intercept is request-disallowed
-     * and similar cases where an event stream in progress will be aborted.
-     */
-    private void resetTouchBehaviors(boolean notifyOnInterceptTouchEvent) {
-        final int childCount = getChildCount();
-        for (int i = 0; i < childCount; i++) {
-            final View child = getChildAt(i);
-            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-            final Behavior b = lp.getBehavior();
-            if (b != null) {
-                final long now = SystemClock.uptimeMillis();
-                final MotionEvent cancelEvent = MotionEvent.obtain(now, now,
-                        MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
-                if (notifyOnInterceptTouchEvent) {
-                    b.onInterceptTouchEvent(this, child, cancelEvent);
-                } else {
-                    b.onTouchEvent(this, child, cancelEvent);
-                }
-                cancelEvent.recycle();
-            }
-        }
-
-        for (int i = 0; i < childCount; i++) {
-            final View child = getChildAt(i);
-            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-            lp.resetTouchBehaviorTracking();
-        }
-        mDisallowInterceptReset = false;
-    }
-
-    /**
-     * Populate a list with the current child views, sorted such that the topmost views
-     * in z-order are at the front of the list. Useful for hit testing and event dispatch.
-     */
-    private void getTopSortedChildren(List<View> out) {
-        out.clear();
-
-        final boolean useCustomOrder = isChildrenDrawingOrderEnabled();
-        final int childCount = getChildCount();
-        for (int i = childCount - 1; i >= 0; i--) {
-            final int childIndex = useCustomOrder ? getChildDrawingOrder(childCount, i) : i;
-            final View child = getChildAt(childIndex);
-            out.add(child);
-        }
-
-        if (TOP_SORTED_CHILDREN_COMPARATOR != null) {
-            Collections.sort(out, TOP_SORTED_CHILDREN_COMPARATOR);
-        }
-    }
-
-    private boolean performIntercept(MotionEvent ev, final int type) {
-        boolean intercepted = false;
-        boolean newBlock = false;
-
-        MotionEvent cancelEvent = null;
-
-        final int action = ev.getActionMasked();
-
-        final List<View> topmostChildList = mTempList1;
-        getTopSortedChildren(topmostChildList);
-
-        // Let topmost child views inspect first
-        final int childCount = topmostChildList.size();
-        for (int i = 0; i < childCount; i++) {
-            final View child = topmostChildList.get(i);
-            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-            final Behavior b = lp.getBehavior();
-
-            if ((intercepted || newBlock) && action != MotionEvent.ACTION_DOWN) {
-                // Cancel all behaviors beneath the one that intercepted.
-                // If the event is "down" then we don't have anything to cancel yet.
-                if (b != null) {
-                    if (cancelEvent == null) {
-                        final long now = SystemClock.uptimeMillis();
-                        cancelEvent = MotionEvent.obtain(now, now,
-                                MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
-                    }
-                    switch (type) {
-                        case TYPE_ON_INTERCEPT:
-                            b.onInterceptTouchEvent(this, child, cancelEvent);
-                            break;
-                        case TYPE_ON_TOUCH:
-                            b.onTouchEvent(this, child, cancelEvent);
-                            break;
-                    }
-                }
-                continue;
-            }
-
-            if (!intercepted && b != null) {
-                switch (type) {
-                    case TYPE_ON_INTERCEPT:
-                        intercepted = b.onInterceptTouchEvent(this, child, ev);
-                        break;
-                    case TYPE_ON_TOUCH:
-                        intercepted = b.onTouchEvent(this, child, ev);
-                        break;
-                }
-                if (intercepted) {
-                    mBehaviorTouchView = child;
-                }
-            }
-
-            // Don't keep going if we're not allowing interaction below this.
-            // Setting newBlock will make sure we cancel the rest of the behaviors.
-            final boolean wasBlocking = lp.didBlockInteraction();
-            final boolean isBlocking = lp.isBlockingInteractionBelow(this, child);
-            newBlock = isBlocking && !wasBlocking;
-            if (isBlocking && !newBlock) {
-                // Stop here since we don't have anything more to cancel - we already did
-                // when the behavior first started blocking things below this point.
-                break;
-            }
-        }
-
-        topmostChildList.clear();
-
-        return intercepted;
-    }
-
-    @Override
-    public boolean onInterceptTouchEvent(MotionEvent ev) {
-        MotionEvent cancelEvent = null;
-
-        final int action = ev.getActionMasked();
-
-        // Make sure we reset in case we had missed a previous important event.
-        if (action == MotionEvent.ACTION_DOWN) {
-            resetTouchBehaviors(true);
-        }
-
-        final boolean intercepted = performIntercept(ev, TYPE_ON_INTERCEPT);
-
-        if (cancelEvent != null) {
-            cancelEvent.recycle();
-        }
-
-        if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
-            resetTouchBehaviors(true);
-        }
-
-        return intercepted;
-    }
-
-    @Override
-    public boolean onTouchEvent(MotionEvent ev) {
-        boolean handled = false;
-        boolean cancelSuper = false;
-        MotionEvent cancelEvent = null;
-
-        final int action = ev.getActionMasked();
-
-        if (mBehaviorTouchView != null || (cancelSuper = performIntercept(ev, TYPE_ON_TOUCH))) {
-            // Safe since performIntercept guarantees that
-            // mBehaviorTouchView != null if it returns true
-            final LayoutParams lp = (LayoutParams) mBehaviorTouchView.getLayoutParams();
-            final Behavior b = lp.getBehavior();
-            if (b != null) {
-                handled = b.onTouchEvent(this, mBehaviorTouchView, ev);
-            }
-        }
-
-        // Keep the super implementation correct
-        if (mBehaviorTouchView == null) {
-            handled |= super.onTouchEvent(ev);
-        } else if (cancelSuper) {
-            if (cancelEvent == null) {
-                final long now = SystemClock.uptimeMillis();
-                cancelEvent = MotionEvent.obtain(now, now,
-                        MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
-            }
-            super.onTouchEvent(cancelEvent);
-        }
-
-        if (!handled && action == MotionEvent.ACTION_DOWN) {
-
-        }
-
-        if (cancelEvent != null) {
-            cancelEvent.recycle();
-        }
-
-        if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
-            resetTouchBehaviors(false);
-        }
-
-        return handled;
-    }
-
-    @Override
-    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
-        super.requestDisallowInterceptTouchEvent(disallowIntercept);
-        if (disallowIntercept && !mDisallowInterceptReset) {
-            resetTouchBehaviors(false);
-            mDisallowInterceptReset = true;
-        }
-    }
-
-    private int getKeyline(int index) {
-        if (mKeylines == null) {
-            Log.e(TAG, "No keylines defined for " + this + " - attempted index lookup " + index);
-            return 0;
-        }
-
-        if (index < 0 || index >= mKeylines.length) {
-            Log.e(TAG, "Keyline index " + index + " out of range for " + this);
-            return 0;
-        }
-
-        return mKeylines[index];
-    }
-
-    static Behavior parseBehavior(Context context, AttributeSet attrs, String name) {
-        if (TextUtils.isEmpty(name)) {
-            return null;
-        }
-
-        final String fullName;
-        if (name.startsWith(".")) {
-            // Relative to the app package. Prepend the app package name.
-            fullName = context.getPackageName() + name;
-        } else if (name.indexOf('.') >= 0) {
-            // Fully qualified package name.
-            fullName = name;
-        } else {
-            // Assume stock behavior in this package (if we have one)
-            fullName = !TextUtils.isEmpty(WIDGET_PACKAGE_NAME)
-                    ? (WIDGET_PACKAGE_NAME + '.' + name)
-                    : name;
-        }
-
-        try {
-            Map<String, Constructor<Behavior>> constructors = sConstructors.get();
-            if (constructors == null) {
-                constructors = new HashMap<>();
-                sConstructors.set(constructors);
-            }
-            Constructor<Behavior> c = constructors.get(fullName);
-            if (c == null) {
-                final Class<Behavior> clazz = (Class<Behavior>) Class.forName(fullName, true,
-                        context.getClassLoader());
-                c = clazz.getConstructor(CONSTRUCTOR_PARAMS);
-                c.setAccessible(true);
-                constructors.put(fullName, c);
-            }
-            return c.newInstance(context, attrs);
-        } catch (Exception e) {
-            throw new RuntimeException("Could not inflate Behavior subclass " + fullName, e);
-        }
-    }
-
-    LayoutParams getResolvedLayoutParams(View child) {
-        final LayoutParams result = (LayoutParams) child.getLayoutParams();
-        if (!result.mBehaviorResolved) {
-            Class<?> childClass = child.getClass();
-            DefaultBehavior defaultBehavior = null;
-            while (childClass != null &&
-                    (defaultBehavior = childClass.getAnnotation(DefaultBehavior.class)) == null) {
-                childClass = childClass.getSuperclass();
-            }
-            if (defaultBehavior != null) {
-                try {
-                    result.setBehavior(
-                            defaultBehavior.value().getDeclaredConstructor().newInstance());
-                } catch (Exception e) {
-                    Log.e(TAG, "Default behavior class " + defaultBehavior.value().getName() +
-                            " could not be instantiated. Did you forget a default constructor?", e);
-                }
-            }
-            result.mBehaviorResolved = true;
-        }
-        return result;
-    }
-
-    private void prepareChildren() {
-        mDependencySortedChildren.clear();
-        mChildDag.clear();
-
-        for (int i = 0, count = getChildCount(); i < count; i++) {
-            final View view = getChildAt(i);
-
-            final LayoutParams lp = getResolvedLayoutParams(view);
-            lp.findAnchorView(this, view);
-
-            mChildDag.addNode(view);
-
-            // Now iterate again over the other children, adding any dependencies to the graph
-            for (int j = 0; j < count; j++) {
-                if (j == i) {
-                    continue;
-                }
-                final View other = getChildAt(j);
-                if (lp.dependsOn(this, view, other)) {
-                    if (!mChildDag.contains(other)) {
-                        // Make sure that the other node is added
-                        mChildDag.addNode(other);
-                    }
-                    // Now add the dependency to the graph
-                    mChildDag.addEdge(other, view);
-                }
-            }
-        }
-
-        // Finally add the sorted graph list to our list
-        mDependencySortedChildren.addAll(mChildDag.getSortedList());
-        // We also need to reverse the result since we want the start of the list to contain
-        // Views which have no dependencies, then dependent views after that
-        Collections.reverse(mDependencySortedChildren);
-    }
-
-    /**
-     * Retrieve the transformed bounding rect of an arbitrary descendant view.
-     * This does not need to be a direct child.
-     *
-     * @param descendant descendant view to reference
-     * @param out rect to set to the bounds of the descendant view
-     */
-    void getDescendantRect(View descendant, Rect out) {
-        ViewGroupUtils.getDescendantRect(this, descendant, out);
-    }
-
-    @Override
-    protected int getSuggestedMinimumWidth() {
-        return Math.max(super.getSuggestedMinimumWidth(), getPaddingLeft() + getPaddingRight());
-    }
-
-    @Override
-    protected int getSuggestedMinimumHeight() {
-        return Math.max(super.getSuggestedMinimumHeight(), getPaddingTop() + getPaddingBottom());
-    }
-
-    /**
-     * Called to measure each individual child view unless a
-     * {@link CoordinatorLayout.Behavior Behavior} is present. The Behavior may choose to delegate
-     * child measurement to this method.
-     *
-     * @param child the child to measure
-     * @param parentWidthMeasureSpec the width requirements for this view
-     * @param widthUsed extra space that has been used up by the parent
-     *        horizontally (possibly by other children of the parent)
-     * @param parentHeightMeasureSpec the height requirements for this view
-     * @param heightUsed extra space that has been used up by the parent
-     *        vertically (possibly by other children of the parent)
-     */
-    public void onMeasureChild(View child, int parentWidthMeasureSpec, int widthUsed,
-            int parentHeightMeasureSpec, int heightUsed) {
-        measureChildWithMargins(child, parentWidthMeasureSpec, widthUsed,
-                parentHeightMeasureSpec, heightUsed);
-    }
-
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        prepareChildren();
-        ensurePreDrawListener();
-
-        final int paddingLeft = getPaddingLeft();
-        final int paddingTop = getPaddingTop();
-        final int paddingRight = getPaddingRight();
-        final int paddingBottom = getPaddingBottom();
-        final int layoutDirection = ViewCompat.getLayoutDirection(this);
-        final boolean isRtl = layoutDirection == ViewCompat.LAYOUT_DIRECTION_RTL;
-        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
-        final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
-        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
-        final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
-
-        final int widthPadding = paddingLeft + paddingRight;
-        final int heightPadding = paddingTop + paddingBottom;
-        int widthUsed = getSuggestedMinimumWidth();
-        int heightUsed = getSuggestedMinimumHeight();
-        int childState = 0;
-
-        final boolean applyInsets = mLastInsets != null && ViewCompat.getFitsSystemWindows(this);
-
-        final int childCount = mDependencySortedChildren.size();
-        for (int i = 0; i < childCount; i++) {
-            final View child = mDependencySortedChildren.get(i);
-            if (child.getVisibility() == GONE) {
-                // If the child is GONE, skip...
-                continue;
-            }
-
-            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-
-            int keylineWidthUsed = 0;
-            if (lp.keyline >= 0 && widthMode != MeasureSpec.UNSPECIFIED) {
-                final int keylinePos = getKeyline(lp.keyline);
-                final int keylineGravity = GravityCompat.getAbsoluteGravity(
-                        resolveKeylineGravity(lp.gravity), layoutDirection)
-                        & Gravity.HORIZONTAL_GRAVITY_MASK;
-                if ((keylineGravity == Gravity.LEFT && !isRtl)
-                        || (keylineGravity == Gravity.RIGHT && isRtl)) {
-                    keylineWidthUsed = Math.max(0, widthSize - paddingRight - keylinePos);
-                } else if ((keylineGravity == Gravity.RIGHT && !isRtl)
-                        || (keylineGravity == Gravity.LEFT && isRtl)) {
-                    keylineWidthUsed = Math.max(0, keylinePos - paddingLeft);
-                }
-            }
-
-            int childWidthMeasureSpec = widthMeasureSpec;
-            int childHeightMeasureSpec = heightMeasureSpec;
-            if (applyInsets && !ViewCompat.getFitsSystemWindows(child)) {
-                // We're set to handle insets but this child isn't, so we will measure the
-                // child as if there are no insets
-                final int horizInsets = mLastInsets.getSystemWindowInsetLeft()
-                        + mLastInsets.getSystemWindowInsetRight();
-                final int vertInsets = mLastInsets.getSystemWindowInsetTop()
-                        + mLastInsets.getSystemWindowInsetBottom();
-
-                childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
-                        widthSize - horizInsets, widthMode);
-                childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
-                        heightSize - vertInsets, heightMode);
-            }
-
-            final Behavior b = lp.getBehavior();
-            if (b == null || !b.onMeasureChild(this, child, childWidthMeasureSpec, keylineWidthUsed,
-                    childHeightMeasureSpec, 0)) {
-                onMeasureChild(child, childWidthMeasureSpec, keylineWidthUsed,
-                        childHeightMeasureSpec, 0);
-            }
-
-            widthUsed = Math.max(widthUsed, widthPadding + child.getMeasuredWidth() +
-                    lp.leftMargin + lp.rightMargin);
-
-            heightUsed = Math.max(heightUsed, heightPadding + child.getMeasuredHeight() +
-                    lp.topMargin + lp.bottomMargin);
-            childState = View.combineMeasuredStates(childState, child.getMeasuredState());
-        }
-
-        final int width = View.resolveSizeAndState(widthUsed, widthMeasureSpec,
-                childState & View.MEASURED_STATE_MASK);
-        final int height = View.resolveSizeAndState(heightUsed, heightMeasureSpec,
-                childState << View.MEASURED_HEIGHT_STATE_SHIFT);
-        setMeasuredDimension(width, height);
-    }
-
-    private WindowInsetsCompat dispatchApplyWindowInsetsToBehaviors(WindowInsetsCompat insets) {
-        if (insets.isConsumed()) {
-            return insets;
-        }
-
-        for (int i = 0, z = getChildCount(); i < z; i++) {
-            final View child = getChildAt(i);
-            if (ViewCompat.getFitsSystemWindows(child)) {
-                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-                final Behavior b = lp.getBehavior();
-
-                if (b != null) {
-                    // If the view has a behavior, let it try first
-                    insets = b.onApplyWindowInsets(this, child, insets);
-                    if (insets.isConsumed()) {
-                        // If it consumed the insets, break
-                        break;
-                    }
-                }
-            }
-        }
-
-        return insets;
-    }
-
-    /**
-     * Called to lay out each individual child view unless a
-     * {@link CoordinatorLayout.Behavior Behavior} is present. The Behavior may choose to
-     * delegate child measurement to this method.
-     *
-     * @param child child view to lay out
-     * @param layoutDirection the resolved layout direction for the CoordinatorLayout, such as
-     *                        {@link ViewCompat#LAYOUT_DIRECTION_LTR} or
-     *                        {@link ViewCompat#LAYOUT_DIRECTION_RTL}.
-     */
-    public void onLayoutChild(View child, int layoutDirection) {
-        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-        if (lp.checkAnchorChanged()) {
-            throw new IllegalStateException("An anchor may not be changed after CoordinatorLayout"
-                    + " measurement begins before layout is complete.");
-        }
-        if (lp.mAnchorView != null) {
-            layoutChildWithAnchor(child, lp.mAnchorView, layoutDirection);
-        } else if (lp.keyline >= 0) {
-            layoutChildWithKeyline(child, lp.keyline, layoutDirection);
-        } else {
-            layoutChild(child, layoutDirection);
-        }
-    }
-
-    @Override
-    protected void onLayout(boolean changed, int l, int t, int r, int b) {
-        final int layoutDirection = ViewCompat.getLayoutDirection(this);
-        final int childCount = mDependencySortedChildren.size();
-        for (int i = 0; i < childCount; i++) {
-            final View child = mDependencySortedChildren.get(i);
-            if (child.getVisibility() == GONE) {
-                // If the child is GONE, skip...
-                continue;
-            }
-
-            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-            final Behavior behavior = lp.getBehavior();
-
-            if (behavior == null || !behavior.onLayoutChild(this, child, layoutDirection)) {
-                onLayoutChild(child, layoutDirection);
-            }
-        }
-    }
-
-    @Override
-    public void onDraw(Canvas c) {
-        super.onDraw(c);
-        if (mDrawStatusBarBackground && mStatusBarBackground != null) {
-            final int inset = mLastInsets != null ? mLastInsets.getSystemWindowInsetTop() : 0;
-            if (inset > 0) {
-                mStatusBarBackground.setBounds(0, 0, getWidth(), inset);
-                mStatusBarBackground.draw(c);
-            }
-        }
-    }
-
-    @Override
-    public void setFitsSystemWindows(boolean fitSystemWindows) {
-        super.setFitsSystemWindows(fitSystemWindows);
-        setupForInsets();
-    }
-
-    /**
-     * Mark the last known child position rect for the given child view.
-     * This will be used when checking if a child view's position has changed between frames.
-     * The rect used here should be one returned by
-     * {@link #getChildRect(android.view.View, boolean, android.graphics.Rect)}, with translation
-     * disabled.
-     *
-     * @param child child view to set for
-     * @param r rect to set
-     */
-    void recordLastChildRect(View child, Rect r) {
-        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-        lp.setLastChildRect(r);
-    }
-
-    /**
-     * Get the last known child rect recorded by
-     * {@link #recordLastChildRect(android.view.View, android.graphics.Rect)}.
-     *
-     * @param child child view to retrieve from
-     * @param out rect to set to the outpur values
-     */
-    void getLastChildRect(View child, Rect out) {
-        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-        out.set(lp.getLastChildRect());
-    }
-
-    /**
-     * Get the position rect for the given child. If the child has currently requested layout
-     * or has a visibility of GONE.
-     *
-     * @param child child view to check
-     * @param transform true to include transformation in the output rect, false to
-     *                        only account for the base position
-     * @param out rect to set to the output values
-     */
-    void getChildRect(View child, boolean transform, Rect out) {
-        if (child.isLayoutRequested() || child.getVisibility() == View.GONE) {
-            out.setEmpty();
-            return;
-        }
-        if (transform) {
-            getDescendantRect(child, out);
-        } else {
-            out.set(child.getLeft(), child.getTop(), child.getRight(), child.getBottom());
-        }
-    }
-
-    private void getDesiredAnchoredChildRectWithoutConstraints(View child, int layoutDirection,
-            Rect anchorRect, Rect out, LayoutParams lp, int childWidth, int childHeight) {
-        final int absGravity = GravityCompat.getAbsoluteGravity(
-                resolveAnchoredChildGravity(lp.gravity), layoutDirection);
-        final int absAnchorGravity = GravityCompat.getAbsoluteGravity(
-                resolveGravity(lp.anchorGravity),
-                layoutDirection);
-
-        final int hgrav = absGravity & Gravity.HORIZONTAL_GRAVITY_MASK;
-        final int vgrav = absGravity & Gravity.VERTICAL_GRAVITY_MASK;
-        final int anchorHgrav = absAnchorGravity & Gravity.HORIZONTAL_GRAVITY_MASK;
-        final int anchorVgrav = absAnchorGravity & Gravity.VERTICAL_GRAVITY_MASK;
-
-        int left;
-        int top;
-
-        // Align to the anchor. This puts us in an assumed right/bottom child view gravity.
-        // If this is not the case we will subtract out the appropriate portion of
-        // the child size below.
-        switch (anchorHgrav) {
-            default:
-            case Gravity.LEFT:
-                left = anchorRect.left;
-                break;
-            case Gravity.RIGHT:
-                left = anchorRect.right;
-                break;
-            case Gravity.CENTER_HORIZONTAL:
-                left = anchorRect.left + anchorRect.width() / 2;
-                break;
-        }
-
-        switch (anchorVgrav) {
-            default:
-            case Gravity.TOP:
-                top = anchorRect.top;
-                break;
-            case Gravity.BOTTOM:
-                top = anchorRect.bottom;
-                break;
-            case Gravity.CENTER_VERTICAL:
-                top = anchorRect.top + anchorRect.height() / 2;
-                break;
-        }
-
-        // Offset by the child view's gravity itself. The above assumed right/bottom gravity.
-        switch (hgrav) {
-            default:
-            case Gravity.LEFT:
-                left -= childWidth;
-                break;
-            case Gravity.RIGHT:
-                // Do nothing, we're already in position.
-                break;
-            case Gravity.CENTER_HORIZONTAL:
-                left -= childWidth / 2;
-                break;
-        }
-
-        switch (vgrav) {
-            default:
-            case Gravity.TOP:
-                top -= childHeight;
-                break;
-            case Gravity.BOTTOM:
-                // Do nothing, we're already in position.
-                break;
-            case Gravity.CENTER_VERTICAL:
-                top -= childHeight / 2;
-                break;
-        }
-
-        out.set(left, top, left + childWidth, top + childHeight);
-    }
-
-    private void constrainChildRect(LayoutParams lp, Rect out, int childWidth, int childHeight) {
-        final int width = getWidth();
-        final int height = getHeight();
-
-        // Obey margins and padding
-        int left = Math.max(getPaddingLeft() + lp.leftMargin,
-                Math.min(out.left,
-                        width - getPaddingRight() - childWidth - lp.rightMargin));
-        int top = Math.max(getPaddingTop() + lp.topMargin,
-                Math.min(out.top,
-                        height - getPaddingBottom() - childHeight - lp.bottomMargin));
-
-        out.set(left, top, left + childWidth, top + childHeight);
-    }
-
-    /**
-     * Calculate the desired child rect relative to an anchor rect, respecting both
-     * gravity and anchorGravity.
-     *
-     * @param child child view to calculate a rect for
-     * @param layoutDirection the desired layout direction for the CoordinatorLayout
-     * @param anchorRect rect in CoordinatorLayout coordinates of the anchor view area
-     * @param out rect to set to the output values
-     */
-    void getDesiredAnchoredChildRect(View child, int layoutDirection, Rect anchorRect, Rect out) {
-        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-        final int childWidth = child.getMeasuredWidth();
-        final int childHeight = child.getMeasuredHeight();
-        getDesiredAnchoredChildRectWithoutConstraints(child, layoutDirection, anchorRect, out, lp,
-                childWidth, childHeight);
-        constrainChildRect(lp, out, childWidth, childHeight);
-    }
-
-    /**
-     * CORE ASSUMPTION: anchor has been laid out by the time this is called for a given child view.
-     *
-     * @param child child to lay out
-     * @param anchor view to anchor child relative to; already laid out.
-     * @param layoutDirection ViewCompat constant for layout direction
-     */
-    private void layoutChildWithAnchor(View child, View anchor, int layoutDirection) {
-        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-
-        final Rect anchorRect = acquireTempRect();
-        final Rect childRect = acquireTempRect();
-        try {
-            getDescendantRect(anchor, anchorRect);
-            getDesiredAnchoredChildRect(child, layoutDirection, anchorRect, childRect);
-            child.layout(childRect.left, childRect.top, childRect.right, childRect.bottom);
-        } finally {
-            releaseTempRect(anchorRect);
-            releaseTempRect(childRect);
-        }
-    }
-
-    /**
-     * Lay out a child view with respect to a keyline.
-     *
-     * <p>The keyline represents a horizontal offset from the unpadded starting edge of
-     * the CoordinatorLayout. The child's gravity will affect how it is positioned with
-     * respect to the keyline.</p>
-     *
-     * @param child child to lay out
-     * @param keyline offset from the starting edge in pixels of the keyline to align with
-     * @param layoutDirection ViewCompat constant for layout direction
-     */
-    private void layoutChildWithKeyline(View child, int keyline, int layoutDirection) {
-        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-        final int absGravity = GravityCompat.getAbsoluteGravity(
-                resolveKeylineGravity(lp.gravity), layoutDirection);
-
-        final int hgrav = absGravity & Gravity.HORIZONTAL_GRAVITY_MASK;
-        final int vgrav = absGravity & Gravity.VERTICAL_GRAVITY_MASK;
-        final int width = getWidth();
-        final int height = getHeight();
-        final int childWidth = child.getMeasuredWidth();
-        final int childHeight = child.getMeasuredHeight();
-
-        if (layoutDirection == ViewCompat.LAYOUT_DIRECTION_RTL) {
-            keyline = width - keyline;
-        }
-
-        int left = getKeyline(keyline) - childWidth;
-        int top = 0;
-
-        switch (hgrav) {
-            default:
-            case Gravity.LEFT:
-                // Nothing to do.
-                break;
-            case Gravity.RIGHT:
-                left += childWidth;
-                break;
-            case Gravity.CENTER_HORIZONTAL:
-                left += childWidth / 2;
-                break;
-        }
-
-        switch (vgrav) {
-            default:
-            case Gravity.TOP:
-                // Do nothing, we're already in position.
-                break;
-            case Gravity.BOTTOM:
-                top += childHeight;
-                break;
-            case Gravity.CENTER_VERTICAL:
-                top += childHeight / 2;
-                break;
-        }
-
-        // Obey margins and padding
-        left = Math.max(getPaddingLeft() + lp.leftMargin,
-                Math.min(left,
-                        width - getPaddingRight() - childWidth - lp.rightMargin));
-        top = Math.max(getPaddingTop() + lp.topMargin,
-                Math.min(top,
-                        height - getPaddingBottom() - childHeight - lp.bottomMargin));
-
-        child.layout(left, top, left + childWidth, top + childHeight);
-    }
-
-    /**
-     * Lay out a child view with no special handling. This will position the child as
-     * if it were within a FrameLayout or similar simple frame.
-     *
-     * @param child child view to lay out
-     * @param layoutDirection ViewCompat constant for the desired layout direction
-     */
-    private void layoutChild(View child, int layoutDirection) {
-        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-        final Rect parent = acquireTempRect();
-        parent.set(getPaddingLeft() + lp.leftMargin,
-                getPaddingTop() + lp.topMargin,
-                getWidth() - getPaddingRight() - lp.rightMargin,
-                getHeight() - getPaddingBottom() - lp.bottomMargin);
-
-        if (mLastInsets != null && ViewCompat.getFitsSystemWindows(this)
-                && !ViewCompat.getFitsSystemWindows(child)) {
-            // If we're set to handle insets but this child isn't, then it has been measured as
-            // if there are no insets. We need to lay it out to match.
-            parent.left += mLastInsets.getSystemWindowInsetLeft();
-            parent.top += mLastInsets.getSystemWindowInsetTop();
-            parent.right -= mLastInsets.getSystemWindowInsetRight();
-            parent.bottom -= mLastInsets.getSystemWindowInsetBottom();
-        }
-
-        final Rect out = acquireTempRect();
-        GravityCompat.apply(resolveGravity(lp.gravity), child.getMeasuredWidth(),
-                child.getMeasuredHeight(), parent, out, layoutDirection);
-        child.layout(out.left, out.top, out.right, out.bottom);
-
-        releaseTempRect(parent);
-        releaseTempRect(out);
-    }
-
-    /**
-     * Return the given gravity value, but if either or both of the axes doesn't have any gravity
-     * specified, the default value (start or top) is specified. This should be used for children
-     * that are not anchored to another view or a keyline.
-     */
-    private static int resolveGravity(int gravity) {
-        if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.NO_GRAVITY) {
-            gravity |= GravityCompat.START;
-        }
-        if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.NO_GRAVITY) {
-            gravity |= Gravity.TOP;
-        }
-        return gravity;
-    }
-
-    /**
-     * Return the given gravity value or the default if the passed value is NO_GRAVITY.
-     * This should be used for children that are positioned relative to a keyline.
-     */
-    private static int resolveKeylineGravity(int gravity) {
-        return gravity == Gravity.NO_GRAVITY ? GravityCompat.END | Gravity.TOP : gravity;
-    }
-
-    /**
-     * Return the given gravity value or the default if the passed value is NO_GRAVITY.
-     * This should be used for children that are anchored to another view.
-     */
-    private static int resolveAnchoredChildGravity(int gravity) {
-        return gravity == Gravity.NO_GRAVITY ? Gravity.CENTER : gravity;
-    }
-
-    @Override
-    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
-        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-        if (lp.mBehavior != null) {
-            final float scrimAlpha = lp.mBehavior.getScrimOpacity(this, child);
-            if (scrimAlpha > 0f) {
-                if (mScrimPaint == null) {
-                    mScrimPaint = new Paint();
-                }
-                mScrimPaint.setColor(lp.mBehavior.getScrimColor(this, child));
-                mScrimPaint.setAlpha(MathUtils.clamp(Math.round(255 * scrimAlpha), 0, 255));
-
-                final int saved = canvas.save();
-                if (child.isOpaque()) {
-                    // If the child is opaque, there is no need to draw behind it so we'll inverse
-                    // clip the canvas
-                    canvas.clipRect(child.getLeft(), child.getTop(), child.getRight(),
-                            child.getBottom(), Region.Op.DIFFERENCE);
-                }
-                // Now draw the rectangle for the scrim
-                canvas.drawRect(getPaddingLeft(), getPaddingTop(),
-                        getWidth() - getPaddingRight(), getHeight() - getPaddingBottom(),
-                        mScrimPaint);
-                canvas.restoreToCount(saved);
-            }
-        }
-        return super.drawChild(canvas, child, drawingTime);
-    }
-
-    /**
-     * Dispatch any dependent view changes to the relevant {@link Behavior} instances.
-     *
-     * Usually run as part of the pre-draw step when at least one child view has a reported
-     * dependency on another view. This allows CoordinatorLayout to account for layout
-     * changes and animations that occur outside of the normal layout pass.
-     *
-     * It can also be ran as part of the nested scrolling dispatch to ensure that any offsetting
-     * is completed within the correct coordinate window.
-     *
-     * The offsetting behavior implemented here does not store the computed offset in
-     * the LayoutParams; instead it expects that the layout process will always reconstruct
-     * the proper positioning.
-     *
-     * @param type the type of event which has caused this call
-     */
-    final void onChildViewsChanged(@DispatchChangeEvent final int type) {
-        final int layoutDirection = ViewCompat.getLayoutDirection(this);
-        final int childCount = mDependencySortedChildren.size();
-        final Rect inset = acquireTempRect();
-        final Rect drawRect = acquireTempRect();
-        final Rect lastDrawRect = acquireTempRect();
-
-        for (int i = 0; i < childCount; i++) {
-            final View child = mDependencySortedChildren.get(i);
-            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-            if (type == EVENT_PRE_DRAW && child.getVisibility() == View.GONE) {
-                // Do not try to update GONE child views in pre draw updates.
-                continue;
-            }
-
-            // Check child views before for anchor
-            for (int j = 0; j < i; j++) {
-                final View checkChild = mDependencySortedChildren.get(j);
-
-                if (lp.mAnchorDirectChild == checkChild) {
-                    offsetChildToAnchor(child, layoutDirection);
-                }
-            }
-
-            // Get the current draw rect of the view
-            getChildRect(child, true, drawRect);
-
-            // Accumulate inset sizes
-            if (lp.insetEdge != Gravity.NO_GRAVITY && !drawRect.isEmpty()) {
-                final int absInsetEdge = GravityCompat.getAbsoluteGravity(
-                        lp.insetEdge, layoutDirection);
-                switch (absInsetEdge & Gravity.VERTICAL_GRAVITY_MASK) {
-                    case Gravity.TOP:
-                        inset.top = Math.max(inset.top, drawRect.bottom);
-                        break;
-                    case Gravity.BOTTOM:
-                        inset.bottom = Math.max(inset.bottom, getHeight() - drawRect.top);
-                        break;
-                }
-                switch (absInsetEdge & Gravity.HORIZONTAL_GRAVITY_MASK) {
-                    case Gravity.LEFT:
-                        inset.left = Math.max(inset.left, drawRect.right);
-                        break;
-                    case Gravity.RIGHT:
-                        inset.right = Math.max(inset.right, getWidth() - drawRect.left);
-                        break;
-                }
-            }
-
-            // Dodge inset edges if necessary
-            if (lp.dodgeInsetEdges != Gravity.NO_GRAVITY && child.getVisibility() == View.VISIBLE) {
-                offsetChildByInset(child, inset, layoutDirection);
-            }
-
-            if (type != EVENT_VIEW_REMOVED) {
-                // Did it change? if not continue
-                getLastChildRect(child, lastDrawRect);
-                if (lastDrawRect.equals(drawRect)) {
-                    continue;
-                }
-                recordLastChildRect(child, drawRect);
-            }
-
-            // Update any behavior-dependent views for the change
-            for (int j = i + 1; j < childCount; j++) {
-                final View checkChild = mDependencySortedChildren.get(j);
-                final LayoutParams checkLp = (LayoutParams) checkChild.getLayoutParams();
-                final Behavior b = checkLp.getBehavior();
-
-                if (b != null && b.layoutDependsOn(this, checkChild, child)) {
-                    if (type == EVENT_PRE_DRAW && checkLp.getChangedAfterNestedScroll()) {
-                        // If this is from a pre-draw and we have already been changed
-                        // from a nested scroll, skip the dispatch and reset the flag
-                        checkLp.resetChangedAfterNestedScroll();
-                        continue;
-                    }
-
-                    final boolean handled;
-                    switch (type) {
-                        case EVENT_VIEW_REMOVED:
-                            // EVENT_VIEW_REMOVED means that we need to dispatch
-                            // onDependentViewRemoved() instead
-                            b.onDependentViewRemoved(this, checkChild, child);
-                            handled = true;
-                            break;
-                        default:
-                            // Otherwise we dispatch onDependentViewChanged()
-                            handled = b.onDependentViewChanged(this, checkChild, child);
-                            break;
-                    }
-
-                    if (type == EVENT_NESTED_SCROLL) {
-                        // If this is from a nested scroll, set the flag so that we may skip
-                        // any resulting onPreDraw dispatch (if needed)
-                        checkLp.setChangedAfterNestedScroll(handled);
-                    }
-                }
-            }
-        }
-
-        releaseTempRect(inset);
-        releaseTempRect(drawRect);
-        releaseTempRect(lastDrawRect);
-    }
-
-    private void offsetChildByInset(final View child, final Rect inset, final int layoutDirection) {
-        if (!ViewCompat.isLaidOut(child)) {
-            // The view has not been laid out yet, so we can't obtain its bounds.
-            return;
-        }
-
-        if (child.getWidth() <= 0 || child.getHeight() <= 0) {
-            // Bounds are empty so there is nothing to dodge against, skip...
-            return;
-        }
-
-        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-        final Behavior behavior = lp.getBehavior();
-        final Rect dodgeRect = acquireTempRect();
-        final Rect bounds = acquireTempRect();
-        bounds.set(child.getLeft(), child.getTop(), child.getRight(), child.getBottom());
-
-        if (behavior != null && behavior.getInsetDodgeRect(this, child, dodgeRect)) {
-            // Make sure that the rect is within the view's bounds
-            if (!bounds.contains(dodgeRect)) {
-                throw new IllegalArgumentException("Rect should be within the child's bounds."
-                        + " Rect:" + dodgeRect.toShortString()
-                        + " | Bounds:" + bounds.toShortString());
-            }
-        } else {
-            dodgeRect.set(bounds);
-        }
-
-        // We can release the bounds rect now
-        releaseTempRect(bounds);
-
-        if (dodgeRect.isEmpty()) {
-            // Rect is empty so there is nothing to dodge against, skip...
-            releaseTempRect(dodgeRect);
-            return;
-        }
-
-        final int absDodgeInsetEdges = GravityCompat.getAbsoluteGravity(lp.dodgeInsetEdges,
-                layoutDirection);
-
-        boolean offsetY = false;
-        if ((absDodgeInsetEdges & Gravity.TOP) == Gravity.TOP) {
-            int distance = dodgeRect.top - lp.topMargin - lp.mInsetOffsetY;
-            if (distance < inset.top) {
-                setInsetOffsetY(child, inset.top - distance);
-                offsetY = true;
-            }
-        }
-        if ((absDodgeInsetEdges & Gravity.BOTTOM) == Gravity.BOTTOM) {
-            int distance = getHeight() - dodgeRect.bottom - lp.bottomMargin + lp.mInsetOffsetY;
-            if (distance < inset.bottom) {
-                setInsetOffsetY(child, distance - inset.bottom);
-                offsetY = true;
-            }
-        }
-        if (!offsetY) {
-            setInsetOffsetY(child, 0);
-        }
-
-        boolean offsetX = false;
-        if ((absDodgeInsetEdges & Gravity.LEFT) == Gravity.LEFT) {
-            int distance = dodgeRect.left - lp.leftMargin - lp.mInsetOffsetX;
-            if (distance < inset.left) {
-                setInsetOffsetX(child, inset.left - distance);
-                offsetX = true;
-            }
-        }
-        if ((absDodgeInsetEdges & Gravity.RIGHT) == Gravity.RIGHT) {
-            int distance = getWidth() - dodgeRect.right - lp.rightMargin + lp.mInsetOffsetX;
-            if (distance < inset.right) {
-                setInsetOffsetX(child, distance - inset.right);
-                offsetX = true;
-            }
-        }
-        if (!offsetX) {
-            setInsetOffsetX(child, 0);
-        }
-
-        releaseTempRect(dodgeRect);
-    }
-
-    private void setInsetOffsetX(View child, int offsetX) {
-        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-        if (lp.mInsetOffsetX != offsetX) {
-            final int dx = offsetX - lp.mInsetOffsetX;
-            ViewCompat.offsetLeftAndRight(child, dx);
-            lp.mInsetOffsetX = offsetX;
-        }
-    }
-
-    private void setInsetOffsetY(View child, int offsetY) {
-        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-        if (lp.mInsetOffsetY != offsetY) {
-            final int dy = offsetY - lp.mInsetOffsetY;
-            ViewCompat.offsetTopAndBottom(child, dy);
-            lp.mInsetOffsetY = offsetY;
-        }
-    }
-
-    /**
-     * Allows the caller to manually dispatch
-     * {@link Behavior#onDependentViewChanged(CoordinatorLayout, View, View)} to the associated
-     * {@link Behavior} instances of views which depend on the provided {@link View}.
-     *
-     * <p>You should not normally need to call this method as the it will be automatically done
-     * when the view has changed.
-     *
-     * @param view the View to find dependents of to dispatch the call.
-     */
-    public void dispatchDependentViewsChanged(View view) {
-        final List<View> dependents = mChildDag.getIncomingEdges(view);
-        if (dependents != null && !dependents.isEmpty()) {
-            for (int i = 0; i < dependents.size(); i++) {
-                final View child = dependents.get(i);
-                CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams)
-                        child.getLayoutParams();
-                CoordinatorLayout.Behavior b = lp.getBehavior();
-                if (b != null) {
-                    b.onDependentViewChanged(this, child, view);
-                }
-            }
-        }
-    }
-
-    /**
-     * Returns the list of views which the provided view depends on. Do not store this list as its
-     * contents may not be valid beyond the caller.
-     *
-     * @param child the view to find dependencies for.
-     *
-     * @return the list of views which {@code child} depends on.
-     */
-    @NonNull
-    public List<View> getDependencies(@NonNull View child) {
-        final List<View> dependencies = mChildDag.getOutgoingEdges(child);
-        mTempDependenciesList.clear();
-        if (dependencies != null) {
-            mTempDependenciesList.addAll(dependencies);
-        }
-        return mTempDependenciesList;
-    }
-
-    /**
-     * Returns the list of views which depend on the provided view. Do not store this list as its
-     * contents may not be valid beyond the caller.
-     *
-     * @param child the view to find dependents of.
-     *
-     * @return the list of views which depend on {@code child}.
-     */
-    @NonNull
-    public List<View> getDependents(@NonNull View child) {
-        final List<View> edges = mChildDag.getIncomingEdges(child);
-        mTempDependenciesList.clear();
-        if (edges != null) {
-            mTempDependenciesList.addAll(edges);
-        }
-        return mTempDependenciesList;
-    }
-
-    @VisibleForTesting
-    final List<View> getDependencySortedChildren() {
-        prepareChildren();
-        return Collections.unmodifiableList(mDependencySortedChildren);
-    }
-
-    /**
-     * Add or remove the pre-draw listener as necessary.
-     */
-    void ensurePreDrawListener() {
-        boolean hasDependencies = false;
-        final int childCount = getChildCount();
-        for (int i = 0; i < childCount; i++) {
-            final View child = getChildAt(i);
-            if (hasDependencies(child)) {
-                hasDependencies = true;
-                break;
-            }
-        }
-
-        if (hasDependencies != mNeedsPreDrawListener) {
-            if (hasDependencies) {
-                addPreDrawListener();
-            } else {
-                removePreDrawListener();
-            }
-        }
-    }
-
-    /**
-     * Check if the given child has any layout dependencies on other child views.
-     */
-    private boolean hasDependencies(View child) {
-        return mChildDag.hasOutgoingEdges(child);
-    }
-
-    /**
-     * Add the pre-draw listener if we're attached to a window and mark that we currently
-     * need it when attached.
-     */
-    void addPreDrawListener() {
-        if (mIsAttachedToWindow) {
-            // Add the listener
-            if (mOnPreDrawListener == null) {
-                mOnPreDrawListener = new OnPreDrawListener();
-            }
-            final ViewTreeObserver vto = getViewTreeObserver();
-            vto.addOnPreDrawListener(mOnPreDrawListener);
-        }
-
-        // Record that we need the listener regardless of whether or not we're attached.
-        // We'll add the real listener when we become attached.
-        mNeedsPreDrawListener = true;
-    }
-
-    /**
-     * Remove the pre-draw listener if we're attached to a window and mark that we currently
-     * do not need it when attached.
-     */
-    void removePreDrawListener() {
-        if (mIsAttachedToWindow) {
-            if (mOnPreDrawListener != null) {
-                final ViewTreeObserver vto = getViewTreeObserver();
-                vto.removeOnPreDrawListener(mOnPreDrawListener);
-            }
-        }
-        mNeedsPreDrawListener = false;
-    }
-
-    /**
-     * Adjust the child left, top, right, bottom rect to the correct anchor view position,
-     * respecting gravity and anchor gravity.
-     *
-     * Note that child translation properties are ignored in this process, allowing children
-     * to be animated away from their anchor. However, if the anchor view is animated,
-     * the child will be offset to match the anchor's translated position.
-     */
-    void offsetChildToAnchor(View child, int layoutDirection) {
-        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-        if (lp.mAnchorView != null) {
-            final Rect anchorRect = acquireTempRect();
-            final Rect childRect = acquireTempRect();
-            final Rect desiredChildRect = acquireTempRect();
-
-            getDescendantRect(lp.mAnchorView, anchorRect);
-            getChildRect(child, false, childRect);
-
-            int childWidth = child.getMeasuredWidth();
-            int childHeight = child.getMeasuredHeight();
-            getDesiredAnchoredChildRectWithoutConstraints(child, layoutDirection, anchorRect,
-                    desiredChildRect, lp, childWidth, childHeight);
-            boolean changed = desiredChildRect.left != childRect.left ||
-                    desiredChildRect.top != childRect.top;
-            constrainChildRect(lp, desiredChildRect, childWidth, childHeight);
-
-            final int dx = desiredChildRect.left - childRect.left;
-            final int dy = desiredChildRect.top - childRect.top;
-
-            if (dx != 0) {
-                ViewCompat.offsetLeftAndRight(child, dx);
-            }
-            if (dy != 0) {
-                ViewCompat.offsetTopAndBottom(child, dy);
-            }
-
-            if (changed) {
-                // If we have needed to move, make sure to notify the child's Behavior
-                final Behavior b = lp.getBehavior();
-                if (b != null) {
-                    b.onDependentViewChanged(this, child, lp.mAnchorView);
-                }
-            }
-
-            releaseTempRect(anchorRect);
-            releaseTempRect(childRect);
-            releaseTempRect(desiredChildRect);
-        }
-    }
-
-    /**
-     * Check if a given point in the CoordinatorLayout's coordinates are within the view bounds
-     * of the given direct child view.
-     *
-     * @param child child view to test
-     * @param x X coordinate to test, in the CoordinatorLayout's coordinate system
-     * @param y Y coordinate to test, in the CoordinatorLayout's coordinate system
-     * @return true if the point is within the child view's bounds, false otherwise
-     */
-    public boolean isPointInChildBounds(View child, int x, int y) {
-        final Rect r = acquireTempRect();
-        getDescendantRect(child, r);
-        try {
-            return r.contains(x, y);
-        } finally {
-            releaseTempRect(r);
-        }
-    }
-
-    /**
-     * Check whether two views overlap each other. The views need to be descendants of this
-     * {@link CoordinatorLayout} in the view hierarchy.
-     *
-     * @param first first child view to test
-     * @param second second child view to test
-     * @return true if both views are visible and overlap each other
-     */
-    public boolean doViewsOverlap(View first, View second) {
-        if (first.getVisibility() == VISIBLE && second.getVisibility() == VISIBLE) {
-            final Rect firstRect = acquireTempRect();
-            getChildRect(first, first.getParent() != this, firstRect);
-            final Rect secondRect = acquireTempRect();
-            getChildRect(second, second.getParent() != this, secondRect);
-            try {
-                return !(firstRect.left > secondRect.right || firstRect.top > secondRect.bottom
-                        || firstRect.right < secondRect.left || firstRect.bottom < secondRect.top);
-            } finally {
-                releaseTempRect(firstRect);
-                releaseTempRect(secondRect);
-            }
-        }
-        return false;
-    }
-
-    @Override
-    public LayoutParams generateLayoutParams(AttributeSet attrs) {
-        return new LayoutParams(getContext(), attrs);
-    }
-
-    @Override
-    protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
-        if (p instanceof LayoutParams) {
-            return new LayoutParams((LayoutParams) p);
-        } else if (p instanceof MarginLayoutParams) {
-            return new LayoutParams((MarginLayoutParams) p);
-        }
-        return new LayoutParams(p);
-    }
-
-    @Override
-    protected LayoutParams generateDefaultLayoutParams() {
-        return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
-    }
-
-    @Override
-    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
-        return p instanceof LayoutParams && super.checkLayoutParams(p);
-    }
-
-    @Override
-    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
-        return onStartNestedScroll(child, target, nestedScrollAxes, ViewCompat.TYPE_TOUCH);
-    }
-
-    @Override
-    public boolean onStartNestedScroll(View child, View target, int axes, int type) {
-        boolean handled = false;
-
-        final int childCount = getChildCount();
-        for (int i = 0; i < childCount; i++) {
-            final View view = getChildAt(i);
-            if (view.getVisibility() == View.GONE) {
-                // If it's GONE, don't dispatch
-                continue;
-            }
-            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
-            final Behavior viewBehavior = lp.getBehavior();
-            if (viewBehavior != null) {
-                final boolean accepted = viewBehavior.onStartNestedScroll(this, view, child,
-                        target, axes, type);
-                handled |= accepted;
-                lp.setNestedScrollAccepted(type, accepted);
-            } else {
-                lp.setNestedScrollAccepted(type, false);
-            }
-        }
-        return handled;
-    }
-
-    @Override
-    public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes) {
-        onNestedScrollAccepted(child, target, nestedScrollAxes, ViewCompat.TYPE_TOUCH);
-    }
-
-    @Override
-    public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes, int type) {
-        mNestedScrollingParentHelper.onNestedScrollAccepted(child, target, nestedScrollAxes, type);
-        mNestedScrollingTarget = target;
-
-        final int childCount = getChildCount();
-        for (int i = 0; i < childCount; i++) {
-            final View view = getChildAt(i);
-            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
-            if (!lp.isNestedScrollAccepted(type)) {
-                continue;
-            }
-
-            final Behavior viewBehavior = lp.getBehavior();
-            if (viewBehavior != null) {
-                viewBehavior.onNestedScrollAccepted(this, view, child, target,
-                        nestedScrollAxes, type);
-            }
-        }
-    }
-
-    @Override
-    public void onStopNestedScroll(View target) {
-        onStopNestedScroll(target, ViewCompat.TYPE_TOUCH);
-    }
-
-    @Override
-    public void onStopNestedScroll(View target, int type) {
-        mNestedScrollingParentHelper.onStopNestedScroll(target, type);
-
-        final int childCount = getChildCount();
-        for (int i = 0; i < childCount; i++) {
-            final View view = getChildAt(i);
-            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
-            if (!lp.isNestedScrollAccepted(type)) {
-                continue;
-            }
-
-            final Behavior viewBehavior = lp.getBehavior();
-            if (viewBehavior != null) {
-                viewBehavior.onStopNestedScroll(this, view, target, type);
-            }
-            lp.resetNestedScroll(type);
-            lp.resetChangedAfterNestedScroll();
-        }
-        mNestedScrollingTarget = null;
-    }
-
-    @Override
-    public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
-            int dxUnconsumed, int dyUnconsumed) {
-        onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed,
-                ViewCompat.TYPE_TOUCH);
-    }
-
-    @Override
-    public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
-            int dxUnconsumed, int dyUnconsumed, int type) {
-        final int childCount = getChildCount();
-        boolean accepted = false;
-
-        for (int i = 0; i < childCount; i++) {
-            final View view = getChildAt(i);
-            if (view.getVisibility() == GONE) {
-                // If the child is GONE, skip...
-                continue;
-            }
-
-            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
-            if (!lp.isNestedScrollAccepted(type)) {
-                continue;
-            }
-
-            final Behavior viewBehavior = lp.getBehavior();
-            if (viewBehavior != null) {
-                viewBehavior.onNestedScroll(this, view, target, dxConsumed, dyConsumed,
-                        dxUnconsumed, dyUnconsumed, type);
-                accepted = true;
-            }
-        }
-
-        if (accepted) {
-            onChildViewsChanged(EVENT_NESTED_SCROLL);
-        }
-    }
-
-    @Override
-    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
-        onNestedPreScroll(target, dx, dy, consumed, ViewCompat.TYPE_TOUCH);
-    }
-
-    @Override
-    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed, int  type) {
-        int xConsumed = 0;
-        int yConsumed = 0;
-        boolean accepted = false;
-
-        final int childCount = getChildCount();
-        for (int i = 0; i < childCount; i++) {
-            final View view = getChildAt(i);
-            if (view.getVisibility() == GONE) {
-                // If the child is GONE, skip...
-                continue;
-            }
-
-            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
-            if (!lp.isNestedScrollAccepted(type)) {
-                continue;
-            }
-
-            final Behavior viewBehavior = lp.getBehavior();
-            if (viewBehavior != null) {
-                mTempIntPair[0] = mTempIntPair[1] = 0;
-                viewBehavior.onNestedPreScroll(this, view, target, dx, dy, mTempIntPair, type);
-
-                xConsumed = dx > 0 ? Math.max(xConsumed, mTempIntPair[0])
-                        : Math.min(xConsumed, mTempIntPair[0]);
-                yConsumed = dy > 0 ? Math.max(yConsumed, mTempIntPair[1])
-                        : Math.min(yConsumed, mTempIntPair[1]);
-
-                accepted = true;
-            }
-        }
-
-        consumed[0] = xConsumed;
-        consumed[1] = yConsumed;
-
-        if (accepted) {
-            onChildViewsChanged(EVENT_NESTED_SCROLL);
-        }
-    }
-
-    @Override
-    public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
-        boolean handled = false;
-
-        final int childCount = getChildCount();
-        for (int i = 0; i < childCount; i++) {
-            final View view = getChildAt(i);
-            if (view.getVisibility() == GONE) {
-                // If the child is GONE, skip...
-                continue;
-            }
-
-            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
-            if (!lp.isNestedScrollAccepted(ViewCompat.TYPE_TOUCH)) {
-                continue;
-            }
-
-            final Behavior viewBehavior = lp.getBehavior();
-            if (viewBehavior != null) {
-                handled |= viewBehavior.onNestedFling(this, view, target, velocityX, velocityY,
-                        consumed);
-            }
-        }
-        if (handled) {
-            onChildViewsChanged(EVENT_NESTED_SCROLL);
-        }
-        return handled;
-    }
-
-    @Override
-    public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
-        boolean handled = false;
-
-        final int childCount = getChildCount();
-        for (int i = 0; i < childCount; i++) {
-            final View view = getChildAt(i);
-            if (view.getVisibility() == GONE) {
-                // If the child is GONE, skip...
-                continue;
-            }
-
-            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
-            if (!lp.isNestedScrollAccepted(ViewCompat.TYPE_TOUCH)) {
-                continue;
-            }
-
-            final Behavior viewBehavior = lp.getBehavior();
-            if (viewBehavior != null) {
-                handled |= viewBehavior.onNestedPreFling(this, view, target, velocityX, velocityY);
-            }
-        }
-        return handled;
-    }
-
-    @Override
-    public int getNestedScrollAxes() {
-        return mNestedScrollingParentHelper.getNestedScrollAxes();
-    }
-
-    class OnPreDrawListener implements ViewTreeObserver.OnPreDrawListener {
-        @Override
-        public boolean onPreDraw() {
-            onChildViewsChanged(EVENT_PRE_DRAW);
-            return true;
-        }
-    }
-
-    /**
-     * Sorts child views with higher Z values to the beginning of a collection.
-     */
-    static class ViewElevationComparator implements Comparator<View> {
-        @Override
-        public int compare(View lhs, View rhs) {
-            final float lz = ViewCompat.getZ(lhs);
-            final float rz = ViewCompat.getZ(rhs);
-            if (lz > rz) {
-                return -1;
-            } else if (lz < rz) {
-                return 1;
-            }
-            return 0;
-        }
-    }
-
-    /**
-     * Defines the default {@link Behavior} of a {@link View} class.
-     *
-     * <p>When writing a custom view, use this annotation to define the default behavior
-     * when used as a direct child of an {@link CoordinatorLayout}. The default behavior
-     * can be overridden using {@link LayoutParams#setBehavior}.</p>
-     *
-     * <p>Example: <code>@DefaultBehavior(MyBehavior.class)</code></p>
-     */
-    @Retention(RetentionPolicy.RUNTIME)
-    public @interface DefaultBehavior {
-        Class<? extends Behavior> value();
-    }
-
-    /**
-     * Interaction behavior plugin for child views of {@link CoordinatorLayout}.
-     *
-     * <p>A Behavior implements one or more interactions that a user can take on a child view.
-     * These interactions may include drags, swipes, flings, or any other gestures.</p>
-     *
-     * @param <V> The View type that this Behavior operates on
-     */
-    public static abstract class Behavior<V extends View> {
-
-        /**
-         * Default constructor for instantiating Behaviors.
-         */
-        public Behavior() {
-        }
-
-        /**
-         * Default constructor for inflating Behaviors from layout. The Behavior will have
-         * the opportunity to parse specially defined layout parameters. These parameters will
-         * appear on the child view tag.
-         *
-         * @param context
-         * @param attrs
-         */
-        public Behavior(Context context, AttributeSet attrs) {
-        }
-
-        /**
-         * Called when the Behavior has been attached to a LayoutParams instance.
-         *
-         * <p>This will be called after the LayoutParams has been instantiated and can be
-         * modified.</p>
-         *
-         * @param params the LayoutParams instance that this Behavior has been attached to
-         */
-        public void onAttachedToLayoutParams(@NonNull CoordinatorLayout.LayoutParams params) {
-        }
-
-        /**
-         * Called when the Behavior has been detached from its holding LayoutParams instance.
-         *
-         * <p>This will only be called if the Behavior has been explicitly removed from the
-         * LayoutParams instance via {@link LayoutParams#setBehavior(Behavior)}. It will not be
-         * called if the associated view is removed from the CoordinatorLayout or similar.</p>
-         */
-        public void onDetachedFromLayoutParams() {
-        }
-
-        /**
-         * Respond to CoordinatorLayout touch events before they are dispatched to child views.
-         *
-         * <p>Behaviors can use this to monitor inbound touch events until one decides to
-         * intercept the rest of the event stream to take an action on its associated child view.
-         * This method will return false until it detects the proper intercept conditions, then
-         * return true once those conditions have occurred.</p>
-         *
-         * <p>Once a Behavior intercepts touch events, the rest of the event stream will
-         * be sent to the {@link #onTouchEvent} method.</p>
-         *
-         * <p>This method will be called regardless of the visibility of the associated child
-         * of the behavior. If you only wish to handle touch events when the child is visible, you
-         * should add a check to {@link View#isShown()} on the given child.</p>
-         *
-         * <p>The default implementation of this method always returns false.</p>
-         *
-         * @param parent the parent view currently receiving this touch event
-         * @param child the child view associated with this Behavior
-         * @param ev the MotionEvent describing the touch event being processed
-         * @return true if this Behavior would like to intercept and take over the event stream.
-         *         The default always returns false.
-         */
-        public boolean onInterceptTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev) {
-            return false;
-        }
-
-        /**
-         * Respond to CoordinatorLayout touch events after this Behavior has started
-         * {@link #onInterceptTouchEvent intercepting} them.
-         *
-         * <p>Behaviors may intercept touch events in order to help the CoordinatorLayout
-         * manipulate its child views. For example, a Behavior may allow a user to drag a
-         * UI pane open or closed. This method should perform actual mutations of view
-         * layout state.</p>
-         *
-         * <p>This method will be called regardless of the visibility of the associated child
-         * of the behavior. If you only wish to handle touch events when the child is visible, you
-         * should add a check to {@link View#isShown()} on the given child.</p>
-         *
-         * @param parent the parent view currently receiving this touch event
-         * @param child the child view associated with this Behavior
-         * @param ev the MotionEvent describing the touch event being processed
-         * @return true if this Behavior handled this touch event and would like to continue
-         *         receiving events in this stream. The default always returns false.
-         */
-        public boolean onTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev) {
-            return false;
-        }
-
-        /**
-         * Supply a scrim color that will be painted behind the associated child view.
-         *
-         * <p>A scrim may be used to indicate that the other elements beneath it are not currently
-         * interactive or actionable, drawing user focus and attention to the views above the scrim.
-         * </p>
-         *
-         * <p>The default implementation returns {@link Color#BLACK}.</p>
-         *
-         * @param parent the parent view of the given child
-         * @param child the child view above the scrim
-         * @return the desired scrim color in 0xAARRGGBB format. The default return value is
-         *         {@link Color#BLACK}.
-         * @see #getScrimOpacity(CoordinatorLayout, android.view.View)
-         */
-        @ColorInt
-        public int getScrimColor(CoordinatorLayout parent, V child) {
-            return Color.BLACK;
-        }
-
-        /**
-         * Determine the current opacity of the scrim behind a given child view
-         *
-         * <p>A scrim may be used to indicate that the other elements beneath it are not currently
-         * interactive or actionable, drawing user focus and attention to the views above the scrim.
-         * </p>
-         *
-         * <p>The default implementation returns 0.0f.</p>
-         *
-         * @param parent the parent view of the given child
-         * @param child the child view above the scrim
-         * @return the desired scrim opacity from 0.0f to 1.0f. The default return value is 0.0f.
-         */
-        @FloatRange(from = 0, to = 1)
-        public float getScrimOpacity(CoordinatorLayout parent, V child) {
-            return 0.f;
-        }
-
-        /**
-         * Determine whether interaction with views behind the given child in the child order
-         * should be blocked.
-         *
-         * <p>The default implementation returns true if
-         * {@link #getScrimOpacity(CoordinatorLayout, android.view.View)} would return > 0.0f.</p>
-         *
-         * @param parent the parent view of the given child
-         * @param child the child view to test
-         * @return true if {@link #getScrimOpacity(CoordinatorLayout, android.view.View)} would
-         *         return > 0.0f.
-         */
-        public boolean blocksInteractionBelow(CoordinatorLayout parent, V child) {
-            return getScrimOpacity(parent, child) > 0.f;
-        }
-
-        /**
-         * Determine whether the supplied child view has another specific sibling view as a
-         * layout dependency.
-         *
-         * <p>This method will be called at least once in response to a layout request. If it
-         * returns true for a given child and dependency view pair, the parent CoordinatorLayout
-         * will:</p>
-         * <ol>
-         *     <li>Always lay out this child after the dependent child is laid out, regardless
-         *     of child order.</li>
-         *     <li>Call {@link #onDependentViewChanged} when the dependency view's layout or
-         *     position changes.</li>
-         * </ol>
-         *
-         * @param parent the parent view of the given child
-         * @param child the child view to test
-         * @param dependency the proposed dependency of child
-         * @return true if child's layout depends on the proposed dependency's layout,
-         *         false otherwise
-         *
-         * @see #onDependentViewChanged(CoordinatorLayout, android.view.View, android.view.View)
-         */
-        public boolean layoutDependsOn(CoordinatorLayout parent, V child, View dependency) {
-            return false;
-        }
-
-        /**
-         * Respond to a change in a child's dependent view
-         *
-         * <p>This method is called whenever a dependent view changes in size or position outside
-         * of the standard layout flow. A Behavior may use this method to appropriately update
-         * the child view in response.</p>
-         *
-         * <p>A view's dependency is determined by
-         * {@link #layoutDependsOn(CoordinatorLayout, android.view.View, android.view.View)} or
-         * if {@code child} has set another view as it's anchor.</p>
-         *
-         * <p>Note that if a Behavior changes the layout of a child via this method, it should
-         * also be able to reconstruct the correct position in
-         * {@link #onLayoutChild(CoordinatorLayout, android.view.View, int) onLayoutChild}.
-         * <code>onDependentViewChanged</code> will not be called during normal layout since
-         * the layout of each child view will always happen in dependency order.</p>
-         *
-         * <p>If the Behavior changes the child view's size or position, it should return true.
-         * The default implementation returns false.</p>
-         *
-         * @param parent the parent view of the given child
-         * @param child the child view to manipulate
-         * @param dependency the dependent view that changed
-         * @return true if the Behavior changed the child view's size or position, false otherwise
-         */
-        public boolean onDependentViewChanged(CoordinatorLayout parent, V child, View dependency) {
-            return false;
-        }
-
-        /**
-         * Respond to a child's dependent view being removed.
-         *
-         * <p>This method is called after a dependent view has been removed from the parent.
-         * A Behavior may use this method to appropriately update the child view in response.</p>
-         *
-         * <p>A view's dependency is determined by
-         * {@link #layoutDependsOn(CoordinatorLayout, android.view.View, android.view.View)} or
-         * if {@code child} has set another view as it's anchor.</p>
-         *
-         * @param parent the parent view of the given child
-         * @param child the child view to manipulate
-         * @param dependency the dependent view that has been removed
-         */
-        public void onDependentViewRemoved(CoordinatorLayout parent, V child, View dependency) {
-        }
-
-        /**
-         * Called when the parent CoordinatorLayout is about to measure the given child view.
-         *
-         * <p>This method can be used to perform custom or modified measurement of a child view
-         * in place of the default child measurement behavior. The Behavior's implementation
-         * can delegate to the standard CoordinatorLayout measurement behavior by calling
-         * {@link CoordinatorLayout#onMeasureChild(android.view.View, int, int, int, int)
-         * parent.onMeasureChild}.</p>
-         *
-         * @param parent the parent CoordinatorLayout
-         * @param child the child to measure
-         * @param parentWidthMeasureSpec the width requirements for this view
-         * @param widthUsed extra space that has been used up by the parent
-         *        horizontally (possibly by other children of the parent)
-         * @param parentHeightMeasureSpec the height requirements for this view
-         * @param heightUsed extra space that has been used up by the parent
-         *        vertically (possibly by other children of the parent)
-         * @return true if the Behavior measured the child view, false if the CoordinatorLayout
-         *         should perform its default measurement
-         */
-        public boolean onMeasureChild(CoordinatorLayout parent, V child,
-                int parentWidthMeasureSpec, int widthUsed,
-                int parentHeightMeasureSpec, int heightUsed) {
-            return false;
-        }
-
-        /**
-         * Called when the parent CoordinatorLayout is about the lay out the given child view.
-         *
-         * <p>This method can be used to perform custom or modified layout of a child view
-         * in place of the default child layout behavior. The Behavior's implementation can
-         * delegate to the standard CoordinatorLayout measurement behavior by calling
-         * {@link CoordinatorLayout#onLayoutChild(android.view.View, int)
-         * parent.onLayoutChild}.</p>
-         *
-         * <p>If a Behavior implements
-         * {@link #onDependentViewChanged(CoordinatorLayout, android.view.View, android.view.View)}
-         * to change the position of a view in response to a dependent view changing, it
-         * should also implement <code>onLayoutChild</code> in such a way that respects those
-         * dependent views. <code>onLayoutChild</code> will always be called for a dependent view
-         * <em>after</em> its dependency has been laid out.</p>
-         *
-         * @param parent the parent CoordinatorLayout
-         * @param child child view to lay out
-         * @param layoutDirection the resolved layout direction for the CoordinatorLayout, such as
-         *                        {@link ViewCompat#LAYOUT_DIRECTION_LTR} or
-         *                        {@link ViewCompat#LAYOUT_DIRECTION_RTL}.
-         * @return true if the Behavior performed layout of the child view, false to request
-         *         default layout behavior
-         */
-        public boolean onLayoutChild(CoordinatorLayout parent, V child, int layoutDirection) {
-            return false;
-        }
-
-        // Utility methods for accessing child-specific, behavior-modifiable properties.
-
-        /**
-         * Associate a Behavior-specific tag object with the given child view.
-         * This object will be stored with the child view's LayoutParams.
-         *
-         * @param child child view to set tag with
-         * @param tag tag object to set
-         */
-        public static void setTag(View child, Object tag) {
-            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-            lp.mBehaviorTag = tag;
-        }
-
-        /**
-         * Get the behavior-specific tag object with the given child view.
-         * This object is stored with the child view's LayoutParams.
-         *
-         * @param child child view to get tag with
-         * @return the previously stored tag object
-         */
-        public static Object getTag(View child) {
-            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-            return lp.mBehaviorTag;
-        }
-
-        /**
-         * @deprecated You should now override
-         * {@link #onStartNestedScroll(CoordinatorLayout, View, View, View, int, int)}. This
-         * method will still continue to be called if the type is {@link ViewCompat#TYPE_TOUCH}.
-         */
-        @Deprecated
-        public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout,
-                @NonNull V child, @NonNull View directTargetChild, @NonNull View target,
-                @ScrollAxis int axes) {
-            return false;
-        }
-
-        /**
-         * Called when a descendant of the CoordinatorLayout attempts to initiate a nested scroll.
-         *
-         * <p>Any Behavior associated with any direct child of the CoordinatorLayout may respond
-         * to this event and return true to indicate that the CoordinatorLayout should act as
-         * a nested scrolling parent for this scroll. Only Behaviors that return true from
-         * this method will receive subsequent nested scroll events.</p>
-         *
-         * @param coordinatorLayout the CoordinatorLayout parent of the view this Behavior is
-         *                          associated with
-         * @param child the child view of the CoordinatorLayout this Behavior is associated with
-         * @param directTargetChild the child view of the CoordinatorLayout that either is or
-         *                          contains the target of the nested scroll operation
-         * @param target the descendant view of the CoordinatorLayout initiating the nested scroll
-         * @param axes the axes that this nested scroll applies to. See
-         *                         {@link ViewCompat#SCROLL_AXIS_HORIZONTAL},
-         *                         {@link ViewCompat#SCROLL_AXIS_VERTICAL}
-         * @param type the type of input which cause this scroll event
-         * @return true if the Behavior wishes to accept this nested scroll
-         *
-         * @see NestedScrollingParent2#onStartNestedScroll(View, View, int, int)
-         */
-        public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout,
-                @NonNull V child, @NonNull View directTargetChild, @NonNull View target,
-                @ScrollAxis int axes, @NestedScrollType int type) {
-            if (type == ViewCompat.TYPE_TOUCH) {
-                return onStartNestedScroll(coordinatorLayout, child, directTargetChild,
-                        target, axes);
-            }
-            return false;
-        }
-
-        /**
-         * @deprecated You should now override
-         * {@link #onNestedScrollAccepted(CoordinatorLayout, View, View, View, int, int)}. This
-         * method will still continue to be called if the type is {@link ViewCompat#TYPE_TOUCH}.
-         */
-        @Deprecated
-        public void onNestedScrollAccepted(@NonNull CoordinatorLayout coordinatorLayout,
-                @NonNull V child, @NonNull View directTargetChild, @NonNull View target,
-                @ScrollAxis int axes) {
-            // Do nothing
-        }
-
-        /**
-         * Called when a nested scroll has been accepted by the CoordinatorLayout.
-         *
-         * <p>Any Behavior associated with any direct child of the CoordinatorLayout may elect
-         * to accept the nested scroll as part of {@link #onStartNestedScroll}. Each Behavior
-         * that returned true will receive subsequent nested scroll events for that nested scroll.
-         * </p>
-         *
-         * @param coordinatorLayout the CoordinatorLayout parent of the view this Behavior is
-         *                          associated with
-         * @param child the child view of the CoordinatorLayout this Behavior is associated with
-         * @param directTargetChild the child view of the CoordinatorLayout that either is or
-         *                          contains the target of the nested scroll operation
-         * @param target the descendant view of the CoordinatorLayout initiating the nested scroll
-         * @param axes the axes that this nested scroll applies to. See
-         *                         {@link ViewCompat#SCROLL_AXIS_HORIZONTAL},
-         *                         {@link ViewCompat#SCROLL_AXIS_VERTICAL}
-         * @param type the type of input which cause this scroll event
-         *
-         * @see NestedScrollingParent2#onNestedScrollAccepted(View, View, int, int)
-         */
-        public void onNestedScrollAccepted(@NonNull CoordinatorLayout coordinatorLayout,
-                @NonNull V child, @NonNull View directTargetChild, @NonNull View target,
-                @ScrollAxis int axes, @NestedScrollType int type) {
-            if (type == ViewCompat.TYPE_TOUCH) {
-                onNestedScrollAccepted(coordinatorLayout, child, directTargetChild,
-                        target, axes);
-            }
-        }
-
-        /**
-         * @deprecated You should now override
-         * {@link #onStopNestedScroll(CoordinatorLayout, View, View, int)}. This method will still
-         * continue to be called if the type is {@link ViewCompat#TYPE_TOUCH}.
-         */
-        @Deprecated
-        public void onStopNestedScroll(@NonNull CoordinatorLayout coordinatorLayout,
-                @NonNull V child, @NonNull View target) {
-            // Do nothing
-        }
-
-        /**
-         * Called when a nested scroll has ended.
-         *
-         * <p>Any Behavior associated with any direct child of the CoordinatorLayout may elect
-         * to accept the nested scroll as part of {@link #onStartNestedScroll}. Each Behavior
-         * that returned true will receive subsequent nested scroll events for that nested scroll.
-         * </p>
-         *
-         * <p><code>onStopNestedScroll</code> marks the end of a single nested scroll event
-         * sequence. This is a good place to clean up any state related to the nested scroll.
-         * </p>
-         *
-         * @param coordinatorLayout the CoordinatorLayout parent of the view this Behavior is
-         *                          associated with
-         * @param child the child view of the CoordinatorLayout this Behavior is associated with
-         * @param target the descendant view of the CoordinatorLayout that initiated
-         *               the nested scroll
-         * @param type the type of input which cause this scroll event
-         *
-         * @see NestedScrollingParent2#onStopNestedScroll(View, int)
-         */
-        public void onStopNestedScroll(@NonNull CoordinatorLayout coordinatorLayout,
-                @NonNull V child, @NonNull View target, @NestedScrollType int type) {
-            if (type == ViewCompat.TYPE_TOUCH) {
-                onStopNestedScroll(coordinatorLayout, child, target);
-            }
-        }
-
-        /**
-         * @deprecated You should now override
-         * {@link #onNestedScroll(CoordinatorLayout, View, View, int, int, int, int, int)}.
-         * This method will still continue to be called if the type is
-         * {@link ViewCompat#TYPE_TOUCH}.
-         */
-        @Deprecated
-        public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child,
-                @NonNull View target, int dxConsumed, int dyConsumed,
-                int dxUnconsumed, int dyUnconsumed) {
-            // Do nothing
-        }
-
-        /**
-         * Called when a nested scroll in progress has updated and the target has scrolled or
-         * attempted to scroll.
-         *
-         * <p>Any Behavior associated with the direct child of the CoordinatorLayout may elect
-         * to accept the nested scroll as part of {@link #onStartNestedScroll}. Each Behavior
-         * that returned true will receive subsequent nested scroll events for that nested scroll.
-         * </p>
-         *
-         * <p><code>onNestedScroll</code> is called each time the nested scroll is updated by the
-         * nested scrolling child, with both consumed and unconsumed components of the scroll
-         * supplied in pixels. <em>Each Behavior responding to the nested scroll will receive the
-         * same values.</em>
-         * </p>
-         *
-         * @param coordinatorLayout the CoordinatorLayout parent of the view this Behavior is
-         *                          associated with
-         * @param child the child view of the CoordinatorLayout this Behavior is associated with
-         * @param target the descendant view of the CoordinatorLayout performing the nested scroll
-         * @param dxConsumed horizontal pixels consumed by the target's own scrolling operation
-         * @param dyConsumed vertical pixels consumed by the target's own scrolling operation
-         * @param dxUnconsumed horizontal pixels not consumed by the target's own scrolling
-         *                     operation, but requested by the user
-         * @param dyUnconsumed vertical pixels not consumed by the target's own scrolling operation,
-         *                     but requested by the user
-         * @param type the type of input which cause this scroll event
-         *
-         * @see NestedScrollingParent2#onNestedScroll(View, int, int, int, int, int)
-         */
-        public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child,
-                @NonNull View target, int dxConsumed, int dyConsumed,
-                int dxUnconsumed, int dyUnconsumed, @NestedScrollType int type) {
-            if (type == ViewCompat.TYPE_TOUCH) {
-                onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed,
-                        dxUnconsumed, dyUnconsumed);
-            }
-        }
-
-        /**
-         * @deprecated You should now override
-         * {@link #onNestedPreScroll(CoordinatorLayout, View, View, int, int, int[], int)}.
-         * This method will still continue to be called if the type is
-         * {@link ViewCompat#TYPE_TOUCH}.
-         */
-        @Deprecated
-        public void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout,
-                @NonNull V child, @NonNull View target, int dx, int dy, @NonNull int[] consumed) {
-            // Do nothing
-        }
-
-        /**
-         * Called when a nested scroll in progress is about to update, before the target has
-         * consumed any of the scrolled distance.
-         *
-         * <p>Any Behavior associated with the direct child of the CoordinatorLayout may elect
-         * to accept the nested scroll as part of {@link #onStartNestedScroll}. Each Behavior
-         * that returned true will receive subsequent nested scroll events for that nested scroll.
-         * </p>
-         *
-         * <p><code>onNestedPreScroll</code> is called each time the nested scroll is updated
-         * by the nested scrolling child, before the nested scrolling child has consumed the scroll
-         * distance itself. <em>Each Behavior responding to the nested scroll will receive the
-         * same values.</em> The CoordinatorLayout will report as consumed the maximum number
-         * of pixels in either direction that any Behavior responding to the nested scroll reported
-         * as consumed.</p>
-         *
-         * @param coordinatorLayout the CoordinatorLayout parent of the view this Behavior is
-         *                          associated with
-         * @param child the child view of the CoordinatorLayout this Behavior is associated with
-         * @param target the descendant view of the CoordinatorLayout performing the nested scroll
-         * @param dx the raw horizontal number of pixels that the user attempted to scroll
-         * @param dy the raw vertical number of pixels that the user attempted to scroll
-         * @param consumed out parameter. consumed[0] should be set to the distance of dx that
-         *                 was consumed, consumed[1] should be set to the distance of dy that
-         *                 was consumed
-         * @param type the type of input which cause this scroll event
-         *
-         * @see NestedScrollingParent2#onNestedPreScroll(View, int, int, int[], int)
-         */
-        public void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout,
-                @NonNull V child, @NonNull View target, int dx, int dy, @NonNull int[] consumed,
-                @NestedScrollType int type) {
-            if (type == ViewCompat.TYPE_TOUCH) {
-                onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
-            }
-        }
-
-        /**
-         * Called when a nested scrolling child is starting a fling or an action that would
-         * be a fling.
-         *
-         * <p>Any Behavior associated with the direct child of the CoordinatorLayout may elect
-         * to accept the nested scroll as part of {@link #onStartNestedScroll}. Each Behavior
-         * that returned true will receive subsequent nested scroll events for that nested scroll.
-         * </p>
-         *
-         * <p><code>onNestedFling</code> is called when the current nested scrolling child view
-         * detects the proper conditions for a fling. It reports if the child itself consumed
-         * the fling. If it did not, the child is expected to show some sort of overscroll
-         * indication. This method should return true if it consumes the fling, so that a child
-         * that did not itself take an action in response can choose not to show an overfling
-         * indication.</p>
-         *
-         * @param coordinatorLayout the CoordinatorLayout parent of the view this Behavior is
-         *                          associated with
-         * @param child the child view of the CoordinatorLayout this Behavior is associated with
-         * @param target the descendant view of the CoordinatorLayout performing the nested scroll
-         * @param velocityX horizontal velocity of the attempted fling
-         * @param velocityY vertical velocity of the attempted fling
-         * @param consumed true if the nested child view consumed the fling
-         * @return true if the Behavior consumed the fling
-         *
-         * @see NestedScrollingParent#onNestedFling(View, float, float, boolean)
-         */
-        public boolean onNestedFling(@NonNull CoordinatorLayout coordinatorLayout,
-                @NonNull V child, @NonNull View target, float velocityX, float velocityY,
-                boolean consumed) {
-            return false;
-        }
-
-        /**
-         * Called when a nested scrolling child is about to start a fling.
-         *
-         * <p>Any Behavior associated with the direct child of the CoordinatorLayout may elect
-         * to accept the nested scroll as part of {@link #onStartNestedScroll}. Each Behavior
-         * that returned true will receive subsequent nested scroll events for that nested scroll.
-         * </p>
-         *
-         * <p><code>onNestedPreFling</code> is called when the current nested scrolling child view
-         * detects the proper conditions for a fling, but it has not acted on it yet. A
-         * Behavior can return true to indicate that it consumed the fling. If at least one
-         * Behavior returns true, the fling should not be acted upon by the child.</p>
-         *
-         * @param coordinatorLayout the CoordinatorLayout parent of the view this Behavior is
-         *                          associated with
-         * @param child the child view of the CoordinatorLayout this Behavior is associated with
-         * @param target the descendant view of the CoordinatorLayout performing the nested scroll
-         * @param velocityX horizontal velocity of the attempted fling
-         * @param velocityY vertical velocity of the attempted fling
-         * @return true if the Behavior consumed the fling
-         *
-         * @see NestedScrollingParent#onNestedPreFling(View, float, float)
-         */
-        public boolean onNestedPreFling(@NonNull CoordinatorLayout coordinatorLayout,
-                @NonNull V child, @NonNull View target, float velocityX, float velocityY) {
-            return false;
-        }
-
-        /**
-         * Called when the window insets have changed.
-         *
-         * <p>Any Behavior associated with the direct child of the CoordinatorLayout may elect
-         * to handle the window inset change on behalf of it's associated view.
-         * </p>
-         *
-         * @param coordinatorLayout the CoordinatorLayout parent of the view this Behavior is
-         *                          associated with
-         * @param child the child view of the CoordinatorLayout this Behavior is associated with
-         * @param insets the new window insets.
-         *
-         * @return The insets supplied, minus any insets that were consumed
-         */
-        @NonNull
-        public WindowInsetsCompat onApplyWindowInsets(CoordinatorLayout coordinatorLayout,
-                V child, WindowInsetsCompat insets) {
-            return insets;
-        }
-
-        /**
-         * Called when a child of the view associated with this behavior wants a particular
-         * rectangle to be positioned onto the screen.
-         *
-         * <p>The contract for this method is the same as
-         * {@link ViewParent#requestChildRectangleOnScreen(View, Rect, boolean)}.</p>
-         *
-         * @param coordinatorLayout the CoordinatorLayout parent of the view this Behavior is
-         *                          associated with
-         * @param child             the child view of the CoordinatorLayout this Behavior is
-         *                          associated with
-         * @param rectangle         The rectangle which the child wishes to be on the screen
-         *                          in the child's coordinates
-         * @param immediate         true to forbid animated or delayed scrolling, false otherwise
-         * @return true if the Behavior handled the request
-         * @see ViewParent#requestChildRectangleOnScreen(View, Rect, boolean)
-         */
-        public boolean onRequestChildRectangleOnScreen(CoordinatorLayout coordinatorLayout,
-                V child, Rect rectangle, boolean immediate) {
-            return false;
-        }
-
-        /**
-         * Hook allowing a behavior to re-apply a representation of its internal state that had
-         * previously been generated by {@link #onSaveInstanceState}. This function will never
-         * be called with a null state.
-         *
-         * @param parent the parent CoordinatorLayout
-         * @param child child view to restore from
-         * @param state The frozen state that had previously been returned by
-         *        {@link #onSaveInstanceState}.
-         *
-         * @see #onSaveInstanceState()
-         */
-        public void onRestoreInstanceState(CoordinatorLayout parent, V child, Parcelable state) {
-            // no-op
-        }
-
-        /**
-         * Hook allowing a behavior to generate a representation of its internal state
-         * that can later be used to create a new instance with that same state.
-         * This state should only contain information that is not persistent or can
-         * not be reconstructed later.
-         *
-         * <p>Behavior state is only saved when both the parent {@link CoordinatorLayout} and
-         * a view using this behavior have valid IDs set.</p>
-         *
-         * @param parent the parent CoordinatorLayout
-         * @param child child view to restore from
-         *
-         * @return Returns a Parcelable object containing the behavior's current dynamic
-         *         state.
-         *
-         * @see #onRestoreInstanceState(android.os.Parcelable)
-         * @see View#onSaveInstanceState()
-         */
-        public Parcelable onSaveInstanceState(CoordinatorLayout parent, V child) {
-            return BaseSavedState.EMPTY_STATE;
-        }
-
-        /**
-         * Called when a view is set to dodge view insets.
-         *
-         * <p>This method allows a behavior to update the rectangle that should be dodged.
-         * The rectangle should be in the parent's coordinate system and within the child's
-         * bounds. If not, a {@link IllegalArgumentException} is thrown.</p>
-         *
-         * @param parent the CoordinatorLayout parent of the view this Behavior is
-         *               associated with
-         * @param child  the child view of the CoordinatorLayout this Behavior is associated with
-         * @param rect   the rect to update with the dodge rectangle
-         * @return true the rect was updated, false if we should use the child's bounds
-         */
-        public boolean getInsetDodgeRect(@NonNull CoordinatorLayout parent, @NonNull V child,
-                @NonNull Rect rect) {
-            return false;
-        }
-    }
-
-    /**
-     * Parameters describing the desired layout for a child of a {@link CoordinatorLayout}.
-     */
-    public static class LayoutParams extends ViewGroup.MarginLayoutParams {
-        /**
-         * A {@link Behavior} that the child view should obey.
-         */
-        Behavior mBehavior;
-
-        boolean mBehaviorResolved = false;
-
-        /**
-         * A {@link Gravity} value describing how this child view should lay out.
-         * If either or both of the axes are not specified, they are treated by CoordinatorLayout
-         * as {@link Gravity#TOP} or {@link GravityCompat#START}. If an
-         * {@link #setAnchorId(int) anchor} is also specified, the gravity describes how this child
-         * view should be positioned relative to its anchored position.
-         */
-        public int gravity = Gravity.NO_GRAVITY;
-
-        /**
-         * A {@link Gravity} value describing which edge of a child view's
-         * {@link #getAnchorId() anchor} view the child should position itself relative to.
-         */
-        public int anchorGravity = Gravity.NO_GRAVITY;
-
-        /**
-         * The index of the horizontal keyline specified to the parent CoordinatorLayout that this
-         * child should align relative to. If an {@link #setAnchorId(int) anchor} is present the
-         * keyline will be ignored.
-         */
-        public int keyline = -1;
-
-        /**
-         * A {@link View#getId() view id} of a descendant view of the CoordinatorLayout that
-         * this child should position relative to.
-         */
-        int mAnchorId = View.NO_ID;
-
-        /**
-         * A {@link Gravity} value describing how this child view insets the CoordinatorLayout.
-         * Other child views which are set to dodge the same inset edges will be moved appropriately
-         * so that the views do not overlap.
-         */
-        public int insetEdge = Gravity.NO_GRAVITY;
-
-        /**
-         * A {@link Gravity} value describing how this child view dodges any inset child views in
-         * the CoordinatorLayout. Any views which are inset on the same edge as this view is set to
-         * dodge will result in this view being moved so that the views do not overlap.
-         */
-        public int dodgeInsetEdges = Gravity.NO_GRAVITY;
-
-        int mInsetOffsetX;
-        int mInsetOffsetY;
-
-        View mAnchorView;
-        View mAnchorDirectChild;
-
-        private boolean mDidBlockInteraction;
-        private boolean mDidAcceptNestedScrollTouch;
-        private boolean mDidAcceptNestedScrollNonTouch;
-        private boolean mDidChangeAfterNestedScroll;
-
-        final Rect mLastChildRect = new Rect();
-
-        Object mBehaviorTag;
-
-        public LayoutParams(int width, int height) {
-            super(width, height);
-        }
-
-        LayoutParams(Context context, AttributeSet attrs) {
-            super(context, attrs);
-
-            final TypedArray a = context.obtainStyledAttributes(attrs,
-                    R.styleable.CoordinatorLayout_Layout);
-
-            this.gravity = a.getInteger(
-                    R.styleable.CoordinatorLayout_Layout_android_layout_gravity,
-                    Gravity.NO_GRAVITY);
-            mAnchorId = a.getResourceId(R.styleable.CoordinatorLayout_Layout_layout_anchor,
-                    View.NO_ID);
-            this.anchorGravity = a.getInteger(
-                    R.styleable.CoordinatorLayout_Layout_layout_anchorGravity,
-                    Gravity.NO_GRAVITY);
-
-            this.keyline = a.getInteger(R.styleable.CoordinatorLayout_Layout_layout_keyline,
-                    -1);
-
-            insetEdge = a.getInt(R.styleable.CoordinatorLayout_Layout_layout_insetEdge, 0);
-            dodgeInsetEdges = a.getInt(
-                    R.styleable.CoordinatorLayout_Layout_layout_dodgeInsetEdges, 0);
-            mBehaviorResolved = a.hasValue(
-                    R.styleable.CoordinatorLayout_Layout_layout_behavior);
-            if (mBehaviorResolved) {
-                mBehavior = parseBehavior(context, attrs, a.getString(
-                        R.styleable.CoordinatorLayout_Layout_layout_behavior));
-            }
-            a.recycle();
-
-            if (mBehavior != null) {
-                // If we have a Behavior, dispatch that it has been attached
-                mBehavior.onAttachedToLayoutParams(this);
-            }
-        }
-
-        public LayoutParams(LayoutParams p) {
-            super(p);
-        }
-
-        public LayoutParams(MarginLayoutParams p) {
-            super(p);
-        }
-
-        public LayoutParams(ViewGroup.LayoutParams p) {
-            super(p);
-        }
-
-        /**
-         * Get the id of this view's anchor.
-         *
-         * @return A {@link View#getId() view id} or {@link View#NO_ID} if there is no anchor
-         */
-        @IdRes
-        public int getAnchorId() {
-            return mAnchorId;
-        }
-
-        /**
-         * Set the id of this view's anchor.
-         *
-         * <p>The view with this id must be a descendant of the CoordinatorLayout containing
-         * the child view this LayoutParams belongs to. It may not be the child view with
-         * this LayoutParams or a descendant of it.</p>
-         *
-         * @param id The {@link View#getId() view id} of the anchor or
-         *           {@link View#NO_ID} if there is no anchor
-         */
-        public void setAnchorId(@IdRes int id) {
-            invalidateAnchor();
-            mAnchorId = id;
-        }
-
-        /**
-         * Get the behavior governing the layout and interaction of the child view within
-         * a parent CoordinatorLayout.
-         *
-         * @return The current behavior or null if no behavior is specified
-         */
-        @Nullable
-        public Behavior getBehavior() {
-            return mBehavior;
-        }
-
-        /**
-         * Set the behavior governing the layout and interaction of the child view within
-         * a parent CoordinatorLayout.
-         *
-         * <p>Setting a new behavior will remove any currently associated
-         * {@link Behavior#setTag(android.view.View, Object) Behavior tag}.</p>
-         *
-         * @param behavior The behavior to set or null for no special behavior
-         */
-        public void setBehavior(@Nullable Behavior behavior) {
-            if (mBehavior != behavior) {
-                if (mBehavior != null) {
-                    // First detach any old behavior
-                    mBehavior.onDetachedFromLayoutParams();
-                }
-
-                mBehavior = behavior;
-                mBehaviorTag = null;
-                mBehaviorResolved = true;
-
-                if (behavior != null) {
-                    // Now dispatch that the Behavior has been attached
-                    behavior.onAttachedToLayoutParams(this);
-                }
-            }
-        }
-
-        /**
-         * Set the last known position rect for this child view
-         * @param r the rect to set
-         */
-        void setLastChildRect(Rect r) {
-            mLastChildRect.set(r);
-        }
-
-        /**
-         * Get the last known position rect for this child view.
-         * Note: do not mutate the result of this call.
-         */
-        Rect getLastChildRect() {
-            return mLastChildRect;
-        }
-
-        /**
-         * Returns true if the anchor id changed to another valid view id since the anchor view
-         * was resolved.
-         */
-        boolean checkAnchorChanged() {
-            return mAnchorView == null && mAnchorId != View.NO_ID;
-        }
-
-        /**
-         * Returns true if the associated Behavior previously blocked interaction with other views
-         * below the associated child since the touch behavior tracking was last
-         * {@link #resetTouchBehaviorTracking() reset}.
-         *
-         * @see #isBlockingInteractionBelow(CoordinatorLayout, android.view.View)
-         */
-        boolean didBlockInteraction() {
-            if (mBehavior == null) {
-                mDidBlockInteraction = false;
-            }
-            return mDidBlockInteraction;
-        }
-
-        /**
-         * Check if the associated Behavior wants to block interaction below the given child
-         * view. The given child view should be the child this LayoutParams is associated with.
-         *
-         * <p>Once interaction is blocked, it will remain blocked until touch interaction tracking
-         * is {@link #resetTouchBehaviorTracking() reset}.</p>
-         *
-         * @param parent the parent CoordinatorLayout
-         * @param child the child view this LayoutParams is associated with
-         * @return true to block interaction below the given child
-         */
-        boolean isBlockingInteractionBelow(CoordinatorLayout parent, View child) {
-            if (mDidBlockInteraction) {
-                return true;
-            }
-
-            return mDidBlockInteraction |= mBehavior != null
-                    ? mBehavior.blocksInteractionBelow(parent, child)
-                    : false;
-        }
-
-        /**
-         * Reset tracking of Behavior-specific touch interactions. This includes
-         * interaction blocking.
-         *
-         * @see #isBlockingInteractionBelow(CoordinatorLayout, android.view.View)
-         * @see #didBlockInteraction()
-         */
-        void resetTouchBehaviorTracking() {
-            mDidBlockInteraction = false;
-        }
-
-        void resetNestedScroll(int type) {
-            setNestedScrollAccepted(type, false);
-        }
-
-        void setNestedScrollAccepted(int type, boolean accept) {
-            switch (type) {
-                case ViewCompat.TYPE_TOUCH:
-                    mDidAcceptNestedScrollTouch = accept;
-                    break;
-                case ViewCompat.TYPE_NON_TOUCH:
-                    mDidAcceptNestedScrollNonTouch = accept;
-                    break;
-            }
-        }
-
-        boolean isNestedScrollAccepted(int type) {
-            switch (type) {
-                case ViewCompat.TYPE_TOUCH:
-                    return mDidAcceptNestedScrollTouch;
-                case ViewCompat.TYPE_NON_TOUCH:
-                    return mDidAcceptNestedScrollNonTouch;
-            }
-            return false;
-        }
-
-        boolean getChangedAfterNestedScroll() {
-            return mDidChangeAfterNestedScroll;
-        }
-
-        void setChangedAfterNestedScroll(boolean changed) {
-            mDidChangeAfterNestedScroll = changed;
-        }
-
-        void resetChangedAfterNestedScroll() {
-            mDidChangeAfterNestedScroll = false;
-        }
-
-        /**
-         * Check if an associated child view depends on another child view of the CoordinatorLayout.
-         *
-         * @param parent the parent CoordinatorLayout
-         * @param child the child to check
-         * @param dependency the proposed dependency to check
-         * @return true if child depends on dependency
-         */
-        boolean dependsOn(CoordinatorLayout parent, View child, View dependency) {
-            return dependency == mAnchorDirectChild
-                    || shouldDodge(dependency, ViewCompat.getLayoutDirection(parent))
-                    || (mBehavior != null && mBehavior.layoutDependsOn(parent, child, dependency));
-        }
-
-        /**
-         * Invalidate the cached anchor view and direct child ancestor of that anchor.
-         * The anchor will need to be
-         * {@link #findAnchorView(CoordinatorLayout, android.view.View) found} before
-         * being used again.
-         */
-        void invalidateAnchor() {
-            mAnchorView = mAnchorDirectChild = null;
-        }
-
-        /**
-         * Locate the appropriate anchor view by the current {@link #setAnchorId(int) anchor id}
-         * or return the cached anchor view if already known.
-         *
-         * @param parent the parent CoordinatorLayout
-         * @param forChild the child this LayoutParams is associated with
-         * @return the located descendant anchor view, or null if the anchor id is
-         *         {@link View#NO_ID}.
-         */
-        View findAnchorView(CoordinatorLayout parent, View forChild) {
-            if (mAnchorId == View.NO_ID) {
-                mAnchorView = mAnchorDirectChild = null;
-                return null;
-            }
-
-            if (mAnchorView == null || !verifyAnchorView(forChild, parent)) {
-                resolveAnchorView(forChild, parent);
-            }
-            return mAnchorView;
-        }
-
-        /**
-         * Determine the anchor view for the child view this LayoutParams is assigned to.
-         * Assumes mAnchorId is valid.
-         */
-        private void resolveAnchorView(final View forChild, final CoordinatorLayout parent) {
-            mAnchorView = parent.findViewById(mAnchorId);
-            if (mAnchorView != null) {
-                if (mAnchorView == parent) {
-                    if (parent.isInEditMode()) {
-                        mAnchorView = mAnchorDirectChild = null;
-                        return;
-                    }
-                    throw new IllegalStateException(
-                            "View can not be anchored to the the parent CoordinatorLayout");
-                }
-
-                View directChild = mAnchorView;
-                for (ViewParent p = mAnchorView.getParent();
-                        p != parent && p != null;
-                        p = p.getParent()) {
-                    if (p == forChild) {
-                        if (parent.isInEditMode()) {
-                            mAnchorView = mAnchorDirectChild = null;
-                            return;
-                        }
-                        throw new IllegalStateException(
-                                "Anchor must not be a descendant of the anchored view");
-                    }
-                    if (p instanceof View) {
-                        directChild = (View) p;
-                    }
-                }
-                mAnchorDirectChild = directChild;
-            } else {
-                if (parent.isInEditMode()) {
-                    mAnchorView = mAnchorDirectChild = null;
-                    return;
-                }
-                throw new IllegalStateException("Could not find CoordinatorLayout descendant view"
-                        + " with id " + parent.getResources().getResourceName(mAnchorId)
-                        + " to anchor view " + forChild);
-            }
-        }
-
-        /**
-         * Verify that the previously resolved anchor view is still valid - that it is still
-         * a descendant of the expected parent view, it is not the child this LayoutParams
-         * is assigned to or a descendant of it, and it has the expected id.
-         */
-        private boolean verifyAnchorView(View forChild, CoordinatorLayout parent) {
-            if (mAnchorView.getId() != mAnchorId) {
-                return false;
-            }
-
-            View directChild = mAnchorView;
-            for (ViewParent p = mAnchorView.getParent();
-                    p != parent;
-                    p = p.getParent()) {
-                if (p == null || p == forChild) {
-                    mAnchorView = mAnchorDirectChild = null;
-                    return false;
-                }
-                if (p instanceof View) {
-                    directChild = (View) p;
-                }
-            }
-            mAnchorDirectChild = directChild;
-            return true;
-        }
-
-        /**
-         * Checks whether the view with this LayoutParams should dodge the specified view.
-         */
-        private boolean shouldDodge(View other, int layoutDirection) {
-            LayoutParams lp = (LayoutParams) other.getLayoutParams();
-            final int absInset = GravityCompat.getAbsoluteGravity(lp.insetEdge, layoutDirection);
-            return absInset != Gravity.NO_GRAVITY && (absInset &
-                    GravityCompat.getAbsoluteGravity(dodgeInsetEdges, layoutDirection)) == absInset;
-        }
-    }
-
-    private class HierarchyChangeListener implements OnHierarchyChangeListener {
-        HierarchyChangeListener() {
-        }
-
-        @Override
-        public void onChildViewAdded(View parent, View child) {
-            if (mOnHierarchyChangeListener != null) {
-                mOnHierarchyChangeListener.onChildViewAdded(parent, child);
-            }
-        }
-
-        @Override
-        public void onChildViewRemoved(View parent, View child) {
-            onChildViewsChanged(EVENT_VIEW_REMOVED);
-
-            if (mOnHierarchyChangeListener != null) {
-                mOnHierarchyChangeListener.onChildViewRemoved(parent, child);
-            }
-        }
-    }
-
-    @Override
-    protected void onRestoreInstanceState(Parcelable state) {
-        if (!(state instanceof SavedState)) {
-            super.onRestoreInstanceState(state);
-            return;
-        }
-
-        final SavedState ss = (SavedState) state;
-        super.onRestoreInstanceState(ss.getSuperState());
-
-        final SparseArray<Parcelable> behaviorStates = ss.behaviorStates;
-
-        for (int i = 0, count = getChildCount(); i < count; i++) {
-            final View child = getChildAt(i);
-            final int childId = child.getId();
-            final LayoutParams lp = getResolvedLayoutParams(child);
-            final Behavior b = lp.getBehavior();
-
-            if (childId != NO_ID && b != null) {
-                Parcelable savedState = behaviorStates.get(childId);
-                if (savedState != null) {
-                    b.onRestoreInstanceState(this, child, savedState);
-                }
-            }
-        }
-    }
-
-    @Override
-    protected Parcelable onSaveInstanceState() {
-        final SavedState ss = new SavedState(super.onSaveInstanceState());
-
-        final SparseArray<Parcelable> behaviorStates = new SparseArray<>();
-        for (int i = 0, count = getChildCount(); i < count; i++) {
-            final View child = getChildAt(i);
-            final int childId = child.getId();
-            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-            final Behavior b = lp.getBehavior();
-
-            if (childId != NO_ID && b != null) {
-                // If the child has an ID and a Behavior, let it save some state...
-                Parcelable state = b.onSaveInstanceState(this, child);
-                if (state != null) {
-                    behaviorStates.append(childId, state);
-                }
-            }
-        }
-        ss.behaviorStates = behaviorStates;
-        return ss;
-    }
-
-    @Override
-    public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
-        final CoordinatorLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams();
-        final Behavior behavior = lp.getBehavior();
-
-        if (behavior != null
-                && behavior.onRequestChildRectangleOnScreen(this, child, rectangle, immediate)) {
-            return true;
-        }
-
-        return super.requestChildRectangleOnScreen(child, rectangle, immediate);
-    }
-
-    private void setupForInsets() {
-        if (Build.VERSION.SDK_INT < 21) {
-            return;
-        }
-
-        if (ViewCompat.getFitsSystemWindows(this)) {
-            if (mApplyWindowInsetsListener == null) {
-                mApplyWindowInsetsListener =
-                        new android.support.v4.view.OnApplyWindowInsetsListener() {
-                            @Override
-                            public WindowInsetsCompat onApplyWindowInsets(View v,
-                                    WindowInsetsCompat insets) {
-                                return setWindowInsets(insets);
-                            }
-                        };
-            }
-            // First apply the insets listener
-            ViewCompat.setOnApplyWindowInsetsListener(this, mApplyWindowInsetsListener);
-
-            // Now set the sys ui flags to enable us to lay out in the window insets
-            setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
-                    | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
-        } else {
-            ViewCompat.setOnApplyWindowInsetsListener(this, null);
-        }
-    }
-
-    protected static class SavedState extends AbsSavedState {
-        SparseArray<Parcelable> behaviorStates;
-
-        public SavedState(Parcel source, ClassLoader loader) {
-            super(source, loader);
-
-            final int size = source.readInt();
-
-            final int[] ids = new int[size];
-            source.readIntArray(ids);
-
-            final Parcelable[] states = source.readParcelableArray(loader);
-
-            behaviorStates = new SparseArray<>(size);
-            for (int i = 0; i < size; i++) {
-                behaviorStates.append(ids[i], states[i]);
-            }
-        }
-
-        public SavedState(Parcelable superState) {
-            super(superState);
-        }
-
-        @Override
-        public void writeToParcel(Parcel dest, int flags) {
-            super.writeToParcel(dest, flags);
-
-            final int size = behaviorStates != null ? behaviorStates.size() : 0;
-            dest.writeInt(size);
-
-            final int[] ids = new int[size];
-            final Parcelable[] states = new Parcelable[size];
-
-            for (int i = 0; i < size; i++) {
-                ids[i] = behaviorStates.keyAt(i);
-                states[i] = behaviorStates.valueAt(i);
-            }
-            dest.writeIntArray(ids);
-            dest.writeParcelableArray(states, flags);
-
-        }
-
-        public static final Parcelable.Creator<SavedState> CREATOR =
-                new ClassLoaderCreator<SavedState>() {
-                    @Override
-                    public SavedState createFromParcel(Parcel in, ClassLoader loader) {
-                        return new SavedState(in, loader);
-                    }
-
-                    @Override
-                    public SavedState createFromParcel(Parcel in) {
-                        return new SavedState(in, null);
-                    }
-
-                    @Override
-                    public SavedState[] newArray(int size) {
-                        return new SavedState[size];
-                    }
-                };
-    }
-}
diff --git a/design/src/android/support/design/widget/DirectedAcyclicGraph.java b/design/src/android/support/design/widget/DirectedAcyclicGraph.java
deleted file mode 100644
index 85a32cd..0000000
--- a/design/src/android/support/design/widget/DirectedAcyclicGraph.java
+++ /dev/null
@@ -1,201 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.design.widget;
-
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.v4.util.Pools;
-import android.support.v4.util.SimpleArrayMap;
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-
-/**
- * A class which represents a simple directed acyclic graph.
- */
-final class DirectedAcyclicGraph<T> {
-    private final Pools.Pool<ArrayList<T>> mListPool = new Pools.SimplePool<>(10);
-    private final SimpleArrayMap<T, ArrayList<T>> mGraph = new SimpleArrayMap<>();
-
-    private final ArrayList<T> mSortResult = new ArrayList<>();
-    private final HashSet<T> mSortTmpMarked = new HashSet<>();
-
-    /**
-     * Add a node to the graph.
-     *
-     * <p>If the node already exists in the graph then this method is a no-op.</p>
-     *
-     * @param node the node to add
-     */
-    void addNode(@NonNull T node) {
-        if (!mGraph.containsKey(node)) {
-            mGraph.put(node, null);
-        }
-    }
-
-    /**
-     * Returns true if the node is already present in the graph, false otherwise.
-     */
-    boolean contains(@NonNull T node) {
-        return mGraph.containsKey(node);
-    }
-
-    /**
-     * Add an edge to the graph.
-     *
-     * <p>Both the given nodes should already have been added to the graph through
-     * {@link #addNode(Object)}.</p>
-     *
-     * @param node the parent node
-     * @param incomingEdge the node which has is an incoming edge to {@code node}
-     */
-    void addEdge(@NonNull T node, @NonNull T incomingEdge) {
-        if (!mGraph.containsKey(node) || !mGraph.containsKey(incomingEdge)) {
-            throw new IllegalArgumentException("All nodes must be present in the graph before"
-                    + " being added as an edge");
-        }
-
-        ArrayList<T> edges = mGraph.get(node);
-        if (edges == null) {
-            // If edges is null, we should try and get one from the pool and add it to the graph
-            edges = getEmptyList();
-            mGraph.put(node, edges);
-        }
-        // Finally add the edge to the list
-        edges.add(incomingEdge);
-    }
-
-    /**
-     * Get any incoming edges from the given node.
-     *
-     * @return a list containing any incoming edges, or null if there are none.
-     */
-    @Nullable
-    List getIncomingEdges(@NonNull T node) {
-        return mGraph.get(node);
-    }
-
-    /**
-     * Get any outgoing edges for the given node (i.e. nodes which have an incoming edge
-     * from the given node).
-     *
-     * @return a list containing any outgoing edges, or null if there are none.
-     */
-    @Nullable
-    List<T> getOutgoingEdges(@NonNull T node) {
-        ArrayList<T> result = null;
-        for (int i = 0, size = mGraph.size(); i < size; i++) {
-            ArrayList<T> edges = mGraph.valueAt(i);
-            if (edges != null && edges.contains(node)) {
-                if (result == null) {
-                    result = new ArrayList<>();
-                }
-                result.add(mGraph.keyAt(i));
-            }
-        }
-        return result;
-    }
-
-    boolean hasOutgoingEdges(@NonNull T node) {
-        for (int i = 0, size = mGraph.size(); i < size; i++) {
-            ArrayList<T> edges = mGraph.valueAt(i);
-            if (edges != null && edges.contains(node)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Clears the internal graph, and releases resources to pools.
-     */
-    void clear() {
-        for (int i = 0, size = mGraph.size(); i < size; i++) {
-            ArrayList<T> edges = mGraph.valueAt(i);
-            if (edges != null) {
-                poolList(edges);
-            }
-        }
-        mGraph.clear();
-    }
-
-    /**
-     * Returns a topologically sorted list of the nodes in this graph. This uses the DFS algorithm
-     * as described by Cormen et al. (2001). If this graph contains cyclic dependencies then this
-     * method will throw a {@link RuntimeException}.
-     *
-     * <p>The resulting list will be ordered such that index 0 will contain the node at the bottom
-     * of the graph. The node at the end of the list will have no dependencies on other nodes.</p>
-     */
-    @NonNull
-    ArrayList<T> getSortedList() {
-        mSortResult.clear();
-        mSortTmpMarked.clear();
-
-        // Start a DFS from each node in the graph
-        for (int i = 0, size = mGraph.size(); i < size; i++) {
-            dfs(mGraph.keyAt(i), mSortResult, mSortTmpMarked);
-        }
-
-        return mSortResult;
-    }
-
-    private void dfs(final T node, final ArrayList<T> result, final HashSet<T> tmpMarked) {
-        if (result.contains(node)) {
-            // We've already seen and added the node to the result list, skip...
-            return;
-        }
-        if (tmpMarked.contains(node)) {
-            throw new RuntimeException("This graph contains cyclic dependencies");
-        }
-        // Temporarily mark the node
-        tmpMarked.add(node);
-        // Recursively dfs all of the node's edges
-        final ArrayList<T> edges = mGraph.get(node);
-        if (edges != null) {
-            for (int i = 0, size = edges.size(); i < size; i++) {
-                dfs(edges.get(i), result, tmpMarked);
-            }
-        }
-        // Unmark the node from the temporary list
-        tmpMarked.remove(node);
-        // Finally add it to the result list
-        result.add(node);
-    }
-
-    /**
-     * Returns the size of the graph
-     */
-    int size() {
-        return mGraph.size();
-    }
-
-    @NonNull
-    private ArrayList<T> getEmptyList() {
-        ArrayList<T> list = mListPool.acquire();
-        if (list == null) {
-            list = new ArrayList<>();
-        }
-        return list;
-    }
-
-    private void poolList(@NonNull ArrayList<T> list) {
-        list.clear();
-        mListPool.release(list);
-    }
-}
\ No newline at end of file
diff --git a/design/src/android/support/design/widget/FloatingActionButton.java b/design/src/android/support/design/widget/FloatingActionButton.java
index b938836..f37b379 100644
--- a/design/src/android/support/design/widget/FloatingActionButton.java
+++ b/design/src/android/support/design/widget/FloatingActionButton.java
@@ -36,6 +36,7 @@
 import android.support.design.R;
 import android.support.design.widget.FloatingActionButtonImpl.InternalVisibilityChangedListener;
 import android.support.v4.view.ViewCompat;
+import android.support.v4.widget.ViewGroupUtils;
 import android.support.v7.widget.AppCompatImageHelper;
 import android.util.AttributeSet;
 import android.util.Log;
@@ -116,6 +117,11 @@
     public static final int SIZE_AUTO = -1;
 
     /**
+     * Indicates that FloatingActionButton should not have a custom size.
+     */
+    public static final int NO_CUSTOM_SIZE = 0;
+
+    /**
      * The switch point for the largest screen edge where SIZE_AUTO switches from mini to normal.
      */
     private static final int AUTO_MINI_LARGEST_SCREEN_WIDTH = 470;
@@ -132,6 +138,7 @@
     private int mBorderWidth;
     private int mRippleColor;
     private int mSize;
+    private int mCustomSize;
     int mImagePadding;
     private int mMaxImageSize;
 
@@ -164,6 +171,8 @@
                 R.styleable.FloatingActionButton_backgroundTintMode, -1), null);
         mRippleColor = a.getColor(R.styleable.FloatingActionButton_rippleColor, 0);
         mSize = a.getInt(R.styleable.FloatingActionButton_fabSize, SIZE_AUTO);
+        mCustomSize = a.getDimensionPixelSize(R.styleable.FloatingActionButton_fabCustomSize,
+                    0);
         mBorderWidth = a.getDimensionPixelSize(R.styleable.FloatingActionButton_borderWidth, 0);
         final float elevation = a.getDimension(R.styleable.FloatingActionButton_elevation, 0f);
         final float pressedTranslationZ = a.getDimension(
@@ -430,12 +439,41 @@
         };
     }
 
+    /**
+     * Sets the size of the button to be a custom value in pixels. If set to
+     * {@link #NO_CUSTOM_SIZE}, custom size will not be used and size will be calculated according
+     * to {@link #setSize(int)} method.
+     *
+     * @param size preferred size in pixels, or zero
+     *
+     * @attr ref android.support.design.R.styleable#FloatingActionButton_fabCustomSize
+     */
+    public void setCustomSize(int size) {
+        if (size < 0) {
+            throw new IllegalArgumentException("Custom size should be non-negative.");
+        }
+        mCustomSize = size;
+    }
+
+    /**
+     * Returns the custom size for this button.
+     *
+     * @return size in pixels, or {@link #NO_CUSTOM_SIZE}
+     */
+    public int getCustomSize() {
+        return mCustomSize;
+    }
+
     int getSizeDimension() {
         return getSizeDimension(mSize);
     }
 
     private int getSizeDimension(@Size final int size) {
         final Resources res = getResources();
+        // If custom size is set, return it
+        if (mCustomSize != NO_CUSTOM_SIZE) {
+            return mCustomSize;
+        }
         switch (size) {
             case SIZE_AUTO:
                 // If we're set to auto, grab the size from resources and refresh
diff --git a/design/src/android/support/design/widget/TextInputLayout.java b/design/src/android/support/design/widget/TextInputLayout.java
index 2ed79e4..0540678 100644
--- a/design/src/android/support/design/widget/TextInputLayout.java
+++ b/design/src/android/support/design/widget/TextInputLayout.java
@@ -49,6 +49,7 @@
 import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
 import android.support.v4.widget.Space;
 import android.support.v4.widget.TextViewCompat;
+import android.support.v4.widget.ViewGroupUtils;
 import android.support.v7.content.res.AppCompatResources;
 import android.support.v7.widget.AppCompatDrawableManager;
 import android.support.v7.widget.AppCompatTextView;
diff --git a/design/src/android/support/design/widget/ViewGroupUtils.java b/design/src/android/support/design/widget/ViewGroupUtils.java
deleted file mode 100644
index 0545516..0000000
--- a/design/src/android/support/design/widget/ViewGroupUtils.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.design.widget;
-
-import android.graphics.Matrix;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewParent;
-
-class ViewGroupUtils {
-    private static final ThreadLocal<Matrix> sMatrix = new ThreadLocal<>();
-    private static final ThreadLocal<RectF> sRectF = new ThreadLocal<>();
-
-    /**
-     * This is a port of the common
-     * {@link ViewGroup#offsetDescendantRectToMyCoords(android.view.View, android.graphics.Rect)}
-     * from the framework, but adapted to take transformations into account. The result
-     * will be the bounding rect of the real transformed rect.
-     *
-     * @param descendant view defining the original coordinate system of rect
-     * @param rect (in/out) the rect to offset from descendant to this view's coordinate system
-     */
-    static void offsetDescendantRect(ViewGroup parent, View descendant, Rect rect) {
-        Matrix m = sMatrix.get();
-        if (m == null) {
-            m = new Matrix();
-            sMatrix.set(m);
-        } else {
-            m.reset();
-        }
-
-        offsetDescendantMatrix(parent, descendant, m);
-
-        RectF rectF = sRectF.get();
-        if (rectF == null) {
-            rectF = new RectF();
-            sRectF.set(rectF);
-        }
-        rectF.set(rect);
-        m.mapRect(rectF);
-        rect.set((int) (rectF.left + 0.5f), (int) (rectF.top + 0.5f),
-                (int) (rectF.right + 0.5f), (int) (rectF.bottom + 0.5f));
-    }
-
-    /**
-     * Retrieve the transformed bounding rect of an arbitrary descendant view.
-     * This does not need to be a direct child.
-     *
-     * @param descendant descendant view to reference
-     * @param out rect to set to the bounds of the descendant view
-     */
-    static void getDescendantRect(ViewGroup parent, View descendant, Rect out) {
-        out.set(0, 0, descendant.getWidth(), descendant.getHeight());
-        offsetDescendantRect(parent, descendant, out);
-    }
-
-    private static void offsetDescendantMatrix(ViewParent target, View view, Matrix m) {
-        final ViewParent parent = view.getParent();
-        if (parent instanceof View && parent != target) {
-            final View vp = (View) parent;
-            offsetDescendantMatrix(target, vp, m);
-            m.preTranslate(-vp.getScrollX(), -vp.getScrollY());
-        }
-
-        m.preTranslate(view.getLeft(), view.getTop());
-
-        if (!view.getMatrix().isIdentity()) {
-            m.preConcat(view.getMatrix());
-        }
-    }
-}
diff --git a/design/tests/res/drawable-xxhdpi/ic_airplay_black_24dp.png b/design/tests/res/drawable-xxhdpi/ic_airplay_black_24dp.png
new file mode 100644
index 0000000..ddf2620
--- /dev/null
+++ b/design/tests/res/drawable-xxhdpi/ic_airplay_black_24dp.png
Binary files differ
diff --git a/design/tests/res/drawable-xxhdpi/ic_album_black_24dp.png b/design/tests/res/drawable-xxhdpi/ic_album_black_24dp.png
new file mode 100644
index 0000000..60f59f5
--- /dev/null
+++ b/design/tests/res/drawable-xxhdpi/ic_album_black_24dp.png
Binary files differ
diff --git a/design/tests/res/layout/design_appbar_dodge_left.xml b/design/tests/res/layout/design_appbar_dodge_left.xml
new file mode 100644
index 0000000..7f3ecb9
--- /dev/null
+++ b/design/tests/res/layout/design_appbar_dodge_left.xml
@@ -0,0 +1,45 @@
+<?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.
+  -->
+
+<android.support.design.widget.CoordinatorLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:fitsSystemWindows="true">
+
+    <include layout="@layout/design_content_appbar_toolbar_collapse_pin" />
+
+    <android.support.design.widget.FloatingActionButton
+        android:id="@+id/fab"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:layout_gravity="bottom|left"
+        android:src="@drawable/ic_album_black_24dp"
+        app:layout_insetEdge="left"
+        android:clickable="true" />
+
+    <android.support.design.widget.FloatingActionButton
+        android:id="@+id/fab2"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:layout_gravity="bottom|left"
+        android:src="@drawable/ic_airplay_black_24dp"
+        app:layout_dodgeInsetEdges="left"
+        android:clickable="true" />
+
+</android.support.design.widget.CoordinatorLayout>
diff --git a/design/tests/res/layout/design_appbar_dodge_right.xml b/design/tests/res/layout/design_appbar_dodge_right.xml
new file mode 100644
index 0000000..10815c0
--- /dev/null
+++ b/design/tests/res/layout/design_appbar_dodge_right.xml
@@ -0,0 +1,45 @@
+<?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.
+  -->
+
+<android.support.design.widget.CoordinatorLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:fitsSystemWindows="true">
+
+    <include layout="@layout/design_content_appbar_toolbar_collapse_pin" />
+
+    <android.support.design.widget.FloatingActionButton
+        android:id="@+id/fab"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:layout_gravity="bottom|right"
+        android:src="@drawable/ic_album_black_24dp"
+        app:layout_insetEdge="right"
+        android:clickable="true" />
+
+    <android.support.design.widget.FloatingActionButton
+        android:id="@+id/fab2"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:layout_gravity="bottom|right"
+        android:src="@drawable/ic_airplay_black_24dp"
+        app:layout_dodgeInsetEdges="right"
+        android:clickable="true" />
+
+</android.support.design.widget.CoordinatorLayout>
diff --git a/design/tests/res/values/ids.xml b/design/tests/res/values/ids.xml
index 73540b7..52b8356 100644
--- a/design/tests/res/values/ids.xml
+++ b/design/tests/res/values/ids.xml
@@ -26,5 +26,4 @@
     <item name="page_9" type="id"/>
     <item name="textinputlayout" type="id"/>
     <item name="textinputedittext" type="id"/>
-    <item name="anchor" type="id"/>
 </resources>
\ No newline at end of file
diff --git a/design/tests/res/values/strings.xml b/design/tests/res/values/strings.xml
index f456921..02763ec 100644
--- a/design/tests/res/values/strings.xml
+++ b/design/tests/res/values/strings.xml
@@ -42,6 +42,9 @@
     <string name="design_appbar_anchored_fab_margin_left">AppBar + anchored FAB with left margin</string>
     <string name="design_appbar_anchored_fab_margin_right">AppBar + anchored FAB with right margin</string>
 
+    <string name="design_appbar_dodge_left">AppBar + FABs with dodge on left</string>
+    <string name="design_appbar_dodge_right">AppBar + FABs with dodge on right</string>
+
     <string name="textinput_hint">Hint to the user</string>
 
 </resources>
\ No newline at end of file
diff --git a/design/tests/src/android/support/design/testutils/ActivityUtils.java b/design/tests/src/android/support/design/testutils/ActivityUtils.java
deleted file mode 100644
index 1ed6a3f..0000000
--- a/design/tests/src/android/support/design/testutils/ActivityUtils.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.support.design.testutils;
-
-import static org.junit.Assert.assertTrue;
-
-import android.os.Looper;
-import android.support.test.rule.ActivityTestRule;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Utility methods for testing activities.
- */
-public class ActivityUtils {
-    private static final Runnable DO_NOTHING = new Runnable() {
-        @Override
-        public void run() {
-        }
-    };
-
-    public static void waitForExecution(
-            final ActivityTestRule<? extends RecreatedAppCompatActivity> rule) {
-        // Wait for two cycles. When starting a postponed transition, it will post to
-        // the UI thread and then the execution will be added onto the queue after that.
-        // The two-cycle wait makes sure fragments have the opportunity to complete both
-        // before returning.
-        try {
-            rule.runOnUiThread(DO_NOTHING);
-            rule.runOnUiThread(DO_NOTHING);
-        } catch (Throwable throwable) {
-            throw new RuntimeException(throwable);
-        }
-    }
-
-    private static void runOnUiThreadRethrow(
-            ActivityTestRule<? extends RecreatedAppCompatActivity> rule, Runnable r) {
-        if (Looper.getMainLooper() == Looper.myLooper()) {
-            r.run();
-        } else {
-            try {
-                rule.runOnUiThread(r);
-            } catch (Throwable t) {
-                throw new RuntimeException(t);
-            }
-        }
-    }
-
-    /**
-     * Restarts the RecreatedAppCompatActivity and waits for the new activity to be resumed.
-     *
-     * @return The newly-restarted RecreatedAppCompatActivity
-     */
-    public static <T extends RecreatedAppCompatActivity> T recreateActivity(
-            ActivityTestRule<? extends RecreatedAppCompatActivity> rule, final T activity)
-            throws InterruptedException {
-        // Now switch the orientation
-        RecreatedAppCompatActivity.sResumed = new CountDownLatch(1);
-        RecreatedAppCompatActivity.sDestroyed = new CountDownLatch(1);
-
-        runOnUiThreadRethrow(rule, new Runnable() {
-            @Override
-            public void run() {
-                activity.recreate();
-            }
-        });
-        assertTrue(RecreatedAppCompatActivity.sResumed.await(1, TimeUnit.SECONDS));
-        assertTrue(RecreatedAppCompatActivity.sDestroyed.await(1, TimeUnit.SECONDS));
-        T newActivity = (T) RecreatedAppCompatActivity.sActivity;
-
-        waitForExecution(rule);
-
-        RecreatedAppCompatActivity.clearState();
-        return newActivity;
-    }
-}
diff --git a/design/tests/src/android/support/design/testutils/FloatingActionButtonActions.java b/design/tests/src/android/support/design/testutils/FloatingActionButtonActions.java
index 97949e6..a166f6b 100644
--- a/design/tests/src/android/support/design/testutils/FloatingActionButtonActions.java
+++ b/design/tests/src/android/support/design/testutils/FloatingActionButtonActions.java
@@ -107,6 +107,30 @@
         };
     }
 
+    public static ViewAction setCustomSize(final int size) {
+        return new ViewAction() {
+            @Override
+            public Matcher<View> getConstraints() {
+                return isAssignableFrom(FloatingActionButton.class);
+            }
+
+            @Override
+            public String getDescription() {
+                return "Sets FloatingActionButton custom size";
+            }
+
+            @Override
+            public void perform(UiController uiController, View view) {
+                uiController.loopMainThreadUntilIdle();
+
+                final FloatingActionButton fab = (FloatingActionButton) view;
+                fab.setCustomSize(size);
+
+                uiController.loopMainThreadUntilIdle();
+            }
+        };
+    }
+
     public static ViewAction setCompatElevation(final float size) {
         return new ViewAction() {
             @Override
diff --git a/design/tests/src/android/support/design/testutils/RecreatedAppCompatActivity.java b/design/tests/src/android/support/design/testutils/RecreatedAppCompatActivity.java
deleted file mode 100644
index 52ba059..0000000
--- a/design/tests/src/android/support/design/testutils/RecreatedAppCompatActivity.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.design.testutils;
-
-import android.os.Bundle;
-import android.support.annotation.Nullable;
-import android.support.v7.app.AppCompatActivity;
-
-import java.util.concurrent.CountDownLatch;
-
-/**
- * Activity that keeps track of resume / destroy lifecycle events, as well as of the last
- * instance of itself.
- */
-public class RecreatedAppCompatActivity extends AppCompatActivity {
-    // These must be cleared after each test using clearState()
-    public static RecreatedAppCompatActivity sActivity;
-    public static CountDownLatch sResumed;
-    public static CountDownLatch sDestroyed;
-
-    public static void clearState() {
-        sActivity = null;
-        sResumed = null;
-        sDestroyed = null;
-    }
-
-    @Override
-    protected void onCreate(@Nullable Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        sActivity = this;
-    }
-
-    @Override
-    protected void onResume() {
-        super.onResume();
-        if (sResumed != null) {
-            sResumed.countDown();
-        }
-    }
-
-    @Override
-    protected void onDestroy() {
-        super.onDestroy();
-        if (sDestroyed != null) {
-            sDestroyed.countDown();
-        }
-    }
-}
diff --git a/design/tests/src/android/support/design/testutils/TestUtilsMatchers.java b/design/tests/src/android/support/design/testutils/TestUtilsMatchers.java
index 36cdee6..46bb982 100644
--- a/design/tests/src/android/support/design/testutils/TestUtilsMatchers.java
+++ b/design/tests/src/android/support/design/testutils/TestUtilsMatchers.java
@@ -268,6 +268,36 @@
     }
 
     /**
+     * Returns a matcher that matches FloatingActionButtons with the specified custom size.
+     */
+    public static Matcher withFabCustomSize(final int customSize) {
+        return new BoundedMatcher<View, View>(View.class) {
+            private String mFailedCheckDescription;
+
+            @Override
+            public void describeTo(final Description description) {
+                description.appendText(mFailedCheckDescription);
+            }
+
+            @Override
+            public boolean matchesSafely(final View view) {
+                if (!(view instanceof FloatingActionButton)) {
+                    return false;
+                }
+
+                final FloatingActionButton fab = (FloatingActionButton) view;
+                if (Math.abs(fab.getCustomSize() - customSize) > 1.0f) {
+                    mFailedCheckDescription =
+                            "Custom size " + fab.getCustomSize() + " is different than expected "
+                                    + customSize;
+                    return false;
+                }
+                return true;
+            }
+        };
+    }
+
+    /**
      * Returns a matcher that matches FloatingActionButtons with the specified background
      * fill color.
      */
diff --git a/design/tests/src/android/support/design/widget/AppBarWithCollapsingToolbarStateRestoreTest.java b/design/tests/src/android/support/design/widget/AppBarWithCollapsingToolbarStateRestoreTest.java
index b9a6518..e8a29af 100644
--- a/design/tests/src/android/support/design/widget/AppBarWithCollapsingToolbarStateRestoreTest.java
+++ b/design/tests/src/android/support/design/widget/AppBarWithCollapsingToolbarStateRestoreTest.java
@@ -24,8 +24,8 @@
 import static android.support.test.espresso.matcher.ViewMatchers.withId;
 
 import android.support.design.test.R;
-import android.support.design.testutils.ActivityUtils;
 import android.support.test.filters.LargeTest;
+import android.support.testutils.AppCompatActivityUtils;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -59,8 +59,8 @@
                 .check(matches(hasZ()))
                 .check(matches(isCollapsed()));
 
-        mActivity = ActivityUtils.recreateActivity(mActivityTestRule, mActivity);
-        ActivityUtils.waitForExecution(mActivityTestRule);
+        mActivity = AppCompatActivityUtils.recreateActivity(mActivityTestRule, mActivity);
+        AppCompatActivityUtils.waitForExecution(mActivityTestRule);
 
         // And check that the app bar still is restored correctly
         onView(withId(R.id.app_bar))
diff --git a/design/tests/src/android/support/design/widget/AppBarWithDodgingTest.java b/design/tests/src/android/support/design/widget/AppBarWithDodgingTest.java
new file mode 100644
index 0000000..ad337d5
--- /dev/null
+++ b/design/tests/src/android/support/design/widget/AppBarWithDodgingTest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.design.widget;
+
+import static org.junit.Assert.assertTrue;
+
+import android.graphics.Rect;
+import android.support.design.test.R;
+import android.support.test.filters.SmallTest;
+
+import org.junit.Test;
+
+@SmallTest
+public class AppBarWithDodgingTest extends AppBarLayoutBaseTest {
+    @Test
+    public void testLeftDodge() throws Throwable {
+        configureContent(R.layout.design_appbar_dodge_left,
+                R.string.design_appbar_dodge_left);
+
+        final FloatingActionButton fab = mCoordinatorLayout.findViewById(R.id.fab);
+        final FloatingActionButton fab2 = mCoordinatorLayout.findViewById(R.id.fab2);
+
+        final int[] fabOnScreenXY = new int[2];
+        final int[] fab2OnScreenXY = new int[2];
+        fab.getLocationOnScreen(fabOnScreenXY);
+        fab2.getLocationOnScreen(fab2OnScreenXY);
+
+        final Rect fabRect = new Rect();
+        final Rect fab2Rect = new Rect();
+        fab.getContentRect(fabRect);
+        fab2.getContentRect(fab2Rect);
+
+        // Our second FAB is configured to "dodge" the first one - to be displayed to the
+        // right of it
+        int firstRight = fabOnScreenXY[0] + fabRect.right;
+        int secondLeft = fab2OnScreenXY[0] + fab2Rect.left;
+        assertTrue("Second button left edge at " + secondLeft
+                        + " should be dodging the first button right edge at " + firstRight,
+                secondLeft >= firstRight);
+    }
+
+    @Test
+    public void testRightDodge() throws Throwable {
+        configureContent(R.layout.design_appbar_dodge_right,
+                R.string.design_appbar_dodge_right);
+
+        final FloatingActionButton fab = mCoordinatorLayout.findViewById(R.id.fab);
+        final FloatingActionButton fab2 = mCoordinatorLayout.findViewById(R.id.fab2);
+
+        final int[] fabOnScreenXY = new int[2];
+        final int[] fab2OnScreenXY = new int[2];
+        fab.getLocationOnScreen(fabOnScreenXY);
+        fab2.getLocationOnScreen(fab2OnScreenXY);
+
+        final Rect fabRect = new Rect();
+        final Rect fab2Rect = new Rect();
+        fab.getContentRect(fabRect);
+        fab2.getContentRect(fab2Rect);
+
+        // Our second FAB is configured to "dodge" the first one - to be displayed to the
+        // left of it
+        int firstLeft = fabOnScreenXY[0] + fabRect.left;
+        int secondRight = fab2OnScreenXY[0] + fab2Rect.right;
+        assertTrue("Second button right edge at " + secondRight
+                        + " should be dodging the first button left edge at " + firstLeft,
+                secondRight <= firstLeft);
+    }
+}
diff --git a/design/tests/src/android/support/design/widget/BaseTestActivity.java b/design/tests/src/android/support/design/widget/BaseTestActivity.java
index 4662001..e1e44e2 100755
--- a/design/tests/src/android/support/design/widget/BaseTestActivity.java
+++ b/design/tests/src/android/support/design/widget/BaseTestActivity.java
@@ -18,7 +18,7 @@
 
 import android.os.Bundle;
 import android.support.annotation.LayoutRes;
-import android.support.design.testutils.RecreatedAppCompatActivity;
+import android.support.testutils.RecreatedAppCompatActivity;
 import android.view.WindowManager;
 
 abstract class BaseTestActivity extends RecreatedAppCompatActivity {
diff --git a/design/tests/src/android/support/design/widget/CoordinatorLayoutSortTest.java b/design/tests/src/android/support/design/widget/CoordinatorLayoutSortTest.java
deleted file mode 100644
index 0a407b7..0000000
--- a/design/tests/src/android/support/design/widget/CoordinatorLayoutSortTest.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.design.widget;
-
-import static org.junit.Assert.assertEquals;
-
-import android.app.Instrumentation;
-import android.support.design.testutils.CoordinatorLayoutUtils;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.view.View;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-
-@RunWith(Parameterized.class)
-@MediumTest
-public class CoordinatorLayoutSortTest
-        extends BaseInstrumentationTestCase<CoordinatorLayoutActivity> {
-
-    private static final int NUMBER_VIEWS_DEPENDENCY_SORT = 4;
-
-    /**
-     * All 27 permutations of a quad-tuple containing unique values in the range 0-3
-     */
-    @Parameterized.Parameters
-    public static Collection<Object[]> data() {
-        return Arrays.asList(new Object[][] {
-                {0, 1, 2, 3}, {0, 1, 3, 2}, {0, 2, 1, 3}, {0, 2, 3, 1}, {0, 3, 1, 2}, {0, 3, 2, 1},
-                {1, 0, 2, 3}, {1, 0, 3, 2}, {1, 2, 0, 3}, {1, 2, 3, 0}, {1, 3, 0, 2}, {1, 3, 2, 0},
-                {2, 0, 1, 3}, {2, 0, 3, 1}, {2, 1, 0, 3}, {2, 1, 3, 0}, {2, 3, 0, 1}, {2, 3, 1, 0},
-                {3, 0, 1, 2}, {3, 0, 2, 1}, {3, 1, 0, 2}, {3, 1, 2, 0}, {3, 2, 0, 1}, {3, 2, 1, 0}
-        });
-    }
-
-    private int mFirstAddIndex;
-    private int mSecondAddIndex;
-    private int mThirdAddIndex;
-    private int mFourthAddIndex;
-
-    public CoordinatorLayoutSortTest(int firstIndex, int secondIndex, int thirdIndex,
-            int fourthIndex) {
-        super(CoordinatorLayoutActivity.class);
-        mFirstAddIndex = firstIndex;
-        mSecondAddIndex = secondIndex;
-        mThirdAddIndex = thirdIndex;
-        mFourthAddIndex = fourthIndex;
-    }
-
-    @Test
-    public void testDependencySortingOrder() throws Throwable {
-        final CoordinatorLayout col = mActivityTestRule.getActivity().mCoordinatorLayout;
-
-        // Let's create some views where each view depends on the previous view.
-        // i.e C depends on B, B depends on A, A doesn't depend on anything.
-        final List<View> views = new ArrayList<>();
-        for (int i = 0; i < NUMBER_VIEWS_DEPENDENCY_SORT; i++) {
-            // 65 == A in ASCII
-            final String label = Character.toString((char) (65 + i));
-            final View view = new View(col.getContext()) {
-                @Override
-                public String toString() {
-                    return label;
-                }
-            };
-
-            // Create a Behavior which depends on the previously added view
-            View dependency = i > 0 ? views.get(i - 1) : null;
-            final CoordinatorLayout.Behavior<View> behavior
-                    = new CoordinatorLayoutUtils.DependentBehavior(dependency);
-
-            // And set its LayoutParams to use the Behavior
-            CoordinatorLayout.LayoutParams lp = col.generateDefaultLayoutParams();
-            lp.setBehavior(behavior);
-            view.setLayoutParams(lp);
-
-            views.add(view);
-        }
-
-        // Now the add the views in the given order and assert that they still end up in
-        // the expected order A, B, C, D
-        final List<View> testOrder = new ArrayList<>();
-        testOrder.add(views.get(mFirstAddIndex));
-        testOrder.add(views.get(mSecondAddIndex));
-        testOrder.add(views.get(mThirdAddIndex));
-        testOrder.add(views.get(mFourthAddIndex));
-        addViewsAndAssertOrdering(col, views, testOrder);
-    }
-
-    private void addViewsAndAssertOrdering(final CoordinatorLayout col,
-            final List<View> expectedOrder, final List<View> addOrder) throws Throwable {
-        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
-
-        // Add the Views in the given order
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                for (int i = 0; i < addOrder.size(); i++) {
-                    col.addView(addOrder.get(i));
-                }
-            }
-        });
-        instrumentation.waitForIdleSync();
-
-        // Now assert that the dependency sorted order is correct
-        assertEquals(expectedOrder, col.getDependencySortedChildren());
-
-        // Finally remove all of the views
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                col.removeAllViews();
-            }
-        });
-    }
-}
diff --git a/design/tests/src/android/support/design/widget/CoordinatorLayoutTest.java b/design/tests/src/android/support/design/widget/CoordinatorLayoutTest.java
deleted file mode 100644
index 3588043..0000000
--- a/design/tests/src/android/support/design/widget/CoordinatorLayoutTest.java
+++ /dev/null
@@ -1,776 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.design.widget;
-
-import static android.support.test.InstrumentationRegistry.getInstrumentation;
-import static android.support.test.espresso.Espresso.onView;
-import static android.support.test.espresso.action.ViewActions.swipeUp;
-import static android.support.test.espresso.matcher.ViewMatchers.withId;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Matchers.same;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.doCallRealMethod;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-import android.annotation.TargetApi;
-import android.app.Instrumentation;
-import android.content.Context;
-import android.graphics.Rect;
-import android.support.annotation.NonNull;
-import android.support.design.test.R;
-import android.support.design.testutils.CoordinatorLayoutUtils;
-import android.support.design.testutils.CoordinatorLayoutUtils.DependentBehavior;
-import android.support.design.widget.CoordinatorLayout.Behavior;
-import android.support.test.annotation.UiThreadTest;
-import android.support.test.filters.MediumTest;
-import android.support.test.filters.SdkSuppress;
-import android.support.v4.view.GravityCompat;
-import android.support.v4.view.ViewCompat;
-import android.support.v4.view.WindowInsetsCompat;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.View.MeasureSpec;
-import android.widget.ImageView;
-
-import org.junit.Before;
-import org.junit.Test;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicInteger;
-
-@MediumTest
-public class CoordinatorLayoutTest extends BaseInstrumentationTestCase<CoordinatorLayoutActivity> {
-
-    private Instrumentation mInstrumentation;
-
-    public CoordinatorLayoutTest() {
-        super(CoordinatorLayoutActivity.class);
-    }
-
-    @Before
-    public void setup() {
-        mInstrumentation = getInstrumentation();
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 21)
-    @TargetApi(21)
-    public void testSetFitSystemWindows() throws Throwable {
-        final Instrumentation instrumentation = getInstrumentation();
-        final CoordinatorLayout col = mActivityTestRule.getActivity().mCoordinatorLayout;
-        final View view = new View(col.getContext());
-
-        // Create a mock which calls the default impl of onApplyWindowInsets()
-        final CoordinatorLayout.Behavior<View> mockBehavior =
-                mock(CoordinatorLayout.Behavior.class);
-        doCallRealMethod().when(mockBehavior)
-                .onApplyWindowInsets(same(col), same(view), any(WindowInsetsCompat.class));
-
-        // Assert that the CoL is currently not set to fitSystemWindows
-        assertFalse(col.getFitsSystemWindows());
-
-        // Now add a view with our mocked behavior to the CoordinatorLayout
-        view.setFitsSystemWindows(true);
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                final CoordinatorLayout.LayoutParams lp = col.generateDefaultLayoutParams();
-                lp.setBehavior(mockBehavior);
-                col.addView(view, lp);
-            }
-        });
-        instrumentation.waitForIdleSync();
-
-        // Now request some insets and wait for the pass to happen
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                col.requestApplyInsets();
-            }
-        });
-        instrumentation.waitForIdleSync();
-
-        // Verify that onApplyWindowInsets() has not been called
-        verify(mockBehavior, never())
-                .onApplyWindowInsets(same(col), same(view), any(WindowInsetsCompat.class));
-
-        // Now enable fits system windows and wait for a pass to happen
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                col.setFitsSystemWindows(true);
-            }
-        });
-        instrumentation.waitForIdleSync();
-
-        // Verify that onApplyWindowInsets() has been called with some insets
-        verify(mockBehavior, atLeastOnce())
-                .onApplyWindowInsets(same(col), same(view), any(WindowInsetsCompat.class));
-    }
-
-    @Test
-    public void testLayoutChildren() throws Throwable {
-        final Instrumentation instrumentation = getInstrumentation();
-        final CoordinatorLayout col = mActivityTestRule.getActivity().mCoordinatorLayout;
-        final View view = new View(col.getContext());
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                col.addView(view, 100, 100);
-            }
-        });
-        instrumentation.waitForIdleSync();
-        int horizontallyCentered = (col.getWidth() - view.getWidth()) / 2;
-        int end = col.getWidth() - view.getWidth();
-        int verticallyCentered = (col.getHeight() - view.getHeight()) / 2;
-        int bottom = col.getHeight() - view.getHeight();
-        final int[][] testCases = {
-                // gravity, expected left, expected top
-                {Gravity.NO_GRAVITY, 0, 0},
-                {Gravity.LEFT, 0, 0},
-                {GravityCompat.START, 0, 0},
-                {Gravity.TOP, 0, 0},
-                {Gravity.CENTER, horizontallyCentered, verticallyCentered},
-                {Gravity.CENTER_HORIZONTAL, horizontallyCentered, 0},
-                {Gravity.CENTER_VERTICAL, 0, verticallyCentered},
-                {Gravity.RIGHT, end, 0},
-                {GravityCompat.END, end, 0},
-                {Gravity.BOTTOM, 0, bottom},
-                {Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM, horizontallyCentered, bottom},
-                {Gravity.RIGHT | Gravity.CENTER_VERTICAL, end, verticallyCentered},
-        };
-        for (final int[] testCase : testCases) {
-            mActivityTestRule.runOnUiThread(new Runnable() {
-                @Override
-                public void run() {
-                    final CoordinatorLayout.LayoutParams lp =
-                            (CoordinatorLayout.LayoutParams) view.getLayoutParams();
-                    lp.gravity = testCase[0];
-                    view.setLayoutParams(lp);
-                }
-            });
-            instrumentation.waitForIdleSync();
-            mActivityTestRule.runOnUiThread(new Runnable() {
-                @Override
-                public void run() {
-                    assertThat("Gravity: " + testCase[0], view.getLeft(), is(testCase[1]));
-                    assertThat("Gravity: " + testCase[0], view.getTop(), is(testCase[2]));
-                }
-            });
-        }
-    }
-
-    @Test
-    public void testInsetDependency() {
-        final CoordinatorLayout col = mActivityTestRule.getActivity().mCoordinatorLayout;
-
-        final CoordinatorLayout.LayoutParams lpInsetLeft = col.generateDefaultLayoutParams();
-        lpInsetLeft.insetEdge = Gravity.LEFT;
-
-        final CoordinatorLayout.LayoutParams lpInsetRight = col.generateDefaultLayoutParams();
-        lpInsetRight.insetEdge = Gravity.RIGHT;
-
-        final CoordinatorLayout.LayoutParams lpInsetTop = col.generateDefaultLayoutParams();
-        lpInsetTop.insetEdge = Gravity.TOP;
-
-        final CoordinatorLayout.LayoutParams lpInsetBottom = col.generateDefaultLayoutParams();
-        lpInsetBottom.insetEdge = Gravity.BOTTOM;
-
-        final CoordinatorLayout.LayoutParams lpDodgeLeft = col.generateDefaultLayoutParams();
-        lpDodgeLeft.dodgeInsetEdges = Gravity.LEFT;
-
-        final CoordinatorLayout.LayoutParams lpDodgeLeftAndTop = col.generateDefaultLayoutParams();
-        lpDodgeLeftAndTop.dodgeInsetEdges = Gravity.LEFT | Gravity.TOP;
-
-        final CoordinatorLayout.LayoutParams lpDodgeAll = col.generateDefaultLayoutParams();
-        lpDodgeAll.dodgeInsetEdges = Gravity.FILL;
-
-        final View a = new View(col.getContext());
-        final View b = new View(col.getContext());
-
-        assertThat(dependsOn(lpDodgeLeft, lpInsetLeft, col, a, b), is(true));
-        assertThat(dependsOn(lpDodgeLeft, lpInsetRight, col, a, b), is(false));
-        assertThat(dependsOn(lpDodgeLeft, lpInsetTop, col, a, b), is(false));
-        assertThat(dependsOn(lpDodgeLeft, lpInsetBottom, col, a, b), is(false));
-
-        assertThat(dependsOn(lpDodgeLeftAndTop, lpInsetLeft, col, a, b), is(true));
-        assertThat(dependsOn(lpDodgeLeftAndTop, lpInsetRight, col, a, b), is(false));
-        assertThat(dependsOn(lpDodgeLeftAndTop, lpInsetTop, col, a, b), is(true));
-        assertThat(dependsOn(lpDodgeLeftAndTop, lpInsetBottom, col, a, b), is(false));
-
-        assertThat(dependsOn(lpDodgeAll, lpInsetLeft, col, a, b), is(true));
-        assertThat(dependsOn(lpDodgeAll, lpInsetRight, col, a, b), is(true));
-        assertThat(dependsOn(lpDodgeAll, lpInsetTop, col, a, b), is(true));
-        assertThat(dependsOn(lpDodgeAll, lpInsetBottom, col, a, b), is(true));
-
-        assertThat(dependsOn(lpInsetLeft, lpDodgeLeft, col, a, b), is(false));
-    }
-
-    private static boolean dependsOn(CoordinatorLayout.LayoutParams lpChild,
-            CoordinatorLayout.LayoutParams lpDependency, CoordinatorLayout col,
-            View child, View dependency) {
-        child.setLayoutParams(lpChild);
-        dependency.setLayoutParams(lpDependency);
-        return lpChild.dependsOn(col, child, dependency);
-    }
-
-    @Test
-    public void testInsetEdge() throws Throwable {
-        final Instrumentation instrumentation = getInstrumentation();
-        final CoordinatorLayout col = mActivityTestRule.getActivity().mCoordinatorLayout;
-
-        final View insetView = new View(col.getContext());
-        final View dodgeInsetView = new View(col.getContext());
-        final AtomicInteger originalTop = new AtomicInteger();
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                CoordinatorLayout.LayoutParams lpInsetView = col.generateDefaultLayoutParams();
-                lpInsetView.width = CoordinatorLayout.LayoutParams.MATCH_PARENT;
-                lpInsetView.height = 100;
-                lpInsetView.gravity = Gravity.TOP | Gravity.LEFT;
-                lpInsetView.insetEdge = Gravity.TOP;
-                col.addView(insetView, lpInsetView);
-                insetView.setBackgroundColor(0xFF0000FF);
-
-                CoordinatorLayout.LayoutParams lpDodgeInsetView = col.generateDefaultLayoutParams();
-                lpDodgeInsetView.width = 100;
-                lpDodgeInsetView.height = 100;
-                lpDodgeInsetView.gravity = Gravity.TOP | Gravity.LEFT;
-                lpDodgeInsetView.dodgeInsetEdges = Gravity.TOP;
-                col.addView(dodgeInsetView, lpDodgeInsetView);
-                dodgeInsetView.setBackgroundColor(0xFFFF0000);
-            }
-        });
-        instrumentation.waitForIdleSync();
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                List<View> dependencies = col.getDependencies(dodgeInsetView);
-                assertThat(dependencies.size(), is(1));
-                assertThat(dependencies.get(0), is(insetView));
-
-                // Move the insetting view
-                originalTop.set(dodgeInsetView.getTop());
-                assertThat(originalTop.get(), is(insetView.getBottom()));
-                ViewCompat.offsetTopAndBottom(insetView, 123);
-            }
-        });
-        instrumentation.waitForIdleSync();
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                // Confirm that the dodging view was moved by the same size
-                assertThat(dodgeInsetView.getTop() - originalTop.get(), is(123));
-            }
-        });
-    }
-
-    @Test
-    public void testDependentViewChanged() throws Throwable {
-        final Instrumentation instrumentation = getInstrumentation();
-        final CoordinatorLayout col = mActivityTestRule.getActivity().mCoordinatorLayout;
-
-        // Add two views, A & B, where B depends on A
-        final View viewA = new View(col.getContext());
-        final CoordinatorLayout.LayoutParams lpA = col.generateDefaultLayoutParams();
-        lpA.width = 100;
-        lpA.height = 100;
-
-        final View viewB = new View(col.getContext());
-        final CoordinatorLayout.LayoutParams lpB = col.generateDefaultLayoutParams();
-        lpB.width = 100;
-        lpB.height = 100;
-        final CoordinatorLayout.Behavior behavior =
-                spy(new DependentBehavior(viewA));
-        lpB.setBehavior(behavior);
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                col.addView(viewA, lpA);
-                col.addView(viewB, lpB);
-            }
-        });
-        instrumentation.waitForIdleSync();
-
-        // Reset the Behavior since onDependentViewChanged may have already been called as part of
-        // any layout/draw passes already
-        reset(behavior);
-
-        // Now offset view A
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                ViewCompat.offsetLeftAndRight(viewA, 20);
-                ViewCompat.offsetTopAndBottom(viewA, 20);
-            }
-        });
-        instrumentation.waitForIdleSync();
-
-        // And assert that view B's Behavior was called appropriately
-        verify(behavior, times(1)).onDependentViewChanged(col, viewB, viewA);
-    }
-
-    @Test
-    public void testDependentViewRemoved() throws Throwable {
-        final Instrumentation instrumentation = getInstrumentation();
-        final CoordinatorLayout col = mActivityTestRule.getActivity().mCoordinatorLayout;
-
-        // Add two views, A & B, where B depends on A
-        final View viewA = new View(col.getContext());
-        final View viewB = new View(col.getContext());
-        final CoordinatorLayout.LayoutParams lpB = col.generateDefaultLayoutParams();
-        final CoordinatorLayout.Behavior behavior =
-                spy(new DependentBehavior(viewA));
-        lpB.setBehavior(behavior);
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                col.addView(viewA);
-                col.addView(viewB, lpB);
-            }
-        });
-        instrumentation.waitForIdleSync();
-
-        // Now remove view A
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                col.removeView(viewA);
-            }
-        });
-
-        // And assert that View B's Behavior was called appropriately
-        verify(behavior, times(1)).onDependentViewRemoved(col, viewB, viewA);
-    }
-
-    @Test
-    public void testGetDependenciesAfterDependentViewRemoved() throws Throwable {
-        final Instrumentation instrumentation = getInstrumentation();
-        final CoordinatorLayout col = mActivityTestRule.getActivity().mCoordinatorLayout;
-
-        // Add two views, A & B, where B depends on A
-        final View viewA = new View(col.getContext());
-        final View viewB = new View(col.getContext());
-        final CoordinatorLayout.LayoutParams lpB = col.generateDefaultLayoutParams();
-        final CoordinatorLayout.Behavior behavior
-                = new CoordinatorLayoutUtils.DependentBehavior(viewA) {
-            @Override
-            public void onDependentViewRemoved(CoordinatorLayout parent, View child,
-                    View dependency) {
-                parent.getDependencies(child);
-            }
-        };
-        lpB.setBehavior(behavior);
-
-        // Now add views
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                col.addView(viewA);
-                col.addView(viewB, lpB);
-            }
-        });
-
-        // Wait for a layout
-        instrumentation.waitForIdleSync();
-
-        // Now remove view A, which will trigger onDependentViewRemoved() on view B's behavior
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                col.removeView(viewA);
-            }
-        });
-    }
-
-    @Test
-    public void testDodgeInsetBeforeLayout() throws Throwable {
-        final CoordinatorLayout col = mActivityTestRule.getActivity().mCoordinatorLayout;
-
-        // Add a dummy view, which will be used to trigger a hierarchy change.
-        final View dummy = new View(col.getContext());
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                col.addView(dummy);
-            }
-        });
-
-        // Wait for a layout.
-        mInstrumentation.waitForIdleSync();
-
-        final View dodge = new View(col.getContext());
-        final CoordinatorLayout.LayoutParams lpDodge = col.generateDefaultLayoutParams();
-        lpDodge.dodgeInsetEdges = Gravity.BOTTOM;
-        lpDodge.setBehavior(new Behavior() {
-            @Override
-            public boolean getInsetDodgeRect(CoordinatorLayout parent, View child, Rect rect) {
-                // Any non-empty rect is fine here.
-                rect.set(0, 0, 10, 10);
-                return true;
-            }
-        });
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                col.addView(dodge, lpDodge);
-
-                // Ensure the new view is in the list of children.
-                int heightSpec = MeasureSpec.makeMeasureSpec(col.getHeight(), MeasureSpec.EXACTLY);
-                int widthSpec = MeasureSpec.makeMeasureSpec(col.getWidth(), MeasureSpec.EXACTLY);
-                col.measure(widthSpec, heightSpec);
-
-                // Force a hierarchy change.
-                col.removeView(dummy);
-            }
-        });
-
-        // Wait for a layout.
-        mInstrumentation.waitForIdleSync();
-    }
-
-    @Test
-    public void testGoneViewsNotMeasuredLaidOut() throws Throwable {
-        final CoordinatorLayoutActivity activity = mActivityTestRule.getActivity();
-        final CoordinatorLayout col = activity.mCoordinatorLayout;
-
-        // Now create a GONE view and add it to the CoordinatorLayout
-        final View imageView = new View(activity);
-        imageView.setVisibility(View.GONE);
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                col.addView(imageView, 200, 200);
-            }
-        });
-        // Wait for a layout and measure pass
-        mInstrumentation.waitForIdleSync();
-
-        // And assert that it has not been laid out
-        assertFalse(imageView.getMeasuredWidth() > 0);
-        assertFalse(imageView.getMeasuredHeight() > 0);
-        assertFalse(ViewCompat.isLaidOut(imageView));
-
-        // Now set the view to INVISIBLE
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                imageView.setVisibility(View.INVISIBLE);
-            }
-        });
-        // Wait for a layout and measure pass
-        mInstrumentation.waitForIdleSync();
-
-        // And assert that it has been laid out
-        assertTrue(imageView.getMeasuredWidth() > 0);
-        assertTrue(imageView.getMeasuredHeight() > 0);
-        assertTrue(ViewCompat.isLaidOut(imageView));
-    }
-
-    @Test
-    public void testNestedScrollingDispatchesToBehavior() throws Throwable {
-        final CoordinatorLayoutActivity activity = mActivityTestRule.getActivity();
-        final CoordinatorLayout col = activity.mCoordinatorLayout;
-
-        // Now create a view and add it to the CoordinatorLayout with the spy behavior,
-        // along with a NestedScrollView
-        final ImageView imageView = new ImageView(activity);
-        final CoordinatorLayout.Behavior behavior = spy(new NestedScrollingBehavior());
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                LayoutInflater.from(activity).inflate(R.layout.include_nestedscrollview, col, true);
-
-                CoordinatorLayout.LayoutParams clp = new CoordinatorLayout.LayoutParams(200, 200);
-                clp.setBehavior(behavior);
-                col.addView(imageView, clp);
-            }
-        });
-
-        // Now vertically swipe up on the NSV, causing nested scrolling to occur
-        onView(withId(R.id.nested_scrollview)).perform(swipeUp());
-
-        // Verify that the Behavior's onStartNestedScroll was called once
-        verify(behavior, times(1)).onStartNestedScroll(
-                eq(col), // parent
-                eq(imageView), // child
-                any(View.class), // target
-                any(View.class), // direct child target
-                any(int.class)); // axes
-
-        // Verify that the Behavior's onNestedScrollAccepted was called once
-        verify(behavior, times(1)).onNestedScrollAccepted(
-                eq(col), // parent
-                eq(imageView), // child
-                any(View.class), // target
-                any(View.class), // direct child target
-                any(int.class)); // axes
-
-        // Verify that the Behavior's onNestedPreScroll was called at least once
-        verify(behavior, atLeastOnce()).onNestedPreScroll(
-                eq(col), // parent
-                eq(imageView), // child
-                any(View.class), // target
-                any(int.class), // dx
-                any(int.class), // dy
-                any(int[].class)); // consumed
-
-        // Verify that the Behavior's onNestedScroll was called at least once
-        verify(behavior, atLeastOnce()).onNestedScroll(
-                eq(col), // parent
-                eq(imageView), // child
-                any(View.class), // target
-                any(int.class), // dx consumed
-                any(int.class), // dy consumed
-                any(int.class), // dx unconsumed
-                any(int.class)); // dy unconsumed
-
-        // Verify that the Behavior's onStopNestedScroll was called once
-        verify(behavior, times(1)).onStopNestedScroll(
-                eq(col), // parent
-                eq(imageView), // child
-                any(View.class)); // target
-    }
-
-    @Test
-    public void testNestedScrollingDispatchingToBehaviorWithGoneView() throws Throwable {
-        final CoordinatorLayoutActivity activity = mActivityTestRule.getActivity();
-        final CoordinatorLayout col = activity.mCoordinatorLayout;
-
-        // Now create a GONE view and add it to the CoordinatorLayout with the spy behavior,
-        // along with a NestedScrollView
-        final ImageView imageView = new ImageView(activity);
-        imageView.setVisibility(View.GONE);
-        final CoordinatorLayout.Behavior behavior = spy(new NestedScrollingBehavior());
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                LayoutInflater.from(activity).inflate(R.layout.include_nestedscrollview, col, true);
-
-                CoordinatorLayout.LayoutParams clp = new CoordinatorLayout.LayoutParams(200, 200);
-                clp.setBehavior(behavior);
-                col.addView(imageView, clp);
-            }
-        });
-
-        // Now vertically swipe up on the NSV, causing nested scrolling to occur
-        onView(withId(R.id.nested_scrollview)).perform(swipeUp());
-
-        // Verify that the Behavior's onStartNestedScroll was not called
-        verify(behavior, never()).onStartNestedScroll(
-                eq(col), // parent
-                eq(imageView), // child
-                any(View.class), // target
-                any(View.class), // direct child target
-                any(int.class)); // axes
-
-        // Verify that the Behavior's onNestedScrollAccepted was not called
-        verify(behavior, never()).onNestedScrollAccepted(
-                eq(col), // parent
-                eq(imageView), // child
-                any(View.class), // target
-                any(View.class), // direct child target
-                any(int.class)); // axes
-
-        // Verify that the Behavior's onNestedPreScroll was not called
-        verify(behavior, never()).onNestedPreScroll(
-                eq(col), // parent
-                eq(imageView), // child
-                any(View.class), // target
-                any(int.class), // dx
-                any(int.class), // dy
-                any(int[].class)); // consumed
-
-        // Verify that the Behavior's onNestedScroll was not called
-        verify(behavior, never()).onNestedScroll(
-                eq(col), // parent
-                eq(imageView), // child
-                any(View.class), // target
-                any(int.class), // dx consumed
-                any(int.class), // dy consumed
-                any(int.class), // dx unconsumed
-                any(int.class)); // dy unconsumed
-
-        // Verify that the Behavior's onStopNestedScroll was not called
-        verify(behavior, never()).onStopNestedScroll(
-                eq(col), // parent
-                eq(imageView), // child
-                any(View.class)); // target
-    }
-
-    @Test
-    public void testNestedScrollingTriggeringDependentViewChanged() throws Throwable {
-        final CoordinatorLayoutActivity activity = mActivityTestRule.getActivity();
-        final CoordinatorLayout col = activity.mCoordinatorLayout;
-
-        // First a NestedScrollView to trigger nested scrolling
-        final View scrollView = LayoutInflater.from(activity).inflate(
-                R.layout.include_nestedscrollview, col, false);
-
-        // Now create a View and Behavior which depend on the scrollview
-        final ImageView dependentView = new ImageView(activity);
-        final CoordinatorLayout.Behavior dependentBehavior = spy(new DependentBehavior(scrollView));
-
-        // Finally a view which accepts nested scrolling in the CoordinatorLayout
-        final ImageView nestedScrollAwareView = new ImageView(activity);
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                // First add the ScrollView
-                col.addView(scrollView);
-
-                // Now add the view which depends on the scrollview
-                CoordinatorLayout.LayoutParams clp = new CoordinatorLayout.LayoutParams(200, 200);
-                clp.setBehavior(dependentBehavior);
-                col.addView(dependentView, clp);
-
-                // Now add the nested scrolling aware view
-                clp = new CoordinatorLayout.LayoutParams(200, 200);
-                clp.setBehavior(new NestedScrollingBehavior());
-                col.addView(nestedScrollAwareView, clp);
-            }
-        });
-
-        // Wait for any layouts, and reset the Behavior so that the call counts are 0
-        getInstrumentation().waitForIdleSync();
-        reset(dependentBehavior);
-
-        // Now vertically swipe up on the NSV, causing nested scrolling to occur
-        onView(withId(R.id.nested_scrollview)).perform(swipeUp());
-
-        // Verify that the Behavior's onDependentViewChanged is not called due to the
-        // nested scroll
-        verify(dependentBehavior, never()).onDependentViewChanged(
-                eq(col), // parent
-                eq(dependentView), // child
-                eq(scrollView)); // axes
-    }
-
-    @Test
-    public void testDodgeInsetViewWithEmptyBounds() throws Throwable {
-        final CoordinatorLayout col = mActivityTestRule.getActivity().mCoordinatorLayout;
-
-        // Add a view with zero height/width which is set to dodge its bounds
-        final View view = new View(col.getContext());
-        final Behavior spyBehavior = spy(new DodgeBoundsBehavior());
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                final CoordinatorLayout.LayoutParams lp = col.generateDefaultLayoutParams();
-                lp.dodgeInsetEdges = Gravity.BOTTOM;
-                lp.gravity = Gravity.BOTTOM;
-                lp.height = 0;
-                lp.width = 0;
-                lp.setBehavior(spyBehavior);
-                col.addView(view, lp);
-            }
-        });
-
-        // Wait for a layout
-        mInstrumentation.waitForIdleSync();
-
-        // Now add an non-empty bounds inset view to the bottom of the CoordinatorLayout
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                final View dodge = new View(col.getContext());
-                final CoordinatorLayout.LayoutParams lp = col.generateDefaultLayoutParams();
-                lp.insetEdge = Gravity.BOTTOM;
-                lp.gravity = Gravity.BOTTOM;
-                lp.height = 60;
-                lp.width = CoordinatorLayout.LayoutParams.MATCH_PARENT;
-                col.addView(dodge, lp);
-            }
-        });
-
-        // Verify that the Behavior of the view with empty bounds does not have its
-        // getInsetDodgeRect() called
-        verify(spyBehavior, never())
-                .getInsetDodgeRect(same(col), same(view), any(Rect.class));
-    }
-
-    public static class NestedScrollingBehavior extends CoordinatorLayout.Behavior<View> {
-        @Override
-        public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child,
-                View directTargetChild, View target, int nestedScrollAxes) {
-            // Return true so that we always accept nested scroll events
-            return true;
-        }
-    }
-
-    public static class DodgeBoundsBehavior extends Behavior<View> {
-        @Override
-        public boolean getInsetDodgeRect(CoordinatorLayout parent, View child, Rect rect) {
-            rect.set(child.getLeft(), child.getTop(), child.getRight(), child.getBottom());
-            return true;
-        }
-    }
-
-    @UiThreadTest
-    @Test
-    public void testAnchorDependencyGraph() throws Throwable {
-        final CoordinatorLayout col = mActivityTestRule.getActivity().mCoordinatorLayout;
-
-        // Override hashcode because of implementation of SimpleArrayMap used in
-        // DirectedAcyclicGraph used for sorting dependencies. Hashcode of anchored view has to be
-        // greater than of the one it is anchored to in order to reproduce the error.
-        final View anchor = createViewWithHashCode(col.getContext(), 2);
-        anchor.setId(R.id.anchor);
-
-        final View ship = createViewWithHashCode(col.getContext(), 3);
-        final CoordinatorLayout.LayoutParams lp = col.generateDefaultLayoutParams();
-        lp.setAnchorId(R.id.anchor);
-
-        col.addView(anchor);
-        col.addView(ship, lp);
-
-        // Get dependencies immediately to avoid possible call to onMeasure(), since error
-        // only happens on first computing of sorted dependencies.
-        List<View> dependencySortedChildren = col.getDependencySortedChildren();
-        assertThat(dependencySortedChildren, is(Arrays.asList(anchor, ship)));
-    }
-
-    @NonNull
-    private View createViewWithHashCode(final Context context, final int hashCode) {
-        return new View(context) {
-            @Override
-            public int hashCode() {
-                return hashCode;
-            }
-        };
-    }
-}
diff --git a/design/tests/src/android/support/design/widget/FloatingActionButtonTest.java b/design/tests/src/android/support/design/widget/FloatingActionButtonTest.java
index 1037235..f7e9286 100644
--- a/design/tests/src/android/support/design/widget/FloatingActionButtonTest.java
+++ b/design/tests/src/android/support/design/widget/FloatingActionButtonTest.java
@@ -20,6 +20,7 @@
 import static android.support.design.testutils.FloatingActionButtonActions.setBackgroundTintColor;
 import static android.support.design.testutils.FloatingActionButtonActions.setBackgroundTintList;
 import static android.support.design.testutils.FloatingActionButtonActions.setCompatElevation;
+import static android.support.design.testutils.FloatingActionButtonActions.setCustomSize;
 import static android.support.design.testutils.FloatingActionButtonActions.setImageResource;
 import static android.support.design.testutils.FloatingActionButtonActions.setLayoutGravity;
 import static android.support.design.testutils.FloatingActionButtonActions.setSize;
@@ -31,6 +32,7 @@
 import static android.support.design.testutils.TestUtilsMatchers.withFabBackgroundFill;
 import static android.support.design.testutils.TestUtilsMatchers.withFabContentAreaOnMargins;
 import static android.support.design.testutils.TestUtilsMatchers.withFabContentHeight;
+import static android.support.design.testutils.TestUtilsMatchers.withFabCustomSize;
 import static android.support.design.widget.DesignViewActions.setVisibility;
 import static android.support.test.espresso.Espresso.onView;
 import static android.support.test.espresso.action.ViewActions.click;
@@ -271,4 +273,16 @@
                 .perform(setEnabled(true))
                 .perform(setCompatElevation(8));
     }
+
+    @SmallTest
+    @Test
+    public void testSetCustomSize() {
+        onView(withId(R.id.fab_standard))
+                .perform(setCustomSize(10))
+                .check(matches(withFabCustomSize(10)));
+
+        onView(withId(R.id.fab_standard))
+                .perform(setCustomSize(20))
+                .check(matches(withFabCustomSize(20)));
+    }
 }
diff --git a/design/tests/src/android/support/design/widget/TextInputLayoutTest.java b/design/tests/src/android/support/design/widget/TextInputLayoutTest.java
index 52471a9..5969235 100755
--- a/design/tests/src/android/support/design/widget/TextInputLayoutTest.java
+++ b/design/tests/src/android/support/design/widget/TextInputLayoutTest.java
@@ -65,8 +65,6 @@
 import android.os.Build;
 import android.os.Parcelable;
 import android.support.design.test.R;
-import android.support.design.testutils.ActivityUtils;
-import android.support.design.testutils.RecreatedAppCompatActivity;
 import android.support.design.testutils.TestUtils;
 import android.support.design.testutils.ViewStructureImpl;
 import android.support.test.annotation.UiThreadTest;
@@ -75,6 +73,8 @@
 import android.support.test.filters.LargeTest;
 import android.support.test.filters.MediumTest;
 import android.support.test.filters.SdkSuppress;
+import android.support.testutils.AppCompatActivityUtils;
+import android.support.testutils.RecreatedAppCompatActivity;
 import android.support.v4.widget.TextViewCompat;
 import android.text.method.PasswordTransformationMethod;
 import android.text.method.TransformationMethod;
@@ -524,8 +524,8 @@
         onView(withId(R.id.textinput_password)).check(isPasswordToggledVisible(true));
 
         RecreatedAppCompatActivity activity = mActivityTestRule.getActivity();
-        activity = ActivityUtils.recreateActivity(mActivityTestRule, activity);
-        ActivityUtils.waitForExecution(mActivityTestRule);
+        AppCompatActivityUtils.recreateActivity(mActivityTestRule, activity);
+        AppCompatActivityUtils.waitForExecution(mActivityTestRule);
 
         // Check that the password is still toggled to be shown as plain text
         onView(withId(R.id.textinput_password)).check(isPasswordToggledVisible(true));
diff --git a/dynamic-animation/build.gradle b/dynamic-animation/build.gradle
index 21dbfb3..ac5623d 100644
--- a/dynamic-animation/build.gradle
+++ b/dynamic-animation/build.gradle
@@ -9,8 +9,8 @@
 dependencies {
     api(project(":support-core-utils"))
 
-    androidTestImplementation(TEST_RUNNER, libs.exclude_annotations)
-    androidTestImplementation(ESPRESSO_CORE, libs.exclude_annotations)
+    androidTestImplementation(TEST_RUNNER)
+    androidTestImplementation(ESPRESSO_CORE)
     androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
     androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
 }
diff --git a/emoji/core/build.gradle b/emoji/core/build.gradle
index 67d7530..a311f25 100644
--- a/emoji/core/build.gradle
+++ b/emoji/core/build.gradle
@@ -1,6 +1,8 @@
 import static android.support.dependencies.DependenciesKt.*
 import android.support.LibraryGroups
 import android.support.LibraryVersions
+import java.util.zip.ZipException
+import java.util.zip.ZipFile
 
 plugins {
     id("SupportAndroidLibraryPlugin")
@@ -22,8 +24,8 @@
 
     api(project(":support-compat"))
 
-    androidTestImplementation(TEST_RUNNER, libs.exclude_annotations)
-    androidTestImplementation(ESPRESSO_CORE, libs.exclude_annotations)
+    androidTestImplementation(TEST_RUNNER)
+    androidTestImplementation(ESPRESSO_CORE)
     androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
     androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
     androidTestImplementation project(':support-testutils')
@@ -77,3 +79,53 @@
         url = "http://www.unicode.org/copyright.html#License"
     }
 }
+
+import org.gradle.api.Task
+import org.gradle.api.tasks.TaskAction
+
+class ValidateJarInput extends DefaultTask {
+    Task syncTask
+
+    @TaskAction
+    void validate() {
+        for (File f : syncTask.inputs.files.files) {
+            if (f.name.endsWith(".jar")) {
+                ZipFile zip = null
+                for (def i : 0..3) {
+                    try {
+                        zip = new ZipFile(f)
+                        if (i > 0) {
+                            // If we get here, we know this is some timing issues.  The jar file is
+                            // properly created, but only if we wait.
+                            logger.error("Succeeded in opening jar file '$f' after $i retries.  Failing build.")
+                            throw new RuntimeException("Failed opening zip file earlier.")
+                        }
+                        break
+                    } catch (ZipException e) {
+                        logger.error("Error opening jar file '$f' (attempt: $i): $e.message")
+                        sleep(1000)
+                    } finally {
+                        if (zip != null) {
+                            zip.close()
+                        }
+                    }
+                    if (i == 3) {
+                        // We failed after 3 retries, this means the generated file is not a proper
+                        // jar file.
+                        throw new RuntimeException("Failed opening zip file after 3 retries.")
+                    }
+                }
+            }
+        }
+    }
+}
+
+afterEvaluate {
+    def syncJniTask = tasks.getByName("transformNativeLibsWithSyncJniLibsForRelease")
+    println "found $syncJniTask.name"
+    def validateTask = tasks.create("validateJarInputsForRelease", ValidateJarInput.class) { t ->
+        t.syncTask = syncJniTask
+    }
+    validateTask.dependsOn(syncJniTask.getDependsOn().collect())
+    syncJniTask.dependsOn(validateTask)
+}
diff --git a/emoji/core/src/main/java/android/support/text/emoji/EmojiMetadata.java b/emoji/core/src/main/java/android/support/text/emoji/EmojiMetadata.java
index 488dcf9..7d495ed 100644
--- a/emoji/core/src/main/java/android/support/text/emoji/EmojiMetadata.java
+++ b/emoji/core/src/main/java/android/support/text/emoji/EmojiMetadata.java
@@ -26,12 +26,13 @@
 import android.support.annotation.NonNull;
 import android.support.annotation.RequiresApi;
 import android.support.annotation.RestrictTo;
-import android.support.text.emoji.flatbuffer.MetadataItem;
-import android.support.text.emoji.flatbuffer.MetadataList;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
+import androidx.text.emoji.flatbuffer.MetadataItem;
+import androidx.text.emoji.flatbuffer.MetadataList;
+
 /**
  * Information about a single emoji.
  *
diff --git a/emoji/core/src/main/java/android/support/text/emoji/MetadataListReader.java b/emoji/core/src/main/java/android/support/text/emoji/MetadataListReader.java
index 1008c17..02856cb 100644
--- a/emoji/core/src/main/java/android/support/text/emoji/MetadataListReader.java
+++ b/emoji/core/src/main/java/android/support/text/emoji/MetadataListReader.java
@@ -22,13 +22,14 @@
 import android.support.annotation.IntRange;
 import android.support.annotation.RequiresApi;
 import android.support.annotation.RestrictTo;
-import android.support.text.emoji.flatbuffer.MetadataList;
 
 import java.io.IOException;
 import java.io.InputStream;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 
+import androidx.text.emoji.flatbuffer.MetadataList;
+
 /**
  * Reads the emoji metadata from a given InputStream or ByteBuffer.
  *
diff --git a/emoji/core/src/main/java/android/support/text/emoji/MetadataRepo.java b/emoji/core/src/main/java/android/support/text/emoji/MetadataRepo.java
index e86277e..f5afec8 100644
--- a/emoji/core/src/main/java/android/support/text/emoji/MetadataRepo.java
+++ b/emoji/core/src/main/java/android/support/text/emoji/MetadataRepo.java
@@ -24,7 +24,6 @@
 import android.support.annotation.RequiresApi;
 import android.support.annotation.RestrictTo;
 import android.support.annotation.VisibleForTesting;
-import android.support.text.emoji.flatbuffer.MetadataList;
 import android.support.v4.util.Preconditions;
 import android.util.SparseArray;
 
@@ -32,6 +31,8 @@
 import java.io.InputStream;
 import java.nio.ByteBuffer;
 
+import androidx.text.emoji.flatbuffer.MetadataList;
+
 /**
  * Class to hold the emoji metadata required to process and draw emojis.
  */
diff --git a/exifinterface/build.gradle b/exifinterface/build.gradle
index f5e63f6..fa4d7b4 100644
--- a/exifinterface/build.gradle
+++ b/exifinterface/build.gradle
@@ -9,7 +9,7 @@
 dependencies {
     api(project(":support-annotations"))
 
-    androidTestImplementation(TEST_RUNNER, libs.exclude_annotations)
+    androidTestImplementation(TEST_RUNNER)
 }
 
 android {
diff --git a/exifinterface/src/main/java/android/support/media/ExifInterface.java b/exifinterface/src/main/java/android/support/media/ExifInterface.java
index 72b61cb..eea69ab 100644
--- a/exifinterface/src/main/java/android/support/media/ExifInterface.java
+++ b/exifinterface/src/main/java/android/support/media/ExifInterface.java
@@ -4678,9 +4678,7 @@
     private int getMimeType(BufferedInputStream in) throws IOException {
         in.mark(SIGNATURE_CHECK_SIZE);
         byte[] signatureCheckBytes = new byte[SIGNATURE_CHECK_SIZE];
-        if (in.read(signatureCheckBytes) != SIGNATURE_CHECK_SIZE) {
-            throw new EOFException();
-        }
+        in.read(signatureCheckBytes);
         in.reset();
         if (isJpegFormat(signatureCheckBytes)) {
             return IMAGE_TYPE_JPEG;
@@ -5333,7 +5331,7 @@
             int dataFormat = dataInputStream.readUnsignedShort();
             int numberOfComponents = dataInputStream.readInt();
             // Next four bytes is for data offset or value.
-            long nextEntryOffset = dataInputStream.peek() + 4;
+            long nextEntryOffset = dataInputStream.peek() + 4L;
 
             // Look up a corresponding tag from tag number
             ExifTag tag = (ExifTag) sExifTagMapsForReading[ifdType].get(tagNumber);
diff --git a/fragment/api/current.txt b/fragment/api/current.txt
index ccd6f4f..df78c30 100644
--- a/fragment/api/current.txt
+++ b/fragment/api/current.txt
@@ -22,7 +22,7 @@
     field public static final int STYLE_NO_TITLE = 1; // 0x1
   }
 
-  public class Fragment implements android.content.ComponentCallbacks android.view.View.OnCreateContextMenuListener {
+  public class Fragment implements android.content.ComponentCallbacks android.arch.lifecycle.LifecycleOwner android.view.View.OnCreateContextMenuListener {
     ctor public Fragment();
     method public void dump(java.lang.String, java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
     method public final boolean equals(java.lang.Object);
@@ -141,7 +141,7 @@
     field public static final android.os.Parcelable.Creator<android.support.v4.app.Fragment.SavedState> CREATOR;
   }
 
-  public class FragmentActivity extends android.support.v4.app.SupportActivity {
+  public class FragmentActivity extends android.support.v4.app.SupportActivity implements android.support.v4.app.ActivityCompat.OnRequestPermissionsResultCallback android.support.v4.app.ActivityCompat.RequestPermissionsRequestCodeValidator {
     ctor public FragmentActivity();
     method public java.lang.Object getLastCustomNonConfigurationInstance();
     method public android.support.v4.app.FragmentManager getSupportFragmentManager();
diff --git a/fragment/build.gradle b/fragment/build.gradle
index 0b80b62..b1cc47e 100644
--- a/fragment/build.gradle
+++ b/fragment/build.gradle
@@ -12,10 +12,13 @@
     api(project(":support-core-utils"))
     api(project(":support-annotations"))
 
-    androidTestImplementation(TEST_RUNNER, libs.exclude_annotations)
-    androidTestImplementation(ESPRESSO_CORE, libs.exclude_annotations)
+    androidTestImplementation(TEST_RUNNER)
+    androidTestImplementation(ESPRESSO_CORE)
     androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
     androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
+    androidTestImplementation project(':support-testutils'), {
+        exclude group: 'com.android.support', module: 'support-fragment'
+    }
 }
 
 android {
diff --git a/fragment/src/main/java/android/support/v4/app/package.html b/fragment/src/main/java/android/support/v4/app/package.html
deleted file mode 100755
index 02d1b79..0000000
--- a/fragment/src/main/java/android/support/v4/app/package.html
+++ /dev/null
@@ -1,8 +0,0 @@
-<body>
-
-Support android.app classes to assist with development of applications for
-android API level 4 or later.  The main features here are backwards-compatible
-versions of {@link android.support.v4.app.FragmentManager} and
-{@link android.support.v4.app.LoaderManager}.
-
-</body>
diff --git a/fragment/tests/java/android/support/v4/app/FragmentManagerNonConfigTest.java b/fragment/tests/java/android/support/v4/app/FragmentManagerNonConfigTest.java
index eeae2b4..dc62c01 100644
--- a/fragment/tests/java/android/support/v4/app/FragmentManagerNonConfigTest.java
+++ b/fragment/tests/java/android/support/v4/app/FragmentManagerNonConfigTest.java
@@ -21,6 +21,7 @@
 import android.support.test.filters.MediumTest;
 import android.support.test.rule.ActivityTestRule;
 import android.support.test.runner.AndroidJUnit4;
+import android.support.testutils.FragmentActivityUtils;
 import android.support.v4.app.test.NonConfigOnStopActivity;
 
 import org.junit.Rule;
@@ -41,7 +42,7 @@
      */
     @Test
     public void nonConfigStop() throws Throwable {
-        FragmentActivity activity = FragmentTestUtil.recreateActivity(mActivityRule,
+        FragmentActivity activity = FragmentActivityUtils.recreateActivity(mActivityRule,
                 mActivityRule.getActivity());
 
         // A fragment was added in onStop(), but we shouldn't see it here...
diff --git a/fragment/tests/java/android/support/v4/app/FragmentTestUtil.java b/fragment/tests/java/android/support/v4/app/FragmentTestUtil.java
index 1da1af6..604701f 100644
--- a/fragment/tests/java/android/support/v4/app/FragmentTestUtil.java
+++ b/fragment/tests/java/android/support/v4/app/FragmentTestUtil.java
@@ -16,7 +16,6 @@
 package android.support.v4.app;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
 
 import android.app.Activity;
 import android.app.Instrumentation;
@@ -28,15 +27,12 @@
 import android.support.test.InstrumentationRegistry;
 import android.support.test.rule.ActivityTestRule;
 import android.support.v4.app.test.FragmentTestActivity;
-import android.support.v4.app.test.RecreatedActivity;
 import android.util.Pair;
 import android.view.ViewGroup;
 import android.view.animation.Animation;
 
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
 
 public class FragmentTestUtil {
     private static final Runnable DO_NOTHING = new Runnable() {
@@ -247,32 +243,4 @@
             }
         }
     }
-
-    /**
-     * Restarts the RecreatedActivity and waits for the new activity to be resumed.
-     *
-     * @return The newly-restarted Activity
-     */
-    public static <T extends RecreatedActivity> T recreateActivity(
-            ActivityTestRule<? extends RecreatedActivity> rule, final T activity)
-            throws InterruptedException {
-        // Now switch the orientation
-        RecreatedActivity.sResumed = new CountDownLatch(1);
-        RecreatedActivity.sDestroyed = new CountDownLatch(1);
-
-        runOnUiThreadRethrow(rule, new Runnable() {
-            @Override
-            public void run() {
-                activity.recreate();
-            }
-        });
-        assertTrue(RecreatedActivity.sResumed.await(1, TimeUnit.SECONDS));
-        assertTrue(RecreatedActivity.sDestroyed.await(1, TimeUnit.SECONDS));
-        T newActivity = (T) RecreatedActivity.sActivity;
-
-        waitForExecution(rule);
-
-        RecreatedActivity.clearState();
-        return newActivity;
-    }
 }
diff --git a/fragment/tests/java/android/support/v4/app/HangingFragmentTest.java b/fragment/tests/java/android/support/v4/app/HangingFragmentTest.java
index e124b67..bf8726f 100644
--- a/fragment/tests/java/android/support/v4/app/HangingFragmentTest.java
+++ b/fragment/tests/java/android/support/v4/app/HangingFragmentTest.java
@@ -19,6 +19,7 @@
 import android.support.test.filters.SmallTest;
 import android.support.test.rule.ActivityTestRule;
 import android.support.test.runner.AndroidJUnit4;
+import android.support.testutils.FragmentActivityUtils;
 import android.support.v4.app.test.HangingFragmentActivity;
 
 import org.junit.Assert;
@@ -37,7 +38,7 @@
 
     @Test
     public void testNoCrash() throws InterruptedException {
-        HangingFragmentActivity newActivity = FragmentTestUtil.recreateActivity(
+        HangingFragmentActivity newActivity = FragmentActivityUtils.recreateActivity(
                 mActivityRule, mActivityRule.getActivity());
         Assert.assertNotNull(newActivity);
     }
diff --git a/fragment/tests/java/android/support/v4/app/LoaderTest.java b/fragment/tests/java/android/support/v4/app/LoaderTest.java
index b581fe7..523baf0 100644
--- a/fragment/tests/java/android/support/v4/app/LoaderTest.java
+++ b/fragment/tests/java/android/support/v4/app/LoaderTest.java
@@ -30,6 +30,7 @@
 import android.support.test.filters.MediumTest;
 import android.support.test.rule.ActivityTestRule;
 import android.support.test.runner.AndroidJUnit4;
+import android.support.testutils.FragmentActivityUtils;
 import android.support.v4.app.test.LoaderActivity;
 import android.support.v4.content.AsyncTaskLoader;
 import android.support.v4.content.Loader;
@@ -58,7 +59,7 @@
     public void testLeak() throws Throwable {
         // Restart the activity because mActivityRule keeps a strong reference to the
         // old activity.
-        LoaderActivity activity = FragmentTestUtil.recreateActivity(mActivityRule,
+        LoaderActivity activity = FragmentActivityUtils.recreateActivity(mActivityRule,
                 mActivityRule.getActivity());
 
         LoaderFragment fragment = new LoaderFragment();
@@ -80,7 +81,7 @@
 
         WeakReference<LoaderActivity> weakActivity = new WeakReference(LoaderActivity.sActivity);
 
-        activity = FragmentTestUtil.recreateActivity(mActivityRule, activity);
+        activity = FragmentActivityUtils.recreateActivity(mActivityRule, activity);
 
         // Wait for everything to settle. We have to make sure that the old Activity
         // is ready to be collected.
@@ -101,7 +102,7 @@
 
         assertEquals("Loaded!", activity.textView.getText().toString());
 
-        activity = FragmentTestUtil.recreateActivity(mActivityRule, activity);
+        activity = FragmentActivityUtils.recreateActivity(mActivityRule, activity);
 
         FragmentTestUtil.waitForExecution(mActivityRule);
 
diff --git a/fragment/tests/java/android/support/v4/app/test/HangingFragmentActivity.java b/fragment/tests/java/android/support/v4/app/test/HangingFragmentActivity.java
index 9fab4df..80b9aa5 100644
--- a/fragment/tests/java/android/support/v4/app/test/HangingFragmentActivity.java
+++ b/fragment/tests/java/android/support/v4/app/test/HangingFragmentActivity.java
@@ -19,6 +19,7 @@
 import android.os.Bundle;
 import android.support.annotation.Nullable;
 import android.support.fragment.test.R;
+import android.support.testutils.RecreatedActivity;
 
 public class HangingFragmentActivity extends RecreatedActivity {
 
diff --git a/fragment/tests/java/android/support/v4/app/test/LoaderActivity.java b/fragment/tests/java/android/support/v4/app/test/LoaderActivity.java
index 8a051f4..2990f0a 100644
--- a/fragment/tests/java/android/support/v4/app/test/LoaderActivity.java
+++ b/fragment/tests/java/android/support/v4/app/test/LoaderActivity.java
@@ -20,6 +20,7 @@
 import android.os.Bundle;
 import android.support.annotation.Nullable;
 import android.support.fragment.test.R;
+import android.support.testutils.RecreatedActivity;
 import android.support.v4.app.LoaderManager;
 import android.support.v4.content.AsyncTaskLoader;
 import android.support.v4.content.Loader;
diff --git a/fragment/tests/java/android/support/v4/app/test/NonConfigOnStopActivity.java b/fragment/tests/java/android/support/v4/app/test/NonConfigOnStopActivity.java
index fc03b50..9d71388 100644
--- a/fragment/tests/java/android/support/v4/app/test/NonConfigOnStopActivity.java
+++ b/fragment/tests/java/android/support/v4/app/test/NonConfigOnStopActivity.java
@@ -16,6 +16,7 @@
 
 package android.support.v4.app.test;
 
+import android.support.testutils.RecreatedActivity;
 import android.support.v4.app.Fragment;
 
 public class NonConfigOnStopActivity extends RecreatedActivity {
diff --git a/fragment/tests/java/android/support/v4/app/test/RecreatedActivity.java b/fragment/tests/java/android/support/v4/app/test/RecreatedActivity.java
deleted file mode 100644
index c298a88..0000000
--- a/fragment/tests/java/android/support/v4/app/test/RecreatedActivity.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.v4.app.test;
-
-import android.os.Bundle;
-import android.support.annotation.Nullable;
-import android.support.v4.app.FragmentActivity;
-
-import java.util.concurrent.CountDownLatch;
-
-public class RecreatedActivity extends FragmentActivity {
-    // These must be cleared after each test using clearState()
-    public static RecreatedActivity sActivity;
-    public static CountDownLatch sResumed;
-    public static CountDownLatch sDestroyed;
-
-    public static void clearState() {
-        sActivity = null;
-        sResumed = null;
-        sDestroyed = null;
-    }
-
-    @Override
-    protected void onCreate(@Nullable Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        sActivity = this;
-    }
-
-    @Override
-    protected void onResume() {
-        super.onResume();
-        if (sResumed != null) {
-            sResumed.countDown();
-        }
-    }
-
-    @Override
-    protected void onDestroy() {
-        super.onDestroy();
-        if (sDestroyed != null) {
-            sDestroyed.countDown();
-        }
-    }
-}
diff --git a/graphics/drawable/animated/api/27.0.0.ignore b/graphics/drawable/animated/api/27.0.0.ignore
new file mode 100644
index 0000000..34748c7
--- /dev/null
+++ b/graphics/drawable/animated/api/27.0.0.ignore
@@ -0,0 +1 @@
+da32427
diff --git a/graphics/drawable/animated/api/current.txt b/graphics/drawable/animated/api/current.txt
index f2601de..ebdc90b 100644
--- a/graphics/drawable/animated/api/current.txt
+++ b/graphics/drawable/animated/api/current.txt
@@ -1,6 +1,6 @@
 package android.support.graphics.drawable {
 
-  public abstract interface Animatable2Compat {
+  public abstract interface Animatable2Compat implements android.graphics.drawable.Animatable {
     method public abstract void clearAnimationCallbacks();
     method public abstract void registerAnimationCallback(android.support.graphics.drawable.Animatable2Compat.AnimationCallback);
     method public abstract boolean unregisterAnimationCallback(android.support.graphics.drawable.Animatable2Compat.AnimationCallback);
@@ -12,7 +12,7 @@
     method public void onAnimationStart(android.graphics.drawable.Drawable);
   }
 
-  public class AnimatedVectorDrawableCompat extends android.support.graphics.drawable.VectorDrawableCommon implements android.support.graphics.drawable.Animatable2Compat {
+  public class AnimatedVectorDrawableCompat extends android.graphics.drawable.Drawable implements android.support.graphics.drawable.Animatable2Compat android.support.v4.graphics.drawable.TintAwareDrawable {
     method public void clearAnimationCallbacks();
     method public static void clearAnimationCallbacks(android.graphics.drawable.Drawable);
     method public static android.support.graphics.drawable.AnimatedVectorDrawableCompat create(android.content.Context, int);
diff --git a/graphics/drawable/animated/build.gradle b/graphics/drawable/animated/build.gradle
index ffa850f..e76f846 100644
--- a/graphics/drawable/animated/build.gradle
+++ b/graphics/drawable/animated/build.gradle
@@ -10,8 +10,8 @@
     api(project(":support-vector-drawable"))
     api(project(":support-core-ui"))
 
-    androidTestImplementation(TEST_RUNNER, libs.exclude_annotations)
-    androidTestImplementation(ESPRESSO_CORE, libs.exclude_annotations)
+    androidTestImplementation(TEST_RUNNER)
+    androidTestImplementation(ESPRESSO_CORE)
 }
 
 android {
diff --git a/graphics/drawable/static/api/27.0.0.ignore b/graphics/drawable/static/api/27.0.0.ignore
new file mode 100644
index 0000000..dc31648
--- /dev/null
+++ b/graphics/drawable/static/api/27.0.0.ignore
@@ -0,0 +1,2 @@
+57adc08
+4aae3d4
diff --git a/graphics/drawable/static/api/current.txt b/graphics/drawable/static/api/current.txt
index db07bf2..2fe60b8 100644
--- a/graphics/drawable/static/api/current.txt
+++ b/graphics/drawable/static/api/current.txt
@@ -1,9 +1,6 @@
 package android.support.graphics.drawable {
 
-   abstract class VectorDrawableCommon extends android.graphics.drawable.Drawable {
-  }
-
-  public class VectorDrawableCompat extends android.support.graphics.drawable.VectorDrawableCommon {
+  public class VectorDrawableCompat extends android.graphics.drawable.Drawable implements android.support.v4.graphics.drawable.TintAwareDrawable {
     method public static android.support.graphics.drawable.VectorDrawableCompat create(android.content.res.Resources, int, android.content.res.Resources.Theme);
     method public static android.support.graphics.drawable.VectorDrawableCompat createFromXmlInner(android.content.res.Resources, org.xmlpull.v1.XmlPullParser, android.util.AttributeSet, android.content.res.Resources.Theme) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
     method public void draw(android.graphics.Canvas);
diff --git a/graphics/drawable/static/build.gradle b/graphics/drawable/static/build.gradle
index 8e498fe..8575d6a 100644
--- a/graphics/drawable/static/build.gradle
+++ b/graphics/drawable/static/build.gradle
@@ -10,7 +10,7 @@
     api(project(":support-annotations"))
     api(project(":support-compat"))
 
-    androidTestImplementation(TEST_RUNNER, libs.exclude_annotations)
+    androidTestImplementation(TEST_RUNNER)
 }
 
 android {
diff --git a/graphics/drawable/static/src/main/java/android/support/graphics/drawable/VectorDrawableCompat.java b/graphics/drawable/static/src/main/java/android/support/graphics/drawable/VectorDrawableCompat.java
index 2c7ae41..943f1aa 100644
--- a/graphics/drawable/static/src/main/java/android/support/graphics/drawable/VectorDrawableCompat.java
+++ b/graphics/drawable/static/src/main/java/android/support/graphics/drawable/VectorDrawableCompat.java
@@ -56,8 +56,8 @@
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
+import java.util.ArrayDeque;
 import java.util.ArrayList;
-import java.util.Stack;
 
 /**
  * For API 24 and above, this class is delegating to the framework's {@link VectorDrawable}.
@@ -173,6 +173,10 @@
  * <dd>Sets the lineJoin for a stroked path: miter,round,bevel. Default is miter.</dd>
  * <dt><code>android:strokeMiterLimit</code></dt>
  * <dd>Sets the Miter limit for a stroked path. Default is 4.</dd>
+ * <dt><code>android:fillType</code></dt>
+ * <dd>Sets the fillType for a path. The types can be either "evenOdd" or "nonZero". They behave the
+ * same as SVG's "fill-rule" properties. Default is nonZero. For more details, see
+ * <a href="https://www.w3.org/TR/SVG/painting.html#FillRuleProperty">FillRuleProperty</a></dd>
  * </dl></dd>
  * </dl>
  *
@@ -726,7 +730,7 @@
 
         // Use a stack to help to build the group tree.
         // The top of the stack is always the current group.
-        final Stack<VGroup> groupStack = new Stack<VGroup>();
+        final ArrayDeque<VGroup> groupStack = new ArrayDeque<>();
         groupStack.push(pathRenderer.mRootGroup);
 
         int eventType = parser.getEventType();
@@ -781,14 +785,7 @@
         }
 
         if (noPathTag) {
-            final StringBuffer tag = new StringBuffer();
-
-            if (tag.length() > 0) {
-                tag.append(" or ");
-            }
-            tag.append(SHAPE_PATH);
-
-            throw new XmlPullParserException("no " + tag + " defined");
+            throw new XmlPullParserException("no " + SHAPE_PATH + " defined");
         }
     }
 
diff --git a/jetifier/.gitignore b/jetifier/.gitignore
new file mode 100644
index 0000000..4469528
--- /dev/null
+++ b/jetifier/.gitignore
@@ -0,0 +1 @@
+**/build
diff --git a/jetifier/jetifier/build.gradle b/jetifier/jetifier/build.gradle
new file mode 100644
index 0000000..c817220
--- /dev/null
+++ b/jetifier/jetifier/build.gradle
@@ -0,0 +1,96 @@
+/*
+ * 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
+ */
+
+buildscript {
+    ext.supportRootFolder = "${project.projectDir}/../../"
+    apply from: "$supportRootFolder/buildSrc/repos.gradle"
+
+    ext.kotlin_version = '1.2.0'
+
+    repos.addMavenRepositories(repositories)
+
+    dependencies {
+        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+    }
+}
+
+subprojects {
+    group 'android.support.tools.jetifier'
+
+    ext.supportRootFolder = "${project.projectDir}/../../.."
+
+    apply plugin: 'kotlin'
+    apply from: "$supportRootFolder/buildSrc/repos.gradle"
+
+    compileKotlin {
+        kotlinOptions.jvmTarget = "1.8"
+    }
+    compileTestKotlin {
+        kotlinOptions.jvmTarget = "1.8"
+    }
+
+    repos.addMavenRepositories(repositories)
+
+    dependencies {
+        compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
+    }
+}
+
+ext.runningInBuildServer = System.env.DIST_DIR != null && System.env.OUT_DIR != null
+def setupOutDirs() {
+    /*
+     * With the build server you are given two env variables.
+     * The OUT_DIR is a temporary directory you can use to put things during the build.
+     * The DIST_DIR is where you want to save things from the build.
+     *
+     * The build server will copy the contents of DIST_DIR to somewhere and make it available.
+     */
+    if (ext.runningInBuildServer) {
+        buildDir = new File(System.env.OUT_DIR + '/gradle/frameworks/support/build')
+                .getCanonicalFile()
+        project.ext.distDir = new File(System.env.DIST_DIR).getCanonicalFile()
+
+        // the build server should always print out full stack traces for any failures.
+        gradle.startParameter.showStacktrace = ShowStacktrace.ALWAYS
+    } else {
+        buildDir = file("${ext.supportRootFolder}/../../out/host/gradle/frameworks/support/jetifier/build")
+        project.ext.distDir = new File("${ext.supportRootFolder}/../../out/dist")
+    }
+    subprojects {
+        // Change buildDir so that all plugins pick up the new value.
+        project.buildDir = new File("$project.parent.buildDir/../$project.name/build")
+        project.ext.distDir = new File("${ext.supportRootFolder}/../../out/dist")
+    }
+}
+
+def configureBuildOnServer() {
+    def buildOnServerTask = rootProject.tasks.create("buildOnServer")
+    rootProject.tasks.whenTaskAdded { task ->
+        if ("build".equals(task.name)) {
+            buildOnServerTask.dependsOn task
+        }
+    }
+    subprojects {
+        project.tasks.whenTaskAdded { task ->
+            if ("fatJar".equals(task.name)) {
+                buildOnServerTask.dependsOn task
+            }
+        }
+    }
+}
+
+setupOutDirs()
+configureBuildOnServer()
diff --git a/jetifier/jetifier/core/build.gradle b/jetifier/jetifier/core/build.gradle
new file mode 100644
index 0000000..1ac3f36
--- /dev/null
+++ b/jetifier/jetifier/core/build.gradle
@@ -0,0 +1,26 @@
+/*
+ * 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
+ */
+
+version '1.0'
+
+dependencies {
+    compile group: 'org.ow2.asm', name: 'asm', version: '5.2'
+    compile group: 'org.ow2.asm', name: 'asm-commons', version: '5.2'
+    compile group: 'com.google.code.gson', name: 'gson', version: '2.8.0'
+    compile group: 'org.jdom', name: 'jdom2', version: '2.0.6'
+    testCompile group: 'junit', name: 'junit', version: '4.12'
+    testCompile group: 'com.google.truth', name: 'truth', version: '0.31'
+}
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/Processor.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/Processor.kt
new file mode 100644
index 0000000..5af8fb2
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/Processor.kt
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.core
+
+import android.support.tools.jetifier.core.archive.Archive
+import android.support.tools.jetifier.core.archive.ArchiveFile
+import android.support.tools.jetifier.core.archive.ArchiveItemVisitor
+import android.support.tools.jetifier.core.config.Config
+import android.support.tools.jetifier.core.transform.TransformationContext
+import android.support.tools.jetifier.core.transform.Transformer
+import android.support.tools.jetifier.core.transform.bytecode.ByteCodeTransformer
+import android.support.tools.jetifier.core.transform.pom.PomDocument
+import android.support.tools.jetifier.core.transform.pom.PomScanner
+import android.support.tools.jetifier.core.transform.proguard.ProGuardTransformer
+import android.support.tools.jetifier.core.transform.resource.XmlResourcesTransformer
+import android.support.tools.jetifier.core.utils.Log
+import java.io.File
+import java.io.FileNotFoundException
+import java.nio.file.Files
+import java.nio.file.Path
+
+/**
+ * The main entry point to the library. Extracts any given archive recursively and runs all
+ * the registered [Transformer]s over the set and creates new archives that will contain the
+ * transformed files.
+ */
+class Processor(private val config : Config) : ArchiveItemVisitor {
+
+    companion object {
+        private const val TAG = "Processor"
+    }
+
+    private val context = TransformationContext(config)
+
+    private val transformers = listOf(
+            // Register your transformers here
+            ByteCodeTransformer(context),
+            XmlResourcesTransformer(context),
+            ProGuardTransformer(context)
+    )
+
+    /**
+     * Transforms the input libraries given in [inputLibraries] using all the registered
+     * [Transformer]s and returns new libraries stored in [outputPath].
+     *
+     * Currently we have the following transformers:
+     * - [ByteCodeTransformer] for java native code
+     * - [XmlResourcesTransformer] for java native code
+     * - [ProGuardTransformer] for PorGuard files
+     */
+    fun transform(inputLibraries: Set<File>, outputPath: Path) : TransformationResult {
+        // 1) Extract and load all libraries
+        val libraries = loadLibraries(inputLibraries)
+
+        // 2) Search for POM files
+        val pomFiles = scanPomFiles(libraries)
+
+        // 3) Transform all the libraries
+        libraries.forEach{ transformLibrary(it) }
+
+        if (context.wasErrorFound()) {
+            throw IllegalArgumentException("There were ${context.mappingNotFoundFailuresCount}" +
+                " errors found during the remapping. Check the logs for more details.")
+        }
+
+        // TODO: Here we might need to modify the POM files if they point at a library that we have
+        // just refactored.
+
+        // 4) Transform the previously discovered POM files
+        transformPomFiles(pomFiles)
+
+        // 5) Repackage the libraries back to archives
+        val outputLibraries = libraries.map{ it.writeSelfToDir(outputPath) }.toSet()
+
+        // TODO: Filter out only the libraries that have been really changed
+        return TransformationResult(
+            filesToRemove = inputLibraries,
+            filesToAdd = outputLibraries)
+    }
+
+    private fun loadLibraries(inputLibraries : Iterable<File>) : List<Archive> {
+        val libraries = mutableListOf<Archive>()
+        for (library in inputLibraries) {
+            if (!library.canRead()) {
+                throw FileNotFoundException("Cannot open a library at '$library'")
+            }
+
+            libraries.add(Archive.Builder.extract(library))
+        }
+        return libraries.toList()
+    }
+
+    private fun scanPomFiles(libraries: List<Archive>) : List<PomDocument> {
+        val scanner = PomScanner(config)
+
+        libraries.forEach { scanner.scanArchiveForPomFile(it) }
+        if (scanner.wasErrorFound()) {
+            throw IllegalArgumentException("At least one of the libraries depends on an older" +
+                " version of support library. Check the logs for more details.")
+        }
+
+        return scanner.pomFiles
+    }
+
+    private fun transformPomFiles(files: List<PomDocument>) {
+        files.forEach {
+            it.applyRules(config.pomRewriteRules)
+            it.saveBackToFileIfNeeded()
+        }
+    }
+
+    private fun transformLibrary(archive: Archive) {
+        Log.i(TAG, "Started new transformation")
+        Log.i(TAG, "- Input file: %s", archive.relativePath)
+
+        archive.accept(this)
+    }
+
+    override fun visit(archive: Archive) {
+        archive.files.forEach{ it.accept(this) }
+    }
+
+    override fun visit(archiveFile: ArchiveFile) {
+        val transformer = transformers.firstOrNull { it.canTransform(archiveFile) }
+
+        if (transformer == null) {
+            Log.i(TAG, "[Skipped] %s", archiveFile.relativePath)
+            return
+        }
+
+        Log.i(TAG, "[Applied: %s] %s", transformer.javaClass.simpleName, archiveFile.relativePath)
+        transformer.runTransform(archiveFile)
+    }
+
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/TransformationResult.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/TransformationResult.kt
new file mode 100644
index 0000000..3e90483
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/TransformationResult.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.core
+
+import java.io.File
+
+/**
+ * Result of the transformation done by the [Processor]
+ *
+ * @param filesToRemove files to be removed from project's dependencies
+ * @param filesToAdd files generated by Jetifier to be added to project's dependencies
+ */
+data class TransformationResult(val filesToRemove: Set<File>, val filesToAdd: Set<File>)
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/archive/Archive.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/archive/Archive.kt
new file mode 100644
index 0000000..70ea68c
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/archive/Archive.kt
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.core.archive
+
+import android.support.tools.jetifier.core.utils.Log
+import java.io.BufferedOutputStream
+import java.io.File
+import java.io.FileInputStream
+import java.io.FileOutputStream
+import java.io.IOException
+import java.io.InputStream
+import java.io.OutputStream
+import java.nio.file.Files
+import java.nio.file.Path
+import java.nio.file.Paths
+import java.util.zip.ZipEntry
+import java.util.zip.ZipInputStream
+import java.util.zip.ZipOutputStream
+
+/**
+ * Represents an archive (zip, jar, aar ...)
+ */
+class Archive(
+        override val relativePath: Path,
+        val files: List<ArchiveItem>)
+    : ArchiveItem {
+
+    companion object {
+        /** Defines file extensions that are recognized as archives */
+        val ARCHIVE_EXTENSIONS = listOf(".jar", ".zip", ".aar")
+
+        const val TAG = "Archive"
+    }
+
+    override val fileName: String = relativePath.fileName.toString()
+
+    override fun accept(visitor: ArchiveItemVisitor) {
+        visitor.visit(this)
+    }
+
+    @Throws(IOException::class)
+    fun writeSelfToDir(outputDirPath: Path) : File {
+        val outputPath = Paths.get(outputDirPath.toString(), fileName)
+
+        if (Files.exists(outputPath)) {
+            Log.i(TAG, "Deleting old output file")
+            Files.delete(outputPath)
+        }
+
+        // Create directories if they don't exist yet
+        Files.createDirectories(outputDirPath)
+
+        Log.i(TAG, "Writing archive: %s", outputPath.toUri())
+        val file = outputPath.toFile()
+        Files.createFile(outputPath)
+        val stream = BufferedOutputStream(FileOutputStream(file))
+        writeSelfTo(stream)
+        stream.close()
+        return file
+    }
+
+    @Throws(IOException::class)
+    override fun writeSelfTo(outputStream: OutputStream) {
+        val out = ZipOutputStream(outputStream)
+
+        for (file in files) {
+            Log.d(TAG, "Writing file: %s", file.relativePath)
+
+            val entry = ZipEntry(file.relativePath.toString())
+            out.putNextEntry(entry)
+            file.writeSelfTo(out)
+            out.closeEntry()
+        }
+        out.finish()
+    }
+
+
+    object Builder {
+
+        @Throws(IOException::class)
+        fun extract(archiveFile: File): Archive {
+            Log.i(TAG, "Extracting: %s", archiveFile.absolutePath)
+
+            val inputStream = FileInputStream(archiveFile)
+            inputStream.use {
+                return extractArchive(it, archiveFile.toPath())
+            }
+        }
+
+        @Throws(IOException::class)
+        private fun extractArchive(inputStream: InputStream, relativePath: Path)
+                : Archive {
+            val zipIn = ZipInputStream(inputStream)
+            val files = mutableListOf<ArchiveItem>()
+
+            var entry: ZipEntry? = zipIn.nextEntry
+            // iterates over entries in the zip file
+            while (entry != null) {
+                if (!entry.isDirectory) {
+                    val entryPath = Paths.get(entry.name)
+                    if (isArchive(entry)) {
+                        Log.i(TAG, "Extracting nested: %s", entryPath)
+                        files.add(extractArchive(zipIn, entryPath))
+                    } else {
+                        files.add(extractFile(zipIn, entryPath))
+                    }
+                }
+                zipIn.closeEntry()
+                entry = zipIn.nextEntry
+            }
+            // Cannot close the zip stream at this moment as that would close also any parent zip
+            // streams in case we are processing a nested archive.
+
+            return Archive(relativePath, files.toList())
+        }
+
+        @Throws(IOException::class)
+        private fun extractFile(zipIn: ZipInputStream, relativePath: Path): ArchiveFile {
+            Log.d(TAG, "Extracting archive: %s", relativePath)
+
+            val data = zipIn.readBytes()
+            return ArchiveFile(relativePath, data)
+        }
+
+        private fun isArchive(zipEntry: ZipEntry) : Boolean {
+            return ARCHIVE_EXTENSIONS.any { zipEntry.name.endsWith(it, ignoreCase = true) }
+        }
+
+    }
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/archive/ArchiveFile.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/archive/ArchiveFile.kt
new file mode 100644
index 0000000..3054b71
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/archive/ArchiveFile.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.core.archive
+
+import java.io.IOException
+import java.io.OutputStream
+import java.nio.file.Path
+
+/**
+ * Represents a file in the archive that is not an archive.
+ */
+class ArchiveFile(override val relativePath: Path, var data: ByteArray) : ArchiveItem {
+
+    override val fileName: String = relativePath.fileName.toString()
+
+    override fun accept(visitor: ArchiveItemVisitor) {
+        visitor.visit(this)
+    }
+
+    @Throws(IOException::class)
+    override fun writeSelfTo(outputStream: OutputStream) {
+        outputStream.write(data)
+    }
+
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/archive/ArchiveItem.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/archive/ArchiveItem.kt
new file mode 100644
index 0000000..2d35e13
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/archive/ArchiveItem.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.core.archive
+
+import java.io.OutputStream
+import java.nio.file.Path
+
+/**
+ * Abstraction to represent archive and its files as a one thing.
+ */
+interface ArchiveItem {
+
+    /**
+     * Relative path of the item according to its location in the archive.
+     *
+     * Files in a nested archive have a path relative to that archive not to the parent of
+     * the archive. The root archive has the file system path set as its relative path.
+     */
+    val relativePath : Path
+
+    /**
+     * Name of the file.
+     */
+    val fileName : String
+
+    /**
+     * Accepts visitor.
+     */
+    fun accept(visitor: ArchiveItemVisitor)
+
+    /**
+     * Writes its internal data (or other nested files) into the given output stream.
+     */
+    fun writeSelfTo(outputStream: OutputStream)
+
+
+    fun isPomFile() = fileName.equals("pom.xml", ignoreCase = true)
+
+    fun isClassFile() = fileName.endsWith(".class", ignoreCase = true)
+
+    fun isXmlFile() = fileName.endsWith(".xml", ignoreCase = true)
+
+    fun isProGuardFile () = fileName.equals("proguard.txt", ignoreCase = true)
+
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/archive/ArchiveItemVisitor.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/archive/ArchiveItemVisitor.kt
new file mode 100644
index 0000000..7c99fd9
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/archive/ArchiveItemVisitor.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.core.archive
+
+/**
+ * Visitor for [ArchiveItem]
+ */
+interface ArchiveItemVisitor {
+
+    fun visit(archive: Archive)
+
+    fun visit(archiveFile: ArchiveFile)
+
+}
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/config/Config.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/config/Config.kt
new file mode 100644
index 0000000..8d70d87
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/config/Config.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.core.config
+
+import android.support.tools.jetifier.core.rules.RewriteRule
+import android.support.tools.jetifier.core.transform.pom.PomRewriteRule
+import android.support.tools.jetifier.core.map.TypesMap
+import android.support.tools.jetifier.core.transform.proguard.ProGuardTypesMap
+import com.google.gson.annotations.SerializedName
+
+/**
+ * The main and only one configuration that is used by the tool and all its transformers.
+ *
+ * [restrictToPackagePrefixes] Package prefixes that limit the scope of the rewriting
+ * [rewriteRules] Rules to scan support libraries to generate [TypesMap]
+ * [pomRewriteRules] Rules to rewrite POM files
+ * [typesMap] Map of all java types and fields to be used to rewrite libraries.
+ */
+data class Config(
+        val restrictToPackagePrefixes: List<String>,
+        val rewriteRules: List<RewriteRule>,
+        val pomRewriteRules: List<PomRewriteRule>,
+        val typesMap: TypesMap,
+        val proGuardMap: ProGuardTypesMap) {
+
+    companion object {
+        /** Path to the default config file located within the jar file. */
+        const val DEFAULT_CONFIG_RES_PATH = "/default.generated.config"
+    }
+
+    fun setNewMap(mappings: TypesMap) : Config {
+        return Config(
+            restrictToPackagePrefixes, rewriteRules, pomRewriteRules, mappings, proGuardMap)
+    }
+
+    /** Returns JSON data model of this class */
+    fun toJson() : JsonData {
+        return JsonData(
+            restrictToPackagePrefixes,
+            rewriteRules.map { it.toJson() }.toList(),
+            pomRewriteRules.map { it.toJson() }.toList(),
+            typesMap.toJson(),
+            proGuardMap.toJson()
+        )
+    }
+
+
+    /**
+     * JSON data model for [Config].
+     */
+    data class JsonData(
+            @SerializedName("restrictToPackagePrefixes")
+            val restrictToPackages: List<String?>,
+
+            @SerializedName("rules")
+            val rules: List<RewriteRule.JsonData?>,
+
+            @SerializedName("pomRules")
+            val pomRules: List<PomRewriteRule.JsonData?>,
+
+            @SerializedName("map")
+            val mappings: TypesMap.JsonData? = null,
+
+            @SerializedName("proGuardMap")
+            val proGuardMap: ProGuardTypesMap.JsonData? = null) {
+
+        /** Creates instance of [Config] */
+        fun toConfig() : Config {
+            return Config(
+                restrictToPackages.filterNotNull(),
+                rules.filterNotNull().map { it.toRule() },
+                pomRules.filterNotNull().map { it.toRule() },
+                mappings?.toMappings() ?: TypesMap.EMPTY,
+                proGuardMap?.toMappings() ?: ProGuardTypesMap.EMPTY
+            )
+        }
+    }
+
+}
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/config/ConfigParser.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/config/ConfigParser.kt
new file mode 100644
index 0000000..50d510c
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/config/ConfigParser.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.core.config
+
+import android.support.tools.jetifier.core.utils.Log
+import com.google.gson.GsonBuilder
+import java.io.FileNotFoundException
+import java.io.FileWriter
+import java.nio.file.Files
+import java.nio.file.Path
+
+object ConfigParser {
+
+    private const val TAG : String = "Config"
+
+    private val gson = GsonBuilder().setPrettyPrinting().create()
+
+    fun writeToString(config: Config) : String {
+        return gson.toJson(config.toJson())
+    }
+
+    fun writeToFile(config: Config, outputPath: Path) {
+        FileWriter(outputPath.toFile()).use {
+            gson.toJson(config.toJson(), it)
+        }
+    }
+
+    fun parseFromString(inputText: String) : Config? {
+        return gson.fromJson(inputText, Config.JsonData::class.java).toConfig()
+    }
+
+    fun loadFromFile(configPath: Path) : Config? {
+        return loadConfigFileInternal(configPath)
+    }
+
+    fun loadDefaultConfig() : Config? {
+        Log.v(TAG, "Using the default config '%s'", Config.DEFAULT_CONFIG_RES_PATH)
+
+        val inputStream = javaClass.getResourceAsStream(Config.DEFAULT_CONFIG_RES_PATH)
+        return parseFromString(inputStream.reader().readText())
+    }
+
+    fun loadConfigOrFail(configPath: Path?) : Config {
+        if (configPath != null) {
+            val config = loadConfigFileInternal(configPath)
+            if (config != null) {
+                return config
+            }
+            throw FileNotFoundException("Config file was not found at '$configPath'")
+        }
+
+        val config = loadDefaultConfig()
+        if (config != null) {
+            return config
+        }
+        throw AssertionError("The default config could not be found!")
+    }
+
+    private fun loadConfigFileInternal(configPath: Path) : Config? {
+        if (!Files.isReadable(configPath)) {
+            Log.e(TAG, "Cannot access the config file: '%s'", configPath)
+            return null
+        }
+
+        Log.i(TAG, "Parsing config file: '%s'", configPath.toUri())
+        val config = parseFromString(configPath.toFile().readText())
+
+        if (config == null) {
+            Log.e(TAG, "Failed to parseFromString the config file")
+            return null
+        }
+
+        return config
+    }
+}
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/map/LibraryMapGenerator.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/map/LibraryMapGenerator.kt
new file mode 100644
index 0000000..de5a17f
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/map/LibraryMapGenerator.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.core.map
+
+import android.support.tools.jetifier.core.archive.Archive
+import android.support.tools.jetifier.core.archive.ArchiveFile
+import android.support.tools.jetifier.core.archive.ArchiveItemVisitor
+import android.support.tools.jetifier.core.config.Config
+import android.support.tools.jetifier.core.transform.Transformer
+import org.objectweb.asm.ClassReader
+import org.objectweb.asm.ClassWriter
+
+/**
+ * Scans a library java files using [MapGeneratorRemapper] to create [TypesMap].
+ */
+class LibraryMapGenerator constructor(config: Config) : ArchiveItemVisitor {
+
+    val remapper = MapGeneratorRemapper(config)
+
+    /**
+     * Scans the given [library] to extend the types map meta-data. The final map can be retrieved
+     * using [generateMap].
+     */
+    fun scanLibrary(library: Archive) {
+        library.accept(this)
+    }
+
+    /**
+     * Creates the [TypesMap] based on the meta-data aggregated via previous [scanFile] calls
+     */
+    fun generateMap() : TypesMap {
+        return remapper.createTypesMap()
+    }
+
+    override fun visit(archive: Archive) {
+        archive.files.forEach{ it.accept(this) }
+    }
+
+    override fun visit(archiveFile: ArchiveFile) {
+        if (archiveFile.isClassFile()) {
+            scanFile(archiveFile)
+        }
+    }
+
+    private fun scanFile(file: ArchiveFile) {
+        val reader = ClassReader(file.data)
+        val writer = ClassWriter(0 /* flags */)
+
+        val visitor = remapper.createClassRemapper(writer)
+
+        reader.accept(visitor, 0 /* flags */)
+    }
+
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/map/MapGeneratorRemapper.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/map/MapGeneratorRemapper.kt
new file mode 100644
index 0000000..374e213
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/map/MapGeneratorRemapper.kt
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.core.map
+
+import android.support.tools.jetifier.core.config.Config
+import android.support.tools.jetifier.core.rules.JavaField
+import android.support.tools.jetifier.core.rules.JavaType
+import android.support.tools.jetifier.core.transform.bytecode.CoreRemapper
+import android.support.tools.jetifier.core.transform.bytecode.asm.CustomClassRemapper
+import android.support.tools.jetifier.core.transform.bytecode.asm.CustomRemapper
+import android.support.tools.jetifier.core.utils.Log
+import org.objectweb.asm.ClassVisitor
+import java.util.regex.Pattern
+
+/**
+ * Hooks to asm remapping to collect data for [TypesMap] by applying all the [RewriteRule]s from the
+ * given [config] on all discovered and eligible types and fields.
+ */
+class MapGeneratorRemapper(private val config: Config) : CoreRemapper {
+
+    companion object {
+        private const val TAG : String = "MapGeneratorRemapper"
+    }
+
+    private val typesRewritesMap = hashMapOf<JavaType, JavaType>()
+    private val fieldsRewritesMap = hashMapOf<JavaField, JavaField>()
+
+    var isMapNotComplete = false
+        private set
+
+    /**
+     * Ignore mPrefix types and anything that contains $ as these are internal fields that won't be
+     * ever referenced.
+     */
+    private val ignoredFields = Pattern.compile("(^m[A-Z]+.*$)|(.*\\$.*)")
+
+    /**
+     * Ignores types ending with '$digit' as these are private inner classes and won't be ever
+     * referenced.
+     */
+    private val ignoredTypes = Pattern.compile("^(.*)\\$[0-9]+$")
+
+    fun createClassRemapper(visitor: ClassVisitor): CustomClassRemapper {
+        return CustomClassRemapper(visitor, CustomRemapper(this))
+    }
+
+    override fun rewriteType(type: JavaType): JavaType {
+        if (!isTypeSupported(type)) {
+            return type
+        }
+
+        if (typesRewritesMap.contains(type)) {
+            return type
+        }
+
+        if (isTypeIgnored(type)) {
+            return type
+        }
+
+        // Try to find a rule
+        for (rule in config.rewriteRules) {
+            val mappedTypeName = rule.apply(type) ?: continue
+            typesRewritesMap.put(type, mappedTypeName)
+
+            Log.i(TAG, "  map: %s -> %s", type, mappedTypeName)
+            return mappedTypeName
+        }
+
+        isMapNotComplete = true
+        Log.e(TAG, "No rule for: " + type)
+        typesRewritesMap.put(type, type) // Identity
+        return type
+    }
+
+    override fun rewriteField(field : JavaField): JavaField {
+        if (!isTypeSupported(field.owner)) {
+            return field
+        }
+
+        if (isTypeIgnored(field.owner) || isFieldIgnored(field)) {
+            return field
+        }
+
+        if (fieldsRewritesMap.contains(field)) {
+            return field
+        }
+
+        // Try to find a rule
+        for (rule in config.rewriteRules) {
+            val mappedFieldName = rule.apply(field) ?: continue
+            fieldsRewritesMap.put(field, mappedFieldName)
+
+            Log.i(TAG, "  map: %s -> %s", field, mappedFieldName)
+            return mappedFieldName
+        }
+
+        isMapNotComplete = true
+        Log.e(TAG, "No rule for: " + field)
+        fieldsRewritesMap.put(field, field) // Identity
+        return field
+    }
+
+    fun createTypesMap() : TypesMap {
+        return TypesMap(typesRewritesMap, fieldsRewritesMap)
+    }
+
+    private fun isTypeSupported(type: JavaType) : Boolean {
+        return config.restrictToPackagePrefixes.any{ type.fullName.startsWith(it) }
+    }
+
+    private fun isTypeIgnored(type: JavaType) : Boolean {
+        return ignoredTypes.matcher(type.fullName).matches()
+    }
+
+    private fun isFieldIgnored(field: JavaField) : Boolean {
+        return ignoredFields.matcher(field.name).matches()
+    }
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/map/TypesMap.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/map/TypesMap.kt
new file mode 100644
index 0000000..ce02026
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/map/TypesMap.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.core.map
+
+import android.support.tools.jetifier.core.rules.JavaField
+import android.support.tools.jetifier.core.rules.JavaType
+import android.support.tools.jetifier.core.rules.RewriteRule
+
+/**
+ * Contains all the mappings needed to rewrite java types and fields.
+ *
+ * These mappings are generated by the preprocessor from existing support libraries and by applying
+ * the given [RewriteRule]s.
+ */
+data class TypesMap(
+        val types: Map<JavaType, JavaType>,
+        val fields: Map<JavaField, JavaField>) {
+
+    companion object {
+        val EMPTY = TypesMap(emptyMap(), emptyMap())
+    }
+
+    /** Returns JSON data model of this class */
+    fun toJson() : JsonData {
+        return JsonData(
+                types = types.map { it.key.fullName to it.value.fullName }
+                        .toMap(),
+                fields = mapFields())
+    }
+
+    private fun mapFields() : Map<String, Map<String, List<String>>> {
+        val rawMap = mutableMapOf<String, MutableMap<String, MutableList<String>>>()
+
+        fields.forEach{
+            rawMap
+                .getOrPut(it.key.owner.fullName, { mutableMapOf<String, MutableList<String>>()} )
+                .getOrPut(it.value.owner.fullName, { mutableListOf() })
+                .add(it.key.name)
+        }
+        return rawMap
+    }
+
+    /**
+     * JSON data model for [TypesMap].
+     */
+    data class JsonData(
+            val types: Map<String, String>,
+            val fields: Map<String, Map<String, List<String>>>)  {
+
+        /** Creates instance of [TypesMap] */
+        fun toMappings() : TypesMap {
+            return TypesMap(
+                types = types
+                    .orEmpty()
+                    .map { JavaType(it.key) to JavaType(it.value) }
+                    .toMap(),
+                fields = fields
+                    .orEmpty().entries
+                    .flatMap {
+                        top ->
+                        top.value.flatMap {
+                            mid ->
+                            mid.value.map {
+                                JavaField(top.key, it) to JavaField(mid.key, it)
+                            }
+                        }
+                    }
+                    .toMap())
+        }
+    }
+}
+
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/rules/JavaField.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/rules/JavaField.kt
new file mode 100644
index 0000000..c423f0a
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/rules/JavaField.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.core.rules
+
+/**
+ * Wrapper for Java field declaration.
+ */
+data class JavaField(val owner : JavaType, val name : String) {
+
+    constructor(owner : String, name : String) : this(JavaType(owner), name)
+
+
+    fun renameOwner(newOwner: JavaType) = JavaField(newOwner, name)
+
+    override fun toString() = owner.toString() + "." + name
+
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/rules/JavaType.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/rules/JavaType.kt
new file mode 100644
index 0000000..d7a077b
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/rules/JavaType.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.core.rules
+
+/**
+ * Wrapper for Java type declaration.
+ */
+data class JavaType(val fullName: String) {
+
+    init {
+        if (fullName.contains('.')) {
+            throw IllegalArgumentException("The type does not support '.' as package separator!")
+        }
+    }
+
+    companion object {
+        /** Creates the type from notation where packages are separated using '.' */
+        fun fromDotVersion(fullName: String) : JavaType {
+            return JavaType(fullName.replace('.', '/'))
+        }
+    }
+
+    /** Returns the type as a string where packages are separated using '.' */
+    fun toDotNotation() : String {
+        return fullName.replace('/', '.')
+    }
+
+
+    override fun toString() = fullName
+
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/rules/JavaTypeXmlRef.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/rules/JavaTypeXmlRef.kt
new file mode 100644
index 0000000..9d58046
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/rules/JavaTypeXmlRef.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.core.rules
+
+/**
+ * Wrapper for Java type reference used in XML.
+ *
+ * In XML we use '.' as a package separator.
+ */
+data class JavaTypeXmlRef(val fullName : String) {
+
+    constructor(type: JavaType)
+        : this(type.fullName.replace('/', '.'))
+
+    fun toJavaType() : JavaType {
+        return JavaType(fullName.replace('.', '/'))
+    }
+
+    override fun toString() = fullName
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/rules/RewriteRule.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/rules/RewriteRule.kt
new file mode 100644
index 0000000..700e757
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/rules/RewriteRule.kt
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.core.rules
+
+import com.google.gson.annotations.SerializedName
+import java.util.regex.Pattern
+
+/**
+ * Rule that rewrites a Java type or field based on the given arguments.
+ *
+ * Used in the preprocessor when generating [TypesMap].
+ *
+ * @param from Regular expression where packages are separated via '/' and inner class separator
+ * is "$". Used to match the input type.
+ * @param to A string to be used as a replacement if the 'from' pattern is matched. It can also
+ * apply groups matched from the original pattern using {x} annotation, e.g. {0}.
+ * @param fieldSelectors Collection of regular expressions that are used to match fields. If the
+ * type is matched (using 'from') and the field is matched (or the list of fields selectors is
+ * empty) the field's type gets rewritten according to the 'to' parameter.
+ */
+class RewriteRule(
+        private val from: String,
+        private val to: String,
+        private val fieldSelectors: List<String> = emptyList()) {
+
+    // We escape '$' so we don't conflict with regular expression symbols.
+    private val inputPattern = Pattern.compile("^${from.replace("$", "\\$")}$")
+    private val outputPattern = to.replace("$", "\$")
+
+    private val fields = fieldSelectors.map { Pattern.compile("^$it$") }
+
+    /**
+     * Rewrites the given java type. Returns null if this rule is not applicable for the given type.
+     */
+    fun apply(input: JavaType): JavaType? {
+        if (fields.isNotEmpty()) {
+            return null
+        }
+
+        return applyInternal(input)
+    }
+
+    /**
+     * Rewrites the given field type. Returns null if this rule is not applicable for the given
+     * type.
+     */
+    fun apply(inputField: JavaField) : JavaField? {
+        val typeRewriteResult = applyInternal(inputField.owner) ?: return null
+
+        val isFieldInTheFilter = fields.isEmpty()
+                || fields.any { it.matcher(inputField.name).matches() }
+        if (isFieldInTheFilter) {
+            return inputField.renameOwner(typeRewriteResult)
+        }
+
+        return null
+    }
+
+    private fun applyInternal(input: JavaType): JavaType? {
+        val matcher = inputPattern.matcher(input.fullName)
+        if (!matcher.matches()) {
+            return null
+        }
+
+        var result = outputPattern
+        for (i in 0..matcher.groupCount() - 1) {
+            result = result.replace("{$i}", matcher.group(i + 1))
+        }
+
+        return JavaType(result)
+    }
+
+    override fun toString() : String {
+        return "$inputPattern -> $outputPattern " + fields.joinToString { it.toString() }
+    }
+
+    /** Returns JSON data model of this class */
+    fun toJson() : JsonData {
+        return JsonData(from, to, fieldSelectors)
+    }
+
+
+    /**
+     * JSON data model for [RewriteRule].
+     */
+    data class JsonData(
+            @SerializedName("from")
+            val from: String,
+
+            @SerializedName("to")
+            val to: String,
+
+            @SerializedName("fieldSelectors")
+            val fieldSelectors: List<String>? = null)  {
+
+        /** Creates instance of [RewriteRule] */
+        fun toRule() : RewriteRule {
+            return RewriteRule(from, to, fieldSelectors.orEmpty())
+        }
+    }
+
+}
+
+
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/TransformationContext.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/TransformationContext.kt
new file mode 100644
index 0000000..3f8af95
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/TransformationContext.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.core.transform
+
+import android.support.tools.jetifier.core.config.Config
+import android.support.tools.jetifier.core.rules.JavaType
+import android.support.tools.jetifier.core.transform.proguard.ProGuardType
+import java.util.regex.Pattern
+
+/**
+ * Context to share the transformation state between individual [Transformer]s.
+ */
+class TransformationContext(val config: Config) {
+
+    // Merges all packages prefixes into one regEx pattern
+    private val packagePrefixPattern = Pattern.compile(
+        "^(" + config.restrictToPackagePrefixes.map { "($it)" }.joinToString("|") + ").*$")
+
+    /** Counter for [reportNoMappingFoundFailure] calls. */
+    var mappingNotFoundFailuresCount = 0
+        private set
+
+    /** Counter for [reportNoProGuardMappingFoundFailure] calls. */
+    var proGuardMappingNotFoundFailuresCount = 0
+        private set
+
+    /** Returns whether any errors were found during the transformation process */
+    fun wasErrorFound() = mappingNotFoundFailuresCount > 0
+
+    /**
+     * Returns whether the given type is eligible for rewrite.
+     *
+     * If not, the transformers should ignore it.
+     */
+    fun isEligibleForRewrite(type: JavaType) : Boolean {
+        if (config.restrictToPackagePrefixes.isEmpty()) {
+            return false
+        }
+        return packagePrefixPattern.matcher(type.fullName).matches()
+    }
+
+    /**
+     * Returns whether the given ProGuard type reference is eligible for rewrite.
+     *
+     * Keep in mind that his has limited capabilities - mainly when * is used as a prefix. Rules
+     * like *.v7 are not matched by prefix support.v7. So don't rely on it and use
+     * the [ProGuardTypesMap] as first.
+     */
+    fun isEligibleForRewrite(type: ProGuardType) : Boolean {
+        if (config.restrictToPackagePrefixes.isEmpty()) {
+            return false
+        }
+        return packagePrefixPattern.matcher(type.value).matches()
+    }
+
+    /**
+     * Used to report that there was a reference found that satisfies [isEligibleForRewrite] but no
+     * mapping was found to rewrite it.
+     */
+    fun reportNoMappingFoundFailure() {
+        mappingNotFoundFailuresCount++
+    }
+
+    /**
+     * Used to report that there was a reference found in the ProGuard file that satisfies
+     * [isEligibleForRewrite] but no mapping was found to rewrite it.
+     */
+    fun reportNoProGuardMappingFoundFailure() {
+        proGuardMappingNotFoundFailuresCount++
+    }
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/Transformer.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/Transformer.kt
new file mode 100644
index 0000000..0c6c8aa
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/Transformer.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.core.transform
+
+import android.support.tools.jetifier.core.archive.ArchiveFile
+
+/**
+ * Interface to be implemented by any class that wants process files.
+ */
+interface Transformer {
+
+    /**
+     * Returns whether this instance can process the given file.
+     */
+    fun canTransform(file: ArchiveFile): Boolean
+
+    /**
+     * Runs transformation of the given file.
+     */
+    fun runTransform(file: ArchiveFile)
+
+}
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/bytecode/ByteCodeTransformer.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/bytecode/ByteCodeTransformer.kt
new file mode 100644
index 0000000..33235e0
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/bytecode/ByteCodeTransformer.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.core.transform.bytecode
+
+import android.support.tools.jetifier.core.archive.ArchiveFile
+import android.support.tools.jetifier.core.config.Config
+import android.support.tools.jetifier.core.transform.TransformationContext
+import android.support.tools.jetifier.core.transform.Transformer
+import org.objectweb.asm.ClassReader
+import org.objectweb.asm.ClassWriter
+
+/**
+ * The [Transformer] responsible for java byte code refactoring.
+ */
+class ByteCodeTransformer internal constructor(context: TransformationContext) : Transformer {
+
+    private val remapper: CoreRemapperImpl = CoreRemapperImpl(context)
+
+
+    override fun canTransform(file: ArchiveFile) = file.isClassFile()
+
+    override fun runTransform(file: ArchiveFile) {
+        val reader = ClassReader(file.data)
+        val writer = ClassWriter(0 /* flags */)
+
+        val visitor = remapper.createClassRemapper(writer)
+
+        reader.accept(visitor, 0 /* flags */)
+
+        file.data = writer.toByteArray()
+    }
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/bytecode/CoreRemapper.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/bytecode/CoreRemapper.kt
new file mode 100644
index 0000000..50f3b31
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/bytecode/CoreRemapper.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.core.transform.bytecode
+
+import android.support.tools.jetifier.core.rules.JavaField
+import android.support.tools.jetifier.core.rules.JavaType
+
+/**
+ * High-level re-mapping interface to provide only the refactorings needed by jetifier.
+ */
+interface CoreRemapper {
+    fun rewriteType(type: JavaType) : JavaType
+
+    fun rewriteField(field: JavaField) : JavaField
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/bytecode/CoreRemapperImpl.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/bytecode/CoreRemapperImpl.kt
new file mode 100644
index 0000000..486cc25
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/bytecode/CoreRemapperImpl.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.core.transform.bytecode
+
+import android.support.tools.jetifier.core.map.TypesMap
+import android.support.tools.jetifier.core.rules.JavaField
+import android.support.tools.jetifier.core.rules.JavaType
+import android.support.tools.jetifier.core.transform.TransformationContext
+import android.support.tools.jetifier.core.transform.bytecode.asm.CustomClassRemapper
+import android.support.tools.jetifier.core.transform.bytecode.asm.CustomRemapper
+import android.support.tools.jetifier.core.utils.Log
+import org.objectweb.asm.ClassVisitor
+
+/**
+ * Applies mappings defined in [TypesMap] during the remapping process.
+ */
+class CoreRemapperImpl(private val context: TransformationContext) : CoreRemapper {
+
+    companion object {
+        const val TAG = "CoreRemapperImpl"
+    }
+
+    private val typesMap = context.config.typesMap
+
+    fun createClassRemapper(visitor: ClassVisitor): CustomClassRemapper {
+        return CustomClassRemapper(visitor, CustomRemapper(this))
+    }
+
+    override fun rewriteType(type: JavaType): JavaType {
+        val result = typesMap.types[type]
+
+        if (!context.isEligibleForRewrite(type)) {
+            return type
+        }
+
+        if (result != null) {
+            Log.i(TAG, "  map: %s -> %s", type, result)
+            return result
+        }
+
+        context.reportNoMappingFoundFailure()
+        Log.e(TAG, "No mapping for: " + type)
+        return type
+    }
+
+    override fun rewriteField(field : JavaField): JavaField {
+        val result = typesMap.fields[field]
+
+        if (!context.isEligibleForRewrite(field.owner)) {
+            return field
+        }
+
+        if (result != null) {
+            Log.i(TAG, "  map: %s -> %s", field, result)
+            return result
+        }
+
+        context.reportNoMappingFoundFailure()
+        Log.e(TAG, "No mapping for: " + field)
+        return field
+    }
+
+}
+
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/bytecode/asm/CustomClassRemapper.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/bytecode/asm/CustomClassRemapper.kt
new file mode 100644
index 0000000..692e65d
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/bytecode/asm/CustomClassRemapper.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.core.transform.bytecode.asm
+
+import org.objectweb.asm.ClassVisitor
+import org.objectweb.asm.FieldVisitor
+import org.objectweb.asm.MethodVisitor
+import org.objectweb.asm.Opcodes
+import org.objectweb.asm.commons.ClassRemapper
+
+/**
+ * Currently only adds field context awareness into [ClassRemapper] and substitutes the default
+ * method remapper with [CustomMethodRemapper]
+ */
+class CustomClassRemapper(cv: ClassVisitor, private val customRemapper: CustomRemapper)
+    : ClassRemapper(Opcodes.ASM5, cv, customRemapper) {
+
+    override fun visitField(access: Int,
+                            name: String,
+                            desc: String?,
+                            signature: String?,
+                            value: Any?) : FieldVisitor? {
+        cv ?: return null
+
+        val field = customRemapper.mapField(className, name)
+        val fieldVisitor = cv.visitField(
+                access,
+                field.name,
+                remapper.mapDesc(desc),
+                remapper.mapSignature(signature, true),
+                remapper.mapValue(value))
+
+        fieldVisitor ?: return null
+
+        return createFieldRemapper(fieldVisitor)
+    }
+
+    override fun createMethodRemapper(mv: MethodVisitor) : MethodVisitor {
+        return CustomMethodRemapper(mv, customRemapper)
+    }
+}
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/bytecode/asm/CustomMethodRemapper.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/bytecode/asm/CustomMethodRemapper.kt
new file mode 100644
index 0000000..cc60cbf
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/bytecode/asm/CustomMethodRemapper.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.core.transform.bytecode.asm
+
+import org.objectweb.asm.MethodVisitor
+import org.objectweb.asm.Opcodes
+import org.objectweb.asm.commons.MethodRemapper
+
+/**
+ * Currently only adds field context awareness into [MethodRemapper]
+ */
+internal class CustomMethodRemapper(mv:MethodVisitor,
+                                    private val customRemapper: CustomRemapper)
+    : MethodRemapper(Opcodes.ASM5, mv, customRemapper) {
+
+    override fun visitFieldInsn(opcode: Int, owner: String, name: String, desc: String?) {
+        mv ?: return
+
+        val field = customRemapper.mapField(owner, name)
+        mv.visitFieldInsn(opcode, field.owner.fullName, field.name, remapper.mapDesc(desc))
+    }
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/bytecode/asm/CustomRemapper.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/bytecode/asm/CustomRemapper.kt
new file mode 100644
index 0000000..5debf70
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/bytecode/asm/CustomRemapper.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.core.transform.bytecode.asm
+
+import android.support.tools.jetifier.core.rules.JavaField
+import android.support.tools.jetifier.core.rules.JavaType
+import android.support.tools.jetifier.core.transform.bytecode.CoreRemapper
+import org.objectweb.asm.commons.Remapper
+
+/**
+ * Extends [Remapper] with a capability to rewrite field names together with their owner.
+ */
+class CustomRemapper(val remapperImpl: CoreRemapper) : Remapper() {
+
+    override fun map(typeName: String): String {
+        return remapperImpl.rewriteType(JavaType(typeName)).fullName
+    }
+
+    fun mapField(ownerName: String, fieldName: String): JavaField {
+        return remapperImpl.rewriteField(JavaField(ownerName, fieldName))
+    }
+
+    override fun mapFieldName(owner: String?, name: String, desc: String?): String {
+        throw RuntimeException("This should not be called")
+    }
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/pom/PomDependency.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/pom/PomDependency.kt
new file mode 100644
index 0000000..1622fd7
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/pom/PomDependency.kt
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.core.transform.pom
+
+import com.google.gson.annotations.SerializedName
+import org.jdom2.Document
+import org.jdom2.Element
+
+/**
+ * Represents a '<dependency>' XML node of a POM file.
+ *
+ * See documentation of the content at https://maven.apache.org/pom.html#Dependencies
+ */
+data class PomDependency(
+        @SerializedName("groupId")
+        val groupId: String? = null,
+
+        @SerializedName("artifactId")
+        val artifactId: String? = null,
+
+        @SerializedName("version")
+        var version: String? = null,
+
+        @SerializedName("classifier")
+        val classifier: String? = null,
+
+        @SerializedName("type")
+        val type: String? = null,
+
+        @SerializedName("scope")
+        val scope: String? = null,
+
+        @SerializedName("systemPath")
+        val systemPath: String? = null,
+
+        @SerializedName("optional")
+        val optional: String? = null) {
+
+    companion object {
+
+        /**
+         * Creates a new [PomDependency] from the given XML [Element].
+         */
+        fun fromXmlElement(node: Element, properties: Map<String, String>) : PomDependency {
+            var groupId : String? = null
+            var artifactId : String? = null
+            var version : String? = null
+            var classifier : String? = null
+            var type : String? = null
+            var scope : String? = null
+            var systemPath : String? = null
+            var optional : String? = null
+
+            for (childNode in node.children) {
+                when (childNode.name) {
+                    "groupId" -> groupId = XmlUtils.resolveValue(childNode.value, properties)
+                    "artifactId" -> artifactId = XmlUtils.resolveValue(childNode.value, properties)
+                    "version" -> version = XmlUtils.resolveValue(childNode.value, properties)
+                    "classifier" -> classifier = XmlUtils.resolveValue(childNode.value, properties)
+                    "type" -> type = XmlUtils.resolveValue(childNode.value, properties)
+                    "scope" -> scope = XmlUtils.resolveValue(childNode.value, properties)
+                    "systemPath" -> systemPath = XmlUtils.resolveValue(childNode.value, properties)
+                    "optional" -> optional = XmlUtils.resolveValue(childNode.value, properties)
+                }
+            }
+
+            return PomDependency(
+                    groupId = groupId,
+                    artifactId = artifactId,
+                    version = version,
+                    classifier = classifier,
+                    type = type,
+                    scope = scope,
+                    systemPath = systemPath,
+                    optional = optional)
+        }
+
+    }
+
+    init {
+        if (version != null) {
+            version = version!!.toLowerCase()
+        }
+    }
+
+    /**
+     * Whether this dependency should be skipped from the rewriting process
+     */
+    fun shouldSkipRewrite() : Boolean {
+        return scope != null && scope.toLowerCase() == "test"
+    }
+
+    /**
+     * Returns a new dependency created by taking all the items from the [input] dependency and then
+     * overwriting these with all of its non-null items.
+     */
+    fun rewrite(input: PomDependency) : PomDependency {
+        return PomDependency(
+            groupId = groupId ?: input.groupId,
+            artifactId = artifactId ?: input.artifactId,
+            version = version ?: input.version,
+            classifier = classifier ?: input.classifier,
+            type = type ?: input.type,
+            scope = scope ?: input.scope,
+            systemPath = systemPath ?: input.systemPath,
+            optional = optional ?: input.optional
+        )
+    }
+
+    /**
+     * Transforms the current data into XML '<dependency>' node.
+     */
+    fun toXmlElement(document: Document) : Element {
+        val node = Element("dependency")
+        node.namespace = document.rootElement.namespace
+
+        XmlUtils.addStringNodeToNode(node, "groupId", groupId)
+        XmlUtils.addStringNodeToNode(node, "artifactId", artifactId)
+        XmlUtils.addStringNodeToNode(node, "version", version)
+        XmlUtils.addStringNodeToNode(node, "classifier", classifier)
+        XmlUtils.addStringNodeToNode(node, "type", type)
+        XmlUtils.addStringNodeToNode(node, "scope", scope)
+        XmlUtils.addStringNodeToNode(node, "systemPath", systemPath)
+        XmlUtils.addStringNodeToNode(node, "optional", optional)
+
+        return node
+    }
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/pom/PomDocument.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/pom/PomDocument.kt
new file mode 100644
index 0000000..d5bdc3a
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/pom/PomDocument.kt
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.core.transform.pom
+
+import android.support.tools.jetifier.core.archive.ArchiveFile
+import android.support.tools.jetifier.core.utils.Log
+import org.jdom2.Document
+import org.jdom2.Element
+
+/**
+ * Wraps a single POM XML [ArchiveFile] with parsed metadata about transformation related sections.
+ */
+class PomDocument(val file: ArchiveFile, private val document: Document) {
+
+    companion object {
+        private const val TAG = "Pom"
+
+        fun loadFrom(file: ArchiveFile) : PomDocument {
+            val document = XmlUtils.createDocumentFromByteArray(file.data)
+            val pomDoc = PomDocument(file, document)
+            pomDoc.initialize()
+            return pomDoc
+        }
+    }
+
+    val dependencies : MutableSet<PomDependency> = mutableSetOf()
+    private val properties : MutableMap<String, String> = mutableMapOf()
+    private var dependenciesGroup : Element? = null
+    private var hasChanged : Boolean = false
+
+    private fun initialize() {
+        val propertiesGroup = document.rootElement
+                .getChild("properties", document.rootElement.namespace)
+        if (propertiesGroup != null) {
+            propertiesGroup.children
+                .filterNot { it.value.isNullOrEmpty() }
+                .forEach { properties[it.name] = it.value }
+        }
+
+        dependenciesGroup = document.rootElement
+                .getChild("dependencies", document.rootElement.namespace) ?: return
+        dependenciesGroup!!.children.mapTo(dependencies) {
+            PomDependency.fromXmlElement(it, properties)
+        }
+    }
+
+    /**
+     * Validates that this document is consistent with the provided [rules].
+     *
+     * Currently it checks that all the dependencies that are going to be rewritten by the given
+     * rules satisfy the minimal version requirements defined by the rules.
+     */
+    fun validate(rules: List<PomRewriteRule>) : Boolean {
+        if (dependenciesGroup == null) {
+            // Nothing to validate as this file has no dependencies section
+            return true
+        }
+
+        return dependencies.all { dep -> rules.all { it.validateVersion(dep) } }
+    }
+
+    /**
+     * Applies the given [rules] to rewrite the POM file.
+     *
+     * Changes are not saved back until requested.
+     */
+    fun applyRules(rules: List<PomRewriteRule>) {
+        if (dependenciesGroup == null) {
+            // Nothing to transform as this file has no dependencies section
+            return
+        }
+
+        val newDependencies = mutableSetOf<PomDependency>()
+        for (dependency in dependencies) {
+            if (dependency.shouldSkipRewrite()) {
+                continue
+            }
+
+            val rule = rules.firstOrNull { it.matches(dependency) }
+            if (rule == null) {
+                // No rule to rewrite => keep it
+                newDependencies.add(dependency)
+            } else {
+                // Replace with new dependencies
+                newDependencies.addAll(rule.to.mapTo(newDependencies){ it.rewrite(dependency) })
+            }
+        }
+
+        if (newDependencies.isEmpty()) {
+            // No changes
+            return
+        }
+
+        dependenciesGroup!!.children.clear()
+        newDependencies.forEach { dependenciesGroup!!.addContent(it.toXmlElement(document)) }
+        hasChanged = true
+    }
+
+    /**
+     * Saves any current pending changes back to the file if needed.
+     */
+    fun saveBackToFileIfNeeded() {
+        if (!hasChanged) {
+            return
+        }
+
+        file.data =  XmlUtils.convertDocumentToByteArray(document)
+    }
+
+    /**
+     * Logs the information about the current file using info level.
+     */
+    fun logDocumentDetails() {
+        Log.i(TAG, "POM file at: '%s'", file.relativePath)
+        for ((groupId, artifactId, version) in dependencies) {
+            Log.i(TAG, "- Dep: %s:%s:%s", groupId, artifactId, version)
+        }
+    }
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/pom/PomRewriteRule.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/pom/PomRewriteRule.kt
new file mode 100644
index 0000000..070a640
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/pom/PomRewriteRule.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.core.transform.pom
+
+import android.support.tools.jetifier.core.utils.Log
+import com.google.gson.annotations.SerializedName
+
+/**
+ * Rule that defines how to rewrite a dependency element in a POM file.
+ *
+ * Any dependency that is matched against [from] should be rewritten to list of the dependencies
+ * defined in [to].
+ */
+data class PomRewriteRule(val from: PomDependency, val to: List<PomDependency>) {
+
+    companion object {
+        val TAG : String = "PomRule"
+    }
+
+    /**
+     * Validates that the given [input] dependency has a valid version.
+     */
+    fun validateVersion(input: PomDependency, document: PomDocument? = null) : Boolean {
+        if (from.version == null || input.version == null) {
+            return true
+        }
+
+        if (!matches(input)) {
+            return true
+        }
+
+        if (!areVersionsMatching(from.version!!, input.version!!)) {
+            Log.e(TAG, "Version mismatch! Expected version '%s' but found version '%s' for " +
+                    "'%s:%s' in '%s' file.", from.version, input.version, input.groupId,
+                    input.artifactId, document?.file?.relativePath)
+            return false
+        }
+
+        return true
+    }
+
+    /**
+     * Checks if the given [version] is supported to be rewritten with a rule having [ourVersion].
+     *
+     * Version entry can be actually quite complicated, see the full documentation at:
+     * https://maven.apache.org/pom.html#Dependencies
+     */
+    private fun areVersionsMatching(ourVersion: String, version: String) : Boolean {
+        if (version == "latest" || version == "release") {
+            return true
+        }
+
+        if (version.endsWith(",)") || version.endsWith(",]")) {
+            return true
+        }
+
+        if (version.endsWith("$ourVersion]")) {
+            return true
+        }
+
+        return ourVersion == version
+    }
+
+    fun matches(input: PomDependency) : Boolean {
+        return input.artifactId == from.artifactId && input.groupId == from.groupId
+    }
+
+    /** Returns JSON data model of this class */
+    fun toJson() : PomRewriteRule.JsonData {
+        return PomRewriteRule.JsonData(from, to)
+    }
+
+
+    /**
+     * JSON data model for [PomRewriteRule].
+     */
+    data class JsonData(
+            @SerializedName("from")
+            val from: PomDependency,
+            @SerializedName("to")
+            val to: List<PomDependency>)  {
+
+        /** Creates instance of [PomRewriteRule] */
+        fun toRule() : PomRewriteRule {
+            return PomRewriteRule(from, to.filterNotNull())
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/pom/PomScanner.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/pom/PomScanner.kt
new file mode 100644
index 0000000..e9cc511
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/pom/PomScanner.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.core.transform.pom
+
+import android.support.tools.jetifier.core.archive.Archive
+import android.support.tools.jetifier.core.archive.ArchiveFile
+import android.support.tools.jetifier.core.archive.ArchiveItemVisitor
+import android.support.tools.jetifier.core.config.Config
+import android.support.tools.jetifier.core.utils.Log
+
+/**
+ * Helper to scan [Archive]s to find their POM files.
+ */
+class PomScanner(private val config: Config) {
+
+    companion object {
+        private const val TAG = "PomScanner"
+    }
+
+    private val pomFilesInternal = mutableListOf<PomDocument>()
+
+    private var validationFailuresCount = 0
+
+    val pomFiles : List<PomDocument> = pomFilesInternal
+
+    fun wasErrorFound() = validationFailuresCount > 0
+
+    /**
+     * Scans the given [archive] for a POM file
+     *
+     * @return null if POM file was not found
+     */
+    fun scanArchiveForPomFile(archive: Archive) : PomDocument? {
+        val session = PomScannerSession()
+        archive.accept(session)
+
+        if (session.pomFile == null) {
+            return null
+        }
+        val pomFile = session.pomFile!!
+
+        pomFile.logDocumentDetails()
+
+        if (!pomFile.validate(config.pomRewriteRules)) {
+            Log.e(TAG, "Version mismatch!")
+            validationFailuresCount++
+        }
+
+        pomFilesInternal.add(session.pomFile!!)
+
+        return session.pomFile
+    }
+
+
+    private class PomScannerSession : ArchiveItemVisitor {
+
+        var pomFile : PomDocument? = null
+
+        override fun visit(archive: Archive) {
+            for (archiveItem in archive.files) {
+                if (pomFile != null) {
+                    break
+                }
+                archiveItem.accept(this)
+            }
+        }
+
+        override fun visit(archiveFile: ArchiveFile) {
+            if (archiveFile.isPomFile()) {
+                pomFile = PomDocument.loadFrom(archiveFile)
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/pom/XmlUtils.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/pom/XmlUtils.kt
new file mode 100644
index 0000000..67b7a3d
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/pom/XmlUtils.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.core.transform.pom
+
+import android.support.tools.jetifier.core.utils.Log
+import org.jdom2.Document
+import org.jdom2.Element
+import org.jdom2.input.SAXBuilder
+import org.jdom2.output.Format
+import org.jdom2.output.XMLOutputter
+import java.io.ByteArrayOutputStream
+import java.util.regex.Pattern
+
+/**
+ * Utilities for handling XML documents.
+ */
+class XmlUtils {
+
+    companion object {
+
+        private val variablePattern = Pattern.compile("\\$\\{([^}]*)}")
+
+        /** Saves the given [Document] to a new byte array */
+        fun convertDocumentToByteArray(document : Document) : ByteArray {
+            val xmlOutput = XMLOutputter()
+            ByteArrayOutputStream().use {
+                xmlOutput.format = Format.getPrettyFormat()
+                xmlOutput.output(document, it)
+                return it.toByteArray()
+            }
+        }
+
+        /** Creates a new [Document] from the given [ByteArray] */
+        fun createDocumentFromByteArray(data: ByteArray) : Document {
+            val builder = SAXBuilder()
+            data.inputStream().use {
+                return builder.build(it)
+            }
+        }
+
+        /**
+         * Creates a new XML element with the given [id] and text given in [value] and puts it under
+         * the given [parent]. Nothing is created if the [value] argument is null or empty.
+         */
+        fun addStringNodeToNode(parent: Element, id: String, value: String?) {
+            if (value.isNullOrEmpty()) {
+                return
+            }
+
+            val element = Element(id)
+            element.text = value
+            element.namespace = parent.namespace
+            parent.children.add(element)
+        }
+
+
+        fun resolveValue(value: String?, properties: Map<String, String>) : String? {
+            if (value == null) {
+                return null
+            }
+
+            val matcher = variablePattern.matcher(value)
+            if (matcher.matches()) {
+                val variableName = matcher.group(1)
+                val varValue = properties[variableName]
+                if (varValue == null) {
+                    Log.e("TAG", "Failed to resolve variable '%s'", value)
+                    return value
+                }
+                return varValue
+            }
+
+            return value
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardClassFilterParser.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardClassFilterParser.kt
new file mode 100644
index 0000000..c431572
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardClassFilterParser.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.core.transform.proguard
+
+import android.support.tools.jetifier.core.transform.proguard.patterns.GroupsReplacer
+import android.support.tools.jetifier.core.transform.proguard.patterns.PatternHelper
+import java.util.regex.Pattern
+
+/**
+ * Parses and rewrites ProGuard rules that contain class filters. See ProGuard documentation
+ * https://www.guardsquare.com/en/proguard/manual/usage#filters
+ */
+class ProGuardClassFilterParser(private val mapper : ProGuardTypesMapper) {
+
+    companion object {
+        private const val RULES = "(adaptclassstrings|dontnote|dontwarn)"
+    }
+
+    val replacer = GroupsReplacer(
+        pattern = PatternHelper.build("^ *-$RULES ⦅[^-]+⦆ *$", Pattern.MULTILINE),
+        groupsMap = listOf(
+            { filter : String -> rewriteClassFilter(filter) }
+        )
+    )
+
+    private fun rewriteClassFilter(classFilter: String) : String {
+        return classFilter
+            .splitToSequence(",")
+            .filterNotNull()
+            .map { it.trim() }
+            .filter { it.isNotEmpty() }
+            .map { replaceTypeInClassFilter(it) }
+            .joinToString(separator = ", ")
+    }
+
+    private fun replaceTypeInClassFilter(type: String) : String {
+        if (!type.startsWith('!')) {
+            return mapper.replaceType(type)
+        }
+
+        val withoutNegation = type.substring(1, type.length)
+        return '!' + mapper.replaceType(withoutNegation)
+    }
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardClassSpecParser.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardClassSpecParser.kt
new file mode 100644
index 0000000..933ff08
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardClassSpecParser.kt
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.core.transform.proguard
+
+import android.support.tools.jetifier.core.transform.proguard.patterns.GroupsReplacer
+import android.support.tools.jetifier.core.transform.proguard.patterns.PatternHelper
+
+/**
+ * Parses and rewrites ProGuard rules that contain class specification. See ProGuard documentation
+ * https://www.guardsquare.com/en/proguard/manual/usage#classspecification
+ */
+class ProGuardClassSpecParser(private val mapper : ProGuardTypesMapper) {
+
+    companion object {
+        private const val RULES = "(keep[a-z]*|whyareyoukeeping|assumenosideeffects)"
+        private const val RULES_MODIFIERS =
+            "(includedescriptorclasses|allowshrinking|allowoptimization|allowobfuscation)"
+
+        private const val CLASS_NAME = "[\\w.$?*_%]+"
+        private const val CLASS_MODIFIERS = "[!]?(public|final|abstract)"
+        private const val CLASS_TYPES = "[!]?(interface|class|enum)"
+
+        private const val ANNOTATION_TYPE = CLASS_NAME
+
+        private const val FIELD_NAME = "[\\w?*_%]+"
+        private const val FIELD_TYPE = CLASS_NAME
+        private const val FIELD_MODIFIERS =
+            "[!]?(public|private|protected|static|volatile|transient)"
+
+        private const val METHOD_MODIFIERS =
+            "[!]?(public|private|protected|static|synchronized|native|abstract|strictfp)"
+        private const val RETURN_TYPE_NAME = CLASS_NAME
+        private const val METHOD_NAME = "[\\w?*_]+"
+        private const val ARGS = "[^)]*"
+    }
+
+    val replacer = GroupsReplacer(
+        pattern = PatternHelper.build(
+            "-$RULES ($RULES_MODIFIERS )*(@⦅$ANNOTATION_TYPE⦆ )?($CLASS_MODIFIERS )*$CLASS_TYPES " +
+            "⦅$CLASS_NAME⦆( (extends|implements) ⦅$CLASS_NAME⦆)?+ *( *\\{⦅[^}]*⦆\\} *)?+"),
+        groupsMap = listOf(
+            { annotation : String -> mapper.replaceType(annotation) },
+            { className : String -> mapper.replaceType(className) },
+            { className2 : String -> mapper.replaceType(className2) },
+            { bodyGroup : String -> rewriteBodyGroup(bodyGroup) }
+        )
+    )
+
+    private val bodyReplacers = listOf(
+        // [@annotation] [[!]public|private|etc...] <fields>;
+        GroupsReplacer(
+            pattern = PatternHelper.build(
+                "^ *(@⦅$ANNOTATION_TYPE⦆ )?($FIELD_MODIFIERS )*<fields> *$"),
+            groupsMap = listOf(
+                { annotation : String -> mapper.replaceType(annotation) }
+        )),
+
+        // [@annotation] [[!]public|private|etc...] fieldType fieldName;
+        GroupsReplacer(
+            pattern = PatternHelper.build(
+                "^ *(@⦅$ANNOTATION_TYPE⦆ )?($FIELD_MODIFIERS )*(⦅$FIELD_TYPE⦆ $FIELD_NAME) *$"),
+            groupsMap = listOf(
+                { annotation : String -> mapper.replaceType(annotation) },
+                { fieldType : String -> mapper.replaceType(fieldType) }
+        )),
+
+        // [@annotation] [[!]public|private|etc...] <methods>;
+        GroupsReplacer(
+            pattern = PatternHelper.build(
+                "^ *(@⦅$ANNOTATION_TYPE⦆ )?($METHOD_MODIFIERS )*<methods> *$"),
+            groupsMap = listOf(
+                { annotation : String -> mapper.replaceType(annotation) }
+        )),
+
+        // [@annotation] [[!]public|private|etc...] className(argumentType,...));
+        GroupsReplacer(
+            pattern = PatternHelper.build(
+                "^ *(@⦅$ANNOTATION_TYPE⦆ )?($METHOD_MODIFIERS )*⦅$CLASS_NAME⦆ *\\(⦅$ARGS⦆\\) *$"),
+            groupsMap = listOf(
+                { annotation : String -> mapper.replaceType(annotation) },
+                { className : String -> mapper.replaceType(className) },
+                { argsType : String -> mapper.replaceMethodArgs(argsType) }
+            )
+        ),
+
+        // [@annotation] [[!]public|private|etc...] <init>(argumentType,...));
+        GroupsReplacer(
+            pattern = PatternHelper.build(
+                "^ *(@⦅$ANNOTATION_TYPE⦆ )?($METHOD_MODIFIERS )*<init> *\\(⦅$ARGS⦆\\) *$"),
+            groupsMap = listOf(
+                { annotation : String -> mapper.replaceType(annotation) },
+                { argsType : String -> mapper.replaceMethodArgs(argsType) }
+        )),
+
+        // [@annotation] [[!]public|private|etc...] returnType methodName(argumentType,...));
+        GroupsReplacer(
+            pattern = PatternHelper.build("^ *(@⦅$ANNOTATION_TYPE⦆ )?($METHOD_MODIFIERS )*" +
+                "⦅$RETURN_TYPE_NAME⦆ $METHOD_NAME *\\(⦅$ARGS⦆\\) *$"),
+            groupsMap = listOf(
+                { annotation : String -> mapper.replaceType(annotation) },
+                { returnType : String -> mapper.replaceType(returnType) },
+                { argsType : String -> mapper.replaceMethodArgs(argsType) }
+        ))
+    )
+
+    private fun rewriteBodyGroup(bodyGroup: String) : String {
+        if (bodyGroup == "*" || bodyGroup == "**") {
+            return bodyGroup
+        }
+
+        return bodyGroup
+            .split(';')
+            .map {
+                for (replacer in bodyReplacers) {
+                    val matcher = replacer.pattern.matcher(it)
+                    if (matcher.matches()) {
+                        return@map replacer.runReplacements(matcher)
+                    }
+                }
+                return@map it
+            }
+            .joinToString(";")
+    }
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardTransformer.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardTransformer.kt
new file mode 100644
index 0000000..423bf05
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardTransformer.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.core.transform.proguard
+
+import android.support.tools.jetifier.core.archive.ArchiveFile
+import android.support.tools.jetifier.core.transform.TransformationContext
+import android.support.tools.jetifier.core.transform.Transformer
+import android.support.tools.jetifier.core.transform.proguard.patterns.ReplacersRunner
+import java.nio.charset.StandardCharsets
+
+/**
+ * The [Transformer] responsible for ProGuard files refactoring.
+ */
+class ProGuardTransformer internal constructor(context: TransformationContext) : Transformer {
+
+    private val mapper = ProGuardTypesMapper(context)
+
+    val replacer = ReplacersRunner(listOf(
+        ProGuardClassSpecParser(mapper).replacer,
+        ProGuardClassFilterParser(mapper).replacer
+    ))
+
+    override fun canTransform(file: ArchiveFile): Boolean {
+        return file.isProGuardFile()
+    }
+
+    override fun runTransform(file: ArchiveFile) {
+        val sb = StringBuilder(file.data.toString(StandardCharsets.UTF_8))
+        val result = replacer.applyReplacers(sb.toString())
+        file.data = result.toByteArray()
+    }
+}
+
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardType.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardType.kt
new file mode 100644
index 0000000..be15fbf
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardType.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.core.transform.proguard
+
+import android.support.tools.jetifier.core.rules.JavaType
+
+/**
+ * Represents a type reference in ProGuard file. This type is similar to the regular java type but
+ * can also contain wildcards (*,**,?).
+ */
+data class ProGuardType(val value: String) {
+
+    init {
+        if (value.contains('.')) {
+            throw IllegalArgumentException("The type does not support '.' as package separator!")
+        }
+    }
+
+    companion object {
+        /** Creates the type reference from notation where packages are separated using '.' */
+        fun fromDotNotation(type: String) : ProGuardType {
+            return ProGuardType(type.replace('.', '/'))
+        }
+    }
+
+    /**
+     * Whether the type reference is trivial such as "*".
+     */
+    fun isTrivial() = value == "*" || value == "**" || value == "***" || value == "%"
+
+    fun toJavaType() : JavaType? {
+        if (value.contains('*') || value.contains('?')) {
+            return null
+        }
+        return JavaType(value)
+    }
+
+    /** Returns the type reference as a string where packages are separated using '.' */
+    fun toDotNotation() : String {
+        return value.replace('/', '.')
+    }
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardTypesMap.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardTypesMap.kt
new file mode 100644
index 0000000..03d6282
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardTypesMap.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.core.transform.proguard
+
+/**
+ * Contains custom mappings to map support library types referenced in ProGuard to new ones.
+ */
+data class ProGuardTypesMap(val rules: Map<ProGuardType, ProGuardType>) {
+
+    companion object {
+        val EMPTY = ProGuardTypesMap(emptyMap())
+    }
+
+    /** Returns JSON data model of this class */
+    fun toJson() : JsonData {
+        return JsonData(rules.map { it.key.value to it.value.value }.toMap())
+    }
+
+    /**
+     * JSON data model for [ProGuardTypesMap].
+     */
+    data class JsonData(val rules: Map<String, String>)  {
+
+        /** Creates instance of [ProGuardTypesMap] */
+        fun toMappings() : ProGuardTypesMap {
+            return ProGuardTypesMap(rules.map { ProGuardType(it.key) to ProGuardType(it.value) }.toMap())
+        }
+    }
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardTypesMapper.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardTypesMapper.kt
new file mode 100644
index 0000000..28195a3
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardTypesMapper.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.core.transform.proguard
+
+import android.support.tools.jetifier.core.transform.TransformationContext
+import android.support.tools.jetifier.core.utils.Log
+
+/**
+ * Maps ProGuard types using [TypesMap] and [ProGuardTypesMap].
+ */
+class ProGuardTypesMapper(private val context: TransformationContext) {
+
+    companion object {
+        const val TAG = "ProGuardTypesMapper"
+    }
+
+    private val config = context.config
+
+    /**
+     * Replaces the given ProGuard type that was parsed from the ProGuard file (thus having '.' as
+     * a separator.
+     */
+    fun replaceType(typeToReplace: String) : String {
+        val type = ProGuardType.fromDotNotation(typeToReplace)
+        if (type.isTrivial()) {
+            return typeToReplace
+        }
+
+        val javaType = type.toJavaType()
+        if (javaType != null) {
+            // We are dealing with an explicit type definition
+            if (!context.isEligibleForRewrite(javaType)) {
+                return typeToReplace
+            }
+
+            val result = config.typesMap.types[javaType]
+            if (result == null) {
+                context.reportNoProGuardMappingFoundFailure()
+                Log.e(TAG, "No mapping for: " + type)
+                return typeToReplace
+            }
+
+            Log.i(TAG, "  map: %s -> %s", type, result)
+            return result.toDotNotation()
+        }
+
+        // Type contains wildcards - try custom rules map
+        val result = config.proGuardMap.rules[type]
+        if (result != null) {
+            Log.i(TAG, "  map: %s -> %s", type, result)
+            return result.toDotNotation()
+        }
+
+        // Report error only when we are sure
+        if (context.isEligibleForRewrite(type)) {
+            context.reportNoProGuardMappingFoundFailure()
+            Log.e(TAG, "No mapping for: " + type)
+        }
+        return typeToReplace
+    }
+
+    /**
+     * Replaces the given arguments list used in a ProGuard method rule. Argument must be separated
+     * with ','. The method also accepts '...' symbol as defined in the spec.
+     */
+    fun replaceMethodArgs(argsTypes: String) : String {
+        if (argsTypes.isEmpty() || argsTypes == "...") {
+            return argsTypes
+        }
+
+        return argsTypes
+            .splitToSequence(",")
+            .filterNotNull()
+            .map { it.trim() }
+            .filter { it.isNotEmpty() }
+            .map { replaceType(it) }
+            .joinToString(separator = ", ")
+    }
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/proguard/patterns/GroupsReplacer.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/proguard/patterns/GroupsReplacer.kt
new file mode 100644
index 0000000..6213a55
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/proguard/patterns/GroupsReplacer.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.core.transform.proguard.patterns
+
+import java.util.regex.Matcher
+import java.util.regex.Pattern
+
+/**
+ * Applies replacements on a matched string using the given [pattern] and its groups. Each group is
+ * mapped using a lambda from [groupsMap].
+ */
+class GroupsReplacer(val pattern: Pattern,
+                     private val groupsMap: List<(String) -> String>) {
+
+    /**
+     * Takes the given [matcher] and replace its matched groups using mapping functions given in
+     * [groupsMap].
+     */
+    fun runReplacements(matcher: Matcher) : String {
+        var result = matcher.group(0)
+
+        // We go intentionally backwards to replace using indexes
+        for (i in groupsMap.size - 1 downTo 0) {
+            val groupVal = matcher.group(i + 1) ?: continue
+            val localStart = matcher.start(i + 1) - matcher.start()
+            val localEnd =  matcher.end(i + 1) - matcher.start()
+
+            result = result.replaceRange(
+                startIndex = localStart,
+                endIndex = localEnd,
+                replacement = groupsMap[i].invoke(groupVal))
+        }
+        return result
+    }
+
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/proguard/patterns/PatternHelper.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/proguard/patterns/PatternHelper.kt
new file mode 100644
index 0000000..3171185
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/proguard/patterns/PatternHelper.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.core.transform.proguard.patterns
+
+import java.util.regex.Pattern
+
+/**
+ * Helps to build regular expression [Pattern]s defined with less verbose syntax.
+ *
+ * You can use following shortcuts:
+ * '⦅⦆' - denotes a capturing group (normally '()' is capturing group)
+ * '()' - denotes non-capturing group (normally (?:) is non-capturing group)
+ * ' ' - denotes a whitespace characters (at least one)
+ * ' *' - denotes a whitespace characters (any)
+ * ';' - denotes ' *;'
+ */
+object PatternHelper {
+
+    private val rewrites = listOf(
+        " *" to "[\\s]*", // Optional space
+        " " to "[\\s]+", // Space
+        "⦅" to "(", // Capturing group start
+        "ï½ " to ")", // Capturing group end
+        ";" to "[\\s]*;" // Allow spaces in front of ';'
+    )
+
+    /**
+     * Transforms the given [toReplace] according to the rules defined in documentation of this
+     * class and compiles it to a [Pattern].
+     */
+    fun build(toReplace: String, flags : Int = 0) : Pattern {
+        var result = toReplace
+        result = result.replace("(?<!\\\\)\\(".toRegex(), "(?:")
+        rewrites.forEach { result = result.replace(it.first, it.second) }
+        return Pattern.compile(result, flags)
+    }
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/proguard/patterns/ReplacersRunner.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/proguard/patterns/ReplacersRunner.kt
new file mode 100644
index 0000000..54501f9
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/proguard/patterns/ReplacersRunner.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.core.transform.proguard.patterns
+
+/**
+ * Runs multiple [GroupsReplacer]s on given strings.
+ */
+class ReplacersRunner(val replacers: List<GroupsReplacer>) {
+
+    /**
+     * Runs all the [GroupsReplacer]s on the given [input].
+     *
+     * The replacers have to be distinct as this method can't guarantee that output of one replacer
+     * won't be matched by another replacer.
+     */
+    fun applyReplacers(input : String) : String {
+        val sb = StringBuilder()
+        var lastSeenChar = 0
+        var processedInput = input
+
+        for (replacer in replacers) {
+            val matcher = replacer.pattern.matcher(processedInput)
+
+            while (matcher.find()) {
+                if (lastSeenChar < matcher.start()) {
+                    sb.append(input, lastSeenChar, matcher.start())
+                }
+
+                val result = replacer.runReplacements(matcher)
+                sb.append(result)
+                lastSeenChar = matcher.end()
+            }
+
+            if (lastSeenChar == 0) {
+                continue
+            }
+
+            if (lastSeenChar <= processedInput.length - 1) {
+                sb.append(processedInput, lastSeenChar, processedInput.length)
+            }
+
+            lastSeenChar = 0
+            processedInput = sb.toString()
+            sb.setLength(0)
+        }
+
+        return processedInput
+    }
+
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/resource/XmlResourcesTransformer.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/resource/XmlResourcesTransformer.kt
new file mode 100644
index 0000000..0a29828
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/transform/resource/XmlResourcesTransformer.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.core.transform.resource
+
+import android.support.tools.jetifier.core.archive.ArchiveFile
+import android.support.tools.jetifier.core.map.TypesMap
+import android.support.tools.jetifier.core.rules.JavaTypeXmlRef
+import android.support.tools.jetifier.core.transform.TransformationContext
+import android.support.tools.jetifier.core.transform.Transformer
+import android.support.tools.jetifier.core.utils.Log
+import java.nio.charset.Charset
+import java.nio.charset.StandardCharsets
+import java.util.regex.Pattern
+import javax.xml.stream.XMLInputFactory
+
+/**
+ * Transformer for XML resource files.
+ *
+ * Searches for any java type reference that is pointing to the support library and rewrites it
+ * using the available mappings from [TypesMap].
+ */
+class XmlResourcesTransformer internal constructor(private val context: TransformationContext)
+        : Transformer {
+
+    companion object {
+        const val TAG = "XmlResourcesTransformer"
+
+        const val PATTERN_TYPE_GROUP = 1
+    }
+
+    /**
+     * List of regular expression patterns used to find support library references in XML files.
+     *
+     * Matches xml tags in form of:
+     * 1. '<(/)prefix(SOMETHING)'.
+     * 2. <view ... class="prefix(SOMETHING)" ...>
+     *
+     * Note that this can also rewrite commented blocks of XML. But on a library level we don't care
+     * much about comments.
+     */
+    private val patterns = listOf(
+        Pattern.compile("</?([a-zA-Z0-9.]+)"),
+        Pattern.compile("<view[^>]*class=\"([a-zA-Z0-9.\$_]+)\"[^>]*>")
+    )
+
+    private val typesMap = context.config.typesMap
+
+    override fun canTransform(file: ArchiveFile) = file.isXmlFile() && !file.isPomFile()
+
+    override fun runTransform(file: ArchiveFile) {
+        file.data = transform(file.data)
+    }
+
+    fun transform(data: ByteArray) : ByteArray {
+        var changesDone = false
+
+        val charset = getCharset(data)
+        val sb = StringBuilder(data.toString(charset))
+        for (pattern in patterns) {
+            var matcher = pattern.matcher(sb)
+            while (matcher.find()) {
+                val typeToReplace = JavaTypeXmlRef(matcher.group(PATTERN_TYPE_GROUP))
+                val result = rewriteType(typeToReplace)
+                if (result == typeToReplace) {
+                    continue
+                }
+                sb.replace(matcher.start(PATTERN_TYPE_GROUP), matcher.end(PATTERN_TYPE_GROUP),
+                    result.fullName)
+                changesDone = true
+                matcher = pattern.matcher(sb)
+            }
+        }
+
+        if (changesDone) {
+            return sb.toString().toByteArray(charset)
+        }
+
+        return data
+    }
+
+    fun getCharset(data: ByteArray) : Charset {
+        data.inputStream().use {
+            val xmlReader = XMLInputFactory.newInstance().createXMLStreamReader(it)
+
+            xmlReader.encoding ?: return StandardCharsets.UTF_8 // Encoding was not detected
+
+            val result = Charset.forName(xmlReader.encoding)
+            if (result == null) {
+                Log.e(TAG, "Failed to find charset for encoding '%s'", xmlReader.encoding)
+                return StandardCharsets.UTF_8
+            }
+            return result
+        }
+    }
+
+    fun rewriteType(type: JavaTypeXmlRef): JavaTypeXmlRef {
+        val javaType = type.toJavaType()
+        if (!context.isEligibleForRewrite(javaType)) {
+            return type
+        }
+
+        val result = typesMap.types[javaType]
+        if (result != null) {
+            Log.i(TAG, "  map: %s -> %s", type, result)
+            return JavaTypeXmlRef(result)
+        }
+
+        context.reportNoMappingFoundFailure()
+        Log.e(TAG, "No mapping for: " + type)
+        return type
+    }
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/utils/Log.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/utils/Log.kt
new file mode 100644
index 0000000..902dea4
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/utils/Log.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.core.utils
+
+object Log {
+
+    var currentLevel : LogLevel = LogLevel.INFO
+
+    var logConsumer : LogConsumer = StdOutLogConsumer()
+
+    fun setLevel(level: String?) {
+        currentLevel = when (level) {
+            "debug" -> LogLevel.DEBUG
+            "verbose" -> LogLevel.VERBOSE
+            else -> LogLevel.INFO
+        }
+
+    }
+
+    fun e(tag: String, message: String, vararg args: Any?) {
+        if (currentLevel >= LogLevel.ERROR) {
+            logConsumer.error("[$tag] $message".format(*args))
+        }
+    }
+
+    fun d(tag: String, message: String, vararg args: Any?) {
+        if (currentLevel >= LogLevel.DEBUG) {
+            logConsumer.debug("[$tag] $message".format(*args))
+        }
+    }
+
+    fun i(tag: String, message: String, vararg args: Any?) {
+        if (currentLevel >= LogLevel.INFO) {
+            logConsumer.info("[$tag] $message".format(*args))
+        }
+    }
+
+    fun v(tag: String, message: String, vararg args: Any?) {
+        if (currentLevel >= LogLevel.VERBOSE) {
+            logConsumer.verbose("[$tag] $message".format(*args))
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/utils/LogConsumer.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/utils/LogConsumer.kt
new file mode 100644
index 0000000..ddebd25
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/utils/LogConsumer.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.core.utils
+
+/**
+ * Interface to plug custom logs consumers to [Log].
+ */
+interface LogConsumer {
+
+    fun error(message: String)
+
+    fun info(message: String)
+
+    fun verbose(message: String)
+
+    fun debug(message: String)
+
+}
+
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/utils/LogLevel.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/utils/LogLevel.kt
new file mode 100644
index 0000000..f46b8f6
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/utils/LogLevel.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.core.utils
+
+enum class LogLevel(val priority : Int) {
+    ERROR(0),
+    INFO(1),
+    VERBOSE(2),
+    DEBUG(3),
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/utils/StdOutLogConsumer.kt b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/utils/StdOutLogConsumer.kt
new file mode 100644
index 0000000..7cfd25e
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/kotlin/android/support/tools/jetifier/core/utils/StdOutLogConsumer.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.core.utils
+
+/**
+ * Prints logs to the standard output.
+ */
+class StdOutLogConsumer : LogConsumer {
+
+    override fun error(message: String) {
+        println("ERROR: $message")
+    }
+
+    override fun info(message: String) {
+        println("INFO: $message")
+    }
+
+    override fun verbose(message: String) {
+        println("VERBOSE: $message")
+    }
+
+    override fun debug(message: String) {
+        println("DEBUG: $message")
+    }
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/resources/default.config b/jetifier/jetifier/core/src/main/resources/default.config
new file mode 100644
index 0000000..a95f7cb
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/resources/default.config
@@ -0,0 +1,251 @@
+# 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
+
+{
+    # Skip packages that don't match the following regex
+    restrictToPackagePrefixes: [
+        "android/support/"
+    ],
+    rules: [
+        {
+            from: "android/support/design/widget/CoordinatorLayout",
+            to: "androidx/widget/CoordinatorLayout"
+        },
+        {
+            from: "android/support/design/widget/DirectedAcyclicGraph",
+            to: "androidx/widget/DirectedAcyclicGraph"
+        },
+        {
+            from: "android/support/design/widget/ViewGroupUtils",
+            to: "androidx/widget/ViewGroupUtils"
+        },
+        {
+            from: "android/support/v4/view/ViewPager",
+            to: "androidx/widget/ViewPager"
+        },
+        {
+            from: "android/support/v4/view/PagerAdapter",
+            to: "androidx/widget/PagerAdapter"
+        },
+        {
+            from: "android/support/v4/view/PagerTabStrip",
+            to: "androidx/widget/PagerTabStrip"
+        },
+        {
+            from: "android/support/v4/view/PagerTitleStrip",
+            to: "androidx/widget/PagerTitleStrip"
+        },
+        {
+            from: "android/support/v17/preference/(.*)",
+            to: "androidx/leanback/preference/{0}"
+        },
+        {
+            from: "android/support/customtabs/(.*)",
+            to: "androidx/browser/customtabs/{0}"
+        },
+        {
+            from: "android/support/v4/(.*)",
+            to: "androidx/{0}"
+        },
+        {
+            from: "android/support/v7/graphics/ColorCutQuantizer",
+            to: "androidx/graphics/palette/ColorCutQuantizer"
+        },
+        {
+            from: "android/support/v7/graphics/Palette",
+            to: "androidx/graphics/palette/Palette"
+        },
+        {
+            from: "android/support/v7/graphics/Target",
+            to: "androidx/graphics/palette/Target"
+        },
+        {
+            from: "android/support/v7/(.*)",
+            to: "androidx/{0}"
+        },
+        {
+            from: "android/support/v13/(.*)",
+            to: "androidx/{0}"
+        },
+        {
+            from: "android/support/v14/(.*)",
+            to: "androidx/{0}"
+        },
+        {
+            from: "android/support/v17/(.*)",
+            to: "androidx/{0}"
+        },
+        {
+            from: "android/support/percent/(.*)",
+            to: "androidx/{0}"
+        },
+        {
+            from: "android/support/(.*)",
+            to: "androidx/{0}"
+        },
+        {
+            from: "android/arch/(.*)",
+            to: "androidx/{0}"
+        }
+    ],
+    pomRules: [
+        {
+            from: { groupId: "com.android.support", artifactId: "animated-vector-drawable", version: "27.0.1" },
+            to: [{ groupId: "com.androidx", artifactId: "animated-vector-drawable", version: "28.0.0" }]
+        },
+        {
+            from: { groupId: "com.android.support", artifactId: "appcompat-v7", version: "27.0.1" },
+            to: [{ groupId: "com.androidx", artifactId: "appcompat", version: "28.0.0" }]
+        },
+        {
+            from: { groupId: "com.android.support", artifactId: "cardview-v7", version: "27.0.1" },
+            to: [{ groupId: "com.androidx", artifactId: "cardview", version: "28.0.0" }]
+        },
+        {
+            from: { groupId: "com.android.support", artifactId: "customtabs", version: "27.0.1" },
+            to: [{ groupId: "com.androidx.browser", artifactId: "customtabs", version: "28.0.0" }]
+        },
+        {
+            from: { groupId: "com.android.support", artifactId: "design", version: "27.0.1" },
+            to: [
+                    { groupId: "com.androidx", artifactId: "design", version: "28.0.0" },
+                    { groupId: "com.androidx", artifactId: "widget", version: "28.0.0" }
+                ]
+        },
+        {
+            from: { groupId: "com.android.support", artifactId: "exifinterface", version: "27.0.1" },
+            to: [{ groupId: "com.androidx", artifactId: "exifinterface", version: "28.0.0" }]
+        },
+        {
+            from: { groupId: "com.android.support", artifactId: "gridlayout-v7", version: "27.0.1" },
+            to: [{ groupId: "com.androidx", artifactId: "gridlayout", version: "28.0.0" }]
+        },
+        {
+            from: { groupId: "com.android.support", artifactId: "instantvideo", version: "26.0.0-alpha1" },
+            to: [{ groupId: "com.androidx", artifactId: "instantvideo", version: "28.0.0" }]
+        },
+        {
+            from: { groupId: "com.android.support", artifactId: "leanback-v17", version: "27.0.1" },
+            to: [{ groupId: "com.androidx", artifactId: "leanback", version: "28.0.0" }]
+        },
+        {
+            from: { groupId: "com.android.support", artifactId: "multidex", version: "1.0.2" },
+            to: [{ groupId: "com.androidx", artifactId: "multidex", version: "2.0.0" }]
+        },
+        {
+            from: { groupId: "com.android.support", artifactId: "multidex-instrumentation", version: "1.0.2" },
+            to: [{ groupId: "com.androidx", artifactId: "multidex-instrumentation", version: "2.0.0" }]
+        },
+        {
+            from: { groupId: "com.android.support", artifactId: "palette-v7", version: "27.0.1" },
+            to: [{ groupId: "com.androidx.graphics", artifactId: "palette", version: "28.0.0" }]
+        },
+        {
+            from: { groupId: "com.android.support", artifactId: "percent", version: "27.0.1" },
+            to: [{ groupId: "com.androidx", artifactId: "widget", version: "28.0.0" }]
+        },
+        {
+            from: { groupId: "com.android.support", artifactId: "preference-leanback-v17", version: "27.0.1" },
+            to: [{ groupId: "com.androidx", artifactId: "preference-leanback", version: "28.0.0" }]
+        },
+        {
+            from: { groupId: "com.android.support", artifactId: "preference-v14", version: "27.0.1" },
+            to: [{ groupId: "com.androidx", artifactId: "preference", version: "28.0.0" }]
+        },
+        {
+            from: { groupId: "com.android.support", artifactId: "preference-v7", version: "27.0.1" },
+            to: [{ groupId: "com.androidx", artifactId: "preference", version: "28.0.0" }]
+        },
+        {
+            from: { groupId: "com.android.support", artifactId: "recommendation", version: "27.0.1" },
+            to: [{ groupId: "com.androidx", artifactId: "recommendation", version: "28.0.0" }]
+        },
+        {
+            from: { groupId: "com.android.support", artifactId: "recyclerview-v7", version: "27.0.1" },
+            to: [{ groupId: "com.androidx", artifactId: "recyclerview", version: "28.0.0" }]
+        },
+        {
+            from: { groupId: "com.android.support", artifactId: "support-annotations", version: "27.0.1" },
+            to: [{ groupId: "com.androidx", artifactId: "annotations", version: "28.0.0" }]
+        },
+        {
+            from: { groupId: "com.android.support", artifactId: "support-compat", version: "27.0.1" },
+            to: [{ groupId: "com.androidx.browser", artifactId: "compat", version: "28.0.0" }]
+        },
+        {
+            from: { groupId: "com.android.support", artifactId: "support-content", version: "27.0.1" },
+            to: [{ groupId: "com.androidx", artifactId: "content", version: "28.0.0" }]
+        },
+        {
+            from: { groupId: "com.android.support", artifactId: "support-core-ui", version: "27.0.1" },
+            to: [{ groupId: "com.androidx", artifactId: "core-ui", version: "28.0.0" }]
+        },
+        {
+            from: { groupId: "com.android.support", artifactId: "support-core-utils", version: "27.0.1" },
+            to: [{ groupId: "com.androidx", artifactId: "core-utils", version: "28.0.0" }]
+        },
+        {
+            from: { groupId: "com.android.support", artifactId: "support-dynamic-animation", version: "27.0.1" },
+            to: [{ groupId: "com.androidx", artifactId: "dynamic-animation", version: "28.0.0" }]
+        },
+        {
+            from: { groupId: "com.android.support", artifactId: "support-emoji", version: "27.0.1" },
+            to: [{ groupId: "com.androidx", artifactId: "emoji", version: "28.0.0" }]
+        },
+        {
+            from: { groupId: "com.android.support", artifactId: "support-emoji-appcompat", version: "27.0.1" },
+            to: [{ groupId: "com.androidx", artifactId: "emoji-appcompat", version: "28.0.0" }]
+        },
+        {
+            from: { groupId: "com.android.support", artifactId: "support-emoji-bundled", version: "27.0.1" },
+            to: [{ groupId: "com.androidx", artifactId: "emoji-bundled", version: "28.0.0" }]
+        },
+        {
+            from: { groupId: "com.android.support", artifactId: "support-fragment", version: "27.0.1" },
+            to: [{ groupId: "com.androidx.browser", artifactId: "fragment", version: "28.0.0" }]
+        },
+        {
+            from: { groupId: "com.android.support", artifactId: "support-media-compat", version: "27.0.1" },
+            to: [{ groupId: "com.androidx", artifactId: "media-compat", version: "28.0.0" }]
+        },
+        {
+            from: { groupId: "com.android.support", artifactId: "support-tv-provider", version: "27.0.1" },
+            to: [{ groupId: "com.androidx", artifactId: "tv-provider", version: "28.0.0" }]
+        },
+        {
+            from: { groupId: "com.android.support", artifactId: "support-v13", version: "27.0.1" },
+            to: [{ groupId: "com.androidx", artifactId: "androidx", version: "28.0.0" }]
+        },
+        {
+            from: { groupId: "com.android.support", artifactId: "support-v4", version: "27.0.1" },
+            to: [{ groupId: "com.androidx", artifactId: "androidx", version: "28.0.0" }]
+        },
+        {
+            from: { groupId: "com.android.support", artifactId: "support-vector-drawable", version: "27.0.1" },
+            to: [{ groupId: "com.androidx", artifactId: "vector-drawable", version: "28.0.0" }]
+        },
+        {
+            from: { groupId: "com.android.support", artifactId: "transition", version: "27.0.1" },
+            to: [{ groupId: "com.androidx", artifactId: "transition", version: "28.0.0" }]
+        },
+        {
+            from: { groupId: "com.android.support", artifactId: "wear", version: "27.0.1" },
+            to: [{ groupId: "com.androidx", artifactId: "wear", version: "28.0.0" }]
+        },
+        {
+            from: { groupId: "com.android.support", artifactId: "wearable", version: "26.0.0-alpha1" },
+            to: [{ groupId: "com.androidx", artifactId: "wearable", version: "28.0.0" }]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/main/resources/default.generated.config b/jetifier/jetifier/core/src/main/resources/default.generated.config
new file mode 100644
index 0000000..3965e19
--- /dev/null
+++ b/jetifier/jetifier/core/src/main/resources/default.generated.config
@@ -0,0 +1,14526 @@
+# 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
+
+# DO NOT EDIT MANUALLY! This file was auto-generated using Jetifier preprocessor.
+# To make some changes in the configuration edit "default.config" and run
+# preprocessor/scripts/processDefaultConfig.sh script to update this file.
+
+{
+  "restrictToPackagePrefixes": [
+    "android/support/"
+  ],
+  "rules": [
+    {
+      "from": "android/support/design/widget/CoordinatorLayout",
+      "to": "androidx/widget/CoordinatorLayout",
+      "fieldSelectors": []
+    },
+    {
+      "from": "android/support/design/widget/DirectedAcyclicGraph",
+      "to": "androidx/widget/DirectedAcyclicGraph",
+      "fieldSelectors": []
+    },
+    {
+      "from": "android/support/design/widget/ViewGroupUtils",
+      "to": "androidx/widget/ViewGroupUtils",
+      "fieldSelectors": []
+    },
+    {
+      "from": "android/support/v4/view/ViewPager",
+      "to": "androidx/widget/ViewPager",
+      "fieldSelectors": []
+    },
+    {
+      "from": "android/support/v4/view/PagerAdapter",
+      "to": "androidx/widget/PagerAdapter",
+      "fieldSelectors": []
+    },
+    {
+      "from": "android/support/v4/view/PagerTabStrip",
+      "to": "androidx/widget/PagerTabStrip",
+      "fieldSelectors": []
+    },
+    {
+      "from": "android/support/v4/view/PagerTitleStrip",
+      "to": "androidx/widget/PagerTitleStrip",
+      "fieldSelectors": []
+    },
+    {
+      "from": "android/support/v17/preference/(.*)",
+      "to": "androidx/leanback/preference/{0}",
+      "fieldSelectors": []
+    },
+    {
+      "from": "android/support/customtabs/(.*)",
+      "to": "androidx/browser/customtabs/{0}",
+      "fieldSelectors": []
+    },
+    {
+      "from": "android/support/v4/(.*)",
+      "to": "androidx/{0}",
+      "fieldSelectors": []
+    },
+    {
+      "from": "android/support/v7/graphics/ColorCutQuantizer",
+      "to": "androidx/graphics/palette/ColorCutQuantizer",
+      "fieldSelectors": []
+    },
+    {
+      "from": "android/support/v7/graphics/Palette",
+      "to": "androidx/graphics/palette/Palette",
+      "fieldSelectors": []
+    },
+    {
+      "from": "android/support/v7/graphics/Target",
+      "to": "androidx/graphics/palette/Target",
+      "fieldSelectors": []
+    },
+    {
+      "from": "android/support/v7/(.*)",
+      "to": "androidx/{0}",
+      "fieldSelectors": []
+    },
+    {
+      "from": "android/support/v13/(.*)",
+      "to": "androidx/{0}",
+      "fieldSelectors": []
+    },
+    {
+      "from": "android/support/v14/(.*)",
+      "to": "androidx/{0}",
+      "fieldSelectors": []
+    },
+    {
+      "from": "android/support/v17/(.*)",
+      "to": "androidx/{0}",
+      "fieldSelectors": []
+    },
+    {
+      "from": "android/support/percent/(.*)",
+      "to": "androidx/{0}",
+      "fieldSelectors": []
+    },
+    {
+      "from": "android/support/(.*)",
+      "to": "androidx/{0}",
+      "fieldSelectors": []
+    },
+    {
+      "from": "android/arch/(.*)",
+      "to": "androidx/{0}",
+      "fieldSelectors": []
+    }
+  ],
+  "pomRules": [
+    {
+      "from": {
+        "groupId": "com.android.support",
+        "artifactId": "animated-vector-drawable",
+        "version": "27.0.1"
+      },
+      "to": [
+        {
+          "groupId": "com.androidx",
+          "artifactId": "animated-vector-drawable",
+          "version": "28.0.0"
+        }
+      ]
+    },
+    {
+      "from": {
+        "groupId": "com.android.support",
+        "artifactId": "appcompat-v7",
+        "version": "27.0.1"
+      },
+      "to": [
+        {
+          "groupId": "com.androidx",
+          "artifactId": "appcompat",
+          "version": "28.0.0"
+        }
+      ]
+    },
+    {
+      "from": {
+        "groupId": "com.android.support",
+        "artifactId": "cardview-v7",
+        "version": "27.0.1"
+      },
+      "to": [
+        {
+          "groupId": "com.androidx",
+          "artifactId": "cardview",
+          "version": "28.0.0"
+        }
+      ]
+    },
+    {
+      "from": {
+        "groupId": "com.android.support",
+        "artifactId": "customtabs",
+        "version": "27.0.1"
+      },
+      "to": [
+        {
+          "groupId": "com.androidx.browser",
+          "artifactId": "customtabs",
+          "version": "28.0.0"
+        }
+      ]
+    },
+    {
+      "from": {
+        "groupId": "com.android.support",
+        "artifactId": "design",
+        "version": "27.0.1"
+      },
+      "to": [
+        {
+          "groupId": "com.androidx",
+          "artifactId": "design",
+          "version": "28.0.0"
+        },
+        {
+          "groupId": "com.androidx",
+          "artifactId": "widget",
+          "version": "28.0.0"
+        }
+      ]
+    },
+    {
+      "from": {
+        "groupId": "com.android.support",
+        "artifactId": "exifinterface",
+        "version": "27.0.1"
+      },
+      "to": [
+        {
+          "groupId": "com.androidx",
+          "artifactId": "exifinterface",
+          "version": "28.0.0"
+        }
+      ]
+    },
+    {
+      "from": {
+        "groupId": "com.android.support",
+        "artifactId": "gridlayout-v7",
+        "version": "27.0.1"
+      },
+      "to": [
+        {
+          "groupId": "com.androidx",
+          "artifactId": "gridlayout",
+          "version": "28.0.0"
+        }
+      ]
+    },
+    {
+      "from": {
+        "groupId": "com.android.support",
+        "artifactId": "instantvideo",
+        "version": "26.0.0-alpha1"
+      },
+      "to": [
+        {
+          "groupId": "com.androidx",
+          "artifactId": "instantvideo",
+          "version": "28.0.0"
+        }
+      ]
+    },
+    {
+      "from": {
+        "groupId": "com.android.support",
+        "artifactId": "leanback-v17",
+        "version": "27.0.1"
+      },
+      "to": [
+        {
+          "groupId": "com.androidx",
+          "artifactId": "leanback",
+          "version": "28.0.0"
+        }
+      ]
+    },
+    {
+      "from": {
+        "groupId": "com.android.support",
+        "artifactId": "multidex",
+        "version": "1.0.2"
+      },
+      "to": [
+        {
+          "groupId": "com.androidx",
+          "artifactId": "multidex",
+          "version": "2.0.0"
+        }
+      ]
+    },
+    {
+      "from": {
+        "groupId": "com.android.support",
+        "artifactId": "multidex-instrumentation",
+        "version": "1.0.2"
+      },
+      "to": [
+        {
+          "groupId": "com.androidx",
+          "artifactId": "multidex-instrumentation",
+          "version": "2.0.0"
+        }
+      ]
+    },
+    {
+      "from": {
+        "groupId": "com.android.support",
+        "artifactId": "palette-v7",
+        "version": "27.0.1"
+      },
+      "to": [
+        {
+          "groupId": "com.androidx.graphics",
+          "artifactId": "palette",
+          "version": "28.0.0"
+        }
+      ]
+    },
+    {
+      "from": {
+        "groupId": "com.android.support",
+        "artifactId": "percent",
+        "version": "27.0.1"
+      },
+      "to": [
+        {
+          "groupId": "com.androidx",
+          "artifactId": "widget",
+          "version": "28.0.0"
+        }
+      ]
+    },
+    {
+      "from": {
+        "groupId": "com.android.support",
+        "artifactId": "preference-leanback-v17",
+        "version": "27.0.1"
+      },
+      "to": [
+        {
+          "groupId": "com.androidx",
+          "artifactId": "preference-leanback",
+          "version": "28.0.0"
+        }
+      ]
+    },
+    {
+      "from": {
+        "groupId": "com.android.support",
+        "artifactId": "preference-v14",
+        "version": "27.0.1"
+      },
+      "to": [
+        {
+          "groupId": "com.androidx",
+          "artifactId": "preference",
+          "version": "28.0.0"
+        }
+      ]
+    },
+    {
+      "from": {
+        "groupId": "com.android.support",
+        "artifactId": "preference-v7",
+        "version": "27.0.1"
+      },
+      "to": [
+        {
+          "groupId": "com.androidx",
+          "artifactId": "preference",
+          "version": "28.0.0"
+        }
+      ]
+    },
+    {
+      "from": {
+        "groupId": "com.android.support",
+        "artifactId": "recommendation",
+        "version": "27.0.1"
+      },
+      "to": [
+        {
+          "groupId": "com.androidx",
+          "artifactId": "recommendation",
+          "version": "28.0.0"
+        }
+      ]
+    },
+    {
+      "from": {
+        "groupId": "com.android.support",
+        "artifactId": "recyclerview-v7",
+        "version": "27.0.1"
+      },
+      "to": [
+        {
+          "groupId": "com.androidx",
+          "artifactId": "recyclerview",
+          "version": "28.0.0"
+        }
+      ]
+    },
+    {
+      "from": {
+        "groupId": "com.android.support",
+        "artifactId": "support-annotations",
+        "version": "27.0.1"
+      },
+      "to": [
+        {
+          "groupId": "com.androidx",
+          "artifactId": "annotations",
+          "version": "28.0.0"
+        }
+      ]
+    },
+    {
+      "from": {
+        "groupId": "com.android.support",
+        "artifactId": "support-compat",
+        "version": "27.0.1"
+      },
+      "to": [
+        {
+          "groupId": "com.androidx.browser",
+          "artifactId": "compat",
+          "version": "28.0.0"
+        }
+      ]
+    },
+    {
+      "from": {
+        "groupId": "com.android.support",
+        "artifactId": "support-content",
+        "version": "27.0.1"
+      },
+      "to": [
+        {
+          "groupId": "com.androidx",
+          "artifactId": "content",
+          "version": "28.0.0"
+        }
+      ]
+    },
+    {
+      "from": {
+        "groupId": "com.android.support",
+        "artifactId": "support-core-ui",
+        "version": "27.0.1"
+      },
+      "to": [
+        {
+          "groupId": "com.androidx",
+          "artifactId": "core-ui",
+          "version": "28.0.0"
+        }
+      ]
+    },
+    {
+      "from": {
+        "groupId": "com.android.support",
+        "artifactId": "support-core-utils",
+        "version": "27.0.1"
+      },
+      "to": [
+        {
+          "groupId": "com.androidx",
+          "artifactId": "core-utils",
+          "version": "28.0.0"
+        }
+      ]
+    },
+    {
+      "from": {
+        "groupId": "com.android.support",
+        "artifactId": "support-dynamic-animation",
+        "version": "27.0.1"
+      },
+      "to": [
+        {
+          "groupId": "com.androidx",
+          "artifactId": "dynamic-animation",
+          "version": "28.0.0"
+        }
+      ]
+    },
+    {
+      "from": {
+        "groupId": "com.android.support",
+        "artifactId": "support-emoji",
+        "version": "27.0.1"
+      },
+      "to": [
+        {
+          "groupId": "com.androidx",
+          "artifactId": "emoji",
+          "version": "28.0.0"
+        }
+      ]
+    },
+    {
+      "from": {
+        "groupId": "com.android.support",
+        "artifactId": "support-emoji-appcompat",
+        "version": "27.0.1"
+      },
+      "to": [
+        {
+          "groupId": "com.androidx",
+          "artifactId": "emoji-appcompat",
+          "version": "28.0.0"
+        }
+      ]
+    },
+    {
+      "from": {
+        "groupId": "com.android.support",
+        "artifactId": "support-emoji-bundled",
+        "version": "27.0.1"
+      },
+      "to": [
+        {
+          "groupId": "com.androidx",
+          "artifactId": "emoji-bundled",
+          "version": "28.0.0"
+        }
+      ]
+    },
+    {
+      "from": {
+        "groupId": "com.android.support",
+        "artifactId": "support-fragment",
+        "version": "27.0.1"
+      },
+      "to": [
+        {
+          "groupId": "com.androidx.browser",
+          "artifactId": "fragment",
+          "version": "28.0.0"
+        }
+      ]
+    },
+    {
+      "from": {
+        "groupId": "com.android.support",
+        "artifactId": "support-media-compat",
+        "version": "27.0.1"
+      },
+      "to": [
+        {
+          "groupId": "com.androidx",
+          "artifactId": "media-compat",
+          "version": "28.0.0"
+        }
+      ]
+    },
+    {
+      "from": {
+        "groupId": "com.android.support",
+        "artifactId": "support-tv-provider",
+        "version": "27.0.1"
+      },
+      "to": [
+        {
+          "groupId": "com.androidx",
+          "artifactId": "tv-provider",
+          "version": "28.0.0"
+        }
+      ]
+    },
+    {
+      "from": {
+        "groupId": "com.android.support",
+        "artifactId": "support-v13",
+        "version": "27.0.1"
+      },
+      "to": [
+        {
+          "groupId": "com.androidx",
+          "artifactId": "androidx",
+          "version": "28.0.0"
+        }
+      ]
+    },
+    {
+      "from": {
+        "groupId": "com.android.support",
+        "artifactId": "support-v4",
+        "version": "27.0.1"
+      },
+      "to": [
+        {
+          "groupId": "com.androidx",
+          "artifactId": "androidx",
+          "version": "28.0.0"
+        }
+      ]
+    },
+    {
+      "from": {
+        "groupId": "com.android.support",
+        "artifactId": "support-vector-drawable",
+        "version": "27.0.1"
+      },
+      "to": [
+        {
+          "groupId": "com.androidx",
+          "artifactId": "vector-drawable",
+          "version": "28.0.0"
+        }
+      ]
+    },
+    {
+      "from": {
+        "groupId": "com.android.support",
+        "artifactId": "transition",
+        "version": "27.0.1"
+      },
+      "to": [
+        {
+          "groupId": "com.androidx",
+          "artifactId": "transition",
+          "version": "28.0.0"
+        }
+      ]
+    },
+    {
+      "from": {
+        "groupId": "com.android.support",
+        "artifactId": "wear",
+        "version": "27.0.1"
+      },
+      "to": [
+        {
+          "groupId": "com.androidx",
+          "artifactId": "wear",
+          "version": "28.0.0"
+        }
+      ]
+    },
+    {
+      "from": {
+        "groupId": "com.android.support",
+        "artifactId": "wearable",
+        "version": "26.0.0-alpha1"
+      },
+      "to": [
+        {
+          "groupId": "com.androidx",
+          "artifactId": "wearable",
+          "version": "28.0.0"
+        }
+      ]
+    }
+  ],
+  "map": {
+    "types": {
+      "android/support/v4/provider/FontsContractCompat$Columns": "androidx/provider/FontsContractCompat$Columns",
+      "android/support/design/widget/AppBarLayout$Behavior$SavedState": "androidx/design/widget/AppBarLayout$Behavior$SavedState",
+      "android/support/v4/internal/view/SupportMenu": "androidx/internal/view/SupportMenu",
+      "android/support/v4/media/MediaDescriptionCompat": "androidx/media/MediaDescriptionCompat",
+      "android/support/transition/ChangeTransform$GhostListener": "androidx/transition/ChangeTransform$GhostListener",
+      "android/support/design/widget/BaseTransientBottomBar$BaseCallback": "androidx/design/widget/BaseTransientBottomBar$BaseCallback",
+      "android/support/v4/print/PrintHelper$ColorMode": "androidx/print/PrintHelper$ColorMode",
+      "android/support/annotation/RequiresPermission$Write": "androidx/annotation/RequiresPermission$Write",
+      "android/support/v7/widget/FastScroller$AnimatorUpdater": "androidx/widget/FastScroller$AnimatorUpdater",
+      "android/support/multidex/MultiDex$V14": "androidx/multidex/MultiDex$V14",
+      "android/support/v17/leanback/app/BrowseSupportFragment$MainFragmentAdapterRegistry": "androidx/leanback/app/BrowseSupportFragment$MainFragmentAdapterRegistry",
+      "android/support/v7/preference/PreferenceDataStore": "androidx/preference/PreferenceDataStore",
+      "android/support/v17/leanback/app/DetailsBackgroundVideoHelper$PlaybackControlStateCallback": "androidx/leanback/app/DetailsBackgroundVideoHelper$PlaybackControlStateCallback",
+      "android/support/v4/content/LocalBroadcastManager": "androidx/content/LocalBroadcastManager",
+      "android/support/v7/view/ActionBarPolicy": "androidx/view/ActionBarPolicy",
+      "android/support/v4/content/Loader$OnLoadCanceledListener": "androidx/content/Loader$OnLoadCanceledListener",
+      "android/support/v4/view/GestureDetectorCompat$GestureDetectorCompatImplBase": "androidx/view/GestureDetectorCompat$GestureDetectorCompatImplBase",
+      "android/support/v4/app/SharedElementCallback$OnSharedElementsReadyListener": "androidx/app/SharedElementCallback$OnSharedElementsReadyListener",
+      "android/support/v7/app/MediaRouteVolumeSlider": "androidx/app/MediaRouteVolumeSlider",
+      "android/support/multidex/MultiDex$V19": "androidx/multidex/MultiDex$V19",
+      "android/support/v17/leanback/widget/StaggeredGridDefault": "androidx/leanback/widget/StaggeredGridDefault",
+      "android/support/v17/leanback/widget/GuidedActionAdapter$ActionOnKeyListener": "androidx/leanback/widget/GuidedActionAdapter$ActionOnKeyListener",
+      "android/support/design/widget/BottomNavigationView$OnNavigationItemReselectedListener": "androidx/design/widget/BottomNavigationView$OnNavigationItemReselectedListener",
+      "android/support/app/recommendation/ContentRecommendation$ContentPricing": "androidx/app/recommendation/ContentRecommendation$ContentPricing",
+      "android/support/v17/leanback/widget/BaseCardView$LayoutParams": "androidx/leanback/widget/BaseCardView$LayoutParams",
+      "android/support/text/emoji/MetadataListReader": "androidx/text/emoji/MetadataListReader",
+      "android/support/v7/appcompat/R$drawable": "androidx/appcompat/R$drawable",
+      "android/support/design/widget/BaseTransientBottomBar$OnAttachStateChangeListener": "androidx/design/widget/BaseTransientBottomBar$OnAttachStateChangeListener",
+      "android/support/v4/view/GravityCompat": "androidx/view/GravityCompat",
+      "android/support/v7/view/menu/SubMenuWrapperICS": "androidx/view/menu/SubMenuWrapperICS",
+      "android/support/graphics/drawable/VectorDrawableCompat": "androidx/graphics/drawable/VectorDrawableCompat",
+      "android/support/v4/view/AsyncLayoutInflater$InflateRequest": "androidx/view/AsyncLayoutInflater$InflateRequest",
+      "android/support/v17/leanback/app/HeadersFragment$NoOverlappingFrameLayout": "androidx/leanback/app/HeadersFragment$NoOverlappingFrameLayout",
+      "android/support/v17/leanback/widget/ShadowHelperJbmr2": "androidx/leanback/widget/ShadowHelperJbmr2",
+      "android/support/design/internal/ScrimInsetsFrameLayout": "androidx/design/internal/ScrimInsetsFrameLayout",
+      "android/support/v17/leanback/widget/HorizontalHoverCardSwitcher": "androidx/leanback/widget/HorizontalHoverCardSwitcher",
+      "android/support/v7/widget/RecyclerView$Recycler": "androidx/widget/RecyclerView$Recycler",
+      "android/support/v7/widget/FastScroller$State": "androidx/widget/FastScroller$State",
+      "android/support/v7/media/SystemMediaRouteProvider$JellybeanMr2Impl": "androidx/media/SystemMediaRouteProvider$JellybeanMr2Impl",
+      "android/support/wear/R$id": "androidx/wear/R$id",
+      "android/support/v17/leanback/widget/FocusHighlight": "androidx/leanback/widget/FocusHighlight",
+      "android/support/v7/view/menu/ExpandedMenuView": "androidx/view/menu/ExpandedMenuView",
+      "android/support/graphics/drawable/PathInterpolatorCompat": "androidx/graphics/drawable/PathInterpolatorCompat",
+      "android/support/v17/leanback/widget/MediaItemActionPresenter": "androidx/leanback/widget/MediaItemActionPresenter",
+      "android/support/v4/content/Loader$OnLoadCompleteListener": "androidx/content/Loader$OnLoadCompleteListener",
+      "android/support/v17/leanback/widget/NonOverlappingLinearLayout": "androidx/leanback/widget/NonOverlappingLinearLayout",
+      "android/support/v7/view/menu/MenuItemWrapperICS$CollapsibleActionViewWrapper": "androidx/view/menu/MenuItemWrapperICS$CollapsibleActionViewWrapper",
+      "android/support/v4/widget/SlidingPaneLayout$SlidingPanelLayoutImplJB": "androidx/widget/SlidingPaneLayout$SlidingPanelLayoutImplJB",
+      "android/support/v17/leanback/app/ErrorFragment": "androidx/leanback/app/ErrorFragment",
+      "android/support/v7/view/menu/MenuItemWrapperICS$OnActionExpandListenerWrapper": "androidx/view/menu/MenuItemWrapperICS$OnActionExpandListenerWrapper",
+      "android/support/v7/app/AppCompatActivity": "androidx/app/AppCompatActivity",
+      "android/support/v17/preference/LeanbackSettingsFragment$DummyFragment": "androidx/leanback/preference/LeanbackSettingsFragment$DummyFragment",
+      "android/support/app/recommendation/ContentRecommendation$IntentType": "androidx/app/recommendation/ContentRecommendation$IntentType",
+      "android/support/v17/leanback/widget/PlaybackControlsRowView": "androidx/leanback/widget/PlaybackControlsRowView",
+      "android/support/v4/text/TextDirectionHeuristicsCompat$TextDirectionHeuristicLocale": "androidx/text/TextDirectionHeuristicsCompat$TextDirectionHeuristicLocale",
+      "android/support/v4/util/SparseArrayCompat": "androidx/util/SparseArrayCompat",
+      "android/support/v4/net/TrafficStatsCompat": "androidx/net/TrafficStatsCompat",
+      "android/support/design/widget/TabLayout$Tab": "androidx/design/widget/TabLayout$Tab",
+      "android/support/v7/view/CollapsibleActionView": "androidx/view/CollapsibleActionView",
+      "android/support/v17/leanback/R$id": "androidx/leanback/R$id",
+      "android/support/v4/content/MimeTypeFilter": "androidx/content/MimeTypeFilter",
+      "android/support/media/tv/TvContractCompat$WatchNextPrograms$WatchNextType": "androidx/media/tv/TvContractCompat$WatchNextPrograms$WatchNextType",
+      "android/support/transition/ViewUtilsImpl": "androidx/transition/ViewUtilsImpl",
+      "android/support/v17/leanback/app/DetailsFragment$SetSelectionRunnable": "androidx/leanback/app/DetailsFragment$SetSelectionRunnable",
+      "android/support/v7/app/MediaRouteControllerDialog$ClickListener": "androidx/app/MediaRouteControllerDialog$ClickListener",
+      "android/support/design/widget/VisibilityAwareImageButton": "androidx/design/widget/VisibilityAwareImageButton",
+      "android/support/transition/ViewGroupUtilsImpl": "androidx/transition/ViewGroupUtilsImpl",
+      "android/support/design/internal/package-info": "androidx/design/internal/package-info",
+      "android/support/v7/widget/SearchView$SearchAutoComplete": "androidx/widget/SearchView$SearchAutoComplete",
+      "android/support/v17/leanback/widget/GuidedActionAdapterGroup": "androidx/leanback/widget/GuidedActionAdapterGroup",
+      "android/support/v7/widget/AppCompatBackgroundHelper": "androidx/widget/AppCompatBackgroundHelper",
+      "android/support/v4/widget/EdgeEffectCompat$EdgeEffectApi21Impl": "androidx/widget/EdgeEffectCompat$EdgeEffectApi21Impl",
+      "android/support/v17/leanback/R$fraction": "androidx/leanback/R$fraction",
+      "android/support/wear/widget/drawer/WearableActionDrawerMenu": "androidx/wear/widget/drawer/WearableActionDrawerMenu",
+      "android/support/v4/content/IntentCompat": "androidx/content/IntentCompat",
+      "android/support/compat/R": "androidx/compat/R",
+      "android/support/v7/widget/RecyclerView$ViewCacheExtension": "androidx/widget/RecyclerView$ViewCacheExtension",
+      "android/support/v7/widget/DecorContentParent": "androidx/widget/DecorContentParent",
+      "android/support/design/widget/BaseTransientBottomBar$Behavior": "androidx/design/widget/BaseTransientBottomBar$Behavior",
+      "android/support/design/widget/TabLayout$ViewPagerOnTabSelectedListener": "androidx/design/widget/TabLayout$ViewPagerOnTabSelectedListener",
+      "android/support/v7/preference/EditTextPreference$SavedState": "androidx/preference/EditTextPreference$SavedState",
+      "android/support/design/widget/ShadowViewDelegate": "androidx/design/widget/ShadowViewDelegate",
+      "android/support/v7/cardview/BuildConfig": "androidx/cardview/BuildConfig",
+      "android/support/v7/app/AlertController$ButtonHandler": "androidx/app/AlertController$ButtonHandler",
+      "android/support/v4/widget/AutoScrollHelper$ScrollAnimationRunnable": "androidx/widget/AutoScrollHelper$ScrollAnimationRunnable",
+      "android/support/v7/view/menu/ShowableListMenu": "androidx/view/menu/ShowableListMenu",
+      "android/support/v17/leanback/widget/DetailsOverviewSharedElementHelper": "androidx/leanback/widget/DetailsOverviewSharedElementHelper",
+      "android/support/v7/widget/MenuPopupWindow$MenuDropDownListView": "androidx/widget/MenuPopupWindow$MenuDropDownListView",
+      "android/support/v17/leanback/transition/LeanbackTransitionHelper": "androidx/leanback/transition/LeanbackTransitionHelper",
+      "android/support/design/widget/BottomSheetDialogFragment": "androidx/design/widget/BottomSheetDialogFragment",
+      "android/support/v4/app/NotificationCompat$Extender": "androidx/app/NotificationCompat$Extender",
+      "android/support/v4/text/TextDirectionHeuristicsCompat$TextDirectionHeuristicInternal": "androidx/text/TextDirectionHeuristicsCompat$TextDirectionHeuristicInternal",
+      "android/support/wear/widget/SwipeDismissLayout$OnPreSwipeListener": "androidx/wear/widget/SwipeDismissLayout$OnPreSwipeListener",
+      "android/support/v4/graphics/BitmapCompat$BitmapCompatApi18Impl": "androidx/graphics/BitmapCompat$BitmapCompatApi18Impl",
+      "android/support/v4/view/ViewCompat$AutofillImportance": "androidx/view/ViewCompat$AutofillImportance",
+      "android/support/v4/app/AppOpsManagerCompat": "androidx/app/AppOpsManagerCompat",
+      "android/support/annotation/FractionRes": "androidx/annotation/FractionRes",
+      "android/support/media/instantvideo/widget/InstantVideoView": "androidx/media/instantvideo/widget/InstantVideoView",
+      "android/support/design/internal/NavigationMenuPresenter$NormalViewHolder": "androidx/design/internal/NavigationMenuPresenter$NormalViewHolder",
+      "android/support/v4/view/accessibility/AccessibilityRecordCompat": "androidx/view/accessibility/AccessibilityRecordCompat",
+      "android/support/v7/view/menu/MenuWrapperFactory": "androidx/view/menu/MenuWrapperFactory",
+      "android/support/v17/leanback/app/HeadersSupportFragment$NoOverlappingFrameLayout": "androidx/leanback/app/HeadersSupportFragment$NoOverlappingFrameLayout",
+      "android/support/v7/widget/ScrollingTabContainerView$VisibilityAnimListener": "androidx/widget/ScrollingTabContainerView$VisibilityAnimListener",
+      "android/support/v7/internal/package-info": "androidx/internal/package-info",
+      "android/support/v4/media/MediaBrowserServiceCompatApi26$ResultWrapper": "androidx/media/MediaBrowserServiceCompatApi26$ResultWrapper",
+      "android/support/v4/media/MediaBrowserServiceCompat$ResultFlags": "androidx/media/MediaBrowserServiceCompat$ResultFlags",
+      "android/support/v4/media/session/MediaControllerCompat$MediaControllerImplApi21$ExtraBinderRequestResultReceiver": "androidx/media/session/MediaControllerCompat$MediaControllerImplApi21$ExtraBinderRequestResultReceiver",
+      "android/support/v17/leanback/widget/OnChildSelectedListener": "androidx/leanback/widget/OnChildSelectedListener",
+      "android/support/v7/mediarouter/BuildConfig": "androidx/mediarouter/BuildConfig",
+      "android/support/v4/os/LocaleListHelper": "androidx/os/LocaleListHelper",
+      "android/support/v7/widget/ChildHelper": "androidx/widget/ChildHelper",
+      "android/support/v17/leanback/widget/BaseOnItemViewClickedListener": "androidx/leanback/widget/BaseOnItemViewClickedListener",
+      "android/support/v17/leanback/widget/VerticalGridPresenter": "androidx/leanback/widget/VerticalGridPresenter",
+      "android/support/v7/widget/Toolbar$LayoutParams": "androidx/widget/Toolbar$LayoutParams",
+      "android/support/v4/provider/SelfDestructiveThread": "androidx/provider/SelfDestructiveThread",
+      "android/support/v17/leanback/widget/DetailsParallax": "androidx/leanback/widget/DetailsParallax",
+      "android/support/v4/content/pm/ActivityInfoCompat": "androidx/content/pm/ActivityInfoCompat",
+      "android/support/percent/PercentFrameLayout$LayoutParams": "androidx/PercentFrameLayout$LayoutParams",
+      "android/support/v7/widget/RecyclerView$ItemAnimator$ItemAnimatorFinishedListener": "androidx/widget/RecyclerView$ItemAnimator$ItemAnimatorFinishedListener",
+      "android/support/v4/media/MediaBrowserCompat$ConnectionCallback": "androidx/media/MediaBrowserCompat$ConnectionCallback",
+      "android/support/design/widget/ThemeUtils": "androidx/design/widget/ThemeUtils",
+      "android/support/wear/widget/drawer/RecyclerViewFlingWatcher": "androidx/wear/widget/drawer/RecyclerViewFlingWatcher",
+      "android/support/v4/app/SupportActivity": "androidx/app/SupportActivity",
+      "android/support/transition/TransitionUtils": "androidx/transition/TransitionUtils",
+      "android/support/v4/graphics/TypefaceCompatApi21Impl": "androidx/graphics/TypefaceCompatApi21Impl",
+      "android/support/v7/media/MediaRouter$CallbackRecord": "androidx/media/MediaRouter$CallbackRecord",
+      "android/support/wear/widget/drawer/NestedScrollViewFlingWatcher": "androidx/wear/widget/drawer/NestedScrollViewFlingWatcher",
+      "android/support/v4/graphics/drawable/TintAwareDrawable": "androidx/graphics/drawable/TintAwareDrawable",
+      "android/support/text/emoji/widget/EmojiTextViewHelper$HelperInternal": "androidx/text/emoji/widget/EmojiTextViewHelper$HelperInternal",
+      "android/support/transition/Styleable": "androidx/transition/Styleable",
+      "android/support/v17/leanback/widget/BaseCardView": "androidx/leanback/widget/BaseCardView",
+      "android/support/v17/leanback/app/DetailsSupportFragment": "androidx/leanback/app/DetailsSupportFragment",
+      "android/support/design/internal/NavigationMenuPresenter$NavigationMenuSeparatorItem": "androidx/design/internal/NavigationMenuPresenter$NavigationMenuSeparatorItem",
+      "android/support/app/recommendation/ContentRecommendation$IntentData": "androidx/app/recommendation/ContentRecommendation$IntentData",
+      "android/support/wear/widget/drawer/WearableDrawerLayout$DrawerStateCallback": "androidx/wear/widget/drawer/WearableDrawerLayout$DrawerStateCallback",
+      "android/support/wear/widget/CircularProgressLayoutController$CircularProgressTimer": "androidx/wear/widget/CircularProgressLayoutController$CircularProgressTimer",
+      "android/support/v4/widget/CircleImageView": "androidx/widget/CircleImageView",
+      "android/support/v17/leanback/app/BaseRowFragment": "androidx/leanback/app/BaseRowFragment",
+      "android/support/text/emoji/EmojiCompat$ReplaceStrategy": "androidx/text/emoji/EmojiCompat$ReplaceStrategy",
+      "android/support/v7/widget/ViewInfoStore$InfoRecord": "androidx/widget/ViewInfoStore$InfoRecord",
+      "android/support/transition/TransitionInflater": "androidx/transition/TransitionInflater",
+      "android/support/v17/leanback/media/PlaybackControlGlue$UpdatePlaybackStateHandler": "androidx/leanback/media/PlaybackControlGlue$UpdatePlaybackStateHandler",
+      "android/support/v7/widget/RecyclerView$State$LayoutState": "androidx/widget/RecyclerView$State$LayoutState",
+      "android/support/v17/leanback/widget/RoundedRectHelper": "androidx/leanback/widget/RoundedRectHelper",
+      "android/support/v4/app/ServiceCompat": "androidx/app/ServiceCompat",
+      "android/support/percent/PercentRelativeLayout$LayoutParams": "androidx/PercentRelativeLayout$LayoutParams",
+      "android/support/v17/leanback/widget/RowHeaderPresenter": "androidx/leanback/widget/RowHeaderPresenter",
+      "android/support/v7/widget/SearchView$UpdatableTouchDelegate": "androidx/widget/SearchView$UpdatableTouchDelegate",
+      "android/support/media/tv/TvContractCompat$PreviewProgramColumns$AspectRatio": "androidx/media/tv/TvContractCompat$PreviewProgramColumns$AspectRatio",
+      "android/support/text/emoji/EmojiMetadata$HasGlyph": "androidx/text/emoji/EmojiMetadata$HasGlyph",
+      "android/support/media/tv/BasePreviewProgram$Builder": "androidx/media/tv/BasePreviewProgram$Builder",
+      "android/support/multidex/ZipUtil$CentralDirectory": "androidx/multidex/ZipUtil$CentralDirectory",
+      "android/support/v7/widget/helper/ItemTouchUIUtil": "androidx/widget/helper/ItemTouchUIUtil",
+      "android/support/v4/widget/DrawerLayout$ChildAccessibilityDelegate": "androidx/widget/DrawerLayout$ChildAccessibilityDelegate",
+      "android/support/v7/media/SystemMediaRouteProvider": "androidx/media/SystemMediaRouteProvider",
+      "android/support/v4/app/LoaderManagerImpl": "androidx/app/LoaderManagerImpl",
+      "android/support/media/ExifInterface$IfdType": "androidx/media/ExifInterface$IfdType",
+      "android/support/v4/widget/CompoundButtonCompat$CompoundButtonCompatBaseImpl": "androidx/widget/CompoundButtonCompat$CompoundButtonCompatBaseImpl",
+      "android/support/v4/graphics/drawable/DrawableWrapperApi14": "androidx/graphics/drawable/DrawableWrapperApi14",
+      "android/support/wear/widget/drawer/WearableDrawerLayout$BottomDrawerDraggerCallback": "androidx/wear/widget/drawer/WearableDrawerLayout$BottomDrawerDraggerCallback",
+      "android/support/v4/view/TintableBackgroundView": "androidx/view/TintableBackgroundView",
+      "android/support/v17/leanback/graphics/BoundsRule": "androidx/leanback/graphics/BoundsRule",
+      "android/support/v17/leanback/widget/RecyclerViewParallax$ChildPositionProperty": "androidx/leanback/widget/RecyclerViewParallax$ChildPositionProperty",
+      "android/support/v4/text/TextDirectionHeuristicsCompat$TextDirectionHeuristicImpl": "androidx/text/TextDirectionHeuristicsCompat$TextDirectionHeuristicImpl",
+      "android/support/v17/leanback/widget/PlaybackControlsPresenter": "androidx/leanback/widget/PlaybackControlsPresenter",
+      "android/support/v7/app/ActionBarDrawerToggle": "androidx/app/ActionBarDrawerToggle",
+      "android/support/design/internal/BaselineLayout": "androidx/design/internal/BaselineLayout",
+      "android/support/v7/preference/PreferenceScreen": "androidx/preference/PreferenceScreen",
+      "android/support/v4/graphics/drawable/DrawableWrapperApi19": "androidx/graphics/drawable/DrawableWrapperApi19",
+      "android/support/v17/leanback/app/DetailsFragment": "androidx/leanback/app/DetailsFragment",
+      "android/support/annotation/ColorLong": "androidx/annotation/ColorLong",
+      "android/support/annotation/IntRange": "androidx/annotation/IntRange",
+      "android/support/v7/widget/LinearLayoutCompat$DividerMode": "androidx/widget/LinearLayoutCompat$DividerMode",
+      "android/support/annotation/RestrictTo": "androidx/annotation/RestrictTo",
+      "android/support/v4/media/session/PlaybackStateCompat": "androidx/media/session/PlaybackStateCompat",
+      "android/support/v17/leanback/widget/RecyclerViewParallax": "androidx/leanback/widget/RecyclerViewParallax",
+      "android/support/v7/widget/ChildHelper$Callback": "androidx/widget/ChildHelper$Callback",
+      "android/support/v17/leanback/widget/DetailsOverviewLogoPresenter": "androidx/leanback/widget/DetailsOverviewLogoPresenter",
+      "android/support/constraint/Guideline": "androidx/constraint/Guideline",
+      "android/support/design/widget/CircularBorderDrawable": "androidx/design/widget/CircularBorderDrawable",
+      "android/support/text/emoji/widget/EmojiInputConnection": "androidx/text/emoji/widget/EmojiInputConnection",
+      "android/support/v4/widget/ImageViewCompat$ImageViewCompatImpl": "androidx/widget/ImageViewCompat$ImageViewCompatImpl",
+      "android/support/v17/leanback/widget/ItemBridgeAdapterShadowOverlayWrapper": "androidx/leanback/widget/ItemBridgeAdapterShadowOverlayWrapper",
+      "android/support/v7/view/menu/MenuBuilder": "androidx/view/menu/MenuBuilder",
+      "android/support/v7/media/MediaRouteDiscoveryRequest": "androidx/media/MediaRouteDiscoveryRequest",
+      "android/support/design/widget/AppBarLayout$ScrollingViewBehavior": "androidx/design/widget/AppBarLayout$ScrollingViewBehavior",
+      "android/support/v17/leanback/widget/PersistentFocusWrapper$SavedState": "androidx/leanback/widget/PersistentFocusWrapper$SavedState",
+      "android/support/v7/widget/RecyclerView$ItemAnimator$ItemAnimatorListener": "androidx/widget/RecyclerView$ItemAnimator$ItemAnimatorListener",
+      "android/support/v17/leanback/media/PlaybackBannerControlGlue": "androidx/leanback/media/PlaybackBannerControlGlue",
+      "android/support/design/internal/NavigationMenuPresenter$ViewHolder": "androidx/design/internal/NavigationMenuPresenter$ViewHolder",
+      "android/support/v7/widget/AppCompatDrawableManager$VdcInflateDelegate": "androidx/widget/AppCompatDrawableManager$VdcInflateDelegate",
+      "android/support/graphics/drawable/AndroidResources": "androidx/graphics/drawable/AndroidResources",
+      "android/support/v7/widget/RecyclerView$RecyclerViewDataObserver": "androidx/widget/RecyclerView$RecyclerViewDataObserver",
+      "android/support/design/R$integer": "androidx/design/R$integer",
+      "android/support/v4/app/AppLaunchChecker": "androidx/app/AppLaunchChecker",
+      "android/support/design/BuildConfig": "androidx/design/BuildConfig",
+      "android/support/v17/leanback/transition/ParallaxTransition": "androidx/leanback/transition/ParallaxTransition",
+      "android/support/text/emoji/widget/SpannableBuilder": "androidx/text/emoji/widget/SpannableBuilder",
+      "android/support/text/emoji/widget/ExtractButtonCompat": "androidx/text/emoji/widget/ExtractButtonCompat",
+      "android/support/v17/leanback/app/ListRowDataAdapter$QueueBasedDataObserver": "androidx/leanback/app/ListRowDataAdapter$QueueBasedDataObserver",
+      "android/support/content/ContentPager$QueryRunner": "androidx/content/ContentPager$QueryRunner",
+      "android/support/v4/app/FragmentTabHost$SavedState": "androidx/app/FragmentTabHost$SavedState",
+      "android/support/v7/widget/OpReorderer$Callback": "androidx/widget/OpReorderer$Callback",
+      "android/support/multidex/MultiDexExtractor$ExtractedDex": "androidx/multidex/MultiDexExtractor$ExtractedDex",
+      "android/support/design/widget/SwipeDismissBehavior": "androidx/design/widget/SwipeDismissBehavior",
+      "android/support/v4/content/FileProvider": "androidx/content/FileProvider",
+      "android/support/v17/leanback/app/BrowseFragment$BrowseTransitionListener": "androidx/leanback/app/BrowseFragment$BrowseTransitionListener",
+      "android/support/design/widget/ViewOffsetBehavior": "androidx/design/widget/ViewOffsetBehavior",
+      "android/support/v17/leanback/app/ListRowDataAdapter$SimpleDataObserver": "androidx/leanback/app/ListRowDataAdapter$SimpleDataObserver",
+      "android/support/v4/accessibilityservice/AccessibilityServiceInfoCompat": "androidx/accessibilityservice/AccessibilityServiceInfoCompat",
+      "android/support/v7/widget/ScrollbarHelper": "androidx/widget/ScrollbarHelper",
+      "android/support/v17/leanback/widget/BaseCardView$InfoAlphaAnimation": "androidx/leanback/widget/BaseCardView$InfoAlphaAnimation",
+      "android/support/v17/leanback/widget/PlaybackControlsRow$RepeatAction": "androidx/leanback/widget/PlaybackControlsRow$RepeatAction",
+      "android/support/v7/widget/LinearSnapHelper": "androidx/widget/LinearSnapHelper",
+      "android/support/v7/view/menu/BaseWrapper": "androidx/view/menu/BaseWrapper",
+      "android/support/wear/utils/MetadataConstants": "androidx/wear/utils/MetadataConstants",
+      "android/support/v7/widget/RecyclerView$ItemAnimator": "androidx/widget/RecyclerView$ItemAnimator",
+      "android/support/v17/leanback/media/PlaybackGlueHost$HostCallback": "androidx/leanback/media/PlaybackGlueHost$HostCallback",
+      "android/support/v7/widget/ActionMenuPresenter$ActionMenuPopupCallback": "androidx/widget/ActionMenuPresenter$ActionMenuPopupCallback",
+      "android/support/v7/widget/GridLayout$Interval": "androidx/widget/GridLayout$Interval",
+      "android/support/v17/leanback/widget/GuidedActionsStylist$ViewHolder": "androidx/leanback/widget/GuidedActionsStylist$ViewHolder",
+      "android/support/v4/app/Fragment$InstantiationException": "androidx/app/Fragment$InstantiationException",
+      "android/support/v14/preference/EditTextPreferenceDialogFragment": "androidx/preference/EditTextPreferenceDialogFragment",
+      "android/support/text/emoji/EmojiCompat$CompatInternal": "androidx/text/emoji/EmojiCompat$CompatInternal",
+      "android/support/v14/preference/BuildConfig": "androidx/preference/BuildConfig",
+      "android/support/v4/app/FragmentManagerNonConfig": "androidx/app/FragmentManagerNonConfig",
+      "android/support/v7/media/MediaRouter$GlobalMediaRouter": "androidx/media/MediaRouter$GlobalMediaRouter",
+      "android/support/transition/Styleable$TransitionManager": "androidx/transition/Styleable$TransitionManager",
+      "android/support/v4/content/res/ConfigurationHelper": "androidx/content/res/ConfigurationHelper",
+      "android/support/v7/app/MediaRouteDiscoveryFragment": "androidx/app/MediaRouteDiscoveryFragment",
+      "android/support/v7/media/MediaRouteSelector": "androidx/media/MediaRouteSelector",
+      "android/support/v4/media/session/MediaControllerCompat$MediaControllerImplApi21": "androidx/media/session/MediaControllerCompat$MediaControllerImplApi21",
+      "android/support/v17/leanback/widget/ControlBar": "androidx/leanback/widget/ControlBar",
+      "android/support/v4/media/session/MediaControllerCompat$MediaControllerImplApi24": "androidx/media/session/MediaControllerCompat$MediaControllerImplApi24",
+      "android/support/app/recommendation/ContentRecommendation$ContentStatus": "androidx/app/recommendation/ContentRecommendation$ContentStatus",
+      "android/support/v4/media/session/MediaControllerCompat$MediaControllerImplApi23": "androidx/media/session/MediaControllerCompat$MediaControllerImplApi23",
+      "android/support/v4/media/MediaMetadataCompat": "androidx/media/MediaMetadataCompat",
+      "android/support/v4/util/CircularIntArray": "androidx/util/CircularIntArray",
+      "android/support/transition/Slide$CalculateSlide": "androidx/transition/Slide$CalculateSlide",
+      "android/support/transition/GhostViewUtils": "androidx/transition/GhostViewUtils",
+      "android/support/design/widget/NavigationView$OnNavigationItemSelectedListener": "androidx/design/widget/NavigationView$OnNavigationItemSelectedListener",
+      "android/support/v4/hardware/fingerprint/FingerprintManagerCompat$CryptoObject": "androidx/hardware/fingerprint/FingerprintManagerCompat$CryptoObject",
+      "android/support/media/tv/TvContractCompat$Programs": "androidx/media/tv/TvContractCompat$Programs",
+      "android/support/v4/provider/FontsContractCompat": "androidx/provider/FontsContractCompat",
+      "android/support/v4/content/res/FontResourcesParserCompat$ProviderResourceEntry": "androidx/content/res/FontResourcesParserCompat$ProviderResourceEntry",
+      "android/support/v17/leanback/widget/BaseGridView": "androidx/leanback/widget/BaseGridView",
+      "android/support/design/R": "androidx/design/R",
+      "android/support/annotation/GuardedBy": "androidx/annotation/GuardedBy",
+      "android/support/v17/leanback/widget/ListRowPresenter$ViewHolder": "androidx/leanback/widget/ListRowPresenter$ViewHolder",
+      "android/support/constraint/solver/widgets/ConstraintWidgetContainer": "androidx/constraint/solver/widgets/ConstraintWidgetContainer",
+      "android/support/wear/widget/drawer/WearableDrawerView$DrawerState": "androidx/wear/widget/drawer/WearableDrawerView$DrawerState",
+      "android/support/v7/widget/LinearLayoutManager$SavedState": "androidx/widget/LinearLayoutManager$SavedState",
+      "android/support/v4/util/LongSparseArray": "androidx/util/LongSparseArray",
+      "android/support/media/tv/TvContractCompat$ProgramColumns$ReviewRatingStyle": "androidx/media/tv/TvContractCompat$ProgramColumns$ReviewRatingStyle",
+      "android/support/v7/media/MediaRouter$GlobalMediaRouter$RemoteControlClientRecord": "androidx/media/MediaRouter$GlobalMediaRouter$RemoteControlClientRecord",
+      "android/support/v7/widget/TintResources": "androidx/widget/TintResources",
+      "android/support/v7/util/ThreadUtil$MainThreadCallback": "androidx/util/ThreadUtil$MainThreadCallback",
+      "android/support/animation/DynamicAnimation$MassState": "androidx/animation/DynamicAnimation$MassState",
+      "android/support/v4/widget/PopupWindowCompat": "androidx/widget/PopupWindowCompat",
+      "android/support/design/widget/FloatingActionButton$Behavior": "androidx/design/widget/FloatingActionButton$Behavior",
+      "android/support/v7/preference/PreferenceFragmentCompat$DividerDecoration": "androidx/preference/PreferenceFragmentCompat$DividerDecoration",
+      "android/support/transition/Styleable$PatternPathMotion": "androidx/transition/Styleable$PatternPathMotion",
+      "android/support/v4/media/session/MediaSessionCompat$Callback$CallbackHandler": "androidx/media/session/MediaSessionCompat$Callback$CallbackHandler",
+      "android/support/v4/app/INotificationSideChannel": "androidx/app/INotificationSideChannel",
+      "android/support/media/tv/TvContractCompat$Channels$VideoFormat": "androidx/media/tv/TvContractCompat$Channels$VideoFormat",
+      "android/support/v17/preference/LeanbackSettingsFragment$RootViewOnKeyListener": "androidx/leanback/preference/LeanbackSettingsFragment$RootViewOnKeyListener",
+      "android/support/v7/widget/ActionMenuView$MenuBuilderCallback": "androidx/widget/ActionMenuView$MenuBuilderCallback",
+      "android/support/design/widget/TabLayout$Mode": "androidx/design/widget/TabLayout$Mode",
+      "android/support/transition/ChangeTransform$Transforms": "androidx/transition/ChangeTransform$Transforms",
+      "android/support/v4/media/MediaBrowserServiceCompatApi21$ResultWrapper": "androidx/media/MediaBrowserServiceCompatApi21$ResultWrapper",
+      "android/support/v17/leanback/transition/TranslationAnimationCreator$TransitionPositionListener": "androidx/leanback/transition/TranslationAnimationCreator$TransitionPositionListener",
+      "android/support/text/emoji/widget/EmojiEditText": "androidx/text/emoji/widget/EmojiEditText",
+      "android/support/wear/R$styleable": "androidx/wear/R$styleable",
+      "android/support/v17/leanback/app/BrowseSupportFragment$FragmentHost": "androidx/leanback/app/BrowseSupportFragment$FragmentHost",
+      "android/support/transition/Scene": "androidx/transition/Scene",
+      "android/support/text/emoji/widget/EmojiTransformationMethod": "androidx/text/emoji/widget/EmojiTransformationMethod",
+      "android/support/v4/view/ViewCompat$ResolvedLayoutDirectionMode": "androidx/view/ViewCompat$ResolvedLayoutDirectionMode",
+      "android/support/v7/appcompat/R": "androidx/appcompat/R",
+      "android/support/v4/media/session/MediaSessionCompatApi24$Callback": "androidx/media/session/MediaSessionCompatApi24$Callback",
+      "android/support/v7/widget/RecyclerView": "androidx/widget/RecyclerView",
+      "android/support/v4/view/accessibility/AccessibilityManagerCompat$AccessibilityStateChangeListenerCompat": "androidx/view/accessibility/AccessibilityManagerCompat$AccessibilityStateChangeListenerCompat",
+      "android/support/v17/leanback/widget/FragmentAnimationProvider": "androidx/leanback/widget/FragmentAnimationProvider",
+      "android/support/v7/widget/ActivityChooserModel": "androidx/widget/ActivityChooserModel",
+      "android/support/media/tv/TvContractCompat$PreviewProgramColumns$InteractionType": "androidx/media/tv/TvContractCompat$PreviewProgramColumns$InteractionType",
+      "android/support/transition/Transition$TransitionListener": "androidx/transition/Transition$TransitionListener",
+      "android/support/v17/leanback/widget/PlaybackControlsRowPresenter$ViewHolder": "androidx/leanback/widget/PlaybackControlsRowPresenter$ViewHolder",
+      "android/support/design/widget/CoordinatorLayout": "androidx/widget/CoordinatorLayout",
+      "android/support/content/Query": "androidx/content/Query",
+      "android/support/v17/leanback/app/BackgroundManager$EmptyDrawable": "androidx/leanback/app/BackgroundManager$EmptyDrawable",
+      "android/support/v17/leanback/app/PlaybackSupportFragment$OnFadeCompleteListener": "androidx/leanback/app/PlaybackSupportFragment$OnFadeCompleteListener",
+      "android/support/v17/leanback/app/BrowseSupportFragment$MainFragmentAdapter": "androidx/leanback/app/BrowseSupportFragment$MainFragmentAdapter",
+      "android/support/v4/media/MediaBrowserCompat$ItemReceiver": "androidx/media/MediaBrowserCompat$ItemReceiver",
+      "android/support/transition/Styleable$VisibilityTransition": "androidx/transition/Styleable$VisibilityTransition",
+      "android/support/transition/TransitionValues": "androidx/transition/TransitionValues",
+      "android/support/v4/content/res/FontResourcesParserCompat$FamilyResourceEntry": "androidx/content/res/FontResourcesParserCompat$FamilyResourceEntry",
+      "android/support/v17/leanback/app/RowsSupportFragment$MainFragmentAdapter": "androidx/leanback/app/RowsSupportFragment$MainFragmentAdapter",
+      "android/support/v17/leanback/widget/Visibility": "androidx/leanback/widget/Visibility",
+      "android/support/design/widget/AppBarLayout$LayoutParams": "androidx/design/widget/AppBarLayout$LayoutParams",
+      "android/support/v4/media/MediaBrowserCompat$SubscriptionCallback$StubApi26": "androidx/media/MediaBrowserCompat$SubscriptionCallback$StubApi26",
+      "android/support/v4/widget/AutoScrollHelper$ClampedScroller": "androidx/widget/AutoScrollHelper$ClampedScroller",
+      "android/support/v7/preference/PreferenceFragmentCompat$OnPreferenceStartScreenCallback": "androidx/preference/PreferenceFragmentCompat$OnPreferenceStartScreenCallback",
+      "android/support/v7/graphics/Palette$Builder": "androidx/graphics/Palette$Builder",
+      "android/support/v4/view/accessibility/AccessibilityNodeInfoCompat$CollectionItemInfoCompat": "androidx/view/accessibility/AccessibilityNodeInfoCompat$CollectionItemInfoCompat",
+      "android/support/design/widget/StateListAnimator$Tuple": "androidx/design/widget/StateListAnimator$Tuple",
+      "android/support/v4/view/accessibility/AccessibilityManagerCompat$TouchExplorationStateChangeListener": "androidx/view/accessibility/AccessibilityManagerCompat$TouchExplorationStateChangeListener",
+      "android/support/text/emoji/EmojiCompat$SpanFactory": "androidx/text/emoji/EmojiCompat$SpanFactory",
+      "android/support/media/tv/TvContractCompat$Channels$Type": "androidx/media/tv/TvContractCompat$Channels$Type",
+      "android/support/v7/widget/GridLayout$LayoutParams": "androidx/widget/GridLayout$LayoutParams",
+      "android/support/v4/media/MediaBrowserCompat$SubscriptionCallback$StubApi21": "androidx/media/MediaBrowserCompat$SubscriptionCallback$StubApi21",
+      "android/support/v17/leanback/widget/WindowAlignment$Axis": "androidx/leanback/widget/WindowAlignment$Axis",
+      "android/support/v17/leanback/widget/ControlBarPresenter": "androidx/leanback/widget/ControlBarPresenter",
+      "android/support/constraint/ConstraintSet": "androidx/constraint/ConstraintSet",
+      "android/support/v17/leanback/transition/TransitionHelperKitkat": "androidx/leanback/transition/TransitionHelperKitkat",
+      "android/support/wear/widget/ScrollManager": "androidx/wear/widget/ScrollManager",
+      "android/support/content/ContentPager$QueryRunner$Callback": "androidx/content/ContentPager$QueryRunner$Callback",
+      "android/support/v4/graphics/TypefaceCompat$TypefaceCompatImpl": "androidx/graphics/TypefaceCompat$TypefaceCompatImpl",
+      "android/support/v7/widget/ContentFrameLayout": "androidx/widget/ContentFrameLayout",
+      "android/support/v4/app/ActionBarDrawerToggle$SetIndicatorInfo": "androidx/app/ActionBarDrawerToggle$SetIndicatorInfo",
+      "android/support/v4/util/TimeUtils": "androidx/util/TimeUtils",
+      "android/support/v7/widget/ActionBarBackgroundDrawableV21": "androidx/widget/ActionBarBackgroundDrawableV21",
+      "android/support/v17/leanback/widget/PlaybackControlsRow$SkipNextAction": "androidx/leanback/widget/PlaybackControlsRow$SkipNextAction",
+      "android/support/design/internal/ParcelableSparseArray": "androidx/design/internal/ParcelableSparseArray",
+      "android/support/media/tv/Channel$Builder": "androidx/media/tv/Channel$Builder",
+      "android/support/v7/widget/AppCompatCompoundButtonHelper": "androidx/widget/AppCompatCompoundButtonHelper",
+      "android/support/v4/media/MediaBrowserCompatApi23$ItemCallback": "androidx/media/MediaBrowserCompatApi23$ItemCallback",
+      "android/support/v4/app/ActionBarDrawerToggle$Delegate": "androidx/app/ActionBarDrawerToggle$Delegate",
+      "android/support/graphics/drawable/AnimationUtilsCompat": "androidx/graphics/drawable/AnimationUtilsCompat",
+      "android/support/v17/leanback/widget/PlaybackControlsRow$ShuffleAction": "androidx/leanback/widget/PlaybackControlsRow$ShuffleAction",
+      "android/support/v4/app/JobIntentService$JobServiceEngineImpl$WrapperWorkItem": "androidx/app/JobIntentService$JobServiceEngineImpl$WrapperWorkItem",
+      "android/support/v7/media/SystemMediaRouteProvider$SyncCallback": "androidx/media/SystemMediaRouteProvider$SyncCallback",
+      "android/support/v7/util/AsyncListUtil$ViewCallback": "androidx/util/AsyncListUtil$ViewCallback",
+      "android/support/v4/view/ViewPager$OnAdapterChangeListener": "androidx/view/ViewPager$OnAdapterChangeListener",
+      "android/support/v7/media/RegisteredMediaRouteProviderWatcher": "androidx/media/RegisteredMediaRouteProviderWatcher",
+      "android/support/transition/Fade$FadeAnimatorListener": "androidx/transition/Fade$FadeAnimatorListener",
+      "android/support/v4/content/res/FontResourcesParserCompat$FetchStrategy": "androidx/content/res/FontResourcesParserCompat$FetchStrategy",
+      "android/support/v17/leanback/graphics/FitWidthBitmapDrawable": "androidx/leanback/graphics/FitWidthBitmapDrawable",
+      "android/support/v7/widget/CardViewDelegate": "androidx/widget/CardViewDelegate",
+      "android/support/v4/media/MediaBrowserCompat$ItemCallback": "androidx/media/MediaBrowserCompat$ItemCallback",
+      "android/support/design/widget/BottomSheetDialog": "androidx/design/widget/BottomSheetDialog",
+      "android/support/v17/leanback/system/Settings$Customizations": "androidx/leanback/system/Settings$Customizations",
+      "android/support/design/widget/FloatingActionButtonImpl$InternalVisibilityChangedListener": "androidx/design/widget/FloatingActionButtonImpl$InternalVisibilityChangedListener",
+      "android/support/v7/media/RemotePlaybackClient$SessionActionCallback": "androidx/media/RemotePlaybackClient$SessionActionCallback",
+      "android/support/wear/ambient/AmbientDelegate$AmbientCallback": "androidx/wear/ambient/AmbientDelegate$AmbientCallback",
+      "android/support/v4/widget/FocusStrategy$BoundsAdapter": "androidx/widget/FocusStrategy$BoundsAdapter",
+      "android/support/v17/leanback/widget/DividerPresenter": "androidx/leanback/widget/DividerPresenter",
+      "android/support/text/emoji/EmojiProcessor$GlyphChecker": "androidx/text/emoji/EmojiProcessor$GlyphChecker",
+      "android/support/v7/preference/PreferenceFragmentCompat": "androidx/preference/PreferenceFragmentCompat",
+      "android/support/v7/widget/TooltipPopup": "androidx/widget/TooltipPopup",
+      "android/support/v4/view/accessibility/AccessibilityNodeProviderCompat": "androidx/view/accessibility/AccessibilityNodeProviderCompat",
+      "android/support/v4/media/MediaBrowserCompat$SearchCallback": "androidx/media/MediaBrowserCompat$SearchCallback",
+      "android/support/v4/media/session/IMediaControllerCallback": "androidx/media/session/IMediaControllerCallback",
+      "android/support/compat/R$styleable": "androidx/compat/R$styleable",
+      "android/support/v7/view/menu/MenuBuilder$ItemInvoker": "androidx/view/menu/MenuBuilder$ItemInvoker",
+      "android/support/v17/leanback/widget/MultiActionsProvider$MultiAction": "androidx/leanback/widget/MultiActionsProvider$MultiAction",
+      "android/support/v7/gridlayout/R$dimen": "androidx/gridlayout/R$dimen",
+      "android/support/v4/app/FragmentManagerImpl$FragmentTag": "androidx/app/FragmentManagerImpl$FragmentTag",
+      "android/support/v4/media/MediaDescriptionCompat$Builder": "androidx/media/MediaDescriptionCompat$Builder",
+      "android/support/v17/leanback/widget/PlaybackControlsPresenter$BoundData": "androidx/leanback/widget/PlaybackControlsPresenter$BoundData",
+      "android/support/v7/app/MediaRouteChooserDialog$RouteComparator": "androidx/app/MediaRouteChooserDialog$RouteComparator",
+      "android/support/v7/app/ActionBar": "androidx/app/ActionBar",
+      "android/support/design/widget/HeaderBehavior$FlingRunnable": "androidx/design/widget/HeaderBehavior$FlingRunnable",
+      "android/support/v7/widget/GapWorker$Task": "androidx/widget/GapWorker$Task",
+      "android/support/v4/view/AsyncLayoutInflater$InflateThread": "androidx/view/AsyncLayoutInflater$InflateThread",
+      "android/support/v17/leanback/R$string": "androidx/leanback/R$string",
+      "android/support/v4/content/LocalBroadcastManager$BroadcastRecord": "androidx/content/LocalBroadcastManager$BroadcastRecord",
+      "android/support/wear/widget/drawer/AbsListViewFlingWatcher": "androidx/wear/widget/drawer/AbsListViewFlingWatcher",
+      "android/support/v17/leanback/widget/ActionPresenterSelector$ActionViewHolder": "androidx/leanback/widget/ActionPresenterSelector$ActionViewHolder",
+      "android/support/v17/leanback/widget/ImageCardView": "androidx/leanback/widget/ImageCardView",
+      "android/support/v4/app/AlarmManagerCompat": "androidx/app/AlarmManagerCompat",
+      "android/support/v7/app/MediaRouterThemeHelper$ControllerColorType": "androidx/app/MediaRouterThemeHelper$ControllerColorType",
+      "android/support/v4/widget/SlidingPaneLayout$SlidingPanelLayoutImplBase": "androidx/widget/SlidingPaneLayout$SlidingPanelLayoutImplBase",
+      "android/support/v4/view/WindowCompat": "androidx/view/WindowCompat",
+      "android/support/v7/widget/ThemedSpinnerAdapter$Helper": "androidx/widget/ThemedSpinnerAdapter$Helper",
+      "android/support/v4/media/session/MediaControllerCompat$Callback$StubCompat": "androidx/media/session/MediaControllerCompat$Callback$StubCompat",
+      "android/support/v4/widget/NestedScrollView$AccessibilityDelegate": "androidx/widget/NestedScrollView$AccessibilityDelegate",
+      "android/support/annotation/MainThread": "androidx/annotation/MainThread",
+      "android/support/v17/leanback/app/RowsFragment$RowViewHolderExtra": "androidx/leanback/app/RowsFragment$RowViewHolderExtra",
+      "android/support/v17/leanback/widget/MultiActionsProvider": "androidx/leanback/widget/MultiActionsProvider",
+      "android/support/v7/mediarouter/R$id": "androidx/mediarouter/R$id",
+      "android/support/v4/view/ViewCompat$NestedScrollType": "androidx/view/ViewCompat$NestedScrollType",
+      "android/support/v4/text/TextDirectionHeuristicsCompat$TextDirectionAlgorithm": "androidx/text/TextDirectionHeuristicsCompat$TextDirectionAlgorithm",
+      "android/support/v4/media/session/MediaControllerCompatApi21$CallbackProxy": "androidx/media/session/MediaControllerCompatApi21$CallbackProxy",
+      "android/support/customtabs/TrustedWebUtils": "androidx/browser/customtabs/TrustedWebUtils",
+      "android/support/v4/text/BidiFormatter$Builder": "androidx/text/BidiFormatter$Builder",
+      "android/support/design/widget/TabLayout$TabView": "androidx/design/widget/TabLayout$TabView",
+      "android/support/wear/internal/widget/drawer/SinglePageUi$OnSelectedClickHandler": "androidx/wear/internal/widget/drawer/SinglePageUi$OnSelectedClickHandler",
+      "android/support/v17/leanback/widget/BrowseFrameLayout": "androidx/leanback/widget/BrowseFrameLayout",
+      "android/support/v4/widget/ImageViewCompat$BaseViewCompatImpl": "androidx/widget/ImageViewCompat$BaseViewCompatImpl",
+      "android/support/customtabs/PostMessageService": "androidx/browser/customtabs/PostMessageService",
+      "android/support/annotation/FontRes": "androidx/annotation/FontRes",
+      "android/support/transition/ViewGroupUtilsApi14": "androidx/transition/ViewGroupUtilsApi14",
+      "android/support/v4/view/ViewParentCompat": "androidx/view/ViewParentCompat",
+      "android/support/v17/leanback/widget/SectionRow": "androidx/leanback/widget/SectionRow",
+      "android/support/v7/preference/DropDownPreference": "androidx/preference/DropDownPreference",
+      "android/support/v4/widget/DrawerLayout$SavedState": "androidx/widget/DrawerLayout$SavedState",
+      "android/support/transition/AnimatorUtilsApi14": "androidx/transition/AnimatorUtilsApi14",
+      "android/support/transition/AnimatorUtilsApi19": "androidx/transition/AnimatorUtilsApi19",
+      "android/support/constraint/solver/widgets/ConstraintAnchor$Strength": "androidx/constraint/solver/widgets/ConstraintAnchor$Strength",
+      "android/support/v7/widget/RecyclerView$SmoothScroller": "androidx/widget/RecyclerView$SmoothScroller",
+      "android/support/design/R$drawable": "androidx/design/R$drawable",
+      "android/support/v7/util/BatchingListUpdateCallback": "androidx/util/BatchingListUpdateCallback",
+      "android/support/v17/leanback/app/BrandedSupportFragment": "androidx/leanback/app/BrandedSupportFragment",
+      "android/support/transition/ViewGroupUtilsApi18": "androidx/transition/ViewGroupUtilsApi18",
+      "android/support/v17/leanback/app/BrowseFragment$FragmentHost": "androidx/leanback/app/BrowseFragment$FragmentHost",
+      "android/support/v17/leanback/widget/MediaNowPlayingView": "androidx/leanback/widget/MediaNowPlayingView",
+      "android/support/v4/app/ActivityCompat$PermissionCompatDelegate": "androidx/app/ActivityCompat$PermissionCompatDelegate",
+      "android/support/v7/app/ActionBar$TabListener": "androidx/app/ActionBar$TabListener",
+      "android/support/design/widget/CoordinatorLayout$HierarchyChangeListener": "androidx/design/widget/CoordinatorLayout$HierarchyChangeListener",
+      "android/support/v7/app/AppCompatDelegateImplV9": "androidx/app/AppCompatDelegateImplV9",
+      "android/support/v4/app/LoaderManager$LoaderCallbacks": "androidx/app/LoaderManager$LoaderCallbacks",
+      "android/support/v4/view/MenuItemCompat$MenuItemCompatBaseImpl": "androidx/view/MenuItemCompat$MenuItemCompatBaseImpl",
+      "android/support/design/widget/BaseTransientBottomBar$SnackbarBaseLayout": "androidx/design/widget/BaseTransientBottomBar$SnackbarBaseLayout",
+      "android/support/text/emoji/EmojiCompat$CompatInternal19": "androidx/text/emoji/EmojiCompat$CompatInternal19",
+      "android/support/wear/R$fraction": "androidx/wear/R$fraction",
+      "android/support/v7/widget/DividerItemDecoration": "androidx/widget/DividerItemDecoration",
+      "android/support/v4/view/MenuItemCompat$OnActionExpandListener": "androidx/view/MenuItemCompat$OnActionExpandListener",
+      "android/support/v4/widget/SlidingPaneLayout$SlidingPanelLayoutImpl": "androidx/widget/SlidingPaneLayout$SlidingPanelLayoutImpl",
+      "android/support/v4/graphics/PathParser$ExtractFloatResult": "androidx/graphics/PathParser$ExtractFloatResult",
+      "android/support/graphics/drawable/ArgbEvaluator": "androidx/graphics/drawable/ArgbEvaluator",
+      "android/support/v17/leanback/widget/ShadowHelper": "androidx/leanback/widget/ShadowHelper",
+      "android/support/design/widget/FloatingActionButtonImpl$ElevateToTranslationZAnimation": "androidx/design/widget/FloatingActionButtonImpl$ElevateToTranslationZAnimation",
+      "android/support/v4/app/NotificationManagerCompat$SideChannelManager": "androidx/app/NotificationManagerCompat$SideChannelManager",
+      "android/support/v17/leanback/widget/PlaybackRowPresenter$ViewHolder": "androidx/leanback/widget/PlaybackRowPresenter$ViewHolder",
+      "android/support/v17/leanback/media/PlaybackBaseControlGlue": "androidx/leanback/media/PlaybackBaseControlGlue",
+      "android/support/v7/app/MediaRouteButton": "androidx/app/MediaRouteButton",
+      "android/support/v4/view/PagerTabStrip": "androidx/widget/PagerTabStrip",
+      "android/support/v7/widget/ActivityChooserView$InnerLayout": "androidx/widget/ActivityChooserView$InnerLayout",
+      "android/support/v17/leanback/widget/TitleHelper": "androidx/leanback/widget/TitleHelper",
+      "android/support/v7/media/RegisteredMediaRouteProvider": "androidx/media/RegisteredMediaRouteProvider",
+      "android/support/v4/print/PrintHelper$PrintHelperStub": "androidx/print/PrintHelper$PrintHelperStub",
+      "android/support/v7/media/MediaRouter": "androidx/media/MediaRouter",
+      "android/support/v4/widget/ViewDragHelper": "androidx/widget/ViewDragHelper",
+      "android/support/v4/media/MediaBrowserCompat$MediaItem": "androidx/media/MediaBrowserCompat$MediaItem",
+      "android/support/app/recommendation/ContentRecommendation$ContentMaturity": "androidx/app/recommendation/ContentRecommendation$ContentMaturity",
+      "android/support/v7/widget/SwitchCompat": "androidx/widget/SwitchCompat",
+      "android/support/v7/media/MediaSessionStatus": "androidx/media/MediaSessionStatus",
+      "android/support/v17/leanback/widget/picker/Picker$PickerScrollArrayAdapter": "androidx/leanback/widget/picker/Picker$PickerScrollArrayAdapter",
+      "android/support/v4/util/Pools": "androidx/util/Pools",
+      "android/support/v4/widget/SlidingPaneLayout$SavedState": "androidx/widget/SlidingPaneLayout$SavedState",
+      "android/support/v7/widget/ViewStubCompat$OnInflateListener": "androidx/widget/ViewStubCompat$OnInflateListener",
+      "android/support/design/widget/ViewOffsetHelper": "androidx/design/widget/ViewOffsetHelper",
+      "android/support/design/widget/FloatingActionButton$OnVisibilityChangedListener": "androidx/design/widget/FloatingActionButton$OnVisibilityChangedListener",
+      "android/support/compat/BuildConfig": "androidx/compat/BuildConfig",
+      "android/support/v4/app/FragmentManagerImpl$PopBackStackState": "androidx/app/FragmentManagerImpl$PopBackStackState",
+      "android/support/v7/media/MediaRouterJellybeanMr1$ActiveScanWorkaround": "androidx/media/MediaRouterJellybeanMr1$ActiveScanWorkaround",
+      "android/support/v17/leanback/widget/TitleViewAdapter": "androidx/leanback/widget/TitleViewAdapter",
+      "android/support/design/widget/TabLayout": "androidx/design/widget/TabLayout",
+      "android/support/v4/view/MenuItemCompat$MenuVersionImpl": "androidx/view/MenuItemCompat$MenuVersionImpl",
+      "android/support/v7/app/MediaRouteButton$MediaRouterCallback": "androidx/app/MediaRouteButton$MediaRouterCallback",
+      "android/support/v4/media/MediaDescriptionCompatApi21$Builder": "androidx/media/MediaDescriptionCompatApi21$Builder",
+      "android/support/design/widget/TabLayout$TabGravity": "androidx/design/widget/TabLayout$TabGravity",
+      "android/support/v17/leanback/widget/AbstractDetailsDescriptionPresenter": "androidx/leanback/widget/AbstractDetailsDescriptionPresenter",
+      "android/support/v7/widget/RecyclerView$RecycledViewPool$ScrapData": "androidx/widget/RecyclerView$RecycledViewPool$ScrapData",
+      "android/support/v17/leanback/media/MediaControllerAdapter": "androidx/leanback/media/MediaControllerAdapter",
+      "android/support/wear/internal/widget/drawer/MultiPagePresenter$Ui": "androidx/wear/internal/widget/drawer/MultiPagePresenter$Ui",
+      "android/support/v7/app/ToolbarActionBar": "androidx/app/ToolbarActionBar",
+      "android/support/v7/widget/ViewBoundsCheck$Callback": "androidx/widget/ViewBoundsCheck$Callback",
+      "android/support/text/emoji/widget/EmojiExtractEditText": "androidx/text/emoji/widget/EmojiExtractEditText",
+      "android/support/v4/app/FrameMetricsAggregator": "androidx/app/FrameMetricsAggregator",
+      "android/support/constraint/R": "androidx/constraint/R",
+      "android/support/v7/mediarouter/R$string": "androidx/mediarouter/R$string",
+      "android/support/text/emoji/bundled/BundledEmojiCompatConfig$InitRunnable": "androidx/text/emoji/bundled/BundledEmojiCompatConfig$InitRunnable",
+      "android/support/v17/leanback/transition/TransitionHelper$TransitionHelperApi21Impl": "androidx/leanback/transition/TransitionHelper$TransitionHelperApi21Impl",
+      "android/support/v7/widget/LinearLayoutManager": "androidx/widget/LinearLayoutManager",
+      "android/support/graphics/drawable/VectorDrawableCompat$VGroup": "androidx/graphics/drawable/VectorDrawableCompat$VGroup",
+      "android/support/v17/leanback/app/BackgroundManager": "androidx/leanback/app/BackgroundManager",
+      "android/support/v17/leanback/app/VideoFragmentGlueHost": "androidx/leanback/app/VideoFragmentGlueHost",
+      "android/support/v4/net/ConnectivityManagerCompat": "androidx/net/ConnectivityManagerCompat",
+      "android/support/annotation/NonNull": "androidx/annotation/NonNull",
+      "android/support/transition/ImageViewUtilsApi21": "androidx/transition/ImageViewUtilsApi21",
+      "android/support/v7/widget/Toolbar$SavedState": "androidx/widget/Toolbar$SavedState",
+      "android/support/v7/util/ThreadUtil$BackgroundCallback": "androidx/util/ThreadUtil$BackgroundCallback",
+      "android/support/v17/leanback/app/BaseFragment": "androidx/leanback/app/BaseFragment",
+      "android/support/v4/view/AccessibilityDelegateCompat$AccessibilityDelegateApi16Impl": "androidx/view/AccessibilityDelegateCompat$AccessibilityDelegateApi16Impl",
+      "android/support/v7/media/MediaRouterJellybeanMr2$UserRouteInfo": "androidx/media/MediaRouterJellybeanMr2$UserRouteInfo",
+      "android/support/v7/widget/DefaultItemAnimator$ChangeInfo": "androidx/widget/DefaultItemAnimator$ChangeInfo",
+      "android/support/v4/hardware/display/DisplayManagerCompat$DisplayManagerCompatApi14Impl": "androidx/hardware/display/DisplayManagerCompat$DisplayManagerCompatApi14Impl",
+      "android/support/v7/cardview/R": "androidx/cardview/R",
+      "android/support/v4/app/NotificationCompat": "androidx/app/NotificationCompat",
+      "android/support/v17/leanback/transition/TransitionHelper$TransitionHelperKitkatImpl": "androidx/leanback/transition/TransitionHelper$TransitionHelperKitkatImpl",
+      "android/support/v4/widget/CircularProgressDrawable$Ring": "androidx/widget/CircularProgressDrawable$Ring",
+      "android/support/v17/leanback/app/BrowseSupportFragment$ListRowFragmentFactory": "androidx/leanback/app/BrowseSupportFragment$ListRowFragmentFactory",
+      "android/support/v7/widget/LinearLayoutManager$AnchorInfo": "androidx/widget/LinearLayoutManager$AnchorInfo",
+      "android/support/v4/media/MediaBrowserCompatApi26$SubscriptionCallbackProxy": "androidx/media/MediaBrowserCompatApi26$SubscriptionCallbackProxy",
+      "android/support/v4/view/ViewCompat$ViewCompatApi19Impl": "androidx/view/ViewCompat$ViewCompatApi19Impl",
+      "android/support/v4/view/accessibility/AccessibilityNodeInfoCompat": "androidx/view/accessibility/AccessibilityNodeInfoCompat",
+      "android/support/v4/os/IResultReceiver$Stub$Proxy": "androidx/os/IResultReceiver$Stub$Proxy",
+      "android/support/v17/leanback/widget/PlaybackTransportRowPresenter$BoundData": "androidx/leanback/widget/PlaybackTransportRowPresenter$BoundData",
+      "android/support/transition/TransitionSet": "androidx/transition/TransitionSet",
+      "android/support/v7/graphics/drawable/DrawableWrapper": "androidx/graphics/drawable/DrawableWrapper",
+      "android/support/v7/media/MediaRouterJellybean$UserRouteInfo": "androidx/media/MediaRouterJellybean$UserRouteInfo",
+      "android/support/v7/app/MediaRouteActionProvider": "androidx/app/MediaRouteActionProvider",
+      "android/support/compat/R$layout": "androidx/compat/R$layout",
+      "android/support/v17/leanback/widget/ViewHolderTask": "androidx/leanback/widget/ViewHolderTask",
+      "android/support/v7/graphics/ColorCutQuantizer$Vbox": "androidx/graphics/ColorCutQuantizer$Vbox",
+      "android/support/text/emoji/EmojiProcessor$CodepointIndexFinder": "androidx/text/emoji/EmojiProcessor$CodepointIndexFinder",
+      "android/support/v4/content/CursorLoader": "androidx/content/CursorLoader",
+      "android/support/text/emoji/widget/EditTextAttributeHelper": "androidx/text/emoji/widget/EditTextAttributeHelper",
+      "android/support/wear/internal/widget/ResourcesUtil": "androidx/wear/internal/widget/ResourcesUtil",
+      "android/support/wear/R$array": "androidx/wear/R$array",
+      "android/support/v7/widget/RecyclerView$RecycledViewPool": "androidx/widget/RecyclerView$RecycledViewPool",
+      "android/support/transition/ImageViewUtilsApi14": "androidx/transition/ImageViewUtilsApi14",
+      "android/support/v7/gridlayout/R": "androidx/gridlayout/R",
+      "android/support/text/emoji/FontRequestEmojiCompatConfig": "androidx/text/emoji/FontRequestEmojiCompatConfig",
+      "android/support/text/emoji/EmojiMetadata": "androidx/text/emoji/EmojiMetadata",
+      "android/support/v4/widget/SlidingPaneLayout$DisableLayerRunnable": "androidx/widget/SlidingPaneLayout$DisableLayerRunnable",
+      "android/support/v4/util/Pools$SynchronizedPool": "androidx/util/Pools$SynchronizedPool",
+      "android/support/v17/leanback/graphics/BoundsRule$ValueRule": "androidx/leanback/graphics/BoundsRule$ValueRule",
+      "android/support/v7/widget/ActivityChooserView$ActivityChooserViewAdapter": "androidx/widget/ActivityChooserView$ActivityChooserViewAdapter",
+      "android/support/v7/mediarouter/R": "androidx/mediarouter/R",
+      "android/support/text/emoji/FontRequestEmojiCompatConfig$ExponentialBackoffRetryPolicy": "androidx/text/emoji/FontRequestEmojiCompatConfig$ExponentialBackoffRetryPolicy",
+      "android/support/v7/widget/AppCompatImageView": "androidx/widget/AppCompatImageView",
+      "android/support/v17/preference/R$layout": "androidx/leanback/preference/R$layout",
+      "android/support/v4/provider/FontsContractCompat$FontFamilyResult$FontResultStatus": "androidx/provider/FontsContractCompat$FontFamilyResult$FontResultStatus",
+      "android/support/graphics/drawable/VectorDrawableCompat$VPathRenderer": "androidx/graphics/drawable/VectorDrawableCompat$VPathRenderer",
+      "android/support/v4/app/JobIntentService$CompatWorkEnqueuer": "androidx/app/JobIntentService$CompatWorkEnqueuer",
+      "android/support/v17/leanback/app/BrowseSupportFragment$SetSelectionRunnable": "androidx/leanback/app/BrowseSupportFragment$SetSelectionRunnable",
+      "android/support/v4/media/MediaDescriptionCompatApi23$Builder": "androidx/media/MediaDescriptionCompatApi23$Builder",
+      "android/support/v17/leanback/widget/DetailsOverviewRowPresenter": "androidx/leanback/widget/DetailsOverviewRowPresenter",
+      "android/support/text/emoji/FontRequestEmojiCompatConfig$RetryPolicy": "androidx/text/emoji/FontRequestEmojiCompatConfig$RetryPolicy",
+      "android/support/v7/widget/ScrollingTabContainerView$TabView": "androidx/widget/ScrollingTabContainerView$TabView",
+      "android/support/annotation/DrawableRes": "androidx/annotation/DrawableRes",
+      "android/support/v4/view/ViewPager$LayoutParams": "androidx/view/ViewPager$LayoutParams",
+      "android/support/v17/leanback/widget/MediaItemActionPresenter$ViewHolder": "androidx/leanback/widget/MediaItemActionPresenter$ViewHolder",
+      "android/support/v7/app/AlertDialog$Builder": "androidx/app/AlertDialog$Builder",
+      "android/support/v4/util/Preconditions": "androidx/util/Preconditions",
+      "android/support/v4/app/FragmentTabHost": "androidx/app/FragmentTabHost",
+      "android/support/v17/leanback/widget/BaseGridView$OnTouchInterceptListener": "androidx/leanback/widget/BaseGridView$OnTouchInterceptListener",
+      "android/support/text/emoji/EmojiCompat$ListenerDispatcher": "androidx/text/emoji/EmojiCompat$ListenerDispatcher",
+      "android/support/wear/widget/drawer/WearableActionDrawerMenu$WearableActionDrawerMenuListener": "androidx/wear/widget/drawer/WearableActionDrawerMenu$WearableActionDrawerMenuListener",
+      "android/support/v4/app/INotificationSideChannel$Stub$Proxy": "androidx/app/INotificationSideChannel$Stub$Proxy",
+      "android/support/media/tv/TvContractCompat$Programs$Genres$Genre": "androidx/media/tv/TvContractCompat$Programs$Genres$Genre",
+      "android/support/text/emoji/widget/EmojiTextWatcher$InitCallbackImpl": "androidx/text/emoji/widget/EmojiTextWatcher$InitCallbackImpl",
+      "android/support/v17/leanback/widget/ParallaxTarget$DirectPropertyTarget": "androidx/leanback/widget/ParallaxTarget$DirectPropertyTarget",
+      "android/support/v7/app/ToolbarActionBar$ToolbarCallbackWrapper": "androidx/app/ToolbarActionBar$ToolbarCallbackWrapper",
+      "android/support/v4/widget/CompoundButtonCompat": "androidx/widget/CompoundButtonCompat",
+      "android/support/v4/content/ContentResolverCompat": "androidx/content/ContentResolverCompat",
+      "android/support/v17/leanback/widget/NonOverlappingRelativeLayout": "androidx/leanback/widget/NonOverlappingRelativeLayout",
+      "android/support/text/emoji/EmojiCompat$MetadataRepoLoaderCallback": "androidx/text/emoji/EmojiCompat$MetadataRepoLoaderCallback",
+      "android/support/v4/app/FragmentStatePagerAdapter": "androidx/app/FragmentStatePagerAdapter",
+      "android/support/v17/leanback/app/PlaybackSupportFragment": "androidx/leanback/app/PlaybackSupportFragment",
+      "android/support/v7/app/OverlayListView": "androidx/app/OverlayListView",
+      "android/support/v7/mediarouter/R$styleable": "androidx/mediarouter/R$styleable",
+      "android/support/v7/widget/DrawableUtils": "androidx/widget/DrawableUtils",
+      "android/support/v4/content/ModernAsyncTask$AsyncTaskResult": "androidx/content/ModernAsyncTask$AsyncTaskResult",
+      "android/support/constraint/BuildConfig": "androidx/constraint/BuildConfig",
+      "android/support/v4/app/SharedElementCallback": "androidx/app/SharedElementCallback",
+      "android/support/v7/recyclerview/BuildConfig": "androidx/recyclerview/BuildConfig",
+      "android/support/text/emoji/widget/EmojiKeyListener": "androidx/text/emoji/widget/EmojiKeyListener",
+      "android/support/v17/leanback/transition/FadeAndShortSlide$CalculateSlide": "androidx/leanback/transition/FadeAndShortSlide$CalculateSlide",
+      "android/support/v7/widget/ActionMenuPresenter$ActionButtonSubmenu": "androidx/widget/ActionMenuPresenter$ActionButtonSubmenu",
+      "android/support/v4/media/MediaMetadataCompat$Builder": "androidx/media/MediaMetadataCompat$Builder",
+      "android/support/design/widget/BottomSheetBehavior$SavedState": "androidx/design/widget/BottomSheetBehavior$SavedState",
+      "android/support/v4/media/session/MediaControllerCompatApi21$PlaybackInfo": "androidx/media/session/MediaControllerCompatApi21$PlaybackInfo",
+      "android/support/v7/widget/ActionMenuPresenter$PopupPresenterCallback": "androidx/widget/ActionMenuPresenter$PopupPresenterCallback",
+      "android/support/media/ExifInterface$ByteOrderedDataOutputStream": "androidx/media/ExifInterface$ByteOrderedDataOutputStream",
+      "android/support/v17/leanback/widget/Grid$Location": "androidx/leanback/widget/Grid$Location",
+      "android/support/v7/media/MediaRouterJellybeanMr1$CallbackProxy": "androidx/media/MediaRouterJellybeanMr1$CallbackProxy",
+      "android/support/v17/leanback/R$raw": "androidx/leanback/R$raw",
+      "android/support/media/tv/TvContractCompat$PreviewProgramColumns": "androidx/media/tv/TvContractCompat$PreviewProgramColumns",
+      "android/support/v17/leanback/widget/SeekBar$AccessibilitySeekListener": "androidx/leanback/widget/SeekBar$AccessibilitySeekListener",
+      "android/support/v4/print/PrintHelper$PrintHelperVersionImpl": "androidx/print/PrintHelper$PrintHelperVersionImpl",
+      "android/support/v17/leanback/app/ProgressBarManager": "androidx/leanback/app/ProgressBarManager",
+      "android/support/v7/app/MediaRouteChooserDialogFragment": "androidx/app/MediaRouteChooserDialogFragment",
+      "android/support/v4/content/FileProvider$SimplePathStrategy": "androidx/content/FileProvider$SimplePathStrategy",
+      "android/support/v17/leanback/app/BackgroundManager$BackgroundContinuityService": "androidx/leanback/app/BackgroundManager$BackgroundContinuityService",
+      "android/support/v4/media/session/IMediaSession$Stub": "androidx/media/session/IMediaSession$Stub",
+      "android/support/v4/widget/TintableImageSourceView": "androidx/widget/TintableImageSourceView",
+      "android/support/transition/ViewOverlayApi14$OverlayViewGroup": "androidx/transition/ViewOverlayApi14$OverlayViewGroup",
+      "android/support/design/widget/FloatingActionButtonImpl": "androidx/design/widget/FloatingActionButtonImpl",
+      "android/support/text/emoji/widget/EmojiEditableFactory": "androidx/text/emoji/widget/EmojiEditableFactory",
+      "android/support/transition/Visibility$Mode": "androidx/transition/Visibility$Mode",
+      "android/support/v17/leanback/widget/PagingIndicator$Dot": "androidx/leanback/widget/PagingIndicator$Dot",
+      "android/support/design/internal/NavigationSubMenu": "androidx/design/internal/NavigationSubMenu",
+      "android/support/v17/leanback/widget/AbstractDetailsDescriptionPresenter$ViewHolder": "androidx/leanback/widget/AbstractDetailsDescriptionPresenter$ViewHolder",
+      "android/support/v4/media/AudioAttributesCompat$AudioManagerHidden": "androidx/media/AudioAttributesCompat$AudioManagerHidden",
+      "android/support/v7/preference/CheckBoxPreference": "androidx/preference/CheckBoxPreference",
+      "android/support/design/widget/FloatingActionButtonLollipop$AlwaysStatefulGradientDrawable": "androidx/design/widget/FloatingActionButtonLollipop$AlwaysStatefulGradientDrawable",
+      "android/support/v4/media/RatingCompat$StarStyle": "androidx/media/RatingCompat$StarStyle",
+      "android/support/v7/preference/PreferenceGroup$PreferencePositionCallback": "androidx/preference/PreferenceGroup$PreferencePositionCallback",
+      "android/support/design/widget/BottomNavigationView": "androidx/design/widget/BottomNavigationView",
+      "android/support/v17/leanback/media/MediaPlayerAdapter": "androidx/leanback/media/MediaPlayerAdapter",
+      "android/support/v7/widget/SuggestionsAdapter$ChildViewCache": "androidx/widget/SuggestionsAdapter$ChildViewCache",
+      "android/support/v4/media/session/MediaSessionCompat$Callback$StubApi21": "androidx/media/session/MediaSessionCompat$Callback$StubApi21",
+      "android/support/v7/widget/ListPopupWindow$PopupScrollListener": "androidx/widget/ListPopupWindow$PopupScrollListener",
+      "android/support/v4/app/NotificationCompat$MessagingStyle": "androidx/app/NotificationCompat$MessagingStyle",
+      "android/support/wear/widget/SwipeDismissLayout$OnSwipeProgressChangedListener": "androidx/wear/widget/SwipeDismissLayout$OnSwipeProgressChangedListener",
+      "android/support/v7/media/RemotePlaybackClient$OnMessageReceivedListener": "androidx/media/RemotePlaybackClient$OnMessageReceivedListener",
+      "android/support/v7/util/MessageThreadUtil$SyncQueueItem": "androidx/util/MessageThreadUtil$SyncQueueItem",
+      "android/support/v4/view/MarginLayoutParamsCompat": "androidx/view/MarginLayoutParamsCompat",
+      "android/support/v4/media/session/MediaSessionCompat$Callback$StubApi24": "androidx/media/session/MediaSessionCompat$Callback$StubApi24",
+      "android/support/v17/leanback/widget/CheckableImageView": "androidx/leanback/widget/CheckableImageView",
+      "android/support/v4/media/session/MediaSessionCompat$Callback$StubApi23": "androidx/media/session/MediaSessionCompat$Callback$StubApi23",
+      "android/support/v7/widget/ThemeUtils": "androidx/widget/ThemeUtils",
+      "android/support/v7/preference/Preference$BaseSavedState": "androidx/preference/Preference$BaseSavedState",
+      "android/support/v4/widget/DrawerLayout$AccessibilityDelegate": "androidx/widget/DrawerLayout$AccessibilityDelegate",
+      "android/support/v4/app/ActivityOptionsCompat": "androidx/app/ActivityOptionsCompat",
+      "android/support/v4/media/session/PlaybackStateCompat$State": "androidx/media/session/PlaybackStateCompat$State",
+      "android/support/v7/widget/RecyclerView$ItemAnimator$ItemHolderInfo": "androidx/widget/RecyclerView$ItemAnimator$ItemHolderInfo",
+      "android/support/constraint/solver/widgets/ConstraintAnchor$Type": "androidx/constraint/solver/widgets/ConstraintAnchor$Type",
+      "android/support/v4/widget/TextViewCompat$TextViewCompatApi17Impl": "androidx/widget/TextViewCompat$TextViewCompatApi17Impl",
+      "android/support/v17/leanback/widget/ImeKeyMonitor": "androidx/leanback/widget/ImeKeyMonitor",
+      "android/support/v17/leanback/app/BrowseSupportFragment$MainFragmentRowsAdapter": "androidx/leanback/app/BrowseSupportFragment$MainFragmentRowsAdapter",
+      "android/support/v4/app/ActivityCompat": "androidx/app/ActivityCompat",
+      "android/support/v4/util/ObjectsCompat": "androidx/util/ObjectsCompat",
+      "android/support/v4/app/SupportActivity$ExtraData": "androidx/app/SupportActivity$ExtraData",
+      "android/support/v4/media/MediaBrowserProtocol": "androidx/media/MediaBrowserProtocol",
+      "android/support/design/widget/CollapsingTextHelper": "androidx/design/widget/CollapsingTextHelper",
+      "android/support/v14/preference/SwitchPreference$Listener": "androidx/preference/SwitchPreference$Listener",
+      "android/support/v7/preference/SwitchPreferenceCompat$Listener": "androidx/preference/SwitchPreferenceCompat$Listener",
+      "android/support/wear/internal/widget/drawer/SinglePageUi": "androidx/wear/internal/widget/drawer/SinglePageUi",
+      "android/support/v4/view/NestedScrollingChild2": "androidx/view/NestedScrollingChild2",
+      "android/support/customtabs/ICustomTabsService": "androidx/browser/customtabs/ICustomTabsService",
+      "android/support/recommendation/BuildConfig": "androidx/recommendation/BuildConfig",
+      "android/support/text/emoji/widget/EmojiAppCompatTextView": "androidx/text/emoji/widget/EmojiAppCompatTextView",
+      "android/support/v7/media/MediaRouterJellybean$RouteGroup": "androidx/media/MediaRouterJellybean$RouteGroup",
+      "android/support/v4/widget/DrawerLayout": "androidx/widget/DrawerLayout",
+      "android/support/v4/os/CancellationSignal$OnCancelListener": "androidx/os/CancellationSignal$OnCancelListener",
+      "android/support/v4/view/ViewCompat$ViewCompatApi17Impl": "androidx/view/ViewCompat$ViewCompatApi17Impl",
+      "android/support/v4/app/JobIntentService$CompatJobEngine": "androidx/app/JobIntentService$CompatJobEngine",
+      "android/support/v4/media/MediaBrowserCompat$CustomActionCallback": "androidx/media/MediaBrowserCompat$CustomActionCallback",
+      "android/support/v4/media/app/NotificationCompat$DecoratedMediaCustomViewStyle": "androidx/media/app/NotificationCompat$DecoratedMediaCustomViewStyle",
+      "android/support/v7/app/ActionBarDrawerToggle$Delegate": "androidx/app/ActionBarDrawerToggle$Delegate",
+      "android/support/v17/leanback/R$anim": "androidx/leanback/R$anim",
+      "android/support/v13/app/FragmentCompat$FragmentCompatBaseImpl": "androidx/app/FragmentCompat$FragmentCompatBaseImpl",
+      "android/support/v7/widget/SearchView$OnCloseListener": "androidx/widget/SearchView$OnCloseListener",
+      "android/support/v17/leanback/widget/PlaybackControlsRow$OnPlaybackProgressCallback": "androidx/leanback/widget/PlaybackControlsRow$OnPlaybackProgressCallback",
+      "android/support/v17/leanback/widget/PlaybackControlsRow$MoreActions": "androidx/leanback/widget/PlaybackControlsRow$MoreActions",
+      "android/support/v7/widget/ActivityChooserModel$ActivityResolveInfo": "androidx/widget/ActivityChooserModel$ActivityResolveInfo",
+      "android/support/annotation/AnimRes": "androidx/annotation/AnimRes",
+      "android/support/animation/Force": "androidx/animation/Force",
+      "android/support/v17/leanback/util/StateMachine$Event": "androidx/leanback/util/StateMachine$Event",
+      "android/support/v17/leanback/app/BrowseFragment$MainFragmentRowsAdapter": "androidx/leanback/app/BrowseFragment$MainFragmentRowsAdapter",
+      "android/support/v4/graphics/BitmapCompat": "androidx/graphics/BitmapCompat",
+      "android/support/v4/app/NotificationCompat$NotificationVisibility": "androidx/app/NotificationCompat$NotificationVisibility",
+      "android/support/v17/leanback/app/PlaybackFragment$SetSelectionRunnable": "androidx/leanback/app/PlaybackFragment$SetSelectionRunnable",
+      "android/support/v7/media/RegisteredMediaRouteProviderWatcher$Callback": "androidx/media/RegisteredMediaRouteProviderWatcher$Callback",
+      "android/support/v4/view/ViewCompat$FocusDirection": "androidx/view/ViewCompat$FocusDirection",
+      "android/support/design/widget/BottomSheetBehavior$SettleRunnable": "androidx/design/widget/BottomSheetBehavior$SettleRunnable",
+      "android/support/v17/leanback/widget/RoundedRectHelperApi21$RoundedRectOutlineProvider": "androidx/leanback/widget/RoundedRectHelperApi21$RoundedRectOutlineProvider",
+      "android/support/wear/widget/drawer/FlingWatcherFactory": "androidx/wear/widget/drawer/FlingWatcherFactory",
+      "android/support/v4/app/NotificationManagerCompat$Task": "androidx/app/NotificationManagerCompat$Task",
+      "android/support/animation/FlingAnimation$DragForce": "androidx/animation/FlingAnimation$DragForce",
+      "android/support/v7/widget/GridLayout$MutableInt": "androidx/widget/GridLayout$MutableInt",
+      "android/support/v7/util/DiffUtil": "androidx/util/DiffUtil",
+      "android/support/v4/app/FragmentManagerImpl$OpGenerator": "androidx/app/FragmentManagerImpl$OpGenerator",
+      "android/support/v4/view/ViewPager": "androidx/widget/ViewPager",
+      "android/support/v7/widget/TintTypedArray": "androidx/widget/TintTypedArray",
+      "android/support/text/emoji/widget/EmojiAppCompatEditText": "androidx/text/emoji/widget/EmojiAppCompatEditText",
+      "android/support/v4/view/ViewGroupCompat$ViewGroupCompatApi21Impl": "androidx/view/ViewGroupCompat$ViewGroupCompatApi21Impl",
+      "android/support/v4/media/MediaBrowserServiceCompat$ConnectionRecord": "androidx/media/MediaBrowserServiceCompat$ConnectionRecord",
+      "android/support/v7/view/menu/ActionMenuItemView": "androidx/view/menu/ActionMenuItemView",
+      "android/support/v4/app/NotificationCompatExtras": "androidx/app/NotificationCompatExtras",
+      "android/support/v7/view/menu/ListMenuPresenter": "androidx/view/menu/ListMenuPresenter",
+      "android/support/v14/preference/R": "androidx/preference/R",
+      "android/support/annotation/RequiresPermission": "androidx/annotation/RequiresPermission",
+      "android/support/v4/app/JobIntentService$GenericWorkItem": "androidx/app/JobIntentService$GenericWorkItem",
+      "android/support/v7/widget/RecyclerView$SmoothScroller$Action": "androidx/widget/RecyclerView$SmoothScroller$Action",
+      "android/support/design/R$attr": "androidx/design/R$attr",
+      "android/support/v4/content/SharedPreferencesCompat$EditorCompat$Helper": "androidx/content/SharedPreferencesCompat$EditorCompat$Helper",
+      "android/support/constraint/solver/widgets/ConstraintWidget": "androidx/constraint/solver/widgets/ConstraintWidget",
+      "android/support/fragment/BuildConfig": "androidx/fragment/BuildConfig",
+      "android/support/design/widget/NavigationView": "androidx/design/widget/NavigationView",
+      "android/support/v4/media/session/PlaybackStateCompatApi22": "androidx/media/session/PlaybackStateCompatApi22",
+      "android/support/v4/media/session/PlaybackStateCompatApi21": "androidx/media/session/PlaybackStateCompatApi21",
+      "android/support/media/instantvideo/preload/InstantVideoPreloadManager$InternalVideoPreloaderFactory": "androidx/media/instantvideo/preload/InstantVideoPreloadManager$InternalVideoPreloaderFactory",
+      "android/support/design/widget/BaseTransientBottomBar": "androidx/design/widget/BaseTransientBottomBar",
+      "android/support/wear/internal/widget/drawer/WearableNavigationDrawerPresenter": "androidx/wear/internal/widget/drawer/WearableNavigationDrawerPresenter",
+      "android/support/v4/media/session/MediaControllerCompat$Callback$StubApi21": "androidx/media/session/MediaControllerCompat$Callback$StubApi21",
+      "android/support/wear/widget/drawer/ScrollViewFlingWatcher": "androidx/wear/widget/drawer/ScrollViewFlingWatcher",
+      "android/support/v17/leanback/widget/PlaybackControlsRow$FastForwardAction": "androidx/leanback/widget/PlaybackControlsRow$FastForwardAction",
+      "android/support/v7/widget/ActionBarOverlayLayout": "androidx/widget/ActionBarOverlayLayout",
+      "android/support/v4/app/FragmentTabHost$TabInfo": "androidx/app/FragmentTabHost$TabInfo",
+      "android/support/v7/widget/GridLayout$Bounds": "androidx/widget/GridLayout$Bounds",
+      "android/support/percent/BuildConfig": "androidx/BuildConfig",
+      "android/support/v7/app/ActionBar$OnMenuVisibilityListener": "androidx/app/ActionBar$OnMenuVisibilityListener",
+      "android/support/customtabs/PostMessageServiceConnection": "androidx/browser/customtabs/PostMessageServiceConnection",
+      "android/support/design/widget/TextInputLayout$TextInputAccessibilityDelegate": "androidx/design/widget/TextInputLayout$TextInputAccessibilityDelegate",
+      "android/support/v7/preference/Preference$OnPreferenceClickListener": "androidx/preference/Preference$OnPreferenceClickListener",
+      "android/support/v7/media/MediaRouterJellybean$RouteCategory": "androidx/media/MediaRouterJellybean$RouteCategory",
+      "android/support/v4/text/TextDirectionHeuristicCompat": "androidx/text/TextDirectionHeuristicCompat",
+      "android/support/v4/view/PointerIconCompat": "androidx/view/PointerIconCompat",
+      "android/support/design/internal/NavigationMenuPresenter$NavigationMenuAdapter": "androidx/design/internal/NavigationMenuPresenter$NavigationMenuAdapter",
+      "android/support/v17/leanback/widget/BaseCardView$AnimationBase": "androidx/leanback/widget/BaseCardView$AnimationBase",
+      "android/support/v7/widget/ScrollingTabContainerView": "androidx/widget/ScrollingTabContainerView",
+      "android/support/v4/text/util/LinkifyCompat": "androidx/text/util/LinkifyCompat",
+      "android/support/annotation/RequiresPermission$Read": "androidx/annotation/RequiresPermission$Read",
+      "android/support/customtabs/CustomTabsService$Result": "androidx/browser/customtabs/CustomTabsService$Result",
+      "android/support/v7/app/MediaRouteControllerDialog": "androidx/app/MediaRouteControllerDialog",
+      "android/support/v17/leanback/app/BrowseFragment": "androidx/leanback/app/BrowseFragment",
+      "android/support/v17/leanback/widget/OnChildViewHolderSelectedListener": "androidx/leanback/widget/OnChildViewHolderSelectedListener",
+      "android/support/design/internal/NavigationMenuPresenter$SubheaderViewHolder": "androidx/design/internal/NavigationMenuPresenter$SubheaderViewHolder",
+      "android/support/v7/mediarouter/R$attr": "androidx/mediarouter/R$attr",
+      "android/support/v7/widget/ActivityChooserView$Callbacks": "androidx/widget/ActivityChooserView$Callbacks",
+      "android/support/v4/widget/TextViewCompat$TextViewCompatApi16Impl": "androidx/widget/TextViewCompat$TextViewCompatApi16Impl",
+      "android/support/v4/content/AsyncTaskLoader$LoadTask": "androidx/content/AsyncTaskLoader$LoadTask",
+      "android/support/v17/leanback/widget/GuidedActionAdapter$ActionOnFocusListener": "androidx/leanback/widget/GuidedActionAdapter$ActionOnFocusListener",
+      "android/support/v7/media/SystemMediaRouteProvider$LegacyImpl": "androidx/media/SystemMediaRouteProvider$LegacyImpl",
+      "android/support/transition/ViewUtils": "androidx/transition/ViewUtils",
+      "android/support/v17/leanback/app/RowsSupportFragment$RowViewHolderExtra": "androidx/leanback/app/RowsSupportFragment$RowViewHolderExtra",
+      "android/support/v17/leanback/widget/ActionPresenterSelector$TwoLineActionPresenter": "androidx/leanback/widget/ActionPresenterSelector$TwoLineActionPresenter",
+      "android/support/v7/recyclerview/R$dimen": "androidx/recyclerview/R$dimen",
+      "android/support/v7/app/TwilightCalculator": "androidx/app/TwilightCalculator",
+      "android/support/v17/leanback/widget/ResizingTextView": "androidx/leanback/widget/ResizingTextView",
+      "android/support/design/widget/ViewUtilsLollipop": "androidx/design/widget/ViewUtilsLollipop",
+      "android/support/media/tv/TvContractCompat$PreviewProgramColumns$Availability": "androidx/media/tv/TvContractCompat$PreviewProgramColumns$Availability",
+      "android/support/v7/media/RemotePlaybackClient$ActionCallback": "androidx/media/RemotePlaybackClient$ActionCallback",
+      "android/support/v17/leanback/widget/FullWidthDetailsOverviewRowPresenter$Listener": "androidx/leanback/widget/FullWidthDetailsOverviewRowPresenter$Listener",
+      "android/support/v4/app/FragmentManagerImpl$AnimatorOnHWLayerIfNeededListener": "androidx/app/FragmentManagerImpl$AnimatorOnHWLayerIfNeededListener",
+      "android/support/v17/leanback/transition/TransitionHelper": "androidx/leanback/transition/TransitionHelper",
+      "android/support/v7/view/SupportActionModeWrapper$CallbackWrapper": "androidx/view/SupportActionModeWrapper$CallbackWrapper",
+      "android/support/v13/app/FragmentStatePagerAdapter": "androidx/app/FragmentStatePagerAdapter",
+      "android/support/v7/graphics/Palette$PaletteAsyncListener": "androidx/graphics/Palette$PaletteAsyncListener",
+      "android/support/v4/media/MediaBrowserServiceCompat": "androidx/media/MediaBrowserServiceCompat",
+      "android/support/v4/widget/NestedScrollView": "androidx/widget/NestedScrollView",
+      "android/support/v4/media/MediaBrowserCompat$ItemCallback$StubApi23": "androidx/media/MediaBrowserCompat$ItemCallback$StubApi23",
+      "android/support/v4/text/BidiFormatter$DirectionalityEstimator": "androidx/text/BidiFormatter$DirectionalityEstimator",
+      "android/support/text/emoji/FontRequestEmojiCompatConfig$FontRequestMetadataLoader": "androidx/text/emoji/FontRequestEmojiCompatConfig$FontRequestMetadataLoader",
+      "android/support/transition/ChangeClipBounds": "androidx/transition/ChangeClipBounds",
+      "android/support/v7/widget/PositionMap": "androidx/widget/PositionMap",
+      "android/support/v17/leanback/widget/ItemBridgeAdapter$ViewHolder": "androidx/leanback/widget/ItemBridgeAdapter$ViewHolder",
+      "android/support/v7/widget/TintContextWrapper": "androidx/widget/TintContextWrapper",
+      "android/support/v7/widget/MenuItemHoverListener": "androidx/widget/MenuItemHoverListener",
+      "android/support/v4/app/RemoteInputCompatBase": "androidx/app/RemoteInputCompatBase",
+      "android/support/v17/leanback/media/SurfaceHolderGlueHost": "androidx/leanback/media/SurfaceHolderGlueHost",
+      "android/support/v7/widget/LinearLayoutManager$LayoutChunkResult": "androidx/widget/LinearLayoutManager$LayoutChunkResult",
+      "android/support/v7/view/menu/MenuPresenter": "androidx/view/menu/MenuPresenter",
+      "android/support/v7/media/MediaRouterJellybean": "androidx/media/MediaRouterJellybean",
+      "android/support/annotation/ArrayRes": "androidx/annotation/ArrayRes",
+      "android/support/v4/media/session/MediaSessionCompat$ResultReceiverWrapper": "androidx/media/session/MediaSessionCompat$ResultReceiverWrapper",
+      "android/support/v17/leanback/widget/FocusHighlightHelper": "androidx/leanback/widget/FocusHighlightHelper",
+      "android/support/v4/media/session/MediaControllerCompatApi21$TransportControls": "androidx/media/session/MediaControllerCompatApi21$TransportControls",
+      "android/support/v4/media/MediaBrowserCompatApi21$SubscriptionCallbackProxy": "androidx/media/MediaBrowserCompatApi21$SubscriptionCallbackProxy",
+      "android/support/v7/mediarouter/R$dimen": "androidx/mediarouter/R$dimen",
+      "android/support/v4/app/NotificationManagerCompat$CancelTask": "androidx/app/NotificationManagerCompat$CancelTask",
+      "android/support/design/widget/BottomSheetBehavior$BottomSheetCallback": "androidx/design/widget/BottomSheetBehavior$BottomSheetCallback",
+      "android/support/v4/os/LocaleListCompat": "androidx/os/LocaleListCompat",
+      "android/support/v4/media/MediaBrowserCompat$MediaBrowserImplBase$MediaServiceConnection": "androidx/media/MediaBrowserCompat$MediaBrowserImplBase$MediaServiceConnection",
+      "android/support/design/internal/BottomNavigationPresenter": "androidx/design/internal/BottomNavigationPresenter",
+      "android/support/v7/view/SupportMenuInflater": "androidx/view/SupportMenuInflater",
+      "android/support/v4/app/ActivityOptionsCompat$ActivityOptionsCompatApi16Impl": "androidx/app/ActivityOptionsCompat$ActivityOptionsCompatApi16Impl",
+      "android/support/transition/AutoTransition": "androidx/transition/AutoTransition",
+      "android/support/v7/widget/RoundRectDrawable": "androidx/widget/RoundRectDrawable",
+      "android/support/graphics/drawable/VectorDrawableCompat$VFullPath": "androidx/graphics/drawable/VectorDrawableCompat$VFullPath",
+      "android/support/v4/widget/ListViewAutoScrollHelper": "androidx/widget/ListViewAutoScrollHelper",
+      "android/support/v4/media/MediaBrowserCompatApi21$MediaItem": "androidx/media/MediaBrowserCompatApi21$MediaItem",
+      "android/support/transition/FloatArrayEvaluator": "androidx/transition/FloatArrayEvaluator",
+      "android/support/design/widget/CollapsingToolbarLayout$OffsetUpdateListener": "androidx/design/widget/CollapsingToolbarLayout$OffsetUpdateListener",
+      "android/support/v7/preference/PreferenceManager$OnPreferenceTreeClickListener": "androidx/preference/PreferenceManager$OnPreferenceTreeClickListener",
+      "android/support/v17/leanback/widget/Parallax": "androidx/leanback/widget/Parallax",
+      "android/support/v4/media/MediaBrowserCompat$ConnectionCallback$StubApi21": "androidx/media/MediaBrowserCompat$ConnectionCallback$StubApi21",
+      "android/support/v4/os/CancellationSignal": "androidx/os/CancellationSignal",
+      "android/support/v17/leanback/media/PlaybackGlue$PlayerCallback": "androidx/leanback/media/PlaybackGlue$PlayerCallback",
+      "android/support/v4/widget/CompoundButtonCompat$CompoundButtonCompatApi21Impl": "androidx/widget/CompoundButtonCompat$CompoundButtonCompatApi21Impl",
+      "android/support/design/internal/BottomNavigationItemView": "androidx/design/internal/BottomNavigationItemView",
+      "android/support/v4/view/ViewGroupCompat$ViewGroupCompatApi18Impl": "androidx/view/ViewGroupCompat$ViewGroupCompatApi18Impl",
+      "android/support/text/emoji/flatbuffer/Struct": "androidx/text/emoji/flatbuffer/Struct",
+      "android/support/v17/leanback/widget/VerticalGridPresenter$VerticalGridItemBridgeAdapter": "androidx/leanback/widget/VerticalGridPresenter$VerticalGridItemBridgeAdapter",
+      "android/support/v4/view/ViewCompat$ViewCompatApi21Impl": "androidx/view/ViewCompat$ViewCompatApi21Impl",
+      "android/support/v17/leanback/widget/SearchEditText$OnKeyboardDismissListener": "androidx/leanback/widget/SearchEditText$OnKeyboardDismissListener",
+      "android/support/annotation/AttrRes": "androidx/annotation/AttrRes",
+      "android/support/v17/leanback/app/BrowseFragment$FragmentHostImpl": "androidx/leanback/app/BrowseFragment$FragmentHostImpl",
+      "android/support/v4/graphics/PathParser": "androidx/graphics/PathParser",
+      "android/support/v17/leanback/app/BackgroundManager$BitmapDrawable": "androidx/leanback/app/BackgroundManager$BitmapDrawable",
+      "android/support/v4/util/MapCollections$ArrayIterator": "androidx/util/MapCollections$ArrayIterator",
+      "android/support/v4/view/AsyncLayoutInflater": "androidx/view/AsyncLayoutInflater",
+      "android/support/v4/media/MediaBrowserServiceCompat$Result": "androidx/media/MediaBrowserServiceCompat$Result",
+      "android/support/v4/media/MediaBrowserServiceCompat$MediaBrowserServiceImpl": "androidx/media/MediaBrowserServiceCompat$MediaBrowserServiceImpl",
+      "android/support/v17/leanback/app/RowsFragment$MainFragmentAdapter": "androidx/leanback/app/RowsFragment$MainFragmentAdapter",
+      "android/support/v17/internal/widget/OutlineOnlyWithChildrenFrameLayout": "androidx/internal/widget/OutlineOnlyWithChildrenFrameLayout",
+      "android/support/v4/media/session/MediaSessionCompat$MediaSessionImplApi21$ExtraSession": "androidx/media/session/MediaSessionCompat$MediaSessionImplApi21$ExtraSession",
+      "android/support/v17/leanback/util/MathUtil": "androidx/leanback/util/MathUtil",
+      "android/support/v4/app/RemoteInputCompatBase$RemoteInput$Factory": "androidx/app/RemoteInputCompatBase$RemoteInput$Factory",
+      "android/support/animation/DynamicAnimation": "androidx/animation/DynamicAnimation",
+      "android/support/transition/Visibility": "androidx/transition/Visibility",
+      "android/support/v4/widget/PopupWindowCompat$PopupWindowCompatApi21Impl": "androidx/widget/PopupWindowCompat$PopupWindowCompatApi21Impl",
+      "android/support/v7/app/AppCompatViewInflater": "androidx/app/AppCompatViewInflater",
+      "android/support/v7/widget/ListPopupWindow$PopupTouchInterceptor": "androidx/widget/ListPopupWindow$PopupTouchInterceptor",
+      "android/support/v4/app/ServiceCompat$StopForegroundFlags": "androidx/app/ServiceCompat$StopForegroundFlags",
+      "android/support/graphics/drawable/VectorDrawableCompat$VClipPath": "androidx/graphics/drawable/VectorDrawableCompat$VClipPath",
+      "android/support/v7/app/MediaRouteControllerDialog$VolumeGroupAdapter": "androidx/app/MediaRouteControllerDialog$VolumeGroupAdapter",
+      "android/support/design/R$style": "androidx/design/R$style",
+      "android/support/v4/media/MediaBrowserCompat$MediaBrowserImplApi26": "androidx/media/MediaBrowserCompat$MediaBrowserImplApi26",
+      "android/support/constraint/R$styleable": "androidx/constraint/R$styleable",
+      "android/support/v7/media/MediaRouterJellybeanMr1$RouteInfo": "androidx/media/MediaRouterJellybeanMr1$RouteInfo",
+      "android/support/v4/widget/CircleImageView$OvalShadow": "androidx/widget/CircleImageView$OvalShadow",
+      "android/support/v7/preference/ListPreferenceDialogFragmentCompat": "androidx/preference/ListPreferenceDialogFragmentCompat",
+      "android/support/v7/preference/PreferenceFragmentCompat$OnPreferenceStartFragmentCallback": "androidx/preference/PreferenceFragmentCompat$OnPreferenceStartFragmentCallback",
+      "android/support/v4/view/PagerTitleStrip$PageListener": "androidx/view/PagerTitleStrip$PageListener",
+      "android/support/v4/media/MediaBrowserCompat$MediaBrowserImplApi21": "androidx/media/MediaBrowserCompat$MediaBrowserImplApi21",
+      "android/support/v7/app/AppCompatDelegate$ApplyableNightMode": "androidx/app/AppCompatDelegate$ApplyableNightMode",
+      "android/support/customtabs/ICustomTabsCallback": "androidx/browser/customtabs/ICustomTabsCallback",
+      "android/support/v17/preference/LeanbackListPreferenceDialogFragment": "androidx/leanback/preference/LeanbackListPreferenceDialogFragment",
+      "android/support/v17/leanback/widget/PresenterSwitcher": "androidx/leanback/widget/PresenterSwitcher",
+      "android/support/v4/media/MediaBrowserCompat$MediaBrowserImplApi23": "androidx/media/MediaBrowserCompat$MediaBrowserImplApi23",
+      "android/support/v17/leanback/widget/ShadowOverlayHelper$Options": "androidx/leanback/widget/ShadowOverlayHelper$Options",
+      "android/support/v4/text/TextDirectionHeuristicsCompat$FirstStrong": "androidx/text/TextDirectionHeuristicsCompat$FirstStrong",
+      "android/support/v7/widget/AppCompatSeekBar": "androidx/widget/AppCompatSeekBar",
+      "android/support/v17/leanback/widget/Util": "androidx/leanback/widget/Util",
+      "android/support/v4/util/MapCollections": "androidx/util/MapCollections",
+      "android/support/v4/widget/NestedScrollView$SavedState": "androidx/widget/NestedScrollView$SavedState",
+      "android/support/v7/widget/SimpleItemAnimator": "androidx/widget/SimpleItemAnimator",
+      "android/support/v4/media/MediaMetadataCompatApi21$Builder": "androidx/media/MediaMetadataCompatApi21$Builder",
+      "android/support/design/widget/DrawableUtils": "androidx/design/widget/DrawableUtils",
+      "android/support/wear/internal/widget/drawer/SinglePagePresenter": "androidx/wear/internal/widget/drawer/SinglePagePresenter",
+      "android/support/v4/app/FragmentContainer": "androidx/app/FragmentContainer",
+      "android/support/v7/media/MediaRouteProviderDescriptor": "androidx/media/MediaRouteProviderDescriptor",
+      "android/support/v4/media/session/PlaybackStateCompat$Actions": "androidx/media/session/PlaybackStateCompat$Actions",
+      "android/support/v14/preference/ListPreferenceDialogFragment": "androidx/preference/ListPreferenceDialogFragment",
+      "android/support/v4/widget/SwipeRefreshLayout$OnChildScrollUpCallback": "androidx/widget/SwipeRefreshLayout$OnChildScrollUpCallback",
+      "android/support/v4/media/AudioAttributesCompatApi21": "androidx/media/AudioAttributesCompatApi21",
+      "android/support/v7/media/MediaRouter$Callback": "androidx/media/MediaRouter$Callback",
+      "android/support/v4/content/pm/ShortcutInfoCompat$Builder": "androidx/content/pm/ShortcutInfoCompat$Builder",
+      "android/support/v7/util/SortedList$BatchedCallback": "androidx/util/SortedList$BatchedCallback",
+      "android/support/v7/cardview/R$style": "androidx/cardview/R$style",
+      "android/support/v7/media/MediaRouter$GlobalMediaRouter$ProviderCallback": "androidx/media/MediaRouter$GlobalMediaRouter$ProviderCallback",
+      "android/support/v4/widget/SimpleCursorAdapter$ViewBinder": "androidx/widget/SimpleCursorAdapter$ViewBinder",
+      "android/support/v7/util/TileList$Tile": "androidx/util/TileList$Tile",
+      "android/support/v17/leanback/widget/ItemBridgeAdapter$AdapterListener": "androidx/leanback/widget/ItemBridgeAdapter$AdapterListener",
+      "android/support/v4/widget/SlidingPaneLayout$DragHelperCallback": "androidx/widget/SlidingPaneLayout$DragHelperCallback",
+      "android/support/v4/media/MediaBrowserCompat$SearchResultReceiver": "androidx/media/MediaBrowserCompat$SearchResultReceiver",
+      "android/support/v17/leanback/widget/PlaybackRowPresenter": "androidx/leanback/widget/PlaybackRowPresenter",
+      "android/support/v7/media/MediaRouteProvider$ProviderMetadata": "androidx/media/MediaRouteProvider$ProviderMetadata",
+      "android/support/text/emoji/flatbuffer/MetadataList": "androidx/text/emoji/flatbuffer/MetadataList",
+      "android/support/v4/provider/TreeDocumentFile": "androidx/provider/TreeDocumentFile",
+      "android/support/v7/media/MediaSessionStatus$Builder": "androidx/media/MediaSessionStatus$Builder",
+      "android/support/text/emoji/widget/EmojiTextWatcher": "androidx/text/emoji/widget/EmojiTextWatcher",
+      "android/support/v17/leanback/app/GuidedStepSupportFragment": "androidx/leanback/app/GuidedStepSupportFragment",
+      "android/support/v17/leanback/widget/BrowseFrameLayout$OnFocusSearchListener": "androidx/leanback/widget/BrowseFrameLayout$OnFocusSearchListener",
+      "android/support/animation/AnimationHandler": "androidx/animation/AnimationHandler",
+      "android/support/wear/widget/drawer/WearableActionDrawerView": "androidx/wear/widget/drawer/WearableActionDrawerView",
+      "android/support/v4/media/MediaBrowserCompat$CustomActionResultReceiver": "androidx/media/MediaBrowserCompat$CustomActionResultReceiver",
+      "android/support/v7/widget/AppCompatSeekBarHelper": "androidx/widget/AppCompatSeekBarHelper",
+      "android/support/v4/widget/SlidingPaneLayout$AccessibilityDelegate": "androidx/widget/SlidingPaneLayout$AccessibilityDelegate",
+      "android/support/v17/leanback/widget/TitleViewAdapter$Provider": "androidx/leanback/widget/TitleViewAdapter$Provider",
+      "android/support/v7/widget/GridLayoutManager$SpanSizeLookup": "androidx/widget/GridLayoutManager$SpanSizeLookup",
+      "android/support/v4/content/res/ResourcesCompat$FontCallback": "androidx/content/res/ResourcesCompat$FontCallback",
+      "android/support/v4/util/CircularArray": "androidx/util/CircularArray",
+      "android/support/v17/leanback/util/StateMachine$State": "androidx/leanback/util/StateMachine$State",
+      "android/support/text/emoji/widget/EmojiTextView": "androidx/text/emoji/widget/EmojiTextView",
+      "android/support/v4/widget/CursorAdapter$MyDataSetObserver": "androidx/widget/CursorAdapter$MyDataSetObserver",
+      "android/support/v4/os/LocaleListCompat$LocaleListCompatBaseImpl": "androidx/os/LocaleListCompat$LocaleListCompatBaseImpl",
+      "android/support/v4/app/TaskStackBuilder$SupportParentable": "androidx/app/TaskStackBuilder$SupportParentable",
+      "android/support/v17/leanback/media/PlaybackControlGlue": "androidx/leanback/media/PlaybackControlGlue",
+      "android/support/v17/leanback/widget/GuidedActionEditText": "androidx/leanback/widget/GuidedActionEditText",
+      "android/support/v4/util/Pools$SimplePool": "androidx/util/Pools$SimplePool",
+      "android/support/v17/leanback/widget/ControlButtonPresenterSelector$ControlButtonPresenter": "androidx/leanback/widget/ControlButtonPresenterSelector$ControlButtonPresenter",
+      "android/support/v17/leanback/widget/PlaybackControlsPresenter$ViewHolder": "androidx/leanback/widget/PlaybackControlsPresenter$ViewHolder",
+      "android/support/wear/widget/drawer/WearableDrawerLayout": "androidx/wear/widget/drawer/WearableDrawerLayout",
+      "android/support/v7/widget/ActionBarContainer": "androidx/widget/ActionBarContainer",
+      "android/support/v7/widget/ShareActionProvider$ShareActivityChooserModelPolicy": "androidx/widget/ShareActionProvider$ShareActivityChooserModelPolicy",
+      "android/support/transition/ObjectAnimatorUtilsApi21": "androidx/transition/ObjectAnimatorUtilsApi21",
+      "android/support/v17/leanback/widget/BaseOnItemViewSelectedListener": "androidx/leanback/widget/BaseOnItemViewSelectedListener",
+      "android/support/v7/widget/StaggeredGridLayoutManager$LayoutParams": "androidx/widget/StaggeredGridLayoutManager$LayoutParams",
+      "android/support/v7/mediarouter/R$interpolator": "androidx/mediarouter/R$interpolator",
+      "android/support/v4/media/VolumeProviderCompatApi21": "androidx/media/VolumeProviderCompatApi21",
+      "android/support/v17/leanback/widget/PlaybackControlsRow$PictureInPictureAction": "androidx/leanback/widget/PlaybackControlsRow$PictureInPictureAction",
+      "android/support/v7/util/MessageThreadUtil": "androidx/util/MessageThreadUtil",
+      "android/support/v4/util/LogWriter": "androidx/util/LogWriter",
+      "android/support/v7/preference/PreferenceDialogFragmentCompat": "androidx/preference/PreferenceDialogFragmentCompat",
+      "android/support/transition/ViewOverlayApi14": "androidx/transition/ViewOverlayApi14",
+      "android/support/v4/graphics/TypefaceCompatUtil": "androidx/graphics/TypefaceCompatUtil",
+      "android/support/media/tv/BasePreviewProgram": "androidx/media/tv/BasePreviewProgram",
+      "android/support/v17/leanback/widget/FullWidthDetailsOverviewRowPresenter$ViewHolder$DetailsOverviewRowListener": "androidx/leanback/widget/FullWidthDetailsOverviewRowPresenter$ViewHolder$DetailsOverviewRowListener",
+      "android/support/v7/widget/AlertDialogLayout": "androidx/widget/AlertDialogLayout",
+      "android/support/v7/widget/ActionBarOverlayLayout$LayoutParams": "androidx/widget/ActionBarOverlayLayout$LayoutParams",
+      "android/support/v4/view/NestedScrollingParentHelper": "androidx/view/NestedScrollingParentHelper",
+      "android/support/transition/ObjectAnimatorUtilsApi14": "androidx/transition/ObjectAnimatorUtilsApi14",
+      "android/support/text/emoji/EmojiCompat": "androidx/text/emoji/EmojiCompat",
+      "android/support/v4/media/MediaBrowserServiceCompatApi23$ServiceCompatProxy": "androidx/media/MediaBrowserServiceCompatApi23$ServiceCompatProxy",
+      "android/support/v17/leanback/widget/FocusHighlightHelper$FocusAnimator": "androidx/leanback/widget/FocusHighlightHelper$FocusAnimator",
+      "android/support/v17/leanback/app/PlaybackSupportFragment$SetSelectionRunnable": "androidx/leanback/app/PlaybackSupportFragment$SetSelectionRunnable",
+      "android/support/v4/widget/PopupWindowCompat$PopupWindowCompatApi23Impl": "androidx/widget/PopupWindowCompat$PopupWindowCompatApi23Impl",
+      "android/support/v7/preference/SwitchPreferenceCompat": "androidx/preference/SwitchPreferenceCompat",
+      "android/support/wear/widget/drawer/FlingWatcherFactory$FlingListener": "androidx/wear/widget/drawer/FlingWatcherFactory$FlingListener",
+      "android/support/v7/widget/ShareActionProvider": "androidx/widget/ShareActionProvider",
+      "android/support/v7/content/res/AppCompatResources": "androidx/content/res/AppCompatResources",
+      "android/support/transition/ViewOverlayApi18": "androidx/transition/ViewOverlayApi18",
+      "android/support/v7/widget/ListPopupWindow$ResizePopupRunnable": "androidx/widget/ListPopupWindow$ResizePopupRunnable",
+      "android/support/compat/R$integer": "androidx/compat/R$integer",
+      "android/support/v4/app/ActionBarDrawerToggle$SlideDrawable": "androidx/app/ActionBarDrawerToggle$SlideDrawable",
+      "android/support/transition/ChangeTransform$PathAnimatorMatrix": "androidx/transition/ChangeTransform$PathAnimatorMatrix",
+      "android/support/v17/leanback/graphics/ColorFilterDimmer": "androidx/leanback/graphics/ColorFilterDimmer",
+      "android/support/v7/media/MediaRouteDescriptor": "androidx/media/MediaRouteDescriptor",
+      "android/support/v7/preference/R$id": "androidx/preference/R$id",
+      "android/support/v14/preference/PreferenceFragment$ScrollToPreferenceObserver": "androidx/preference/PreferenceFragment$ScrollToPreferenceObserver",
+      "android/support/v4/internal/package-info": "androidx/internal/package-info",
+      "android/support/v4/media/MediaBrowserCompatApi21$SubscriptionCallback": "androidx/media/MediaBrowserCompatApi21$SubscriptionCallback",
+      "android/support/v7/view/menu/MenuHelper": "androidx/view/menu/MenuHelper",
+      "android/support/v17/leanback/widget/SpeechOrbView": "androidx/leanback/widget/SpeechOrbView",
+      "android/support/v7/media/SystemMediaRouteProvider$JellybeanImpl$SystemRouteController": "androidx/media/SystemMediaRouteProvider$JellybeanImpl$SystemRouteController",
+      "android/support/v7/widget/FitWindowsFrameLayout": "androidx/widget/FitWindowsFrameLayout",
+      "android/support/v7/preference/TwoStatePreference$SavedState": "androidx/preference/TwoStatePreference$SavedState",
+      "android/support/v17/leanback/widget/ShadowOverlayHelper$Builder": "androidx/leanback/widget/ShadowOverlayHelper$Builder",
+      "android/support/v17/leanback/widget/InvisibleRowPresenter": "androidx/leanback/widget/InvisibleRowPresenter",
+      "android/support/design/internal/NavigationMenuPresenter$SeparatorViewHolder": "androidx/design/internal/NavigationMenuPresenter$SeparatorViewHolder",
+      "android/support/design/widget/BaseTransientBottomBar$Duration": "androidx/design/widget/BaseTransientBottomBar$Duration",
+      "android/support/v7/widget/helper/ItemTouchUIUtilImpl": "androidx/widget/helper/ItemTouchUIUtilImpl",
+      "android/support/v17/leanback/widget/GuidedActionsRelativeLayout$InterceptKeyEventListener": "androidx/leanback/widget/GuidedActionsRelativeLayout$InterceptKeyEventListener",
+      "android/support/v4/os/IResultReceiver": "androidx/os/IResultReceiver",
+      "android/support/v17/leanback/widget/ShadowHelperApi21": "androidx/leanback/widget/ShadowHelperApi21",
+      "android/support/v17/leanback/widget/RowPresenter": "androidx/leanback/widget/RowPresenter",
+      "android/support/v4/media/MediaBrowserCompat$MediaBrowserImplBase": "androidx/media/MediaBrowserCompat$MediaBrowserImplBase",
+      "android/support/v4/app/BackStackRecord": "androidx/app/BackStackRecord",
+      "android/support/v4/view/WindowInsetsCompat": "androidx/view/WindowInsetsCompat",
+      "android/support/graphics/drawable/AnimatedVectorDrawableCompat": "androidx/graphics/drawable/AnimatedVectorDrawableCompat",
+      "android/support/v17/leanback/widget/PlaybackControlsRowPresenter": "androidx/leanback/widget/PlaybackControlsRowPresenter",
+      "android/support/transition/VisibilityPropagation": "androidx/transition/VisibilityPropagation",
+      "android/support/v17/leanback/media/PlaybackTransportControlGlue": "androidx/leanback/media/PlaybackTransportControlGlue",
+      "android/support/wear/widget/BoxInsetLayout$LayoutParams$BoxedEdges": "androidx/wear/widget/BoxInsetLayout$LayoutParams$BoxedEdges",
+      "android/support/annotation/LayoutRes": "androidx/annotation/LayoutRes",
+      "android/support/v4/widget/CursorAdapter": "androidx/widget/CursorAdapter",
+      "android/support/wear/ambient/AmbientMode$AmbientCallback": "androidx/wear/ambient/AmbientMode$AmbientCallback",
+      "android/support/v4/media/MediaBrowserServiceCompatApi21$BrowserRoot": "androidx/media/MediaBrowserServiceCompatApi21$BrowserRoot",
+      "android/support/v7/view/menu/MenuItemWrapperJB": "androidx/view/menu/MenuItemWrapperJB",
+      "android/support/text/emoji/widget/EmojiAppCompatButton": "androidx/text/emoji/widget/EmojiAppCompatButton",
+      "android/support/v4/view/ActionProvider$SubUiVisibilityListener": "androidx/view/ActionProvider$SubUiVisibilityListener",
+      "android/support/wear/widget/drawer/WearableActionDrawerMenu$WearableActionDrawerMenuItem$MenuItemChangedListener": "androidx/wear/widget/drawer/WearableActionDrawerMenu$WearableActionDrawerMenuItem$MenuItemChangedListener",
+      "android/support/v4/media/MediaBrowserCompat$ServiceBinderWrapper": "androidx/media/MediaBrowserCompat$ServiceBinderWrapper",
+      "android/support/v13/app/ActivityCompat": "androidx/app/ActivityCompat",
+      "android/support/v4/widget/ImageViewCompat$LollipopViewCompatImpl": "androidx/widget/ImageViewCompat$LollipopViewCompatImpl",
+      "android/support/annotation/UiThread": "androidx/annotation/UiThread",
+      "android/support/v4/media/session/MediaControllerCompatApi24": "androidx/media/session/MediaControllerCompatApi24",
+      "android/support/v7/media/RemoteControlClientCompat$JellybeanImpl$VolumeCallbackWrapper": "androidx/media/RemoteControlClientCompat$JellybeanImpl$VolumeCallbackWrapper",
+      "android/support/v4/media/session/MediaControllerCompatApi23": "androidx/media/session/MediaControllerCompatApi23",
+      "android/support/v4/media/session/MediaControllerCompatApi21": "androidx/media/session/MediaControllerCompatApi21",
+      "android/support/v4/print/PrintHelper$ScaleMode": "androidx/print/PrintHelper$ScaleMode",
+      "android/support/v7/widget/SearchView$SavedState": "androidx/widget/SearchView$SavedState",
+      "android/support/v7/widget/AppCompatDrawableManager$AvdcInflateDelegate": "androidx/widget/AppCompatDrawableManager$AvdcInflateDelegate",
+      "android/support/v7/preference/TwoStatePreference": "androidx/preference/TwoStatePreference",
+      "android/support/text/emoji/EmojiCompat$LoadState": "androidx/text/emoji/EmojiCompat$LoadState",
+      "android/support/v17/leanback/widget/HorizontalGridView": "androidx/leanback/widget/HorizontalGridView",
+      "android/support/v7/view/menu/ListMenuItemView": "androidx/view/menu/ListMenuItemView",
+      "android/support/transition/Styleable$TransitionSet": "androidx/transition/Styleable$TransitionSet",
+      "android/support/v17/leanback/app/PlaybackFragment$OnFadeCompleteListener": "androidx/leanback/app/PlaybackFragment$OnFadeCompleteListener",
+      "android/support/v7/widget/ActivityChooserModel$PersistHistoryAsyncTask": "androidx/widget/ActivityChooserModel$PersistHistoryAsyncTask",
+      "android/support/v4/util/ArraySet": "androidx/util/ArraySet",
+      "android/support/v7/widget/FastScroller$AnimationState": "androidx/widget/FastScroller$AnimationState",
+      "android/support/v7/widget/RecyclerView$ChildDrawingOrderCallback": "androidx/widget/RecyclerView$ChildDrawingOrderCallback",
+      "android/support/text/emoji/flatbuffer/Constants": "androidx/text/emoji/flatbuffer/Constants",
+      "android/support/v4/widget/DrawerLayout$LayoutParams": "androidx/widget/DrawerLayout$LayoutParams",
+      "android/support/v4/app/TaskStackBuilder": "androidx/app/TaskStackBuilder",
+      "android/support/v7/widget/ListPopupWindow$PopupDataSetObserver": "androidx/widget/ListPopupWindow$PopupDataSetObserver",
+      "android/support/v4/media/session/MediaSessionCompatApi24$CallbackProxy": "androidx/media/session/MediaSessionCompatApi24$CallbackProxy",
+      "android/support/v17/leanback/app/RowsSupportFragment$MainFragmentRowsAdapter": "androidx/leanback/app/RowsSupportFragment$MainFragmentRowsAdapter",
+      "android/support/v17/leanback/widget/picker/DatePicker": "androidx/leanback/widget/picker/DatePicker",
+      "android/support/v17/leanback/widget/SearchBar$SearchBarPermissionListener": "androidx/leanback/widget/SearchBar$SearchBarPermissionListener",
+      "android/support/v7/media/MediaItemMetadata": "androidx/media/MediaItemMetadata",
+      "android/support/v17/leanback/widget/ListRowPresenter": "androidx/leanback/widget/ListRowPresenter",
+      "android/support/v17/leanback/media/PlaybackBannerControlGlue$ACTION_": "androidx/leanback/media/PlaybackBannerControlGlue$ACTION_",
+      "android/support/v4/app/RemoteInput": "androidx/app/RemoteInput",
+      "android/support/text/emoji/MetadataListReader$ByteBufferReader": "androidx/text/emoji/MetadataListReader$ByteBufferReader",
+      "android/support/v17/leanback/widget/SearchEditText": "androidx/leanback/widget/SearchEditText",
+      "android/support/v4/app/NotificationCompat$CarExtender$UnreadConversation$Builder": "androidx/app/NotificationCompat$CarExtender$UnreadConversation$Builder",
+      "android/support/v7/preference/SeekBarPreference": "androidx/preference/SeekBarPreference",
+      "android/support/v17/leanback/R$transition": "androidx/leanback/R$transition",
+      "android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory": "androidx/graphics/drawable/RoundedBitmapDrawableFactory",
+      "android/support/v7/widget/PositionMap$ContainerHelpers": "androidx/widget/PositionMap$ContainerHelpers",
+      "android/support/v4/view/accessibility/AccessibilityManagerCompat$AccessibilityStateChangeListenerWrapper": "androidx/view/accessibility/AccessibilityManagerCompat$AccessibilityStateChangeListenerWrapper",
+      "android/support/design/widget/ViewUtils": "androidx/design/widget/ViewUtils",
+      "android/support/v4/widget/CompoundButtonCompat$CompoundButtonCompatApi23Impl": "androidx/widget/CompoundButtonCompat$CompoundButtonCompatApi23Impl",
+      "android/support/v7/widget/StaggeredGridLayoutManager$SavedState": "androidx/widget/StaggeredGridLayoutManager$SavedState",
+      "android/support/transition/PathMotion": "androidx/transition/PathMotion",
+      "android/support/wear/widget/drawer/WearableDrawerView": "androidx/wear/widget/drawer/WearableDrawerView",
+      "android/support/v4/media/VolumeProviderCompat$ControlType": "androidx/media/VolumeProviderCompat$ControlType",
+      "android/support/v4/view/animation/PathInterpolatorApi14": "androidx/view/animation/PathInterpolatorApi14",
+      "android/support/animation/AnimationHandler$FrameCallbackProvider16": "androidx/animation/AnimationHandler$FrameCallbackProvider16",
+      "android/support/v4/view/animation/FastOutSlowInInterpolator": "androidx/view/animation/FastOutSlowInInterpolator",
+      "android/support/animation/AnimationHandler$FrameCallbackProvider14": "androidx/animation/AnimationHandler$FrameCallbackProvider14",
+      "android/support/customtabs/CustomTabsServiceConnection": "androidx/browser/customtabs/CustomTabsServiceConnection",
+      "android/support/v17/leanback/app/BackgroundFragment": "androidx/leanback/app/BackgroundFragment",
+      "android/support/v17/leanback/widget/FocusHighlightHelper$BrowseItemFocusHighlight": "androidx/leanback/widget/FocusHighlightHelper$BrowseItemFocusHighlight",
+      "android/support/v17/leanback/app/HeadersFragment$OnHeaderClickedListener": "androidx/leanback/app/HeadersFragment$OnHeaderClickedListener",
+      "android/support/v7/media/MediaRouter$RouteInfo": "androidx/media/MediaRouter$RouteInfo",
+      "android/support/v14/preference/PreferenceFragment": "androidx/preference/PreferenceFragment",
+      "android/support/v4/media/session/PlaybackStateCompat$MediaKeyAction": "androidx/media/session/PlaybackStateCompat$MediaKeyAction",
+      "android/support/v17/leanback/widget/TitleView": "androidx/leanback/widget/TitleView",
+      "android/support/graphics/drawable/VectorDrawableCompat$VPath": "androidx/graphics/drawable/VectorDrawableCompat$VPath",
+      "android/support/v17/leanback/widget/AbstractMediaItemPresenter$ViewHolder": "androidx/leanback/widget/AbstractMediaItemPresenter$ViewHolder",
+      "android/support/v17/leanback/widget/BrowseRowsFrameLayout": "androidx/leanback/widget/BrowseRowsFrameLayout",
+      "android/support/v7/content/res/AppCompatResources$ColorStateListCacheEntry": "androidx/content/res/AppCompatResources$ColorStateListCacheEntry",
+      "android/support/design/R$dimen": "androidx/design/R$dimen",
+      "android/support/v7/preference/PreferenceInflater": "androidx/preference/PreferenceInflater",
+      "android/support/v17/leanback/widget/PlaybackControlsRow$PlayPauseAction": "androidx/leanback/widget/PlaybackControlsRow$PlayPauseAction",
+      "android/support/v17/leanback/widget/PlaybackTransportRowPresenter": "androidx/leanback/widget/PlaybackTransportRowPresenter",
+      "android/support/wear/BuildConfig": "androidx/wear/BuildConfig",
+      "android/support/wear/widget/CircledImageView": "androidx/wear/widget/CircledImageView",
+      "android/support/v7/media/MediaRouterJellybean$SelectRouteWorkaround": "androidx/media/MediaRouterJellybean$SelectRouteWorkaround",
+      "android/support/v17/leanback/widget/ListRowHoverCardView": "androidx/leanback/widget/ListRowHoverCardView",
+      "android/support/v7/media/MediaRouterJellybean$GetDefaultRouteWorkaround": "androidx/media/MediaRouterJellybean$GetDefaultRouteWorkaround",
+      "android/support/text/emoji/widget/SpannableBuilder$WatcherWrapper": "androidx/text/emoji/widget/SpannableBuilder$WatcherWrapper",
+      "android/support/v17/preference/LeanbackListPreferenceDialogFragment$AdapterSingle": "androidx/leanback/preference/LeanbackListPreferenceDialogFragment$AdapterSingle",
+      "android/support/v17/leanback/app/OnboardingFragment": "androidx/leanback/app/OnboardingFragment",
+      "android/support/v7/view/menu/MenuPresenter$Callback": "androidx/view/menu/MenuPresenter$Callback",
+      "android/support/v4/graphics/drawable/DrawableWrapperApi14$DrawableWrapperStateBase": "androidx/graphics/drawable/DrawableWrapperApi14$DrawableWrapperStateBase",
+      "android/support/v17/leanback/graphics/CompositeDrawable$ChildDrawable": "androidx/leanback/graphics/CompositeDrawable$ChildDrawable",
+      "android/support/v17/leanback/widget/AbstractMediaListHeaderPresenter$ViewHolder": "androidx/leanback/widget/AbstractMediaListHeaderPresenter$ViewHolder",
+      "android/support/v7/view/menu/MenuItemWrapperICS$OnMenuItemClickListenerWrapper": "androidx/view/menu/MenuItemWrapperICS$OnMenuItemClickListenerWrapper",
+      "android/support/v7/widget/ContentFrameLayout$OnAttachListener": "androidx/widget/ContentFrameLayout$OnAttachListener",
+      "android/support/v17/leanback/widget/StaticShadowHelper": "androidx/leanback/widget/StaticShadowHelper",
+      "android/support/v7/widget/RecyclerView$OnScrollListener": "androidx/widget/RecyclerView$OnScrollListener",
+      "android/support/v17/leanback/graphics/FitWidthBitmapDrawable$BitmapState": "androidx/leanback/graphics/FitWidthBitmapDrawable$BitmapState",
+      "android/support/v17/leanback/widget/SeekBar": "androidx/leanback/widget/SeekBar",
+      "android/support/v4/widget/SlidingPaneLayout$SimplePanelSlideListener": "androidx/widget/SlidingPaneLayout$SimplePanelSlideListener",
+      "android/support/v17/leanback/widget/RowContainerView": "androidx/leanback/widget/RowContainerView",
+      "android/support/v7/widget/ForwardingListener$DisallowIntercept": "androidx/widget/ForwardingListener$DisallowIntercept",
+      "android/support/v7/app/AppCompatDelegateImplBase": "androidx/app/AppCompatDelegateImplBase",
+      "android/support/v17/leanback/app/PlaybackSupportFragmentGlueHost": "androidx/leanback/app/PlaybackSupportFragmentGlueHost",
+      "android/support/transition/Styleable$Transition": "androidx/transition/Styleable$Transition",
+      "android/support/v7/widget/ActionMenuPresenter": "androidx/widget/ActionMenuPresenter",
+      "android/support/v7/app/ActionBar$DisplayOptions": "androidx/app/ActionBar$DisplayOptions",
+      "android/support/v4/app/ShareCompat$IntentBuilder": "androidx/app/ShareCompat$IntentBuilder",
+      "android/support/v4/media/MediaMetadataCompat$LongKey": "androidx/media/MediaMetadataCompat$LongKey",
+      "android/support/v4/media/AudioAttributesCompatApi21$Wrapper": "androidx/media/AudioAttributesCompatApi21$Wrapper",
+      "android/support/exifinterface/BuildConfig": "androidx/exifinterface/BuildConfig",
+      "android/support/v17/leanback/widget/DetailsOverviewRow": "androidx/leanback/widget/DetailsOverviewRow",
+      "android/support/annotation/IdRes": "androidx/annotation/IdRes",
+      "android/support/v7/widget/ActivityChooserModel$HistoricalRecord": "androidx/widget/ActivityChooserModel$HistoricalRecord",
+      "android/support/v17/leanback/widget/SearchOrbView$Colors": "androidx/leanback/widget/SearchOrbView$Colors",
+      "android/support/v7/content/res/GrowingArrayUtils": "androidx/content/res/GrowingArrayUtils",
+      "android/support/text/emoji/MetadataRepo$Node": "androidx/text/emoji/MetadataRepo$Node",
+      "android/support/v17/leanback/app/BrowseFragment$MainFragmentItemViewSelectedListener": "androidx/leanback/app/BrowseFragment$MainFragmentItemViewSelectedListener",
+      "android/support/v17/leanback/app/SearchSupportFragment$ExternalQuery": "androidx/leanback/app/SearchSupportFragment$ExternalQuery",
+      "android/support/v17/leanback/widget/ShadowHelperJbmr2$ShadowImpl": "androidx/leanback/widget/ShadowHelperJbmr2$ShadowImpl",
+      "android/support/wear/widget/drawer/PageIndicatorView": "androidx/wear/widget/drawer/PageIndicatorView",
+      "android/support/v17/leanback/app/VerticalGridSupportFragment": "androidx/leanback/app/VerticalGridSupportFragment",
+      "android/support/v7/preference/PreferenceGroupAdapter$PreferenceLayout": "androidx/preference/PreferenceGroupAdapter$PreferenceLayout",
+      "android/support/v7/preference/internal/AbstractMultiSelectListPreference": "androidx/preference/internal/AbstractMultiSelectListPreference",
+      "android/support/v7/media/MediaRouteProviderDescriptor$Builder": "androidx/media/MediaRouteProviderDescriptor$Builder",
+      "android/support/v4/app/NotificationCompatSideChannelService": "androidx/app/NotificationCompatSideChannelService",
+      "android/support/v7/widget/GridLayoutManager": "androidx/widget/GridLayoutManager",
+      "android/support/v4/media/AudioAttributesCompat$AttributeUsage": "androidx/media/AudioAttributesCompat$AttributeUsage",
+      "android/support/v4/util/Pools$Pool": "androidx/util/Pools$Pool",
+      "android/support/transition/SidePropagation": "androidx/transition/SidePropagation",
+      "android/support/v17/leanback/widget/PlaybackSeekUi$Client": "androidx/leanback/widget/PlaybackSeekUi$Client",
+      "android/support/media/tv/TvContractCompat$Channels": "androidx/media/tv/TvContractCompat$Channels",
+      "android/support/v7/appcompat/R$style": "androidx/appcompat/R$style",
+      "android/support/v4/app/INotificationSideChannel$Stub": "androidx/app/INotificationSideChannel$Stub",
+      "android/support/compat/R$string": "androidx/compat/R$string",
+      "android/support/v4/os/EnvironmentCompat": "androidx/os/EnvironmentCompat",
+      "android/support/wear/ambient/SharedLibraryVersion": "androidx/wear/ambient/SharedLibraryVersion",
+      "android/support/v7/view/menu/MenuBuilder$Callback": "androidx/view/menu/MenuBuilder$Callback",
+      "android/support/v17/leanback/app/BrowseSupportFragment$MainFragmentRowsAdapterProvider": "androidx/leanback/app/BrowseSupportFragment$MainFragmentRowsAdapterProvider",
+      "android/support/v17/leanback/R": "androidx/leanback/R",
+      "android/support/transition/Slide": "androidx/transition/Slide",
+      "android/support/v4/os/ParcelableCompat$ParcelableCompatCreatorHoneycombMR2": "androidx/os/ParcelableCompat$ParcelableCompatCreatorHoneycombMR2",
+      "android/support/v17/leanback/widget/PlaybackSeekUi": "androidx/leanback/widget/PlaybackSeekUi",
+      "android/support/v4/view/ViewPropertyAnimatorUpdateListener": "androidx/view/ViewPropertyAnimatorUpdateListener",
+      "android/support/v4/widget/EdgeEffectCompat$EdgeEffectBaseImpl": "androidx/widget/EdgeEffectCompat$EdgeEffectBaseImpl",
+      "android/support/v7/widget/ForwardingListener$TriggerLongPress": "androidx/widget/ForwardingListener$TriggerLongPress",
+      "android/support/text/emoji/bundled/BuildConfig": "androidx/text/emoji/bundled/BuildConfig",
+      "android/support/v4/app/ActivityCompat$SharedElementCallback23Impl": "androidx/app/ActivityCompat$SharedElementCallback23Impl",
+      "android/support/v4/media/MediaBrowserCompatApi21$ConnectionCallback": "androidx/media/MediaBrowserCompatApi21$ConnectionCallback",
+      "android/support/v4/content/Loader$ForceLoadContentObserver": "androidx/content/Loader$ForceLoadContentObserver",
+      "android/support/v7/widget/ScrollingTabContainerView$TabClickListener": "androidx/widget/ScrollingTabContainerView$TabClickListener",
+      "android/support/v4/widget/TextViewCompat$TextViewCompatApi27Impl": "androidx/widget/TextViewCompat$TextViewCompatApi27Impl",
+      "android/support/v17/leanback/widget/GridLayoutManager$OnLayoutCompleteListener": "androidx/leanback/widget/GridLayoutManager$OnLayoutCompleteListener",
+      "android/support/v4/provider/SelfDestructiveThread$ReplyCallback": "androidx/provider/SelfDestructiveThread$ReplyCallback",
+      "android/support/v4/media/VolumeProviderCompat$Callback": "androidx/media/VolumeProviderCompat$Callback",
+      "android/support/v4/app/NotificationCompat$CarExtender$UnreadConversation": "androidx/app/NotificationCompat$CarExtender$UnreadConversation",
+      "android/support/graphics/drawable/AnimatorInflaterCompat": "androidx/graphics/drawable/AnimatorInflaterCompat",
+      "android/support/compat/R$id": "androidx/compat/R$id",
+      "android/support/v17/leanback/app/DetailsFragment$WaitEnterTransitionTimeout": "androidx/leanback/app/DetailsFragment$WaitEnterTransitionTimeout",
+      "android/support/media/tv/TvContractCompat$Channels$ServiceType": "androidx/media/tv/TvContractCompat$Channels$ServiceType",
+      "android/support/v17/leanback/widget/Presenter$ViewHolder": "androidx/leanback/widget/Presenter$ViewHolder",
+      "android/support/text/emoji/EmojiCompat$Config": "androidx/text/emoji/EmojiCompat$Config",
+      "android/support/v17/leanback/app/BackgroundManager$ChangeBackgroundRunnable": "androidx/leanback/app/BackgroundManager$ChangeBackgroundRunnable",
+      "android/support/v17/leanback/util/StateMachine$Transition": "androidx/leanback/util/StateMachine$Transition",
+      "android/support/v17/leanback/widget/picker/TimePicker": "androidx/leanback/widget/picker/TimePicker",
+      "android/support/v7/media/RegisteredMediaRouteProvider$Controller": "androidx/media/RegisteredMediaRouteProvider$Controller",
+      "android/support/text/emoji/TypefaceEmojiSpan": "androidx/text/emoji/TypefaceEmojiSpan",
+      "android/support/wear/widget/SwipeDismissFrameLayout": "androidx/wear/widget/SwipeDismissFrameLayout",
+      "android/support/v4/media/MediaBrowserCompatUtils": "androidx/media/MediaBrowserCompatUtils",
+      "android/support/v17/leanback/app/HeadersSupportFragment": "androidx/leanback/app/HeadersSupportFragment",
+      "android/support/v7/widget/SearchView$OnQueryTextListener": "androidx/widget/SearchView$OnQueryTextListener",
+      "android/support/v17/leanback/widget/AbstractMediaListHeaderPresenter": "androidx/leanback/widget/AbstractMediaListHeaderPresenter",
+      "android/support/design/widget/BaseTransientBottomBar$OnLayoutChangeListener": "androidx/design/widget/BaseTransientBottomBar$OnLayoutChangeListener",
+      "android/support/v14/preference/R$styleable": "androidx/preference/R$styleable",
+      "android/support/v17/leanback/transition/SlideKitkat": "androidx/leanback/transition/SlideKitkat",
+      "android/support/animation/AnimationHandler$AnimationFrameCallbackProvider": "androidx/animation/AnimationHandler$AnimationFrameCallbackProvider",
+      "android/support/v7/view/menu/ActionMenuItem": "androidx/view/menu/ActionMenuItem",
+      "android/support/v4/view/animation/PathInterpolatorCompat": "androidx/view/animation/PathInterpolatorCompat",
+      "android/support/v7/util/AsyncListUtil$DataCallback": "androidx/util/AsyncListUtil$DataCallback",
+      "android/support/v17/leanback/widget/ListRowPresenter$ListRowPresenterItemBridgeAdapter": "androidx/leanback/widget/ListRowPresenter$ListRowPresenterItemBridgeAdapter",
+      "android/support/v7/mediarouter/R$style": "androidx/mediarouter/R$style",
+      "android/support/customtabs/IPostMessageService$Stub": "androidx/browser/customtabs/IPostMessageService$Stub",
+      "android/support/v17/preference/BuildConfig": "androidx/leanback/preference/BuildConfig",
+      "android/support/wear/widget/CircularProgressLayout$OnTimerFinishedListener": "androidx/wear/widget/CircularProgressLayout$OnTimerFinishedListener",
+      "android/support/v7/widget/ActivityChooserModel$OnChooseActivityListener": "androidx/widget/ActivityChooserModel$OnChooseActivityListener",
+      "android/support/v4/graphics/drawable/DrawableWrapperApi21": "androidx/graphics/drawable/DrawableWrapperApi21",
+      "android/support/design/widget/FloatingActionButton$Size": "androidx/design/widget/FloatingActionButton$Size",
+      "android/support/v4/provider/DocumentFile": "androidx/provider/DocumentFile",
+      "android/support/v7/app/AppCompatDelegateImplBase$AppCompatWindowCallbackBase": "androidx/app/AppCompatDelegateImplBase$AppCompatWindowCallbackBase",
+      "android/support/annotation/AnyThread": "androidx/annotation/AnyThread",
+      "android/support/v13/app/FragmentCompat$FragmentCompatApi24Impl": "androidx/app/FragmentCompat$FragmentCompatApi24Impl",
+      "android/support/wear/ambient/SharedLibraryVersion$VersionHolder": "androidx/wear/ambient/SharedLibraryVersion$VersionHolder",
+      "android/support/v7/app/WindowDecorActionBar$ActionModeImpl": "androidx/app/WindowDecorActionBar$ActionModeImpl",
+      "android/support/constraint/solver/widgets/Guideline": "androidx/constraint/solver/widgets/Guideline",
+      "android/support/v17/leanback/widget/NonOverlappingView": "androidx/leanback/widget/NonOverlappingView",
+      "android/support/v7/preference/ListPreference": "androidx/preference/ListPreference",
+      "android/support/v17/leanback/widget/ShadowOverlayContainer": "androidx/leanback/widget/ShadowOverlayContainer",
+      "android/support/design/internal/NavigationMenuItemView": "androidx/design/internal/NavigationMenuItemView",
+      "android/support/v17/leanback/widget/ArrayObjectAdapter": "androidx/leanback/widget/ArrayObjectAdapter",
+      "android/support/v17/leanback/widget/CursorObjectAdapter": "androidx/leanback/widget/CursorObjectAdapter",
+      "android/support/v17/leanback/R$style": "androidx/leanback/R$style",
+      "android/support/v4/media/MediaBrowserCompatApi21": "androidx/media/MediaBrowserCompatApi21",
+      "android/support/v4/media/MediaBrowserCompatApi23": "androidx/media/MediaBrowserCompatApi23",
+      "android/support/v17/leanback/widget/FullWidthDetailsOverviewSharedElementHelper": "androidx/leanback/widget/FullWidthDetailsOverviewSharedElementHelper",
+      "android/support/v7/widget/ShareActionProvider$ShareMenuItemOnMenuItemClickListener": "androidx/widget/ShareActionProvider$ShareMenuItemOnMenuItemClickListener",
+      "android/support/v4/media/MediaBrowserCompatApi26": "androidx/media/MediaBrowserCompatApi26",
+      "android/support/v4/print/PrintHelper$Orientation": "androidx/print/PrintHelper$Orientation",
+      "android/support/v4/view/ViewPager$PageTransformer": "androidx/view/ViewPager$PageTransformer",
+      "android/support/transition/ViewGroupOverlayImpl": "androidx/transition/ViewGroupOverlayImpl",
+      "android/support/v4/app/ActivityOptionsCompat$ActivityOptionsCompatApi23Impl": "androidx/app/ActivityOptionsCompat$ActivityOptionsCompatApi23Impl",
+      "android/support/v4/view/ViewPropertyAnimatorListener": "androidx/view/ViewPropertyAnimatorListener",
+      "android/support/transition/Styleable$ChangeBounds": "androidx/transition/Styleable$ChangeBounds",
+      "android/support/annotation/Keep": "androidx/annotation/Keep",
+      "android/support/v4/app/NotificationCompat$DecoratedCustomViewStyle": "androidx/app/NotificationCompat$DecoratedCustomViewStyle",
+      "android/support/v4/app/ShareCompat$IntentReader": "androidx/app/ShareCompat$IntentReader",
+      "android/support/v17/leanback/widget/ActionPresenterSelector$ActionPresenter": "androidx/leanback/widget/ActionPresenterSelector$ActionPresenter",
+      "android/support/v7/util/MessageThreadUtil$MessageQueue": "androidx/util/MessageThreadUtil$MessageQueue",
+      "android/support/annotation/Px": "androidx/annotation/Px",
+      "android/support/v17/leanback/app/SearchFragment$SearchResultProvider": "androidx/leanback/app/SearchFragment$SearchResultProvider",
+      "android/support/v7/app/AppCompatDelegateImplV9$ListMenuDecorView": "androidx/app/AppCompatDelegateImplV9$ListMenuDecorView",
+      "android/support/v17/leanback/transition/TransitionHelper$TransitionHelperStubImpl$TransitionStub": "androidx/leanback/transition/TransitionHelper$TransitionHelperStubImpl$TransitionStub",
+      "android/support/v7/app/AppCompatDelegate": "androidx/app/AppCompatDelegate",
+      "android/support/v17/leanback/app/BrowseFragment$MainFragmentAdapter": "androidx/leanback/app/BrowseFragment$MainFragmentAdapter",
+      "android/support/v17/leanback/widget/ParallaxEffect$IntEffect": "androidx/leanback/widget/ParallaxEffect$IntEffect",
+      "android/support/v7/widget/GridLayout$Alignment": "androidx/widget/GridLayout$Alignment",
+      "android/support/v7/widget/helper/ItemTouchHelper$RecoverAnimation": "androidx/widget/helper/ItemTouchHelper$RecoverAnimation",
+      "android/support/v17/leanback/widget/picker/Picker": "androidx/leanback/widget/picker/Picker",
+      "android/support/v7/gridlayout/R$styleable": "androidx/gridlayout/R$styleable",
+      "android/support/v4/app/FragmentPagerAdapter": "androidx/app/FragmentPagerAdapter",
+      "android/support/v4/graphics/TypefaceCompatBaseImpl$StyleExtractor": "androidx/graphics/TypefaceCompatBaseImpl$StyleExtractor",
+      "android/support/v17/leanback/widget/SearchBar": "androidx/leanback/widget/SearchBar",
+      "android/support/wear/widget/drawer/WearableDrawerController": "androidx/wear/widget/drawer/WearableDrawerController",
+      "android/support/design/widget/FloatingActionButtonImpl$DisabledElevationAnimation": "androidx/design/widget/FloatingActionButtonImpl$DisabledElevationAnimation",
+      "android/support/annotation/FloatRange": "androidx/annotation/FloatRange",
+      "android/support/media/ExifInterface$ByteOrderedDataInputStream": "androidx/media/ExifInterface$ByteOrderedDataInputStream",
+      "android/support/v7/widget/TooltipCompat$Api26ViewCompatImpl": "androidx/widget/TooltipCompat$Api26ViewCompatImpl",
+      "android/support/v7/widget/RecyclerViewAccessibilityDelegate": "androidx/widget/RecyclerViewAccessibilityDelegate",
+      "android/support/v7/widget/AppCompatTextView": "androidx/widget/AppCompatTextView",
+      "android/support/transition/ChangeImageTransform": "androidx/transition/ChangeImageTransform",
+      "android/support/v4/app/SuperNotCalledException": "androidx/app/SuperNotCalledException",
+      "android/support/text/emoji/widget/EmojiTextViewHelper$HelperInternal19": "androidx/text/emoji/widget/EmojiTextViewHelper$HelperInternal19",
+      "android/support/v7/widget/GapWorker$LayoutPrefetchRegistryImpl": "androidx/widget/GapWorker$LayoutPrefetchRegistryImpl",
+      "android/support/v7/app/AppCompatDelegateImplV14$AppCompatWindowCallbackV14": "androidx/app/AppCompatDelegateImplV14$AppCompatWindowCallbackV14",
+      "android/support/v4/widget/TextViewCompat$TextViewCompatApi23Impl": "androidx/widget/TextViewCompat$TextViewCompatApi23Impl",
+      "android/support/v17/leanback/widget/ParallaxEffect$FloatEffect": "androidx/leanback/widget/ParallaxEffect$FloatEffect",
+      "android/support/v4/media/session/MediaSessionCompat": "androidx/media/session/MediaSessionCompat",
+      "android/support/v17/leanback/app/BrowseSupportFragment$FragmentHostImpl": "androidx/leanback/app/BrowseSupportFragment$FragmentHostImpl",
+      "android/support/design/widget/ShadowDrawableWrapper": "androidx/design/widget/ShadowDrawableWrapper",
+      "android/support/design/internal/NavigationMenuPresenter$HeaderViewHolder": "androidx/design/internal/NavigationMenuPresenter$HeaderViewHolder",
+      "android/support/v7/widget/PopupMenu": "androidx/widget/PopupMenu",
+      "android/support/v17/preference/LeanbackListPreferenceDialogFragment$ViewHolder": "androidx/leanback/preference/LeanbackListPreferenceDialogFragment$ViewHolder",
+      "android/support/annotation/BoolRes": "androidx/annotation/BoolRes",
+      "android/support/v7/media/RemotePlaybackClient": "androidx/media/RemotePlaybackClient",
+      "android/support/v4/view/LayoutInflaterCompat$LayoutInflaterCompatApi21Impl": "androidx/view/LayoutInflaterCompat$LayoutInflaterCompatApi21Impl",
+      "android/support/v7/preference/AndroidResources": "androidx/preference/AndroidResources",
+      "android/support/v7/app/AlertController$AlertParams$OnPrepareListViewListener": "androidx/app/AlertController$AlertParams$OnPrepareListViewListener",
+      "android/support/wear/widget/drawer/WearableNavigationDrawerView": "androidx/wear/widget/drawer/WearableNavigationDrawerView",
+      "android/support/v4/media/session/PlaybackStateCompat$ErrorCode": "androidx/media/session/PlaybackStateCompat$ErrorCode",
+      "android/support/v7/widget/ActionMenuView$ActionMenuPresenterCallback": "androidx/widget/ActionMenuView$ActionMenuPresenterCallback",
+      "android/support/v7/app/AlertController": "androidx/app/AlertController",
+      "android/support/v7/widget/FitWindowsViewGroup$OnFitSystemWindowsListener": "androidx/widget/FitWindowsViewGroup$OnFitSystemWindowsListener",
+      "android/support/v4/app/NotificationBuilderWithBuilderAccessor": "androidx/app/NotificationBuilderWithBuilderAccessor",
+      "android/support/v4/view/AbsSavedState": "androidx/view/AbsSavedState",
+      "android/support/v7/media/MediaRouteProvider$ProviderHandler": "androidx/media/MediaRouteProvider$ProviderHandler",
+      "android/support/v7/widget/AbsActionBarView$VisibilityAnimListener": "androidx/widget/AbsActionBarView$VisibilityAnimListener",
+      "android/support/design/internal/TextScale": "androidx/design/internal/TextScale",
+      "android/support/v7/widget/RecyclerViewAccessibilityDelegate$ItemDelegate": "androidx/widget/RecyclerViewAccessibilityDelegate$ItemDelegate",
+      "android/support/v4/widget/SlidingPaneLayout": "androidx/widget/SlidingPaneLayout",
+      "android/support/v17/leanback/widget/ScaleFrameLayout": "androidx/leanback/widget/ScaleFrameLayout",
+      "android/support/v4/app/FragmentTransaction": "androidx/app/FragmentTransaction",
+      "android/support/graphics/drawable/AnimatorInflaterCompat$PathDataEvaluator": "androidx/graphics/drawable/AnimatorInflaterCompat$PathDataEvaluator",
+      "android/support/wear/widget/drawer/WearableActionDrawerView$ActionItemViewHolder": "androidx/wear/widget/drawer/WearableActionDrawerView$ActionItemViewHolder",
+      "android/support/wear/ambient/AmbientDelegate": "androidx/wear/ambient/AmbientDelegate",
+      "android/support/v17/leanback/widget/PlaybackControlsRow$ThumbsUpAction": "androidx/leanback/widget/PlaybackControlsRow$ThumbsUpAction",
+      "android/support/v7/widget/AppCompatSpinner": "androidx/widget/AppCompatSpinner",
+      "android/support/design/widget/NavigationView$SavedState": "androidx/design/widget/NavigationView$SavedState",
+      "android/support/v17/leanback/animation/LogDecelerateInterpolator": "androidx/leanback/animation/LogDecelerateInterpolator",
+      "android/support/v4/media/session/MediaControllerCompatApi21$Callback": "androidx/media/session/MediaControllerCompatApi21$Callback",
+      "android/support/v7/widget/helper/ItemTouchHelper": "androidx/widget/helper/ItemTouchHelper",
+      "android/support/graphics/drawable/AnimatedVectorDrawableCompat$AnimatedVectorDrawableCompatState": "androidx/graphics/drawable/AnimatedVectorDrawableCompat$AnimatedVectorDrawableCompatState",
+      "android/support/v4/graphics/drawable/DrawableWrapperApi14$DrawableWrapperState": "androidx/graphics/drawable/DrawableWrapperApi14$DrawableWrapperState",
+      "android/support/v4/util/DebugUtils": "androidx/util/DebugUtils",
+      "android/support/customtabs/CustomTabsCallback": "androidx/browser/customtabs/CustomTabsCallback",
+      "android/support/v13/app/FragmentCompat": "androidx/app/FragmentCompat",
+      "android/support/v7/widget/RecyclerView$ViewFlinger": "androidx/widget/RecyclerView$ViewFlinger",
+      "android/support/v17/leanback/app/BrowseFragment$SetSelectionRunnable": "androidx/leanback/app/BrowseFragment$SetSelectionRunnable",
+      "android/support/mediacompat/BuildConfig": "androidx/mediacompat/BuildConfig",
+      "android/support/customtabs/CustomTabsIntent$Builder": "androidx/browser/customtabs/CustomTabsIntent$Builder",
+      "android/support/v4/util/MapCollections$ValuesCollection": "androidx/util/MapCollections$ValuesCollection",
+      "android/support/v17/leanback/widget/StaggeredGrid": "androidx/leanback/widget/StaggeredGrid",
+      "android/support/v13/app/FragmentTabHost$SavedState": "androidx/app/FragmentTabHost$SavedState",
+      "android/support/v4/media/MediaBrowserServiceCompatApi23": "androidx/media/MediaBrowserServiceCompatApi23",
+      "android/support/v4/media/session/MediaButtonReceiver": "androidx/media/session/MediaButtonReceiver",
+      "android/support/v17/leanback/graphics/CompositeDrawable$CompositeState": "androidx/leanback/graphics/CompositeDrawable$CompositeState",
+      "android/support/transition/WindowIdApi14": "androidx/transition/WindowIdApi14",
+      "android/support/v4/media/MediaBrowserServiceCompatApi26": "androidx/media/MediaBrowserServiceCompatApi26",
+      "android/support/v4/media/MediaBrowserServiceCompatApi21": "androidx/media/MediaBrowserServiceCompatApi21",
+      "android/support/transition/WindowIdApi18": "androidx/transition/WindowIdApi18",
+      "android/support/v7/graphics/ColorCutQuantizer": "androidx/graphics/palette/ColorCutQuantizer",
+      "android/support/v4/media/session/MediaSessionCompatApi23$Callback": "androidx/media/session/MediaSessionCompatApi23$Callback",
+      "android/support/v17/leanback/widget/DetailsOverviewRowPresenter$ActionsItemBridgeAdapter": "androidx/leanback/widget/DetailsOverviewRowPresenter$ActionsItemBridgeAdapter",
+      "android/support/v7/app/MediaRouteButton$RemoteIndicatorLoader": "androidx/app/MediaRouteButton$RemoteIndicatorLoader",
+      "android/support/v7/preference/ListPreference$SavedState": "androidx/preference/ListPreference$SavedState",
+      "android/support/v7/media/RegisteredMediaRouteProvider$ReceiveHandler": "androidx/media/RegisteredMediaRouteProvider$ReceiveHandler",
+      "android/support/v17/leanback/widget/MediaRowFocusView": "androidx/leanback/widget/MediaRowFocusView",
+      "android/support/v14/preference/PreferenceFragment$OnPreferenceDisplayDialogCallback": "androidx/preference/PreferenceFragment$OnPreferenceDisplayDialogCallback",
+      "android/support/v17/leanback/widget/PlaybackControlsRow$HighQualityAction": "androidx/leanback/widget/PlaybackControlsRow$HighQualityAction",
+      "android/support/v17/leanback/app/BaseRowSupportFragment": "androidx/leanback/app/BaseRowSupportFragment",
+      "android/support/v17/leanback/app/BrowseSupportFragment$MainFragmentItemViewSelectedListener": "androidx/leanback/app/BrowseSupportFragment$MainFragmentItemViewSelectedListener",
+      "android/support/v17/leanback/widget/StreamingTextView": "androidx/leanback/widget/StreamingTextView",
+      "android/support/transition/GhostViewImpl$Creator": "androidx/transition/GhostViewImpl$Creator",
+      "android/support/wear/widget/drawer/WearableActionDrawerMenu$WearableActionDrawerMenuItem": "androidx/wear/widget/drawer/WearableActionDrawerMenu$WearableActionDrawerMenuItem",
+      "android/support/v4/media/MediaBrowserCompat$Subscription": "androidx/media/MediaBrowserCompat$Subscription",
+      "android/support/v4/view/PagerTitleStrip$SingleLineAllCapsTransform": "androidx/view/PagerTitleStrip$SingleLineAllCapsTransform",
+      "android/support/v4/media/session/MediaControllerCompatApi24$TransportControls": "androidx/media/session/MediaControllerCompatApi24$TransportControls",
+      "android/support/v7/app/ActionBarDrawerToggleHoneycomb": "androidx/app/ActionBarDrawerToggleHoneycomb",
+      "android/support/v4/graphics/TypefaceCompatBaseImpl": "androidx/graphics/TypefaceCompatBaseImpl",
+      "android/support/transition/TranslationAnimationCreator": "androidx/transition/TranslationAnimationCreator",
+      "android/support/v7/app/MediaRouteChooserDialog": "androidx/app/MediaRouteChooserDialog",
+      "android/support/v17/leanback/widget/Parallax$FloatPropertyMarkerValue": "androidx/leanback/widget/Parallax$FloatPropertyMarkerValue",
+      "android/support/v4/widget/FocusStrategy$SequentialComparator": "androidx/widget/FocusStrategy$SequentialComparator",
+      "android/support/v4/media/MediaMetadataCompat$TextKey": "androidx/media/MediaMetadataCompat$TextKey",
+      "android/support/v17/leanback/app/BrowseSupportFragment": "androidx/leanback/app/BrowseSupportFragment",
+      "android/support/v7/widget/ListViewCompat$GateKeeperDrawable": "androidx/widget/ListViewCompat$GateKeeperDrawable",
+      "android/support/v4/media/session/MediaControllerCompatApi23$TransportControls": "androidx/media/session/MediaControllerCompatApi23$TransportControls",
+      "android/support/v13/view/inputmethod/InputConnectionCompat$InputContentInfoCompatApi25Impl": "androidx/view/inputmethod/InputConnectionCompat$InputContentInfoCompatApi25Impl",
+      "android/support/v7/preference/PreferenceManager$OnDisplayPreferenceDialogListener": "androidx/preference/PreferenceManager$OnDisplayPreferenceDialogListener",
+      "android/support/v7/media/MediaRouterJellybeanMr1$IsConnectingWorkaround": "androidx/media/MediaRouterJellybeanMr1$IsConnectingWorkaround",
+      "android/support/v4/view/accessibility/AccessibilityManagerCompat$TouchExplorationStateChangeListenerWrapper": "androidx/view/accessibility/AccessibilityManagerCompat$TouchExplorationStateChangeListenerWrapper",
+      "android/support/v7/widget/RecyclerView$ItemDecoration": "androidx/widget/RecyclerView$ItemDecoration",
+      "android/support/v4/widget/Space": "androidx/widget/Space",
+      "android/support/v17/leanback/app/BrowseSupportFragment$ExpandPreLayout": "androidx/leanback/app/BrowseSupportFragment$ExpandPreLayout",
+      "android/support/media/tv/TvContractCompat$ProgramColumns": "androidx/media/tv/TvContractCompat$ProgramColumns",
+      "android/support/v7/widget/DecorToolbar": "androidx/widget/DecorToolbar",
+      "android/support/design/widget/CheckableImageButton": "androidx/design/widget/CheckableImageButton",
+      "android/support/v4/widget/ImageViewCompat": "androidx/widget/ImageViewCompat",
+      "android/support/v7/preference/UnPressableLinearLayout": "androidx/preference/UnPressableLinearLayout",
+      "android/support/wear/widget/SwipeDismissFrameLayout$MyOnPreSwipeListener": "androidx/wear/widget/SwipeDismissFrameLayout$MyOnPreSwipeListener",
+      "android/support/v4/widget/CircularProgressDrawable": "androidx/widget/CircularProgressDrawable",
+      "android/support/design/R$string": "androidx/design/R$string",
+      "android/support/design/R$color": "androidx/design/R$color",
+      "android/support/v7/widget/TintInfo": "androidx/widget/TintInfo",
+      "android/support/v17/leanback/widget/HeaderItem": "androidx/leanback/widget/HeaderItem",
+      "android/support/v7/media/MediaRouter$RouteInfo$ConnectionState": "androidx/media/MediaRouter$RouteInfo$ConnectionState",
+      "android/support/v7/widget/DialogTitle": "androidx/widget/DialogTitle",
+      "android/support/v17/leanback/media/PlaybackTransportControlGlue$UpdatePlaybackStateHandler": "androidx/leanback/media/PlaybackTransportControlGlue$UpdatePlaybackStateHandler",
+      "android/support/v7/widget/RecyclerView$LayoutParams": "androidx/widget/RecyclerView$LayoutParams",
+      "android/support/v4/media/session/PlaybackStateCompatApi21$CustomAction": "androidx/media/session/PlaybackStateCompatApi21$CustomAction",
+      "android/support/customtabs/CustomTabsService": "androidx/browser/customtabs/CustomTabsService",
+      "android/support/v17/leanback/widget/GuidanceStylist$Guidance": "androidx/leanback/widget/GuidanceStylist$Guidance",
+      "android/support/v4/view/ViewCompat$ViewCompatApi23Impl": "androidx/view/ViewCompat$ViewCompatApi23Impl",
+      "android/support/wear/widget/WearableLinearLayoutManager": "androidx/wear/widget/WearableLinearLayoutManager",
+      "android/support/v17/leanback/widget/PlaybackControlsRow$MultiAction": "androidx/leanback/widget/PlaybackControlsRow$MultiAction",
+      "android/support/v17/leanback/R$integer": "androidx/leanback/R$integer",
+      "android/support/v4/content/ModernAsyncTask$WorkerRunnable": "androidx/content/ModernAsyncTask$WorkerRunnable",
+      "android/support/v4/app/FragmentTabHost$DummyTabFactory": "androidx/app/FragmentTabHost$DummyTabFactory",
+      "android/support/v7/widget/AppCompatSpinner$DropDownAdapter": "androidx/widget/AppCompatSpinner$DropDownAdapter",
+      "android/support/v7/app/ToolbarActionBar$MenuBuilderCallback": "androidx/app/ToolbarActionBar$MenuBuilderCallback",
+      "android/support/v4/content/Loader": "androidx/content/Loader",
+      "android/support/v7/widget/MenuPopupWindow": "androidx/widget/MenuPopupWindow",
+      "android/support/v4/graphics/BitmapCompat$BitmapCompatApi19Impl": "androidx/graphics/BitmapCompat$BitmapCompatApi19Impl",
+      "android/support/v4/app/FrameMetricsAggregator$MetricType": "androidx/app/FrameMetricsAggregator$MetricType",
+      "android/support/v17/leanback/widget/picker/PickerUtility$TimeConstant": "androidx/leanback/widget/picker/PickerUtility$TimeConstant",
+      "android/support/v4/view/NestedScrollingParent2": "androidx/view/NestedScrollingParent2",
+      "android/support/design/widget/Snackbar$SnackbarLayout": "androidx/design/widget/Snackbar$SnackbarLayout",
+      "android/support/v4/app/LoaderManager": "androidx/app/LoaderManager",
+      "android/support/v17/leanback/app/BaseSupportFragment": "androidx/leanback/app/BaseSupportFragment",
+      "android/support/design/internal/NavigationMenuPresenter$NavigationMenuItem": "androidx/design/internal/NavigationMenuPresenter$NavigationMenuItem",
+      "android/support/v4/view/accessibility/AccessibilityNodeInfoCompat$RangeInfoCompat": "androidx/view/accessibility/AccessibilityNodeInfoCompat$RangeInfoCompat",
+      "android/support/text/emoji/MetadataRepo": "androidx/text/emoji/MetadataRepo",
+      "android/support/v17/leanback/widget/ItemAlignment": "androidx/leanback/widget/ItemAlignment",
+      "android/support/design/widget/TextInputEditText": "androidx/design/widget/TextInputEditText",
+      "android/support/v4/app/FragmentTransitionImpl": "androidx/app/FragmentTransitionImpl",
+      "android/support/animation/SpringAnimation": "androidx/animation/SpringAnimation",
+      "android/support/annotation/CheckResult": "androidx/annotation/CheckResult",
+      "android/support/v7/widget/AppCompatSpinner$DropdownPopup": "androidx/widget/AppCompatSpinner$DropdownPopup",
+      "android/support/v7/widget/ActivityChooserModel$ActivitySorter": "androidx/widget/ActivityChooserModel$ActivitySorter",
+      "android/support/wear/widget/drawer/WearableDrawerLayout$ClosePeekRunnable": "androidx/wear/widget/drawer/WearableDrawerLayout$ClosePeekRunnable",
+      "android/support/design/widget/TextInputLayout$SavedState": "androidx/design/widget/TextInputLayout$SavedState",
+      "android/support/v17/leanback/widget/picker/PickerUtility$DateConstant": "androidx/leanback/widget/picker/PickerUtility$DateConstant",
+      "android/support/v13/app/FragmentTabHost": "androidx/app/FragmentTabHost",
+      "android/support/v7/widget/ScrollingTabContainerView$TabAdapter": "androidx/widget/ScrollingTabContainerView$TabAdapter",
+      "android/support/v7/widget/ActionBarContextView": "androidx/widget/ActionBarContextView",
+      "android/support/design/internal/NavigationMenuPresenter$NavigationMenuHeaderItem": "androidx/design/internal/NavigationMenuPresenter$NavigationMenuHeaderItem",
+      "android/support/v17/leanback/graphics/CompositeDrawable": "androidx/leanback/graphics/CompositeDrawable",
+      "android/support/v7/preference/Preference$OnPreferenceChangeListener": "androidx/preference/Preference$OnPreferenceChangeListener",
+      "android/support/v4/media/MediaBrowserServiceCompat$BrowserRoot": "androidx/media/MediaBrowserServiceCompat$BrowserRoot",
+      "android/support/text/emoji/MetadataListReader$OpenTypeReader": "androidx/text/emoji/MetadataListReader$OpenTypeReader",
+      "android/support/v7/media/MediaRouterJellybean$VolumeCallbackProxy": "androidx/media/MediaRouterJellybean$VolumeCallbackProxy",
+      "android/support/v4/app/OneShotPreDrawListener": "androidx/app/OneShotPreDrawListener",
+      "android/support/design/widget/BottomNavigationView$OnNavigationItemSelectedListener": "androidx/design/widget/BottomNavigationView$OnNavigationItemSelectedListener",
+      "android/support/v7/widget/ActionMenuView": "androidx/widget/ActionMenuView",
+      "android/support/v4/view/NestedScrollingChildHelper": "androidx/view/NestedScrollingChildHelper",
+      "android/support/percent/PercentFrameLayout": "androidx/PercentFrameLayout",
+      "android/support/annotation/AnyRes": "androidx/annotation/AnyRes",
+      "android/support/wear/widget/CircledImageView$OvalShadowPainter": "androidx/wear/widget/CircledImageView$OvalShadowPainter",
+      "android/support/media/tv/TvContractCompat$Channels$Logo": "androidx/media/tv/TvContractCompat$Channels$Logo",
+      "android/support/v4/view/animation/LookupTableInterpolator": "androidx/view/animation/LookupTableInterpolator",
+      "android/support/v7/app/MediaRouteControllerDialog$MediaControllerCallback": "androidx/app/MediaRouteControllerDialog$MediaControllerCallback",
+      "android/support/v7/widget/FitWindowsViewGroup": "androidx/widget/FitWindowsViewGroup",
+      "android/support/v7/widget/ActionBarOverlayLayout$ActionBarVisibilityCallback": "androidx/widget/ActionBarOverlayLayout$ActionBarVisibilityCallback",
+      "android/support/wear/ambient/AmbientMode": "androidx/wear/ambient/AmbientMode",
+      "android/support/v17/leanback/widget/PlaybackControlsRow$SkipPreviousAction": "androidx/leanback/widget/PlaybackControlsRow$SkipPreviousAction",
+      "android/support/v4/app/NotificationCompat$Builder": "androidx/app/NotificationCompat$Builder",
+      "android/support/design/R$layout": "androidx/design/R$layout",
+      "android/support/transition/FragmentTransitionSupport": "androidx/transition/FragmentTransitionSupport",
+      "android/support/v4/app/Fragment$AnimationInfo": "androidx/app/Fragment$AnimationInfo",
+      "android/support/transition/ChangeTransform": "androidx/transition/ChangeTransform",
+      "android/support/v7/gridlayout/BuildConfig": "androidx/gridlayout/BuildConfig",
+      "android/support/v4/view/ViewCompat$FocusRelativeDirection": "androidx/view/ViewCompat$FocusRelativeDirection",
+      "android/support/customtabs/CustomTabsSessionToken$MockCallback": "androidx/browser/customtabs/CustomTabsSessionToken$MockCallback",
+      "android/support/v4/app/TaskStackBuilder$TaskStackBuilderBaseImpl": "androidx/app/TaskStackBuilder$TaskStackBuilderBaseImpl",
+      "android/support/v4/media/AudioAttributesCompat$Builder": "androidx/media/AudioAttributesCompat$Builder",
+      "android/support/v17/leanback/widget/PlaybackControlsRow$ThumbsAction": "androidx/leanback/widget/PlaybackControlsRow$ThumbsAction",
+      "android/support/v7/media/MediaItemStatus$Builder": "androidx/media/MediaItemStatus$Builder",
+      "android/support/v17/leanback/app/OnboardingSupportFragment": "androidx/leanback/app/OnboardingSupportFragment",
+      "android/support/v7/widget/LinearLayoutCompat$OrientationMode": "androidx/widget/LinearLayoutCompat$OrientationMode",
+      "android/support/v7/widget/AppCompatImageButton": "androidx/widget/AppCompatImageButton",
+      "android/support/v7/app/AppCompatViewInflater$DeclaredOnClickListener": "androidx/app/AppCompatViewInflater$DeclaredOnClickListener",
+      "android/support/v7/view/SupportMenuInflater$InflatedOnMenuItemClickListener": "androidx/view/SupportMenuInflater$InflatedOnMenuItemClickListener",
+      "android/support/v4/widget/ContentLoadingProgressBar": "androidx/widget/ContentLoadingProgressBar",
+      "android/support/v4/hardware/display/DisplayManagerCompat$DisplayManagerCompatApi17Impl": "androidx/hardware/display/DisplayManagerCompat$DisplayManagerCompatApi17Impl",
+      "android/support/v17/leanback/app/BrowseFragment$ListRowFragmentFactory": "androidx/leanback/app/BrowseFragment$ListRowFragmentFactory",
+      "android/support/v7/app/MediaRouteChooserDialog$RouteAdapter": "androidx/app/MediaRouteChooserDialog$RouteAdapter",
+      "android/support/v7/view/ContextThemeWrapper": "androidx/view/ContextThemeWrapper",
+      "android/support/design/widget/CoordinatorLayout$OnPreDrawListener": "androidx/design/widget/CoordinatorLayout$OnPreDrawListener",
+      "android/support/v4/util/ContainerHelpers": "androidx/util/ContainerHelpers",
+      "android/support/design/widget/SnackbarManager$Callback": "androidx/design/widget/SnackbarManager$Callback",
+      "android/support/design/internal/ForegroundLinearLayout": "androidx/design/internal/ForegroundLinearLayout",
+      "android/support/v4/app/NotificationCompatJellybean": "androidx/app/NotificationCompatJellybean",
+      "android/support/design/widget/CoordinatorLayout$Behavior": "androidx/design/widget/CoordinatorLayout$Behavior",
+      "android/support/v4/BuildConfig": "androidx/BuildConfig",
+      "android/support/compat/R$dimen": "androidx/compat/R$dimen",
+      "android/support/v17/leanback/app/BrowseSupportFragment$MainFragmentAdapterProvider": "androidx/leanback/app/BrowseSupportFragment$MainFragmentAdapterProvider",
+      "android/support/wear/widget/SwipeDismissLayout$OnDismissedListener": "androidx/wear/widget/SwipeDismissLayout$OnDismissedListener",
+      "android/support/v7/recyclerview/R": "androidx/recyclerview/R",
+      "android/support/v4/widget/SlidingPaneLayout$LayoutParams": "androidx/widget/SlidingPaneLayout$LayoutParams",
+      "android/support/v7/app/ToolbarActionBar$ActionMenuPresenterCallback": "androidx/app/ToolbarActionBar$ActionMenuPresenterCallback",
+      "android/support/v4/widget/DrawerLayout$DrawerListener": "androidx/widget/DrawerLayout$DrawerListener",
+      "android/support/v4/net/ConnectivityManagerCompat$RestrictBackgroundStatus": "androidx/net/ConnectivityManagerCompat$RestrictBackgroundStatus",
+      "android/support/v4/media/MediaMetadataCompat$RatingKey": "androidx/media/MediaMetadataCompat$RatingKey",
+      "android/support/v4/view/ActionProvider": "androidx/view/ActionProvider",
+      "android/support/v7/app/MediaRouteControllerDialogFragment": "androidx/app/MediaRouteControllerDialogFragment",
+      "android/support/v7/widget/TooltipCompatHandler": "androidx/widget/TooltipCompatHandler",
+      "android/support/design/widget/TabLayout$TabLayoutOnPageChangeListener": "androidx/design/widget/TabLayout$TabLayoutOnPageChangeListener",
+      "android/support/v7/widget/RecyclerView$ItemAnimatorRestoreListener": "androidx/widget/RecyclerView$ItemAnimatorRestoreListener",
+      "android/support/app/recommendation/ContentRecommendation$ContentType": "androidx/app/recommendation/ContentRecommendation$ContentType",
+      "android/support/v7/app/AlertController$AlertParams": "androidx/app/AlertController$AlertParams",
+      "android/support/v4/widget/DrawerLayout$SimpleDrawerListener": "androidx/widget/DrawerLayout$SimpleDrawerListener",
+      "android/support/v17/leanback/widget/FocusHighlightHelper$HeaderItemFocusHighlight": "androidx/leanback/widget/FocusHighlightHelper$HeaderItemFocusHighlight",
+      "android/support/v7/app/MediaRouteChooserDialog$MediaRouterCallback": "androidx/app/MediaRouteChooserDialog$MediaRouterCallback",
+      "android/support/v4/view/ViewCompat$ImportantForAccessibility": "androidx/view/ViewCompat$ImportantForAccessibility",
+      "android/support/v7/media/MediaRouterJellybean$RouteInfo": "androidx/media/MediaRouterJellybean$RouteInfo",
+      "android/support/v17/leanback/widget/SpeechRecognitionCallback": "androidx/leanback/widget/SpeechRecognitionCallback",
+      "android/support/v4/media/MediaBrowserCompatApi26$SubscriptionCallback": "androidx/media/MediaBrowserCompatApi26$SubscriptionCallback",
+      "android/support/media/tv/Program": "androidx/media/tv/Program",
+      "android/support/v4/view/accessibility/AccessibilityNodeInfoCompat$AccessibilityActionCompat": "androidx/view/accessibility/AccessibilityNodeInfoCompat$AccessibilityActionCompat",
+      "android/support/v4/app/FragmentTransitionCompat21": "androidx/app/FragmentTransitionCompat21",
+      "android/support/v7/media/MediaRouteSelector$Builder": "androidx/media/MediaRouteSelector$Builder",
+      "android/support/v4/hardware/fingerprint/FingerprintManagerCompat": "androidx/hardware/fingerprint/FingerprintManagerCompat",
+      "android/support/v17/leanback/app/HeadersFragment": "androidx/leanback/app/HeadersFragment",
+      "android/support/v17/leanback/R$dimen": "androidx/leanback/R$dimen",
+      "android/support/v4/content/res/ResourcesCompat": "androidx/content/res/ResourcesCompat",
+      "android/support/v4/view/ViewPager$SavedState": "androidx/view/ViewPager$SavedState",
+      "android/support/v17/leanback/widget/StaticShadowHelper$ShadowHelperJbmr2Impl": "androidx/leanback/widget/StaticShadowHelper$ShadowHelperJbmr2Impl",
+      "android/support/v4/widget/SwipeRefreshLayout": "androidx/widget/SwipeRefreshLayout",
+      "android/support/v17/leanback/widget/StaticShadowHelper$ShadowHelperVersionImpl": "androidx/leanback/widget/StaticShadowHelper$ShadowHelperVersionImpl",
+      "android/support/v4/os/ResultReceiver": "androidx/os/ResultReceiver",
+      "android/support/v17/leanback/widget/BaseCardView$InfoOffsetAnimation": "androidx/leanback/widget/BaseCardView$InfoOffsetAnimation",
+      "android/support/design/widget/SwipeDismissBehavior$SettleRunnable": "androidx/design/widget/SwipeDismissBehavior$SettleRunnable",
+      "android/support/v13/view/inputmethod/InputConnectionCompat$InputContentInfoCompatBaseImpl": "androidx/view/inputmethod/InputConnectionCompat$InputContentInfoCompatBaseImpl",
+      "android/support/v17/leanback/widget/FacetProvider": "androidx/leanback/widget/FacetProvider",
+      "android/support/v7/widget/FitWindowsLinearLayout": "androidx/widget/FitWindowsLinearLayout",
+      "android/support/v4/widget/SwipeProgressBar": "androidx/widget/SwipeProgressBar",
+      "android/support/v7/widget/ActionMenuPresenter$OverflowPopup": "androidx/widget/ActionMenuPresenter$OverflowPopup",
+      "android/support/v7/widget/AppCompatImageHelper": "androidx/widget/AppCompatImageHelper",
+      "android/support/v4/text/TextDirectionHeuristicsCompat": "androidx/text/TextDirectionHeuristicsCompat",
+      "android/support/compat/R$color": "androidx/compat/R$color",
+      "android/support/transition/Transition$EpicenterCallback": "androidx/transition/Transition$EpicenterCallback",
+      "android/support/transition/PropertyValuesHolderUtilsApi14": "androidx/transition/PropertyValuesHolderUtilsApi14",
+      "android/support/v17/leanback/widget/ObjectAdapter$DataObservable": "androidx/leanback/widget/ObjectAdapter$DataObservable",
+      "android/support/multidex/ZipUtil": "androidx/multidex/ZipUtil",
+      "android/support/v7/view/menu/MenuDialogHelper": "androidx/view/menu/MenuDialogHelper",
+      "android/support/v17/preference/R$id": "androidx/leanback/preference/R$id",
+      "android/support/v4/app/BundleCompat": "androidx/app/BundleCompat",
+      "android/support/v4/media/session/IMediaSession$Stub$Proxy": "androidx/media/session/IMediaSession$Stub$Proxy",
+      "android/support/v7/widget/AppCompatTextHelper": "androidx/widget/AppCompatTextHelper",
+      "android/support/v7/view/ActionMode$Callback": "androidx/view/ActionMode$Callback",
+      "android/support/v17/leanback/app/RowsSupportFragment": "androidx/leanback/app/RowsSupportFragment",
+      "android/support/v17/leanback/app/GuidedStepFragment$DummyFragment": "androidx/leanback/app/GuidedStepFragment$DummyFragment",
+      "android/support/v7/widget/util/SortedListAdapterCallback": "androidx/widget/util/SortedListAdapterCallback",
+      "android/support/design/internal/BottomNavigationPresenter$SavedState": "androidx/design/internal/BottomNavigationPresenter$SavedState",
+      "android/support/v7/widget/LinearSmoothScroller": "androidx/widget/LinearSmoothScroller",
+      "android/support/v17/leanback/media/MediaPlayerGlue$VideoPlayerSurfaceHolderCallback": "androidx/leanback/media/MediaPlayerGlue$VideoPlayerSurfaceHolderCallback",
+      "android/support/v7/preference/Preference": "androidx/preference/Preference",
+      "android/support/v4/os/UserManagerCompat": "androidx/os/UserManagerCompat",
+      "android/support/v7/widget/GridLayoutManager$DefaultSpanSizeLookup": "androidx/widget/GridLayoutManager$DefaultSpanSizeLookup",
+      "android/support/v17/leanback/app/PlaybackFragment": "androidx/leanback/app/PlaybackFragment",
+      "android/support/transition/ViewOverlayImpl": "androidx/transition/ViewOverlayImpl",
+      "android/support/transition/ViewOverlayApi14$OverlayViewGroup$TouchInterceptor": "androidx/transition/ViewOverlayApi14$OverlayViewGroup$TouchInterceptor",
+      "android/support/v7/view/menu/MenuAdapter": "androidx/view/menu/MenuAdapter",
+      "android/support/v4/app/NotificationCompat$MessagingStyle$Message": "androidx/app/NotificationCompat$MessagingStyle$Message",
+      "android/support/v4/graphics/drawable/DrawableWrapperApi21$DrawableWrapperStateLollipop": "androidx/graphics/drawable/DrawableWrapperApi21$DrawableWrapperStateLollipop",
+      "android/support/coreutils/BuildConfig": "androidx/coreutils/BuildConfig",
+      "android/support/v17/leanback/widget/ItemAlignmentFacet$ItemAlignmentDef": "androidx/leanback/widget/ItemAlignmentFacet$ItemAlignmentDef",
+      "android/support/v4/content/SharedPreferencesCompat": "androidx/content/SharedPreferencesCompat",
+      "android/support/v4/app/FragmentManager$BackStackEntry": "androidx/app/FragmentManager$BackStackEntry",
+      "android/support/transition/PropertyValuesHolderUtilsApi21": "androidx/transition/PropertyValuesHolderUtilsApi21",
+      "android/support/v17/leanback/R$color": "androidx/leanback/R$color",
+      "android/support/v7/view/menu/MenuView": "androidx/view/menu/MenuView",
+      "android/support/transition/Transition": "androidx/transition/Transition",
+      "android/support/v17/leanback/app/PlaybackFragmentGlueHost": "androidx/leanback/app/PlaybackFragmentGlueHost",
+      "android/support/v7/graphics/Target": "androidx/graphics/palette/Target",
+      "android/support/v7/view/StandaloneActionMode": "androidx/view/StandaloneActionMode",
+      "android/support/v13/view/DragAndDropPermissionsCompat$Api24DragAndDropPermissionsCompatImpl": "androidx/view/DragAndDropPermissionsCompat$Api24DragAndDropPermissionsCompatImpl",
+      "android/support/v7/widget/ViewInfoStore$ProcessCallback": "androidx/widget/ViewInfoStore$ProcessCallback",
+      "android/support/v4/media/session/MediaSessionCompatApi24": "androidx/media/session/MediaSessionCompatApi24",
+      "android/support/v4/widget/CursorFilter": "androidx/widget/CursorFilter",
+      "android/support/v4/media/session/MediaSessionCompatApi23": "androidx/media/session/MediaSessionCompatApi23",
+      "android/support/v4/media/session/MediaSessionCompatApi22": "androidx/media/session/MediaSessionCompatApi22",
+      "android/support/v7/app/ActionBarDrawerToggle$DelegateProvider": "androidx/app/ActionBarDrawerToggle$DelegateProvider",
+      "android/support/v4/media/session/MediaSessionCompatApi21": "androidx/media/session/MediaSessionCompatApi21",
+      "android/support/v7/widget/RecyclerView$OnItemTouchListener": "androidx/widget/RecyclerView$OnItemTouchListener",
+      "android/support/v4/media/MediaBrowserServiceCompat$ServiceHandler": "androidx/media/MediaBrowserServiceCompat$ServiceHandler",
+      "android/support/v4/view/PagerTitleStrip": "androidx/widget/PagerTitleStrip",
+      "android/support/v17/leanback/media/MediaPlayerAdapter$VideoPlayerSurfaceHolderCallback": "androidx/leanback/media/MediaPlayerAdapter$VideoPlayerSurfaceHolderCallback",
+      "android/support/v7/widget/ButtonBarLayout": "androidx/widget/ButtonBarLayout",
+      "android/support/annotation/PluralsRes": "androidx/annotation/PluralsRes",
+      "android/support/v7/appcompat/R$styleable": "androidx/appcompat/R$styleable",
+      "android/support/v7/widget/Toolbar": "androidx/widget/Toolbar",
+      "android/support/v17/leanback/app/DetailsFragmentBackgroundController": "androidx/leanback/app/DetailsFragmentBackgroundController",
+      "android/support/v7/view/menu/CascadingMenuPopup$CascadingMenuInfo": "androidx/view/menu/CascadingMenuPopup$CascadingMenuInfo",
+      "android/support/wear/widget/RoundedDrawable": "androidx/wear/widget/RoundedDrawable",
+      "android/support/v4/math/MathUtils": "androidx/math/MathUtils",
+      "android/support/design/widget/FloatingActionButton": "androidx/design/widget/FloatingActionButton",
+      "android/support/v7/mediarouter/R$drawable": "androidx/mediarouter/R$drawable",
+      "android/support/v4/view/MenuCompat": "androidx/view/MenuCompat",
+      "android/support/v17/leanback/widget/ListRowPresenter$SelectItemViewHolderTask": "androidx/leanback/widget/ListRowPresenter$SelectItemViewHolderTask",
+      "android/support/v7/appcompat/R$color": "androidx/appcompat/R$color",
+      "android/support/media/tv/BaseProgram$Builder": "androidx/media/tv/BaseProgram$Builder",
+      "android/support/v17/leanback/widget/ControlButtonPresenterSelector": "androidx/leanback/widget/ControlButtonPresenterSelector",
+      "android/support/v4/util/MapCollections$KeySet": "androidx/util/MapCollections$KeySet",
+      "android/support/annotation/XmlRes": "androidx/annotation/XmlRes",
+      "android/support/v7/widget/RecyclerView$LayoutManager$Properties": "androidx/widget/RecyclerView$LayoutManager$Properties",
+      "android/support/v4/media/MediaBrowserCompat$ConnectionCallback$ConnectionCallbackInternal": "androidx/media/MediaBrowserCompat$ConnectionCallback$ConnectionCallbackInternal",
+      "android/support/v4/app/RemoteInput$Builder": "androidx/app/RemoteInput$Builder",
+      "android/support/wear/widget/WearableRecyclerView": "androidx/wear/widget/WearableRecyclerView",
+      "android/support/v7/app/AppCompatDialog": "androidx/app/AppCompatDialog",
+      "android/support/v4/app/NotificationManagerCompat$SideChannelManager$ListenerRecord": "androidx/app/NotificationManagerCompat$SideChannelManager$ListenerRecord",
+      "android/support/v4/print/PrintHelper$OnPrintFinishCallback": "androidx/print/PrintHelper$OnPrintFinishCallback",
+      "android/support/v17/leanback/app/FragmentUtil": "androidx/leanback/app/FragmentUtil",
+      "android/support/v17/leanback/widget/RowPresenter$ContainerViewHolder": "androidx/leanback/widget/RowPresenter$ContainerViewHolder",
+      "android/support/v4/media/session/MediaControllerCompat$TransportControlsApi24": "androidx/media/session/MediaControllerCompat$TransportControlsApi24",
+      "android/support/v13/view/inputmethod/InputContentInfoCompat$InputContentInfoCompatImpl": "androidx/view/inputmethod/InputContentInfoCompat$InputContentInfoCompatImpl",
+      "android/support/v17/preference/LeanbackListPreferenceDialogFragment$ViewHolder$OnItemClickListener": "androidx/leanback/preference/LeanbackListPreferenceDialogFragment$ViewHolder$OnItemClickListener",
+      "android/support/v17/leanback/app/BackgroundManager$TranslucentLayerDrawable": "androidx/leanback/app/BackgroundManager$TranslucentLayerDrawable",
+      "android/support/v4/app/FragmentManagerState": "androidx/app/FragmentManagerState",
+      "android/support/v4/media/session/MediaControllerCompat$TransportControlsApi23": "androidx/media/session/MediaControllerCompat$TransportControlsApi23",
+      "android/support/v4/view/AsyncLayoutInflater$BasicInflater": "androidx/view/AsyncLayoutInflater$BasicInflater",
+      "android/support/v7/appcompat/R$dimen": "androidx/appcompat/R$dimen",
+      "android/support/v4/media/session/MediaControllerCompat$TransportControlsApi21": "androidx/media/session/MediaControllerCompat$TransportControlsApi21",
+      "android/support/wear/widget/CircularProgressLayout": "androidx/wear/widget/CircularProgressLayout",
+      "android/support/v7/media/SystemMediaRouteProvider$JellybeanMr1Impl": "androidx/media/SystemMediaRouteProvider$JellybeanMr1Impl",
+      "android/support/customtabs/ICustomTabsCallback$Stub": "androidx/browser/customtabs/ICustomTabsCallback$Stub",
+      "android/support/v17/leanback/widget/RowPresenter$ViewHolder": "androidx/leanback/widget/RowPresenter$ViewHolder",
+      "android/support/v7/widget/RecyclerView$LayoutManager$LayoutPrefetchRegistry": "androidx/widget/RecyclerView$LayoutManager$LayoutPrefetchRegistry",
+      "android/support/v4/media/MediaBrowserServiceCompatApi23$MediaBrowserServiceAdaptor": "androidx/media/MediaBrowserServiceCompatApi23$MediaBrowserServiceAdaptor",
+      "android/support/v17/leanback/app/ListRowDataAdapter": "androidx/leanback/app/ListRowDataAdapter",
+      "android/support/v4/graphics/drawable/DrawableWrapper": "androidx/graphics/drawable/DrawableWrapper",
+      "android/support/media/tv/BuildConfig": "androidx/media/tv/BuildConfig",
+      "android/support/v7/widget/SuggestionsAdapter": "androidx/widget/SuggestionsAdapter",
+      "android/support/v4/widget/TextViewCompat": "androidx/widget/TextViewCompat",
+      "android/support/animation/FlingAnimation": "androidx/animation/FlingAnimation",
+      "android/support/media/tv/PreviewProgram$Builder": "androidx/media/tv/PreviewProgram$Builder",
+      "android/support/v7/widget/RecyclerView$AdapterDataObservable": "androidx/widget/RecyclerView$AdapterDataObservable",
+      "android/support/v17/leanback/app/BaseRowSupportFragment$LateSelectionObserver": "androidx/leanback/app/BaseRowSupportFragment$LateSelectionObserver",
+      "android/support/design/internal/SnackbarContentLayout": "androidx/design/internal/SnackbarContentLayout",
+      "android/support/v17/leanback/widget/BaseGridView$OnMotionInterceptListener": "androidx/leanback/widget/BaseGridView$OnMotionInterceptListener",
+      "android/support/v17/leanback/widget/PlaybackTransportRowPresenter$ViewHolder": "androidx/leanback/widget/PlaybackTransportRowPresenter$ViewHolder",
+      "android/support/v7/widget/CardViewBaseImpl": "androidx/widget/CardViewBaseImpl",
+      "android/support/v4/provider/FontsContractCompat$FontFamilyResult": "androidx/provider/FontsContractCompat$FontFamilyResult",
+      "android/support/v4/os/ResultReceiver$MyRunnable": "androidx/os/ResultReceiver$MyRunnable",
+      "android/support/v7/media/MediaRouteProvider$RouteController": "androidx/media/MediaRouteProvider$RouteController",
+      "android/support/v14/preference/PreferenceFragment$DividerDecoration": "androidx/preference/PreferenceFragment$DividerDecoration",
+      "android/support/text/emoji/flatbuffer/MetadataItem": "androidx/text/emoji/flatbuffer/MetadataItem",
+      "android/support/v4/media/session/MediaSessionCompat$MediaSessionImplApi18": "androidx/media/session/MediaSessionCompat$MediaSessionImplApi18",
+      "android/support/v7/widget/ToolbarWidgetWrapper": "androidx/widget/ToolbarWidgetWrapper",
+      "android/support/v4/media/session/MediaSessionCompat$MediaSessionImplApi19": "androidx/media/session/MediaSessionCompat$MediaSessionImplApi19",
+      "android/support/v7/widget/helper/ItemTouchHelper$ItemTouchHelperGestureListener": "androidx/widget/helper/ItemTouchHelper$ItemTouchHelperGestureListener",
+      "android/support/v7/appcompat/R$layout": "androidx/appcompat/R$layout",
+      "android/support/text/emoji/widget/EmojiEditTextHelper$HelperInternal19": "androidx/text/emoji/widget/EmojiEditTextHelper$HelperInternal19",
+      "android/support/v17/leanback/app/BackgroundManager$BitmapDrawable$ConstantState": "androidx/leanback/app/BackgroundManager$BitmapDrawable$ConstantState",
+      "android/support/design/widget/FloatingActionButtonImpl$ShadowAnimatorImpl": "androidx/design/widget/FloatingActionButtonImpl$ShadowAnimatorImpl",
+      "android/support/v7/widget/FastScroller$AnimatorListener": "androidx/widget/FastScroller$AnimatorListener",
+      "android/support/v17/leanback/widget/FullWidthDetailsOverviewRowPresenter": "androidx/leanback/widget/FullWidthDetailsOverviewRowPresenter",
+      "android/support/v4/app/NotificationCompat$WearableExtender": "androidx/app/NotificationCompat$WearableExtender",
+      "android/support/v17/leanback/widget/FocusHighlightHandler": "androidx/leanback/widget/FocusHighlightHandler",
+      "android/support/transition/Slide$GravityFlag": "androidx/transition/Slide$GravityFlag",
+      "android/support/v4/app/FragmentManagerImpl$AnimateOnHWLayerIfNeededListener": "androidx/app/FragmentManagerImpl$AnimateOnHWLayerIfNeededListener",
+      "android/support/v4/widget/ListViewCompat": "androidx/widget/ListViewCompat",
+      "android/support/v4/media/session/MediaSessionCompat$MediaSessionImplApi21": "androidx/media/session/MediaSessionCompat$MediaSessionImplApi21",
+      "android/support/graphics/drawable/Animatable2Compat$AnimationCallback": "androidx/graphics/drawable/Animatable2Compat$AnimationCallback",
+      "android/support/v17/leanback/widget/ControlButtonPresenterSelector$ActionViewHolder": "androidx/leanback/widget/ControlButtonPresenterSelector$ActionViewHolder",
+      "android/support/transition/GhostViewApi14$Creator": "androidx/transition/GhostViewApi14$Creator",
+      "android/support/v4/widget/PopupWindowCompat$PopupWindowCompatApi19Impl": "androidx/widget/PopupWindowCompat$PopupWindowCompatApi19Impl",
+      "android/support/v17/leanback/widget/GuidedActionsStylist": "androidx/leanback/widget/GuidedActionsStylist",
+      "android/support/v4/app/ActivityCompat$SharedElementCallback21Impl": "androidx/app/ActivityCompat$SharedElementCallback21Impl",
+      "android/support/v17/leanback/app/BrowseFragment$FragmentFactory": "androidx/leanback/app/BrowseFragment$FragmentFactory",
+      "android/support/v7/widget/DefaultItemAnimator$MoveInfo": "androidx/widget/DefaultItemAnimator$MoveInfo",
+      "android/support/media/instantvideo/preload/InstantVideoPreloadManager$VideoPreloader": "androidx/media/instantvideo/preload/InstantVideoPreloadManager$VideoPreloader",
+      "android/support/v4/media/MediaBrowserCompat": "androidx/media/MediaBrowserCompat",
+      "android/support/v7/media/MediaRouter$RouteGroup": "androidx/media/MediaRouter$RouteGroup",
+      "android/support/v4/view/ViewCompat$OverScroll": "androidx/view/ViewCompat$OverScroll",
+      "android/support/v7/media/SystemMediaRouteProvider$LegacyImpl$VolumeChangeReceiver": "androidx/media/SystemMediaRouteProvider$LegacyImpl$VolumeChangeReceiver",
+      "android/support/v17/preference/LeanbackPreferenceFragmentTransitionHelperApi21": "androidx/leanback/preference/LeanbackPreferenceFragmentTransitionHelperApi21",
+      "android/support/v17/leanback/widget/BaseGridView$OnUnhandledKeyListener": "androidx/leanback/widget/BaseGridView$OnUnhandledKeyListener",
+      "android/support/v7/view/menu/ActionMenuItemView$ActionMenuItemForwardingListener": "androidx/view/menu/ActionMenuItemView$ActionMenuItemForwardingListener",
+      "android/support/v17/leanback/transition/FadeAndShortSlide": "androidx/leanback/transition/FadeAndShortSlide",
+      "android/support/v7/app/NavItemSelectedListener": "androidx/app/NavItemSelectedListener",
+      "android/support/v4/content/pm/ShortcutManagerCompat": "androidx/content/pm/ShortcutManagerCompat",
+      "android/support/v4/app/FragmentActivity$HostCallbacks": "androidx/app/FragmentActivity$HostCallbacks",
+      "android/support/design/widget/CoordinatorLayout$LayoutParams": "androidx/design/widget/CoordinatorLayout$LayoutParams",
+      "android/support/v4/view/MenuItemCompat$MenuItemCompatApi26Impl": "androidx/view/MenuItemCompat$MenuItemCompatApi26Impl",
+      "android/support/transition/ViewGroupOverlayApi18": "androidx/transition/ViewGroupOverlayApi18",
+      "android/support/v17/preference/LeanbackSettingsFragment": "androidx/leanback/preference/LeanbackSettingsFragment",
+      "android/support/v17/leanback/widget/RowHeaderPresenter$ViewHolder": "androidx/leanback/widget/RowHeaderPresenter$ViewHolder",
+      "android/support/wear/ambient/AmbientMode$AmbientController": "androidx/wear/ambient/AmbientMode$AmbientController",
+      "android/support/v7/cardview/R$color": "androidx/cardview/R$color",
+      "android/support/annotation/MenuRes": "androidx/annotation/MenuRes",
+      "android/support/v7/media/MediaRouterJellybeanMr2": "androidx/media/MediaRouterJellybeanMr2",
+      "android/support/v7/widget/GridLayout$PackedMap": "androidx/widget/GridLayout$PackedMap",
+      "android/support/v7/media/MediaRouterJellybeanMr1": "androidx/media/MediaRouterJellybeanMr1",
+      "android/support/transition/ViewGroupOverlayApi14": "androidx/transition/ViewGroupOverlayApi14",
+      "android/support/v7/app/ActionBarDrawerToggleHoneycomb$SetIndicatorInfo": "androidx/app/ActionBarDrawerToggleHoneycomb$SetIndicatorInfo",
+      "android/support/wear/widget/drawer/FlingWatcherFactory$FlingWatcher": "androidx/wear/widget/drawer/FlingWatcherFactory$FlingWatcher",
+      "android/support/v17/leanback/widget/SparseArrayObjectAdapter": "androidx/leanback/widget/SparseArrayObjectAdapter",
+      "android/support/v4/media/session/MediaSessionCompatApi21$CallbackProxy": "androidx/media/session/MediaSessionCompatApi21$CallbackProxy",
+      "android/support/v4/app/NotificationCompat$CarExtender": "androidx/app/NotificationCompat$CarExtender",
+      "android/support/v17/leanback/widget/PlaybackControlsRowView$OnUnhandledKeyListener": "androidx/leanback/widget/PlaybackControlsRowView$OnUnhandledKeyListener",
+      "android/support/v7/view/menu/MenuPopupHelper": "androidx/view/menu/MenuPopupHelper",
+      "android/support/v17/leanback/app/VideoSupportFragment": "androidx/leanback/app/VideoSupportFragment",
+      "android/support/media/tv/Channel": "androidx/media/tv/Channel",
+      "android/support/v4/media/AudioAttributesCompat$AttributeContentType": "androidx/media/AudioAttributesCompat$AttributeContentType",
+      "android/support/v17/leanback/widget/DetailsOverviewSharedElementHelper$TransitionTimeOutRunnable": "androidx/leanback/widget/DetailsOverviewSharedElementHelper$TransitionTimeOutRunnable",
+      "android/support/v17/leanback/app/BrowseSupportFragment$BrowseTransitionListener": "androidx/leanback/app/BrowseSupportFragment$BrowseTransitionListener",
+      "android/support/v4/view/ViewPager$ViewPositionComparator": "androidx/view/ViewPager$ViewPositionComparator",
+      "android/support/v4/app/NotificationCompat$Action": "androidx/app/NotificationCompat$Action",
+      "android/support/v7/preference/PreferenceManager$SimplePreferenceComparisonCallback": "androidx/preference/PreferenceManager$SimplePreferenceComparisonCallback",
+      "android/support/media/tv/TvContractCompat$BaseTvColumns": "androidx/media/tv/TvContractCompat$BaseTvColumns",
+      "android/support/v4/media/session/PlaybackStateCompat$ShuffleMode": "androidx/media/session/PlaybackStateCompat$ShuffleMode",
+      "android/support/v7/app/WindowDecorActionBar$TabImpl": "androidx/app/WindowDecorActionBar$TabImpl",
+      "android/support/graphics/drawable/VectorDrawableCompat$VectorDrawableDelegateState": "androidx/graphics/drawable/VectorDrawableCompat$VectorDrawableDelegateState",
+      "android/support/v7/media/SystemMediaRouteProvider$JellybeanImpl": "androidx/media/SystemMediaRouteProvider$JellybeanImpl",
+      "android/support/v7/widget/VectorEnabledTintResources": "androidx/widget/VectorEnabledTintResources",
+      "android/support/v7/preference/EditTextPreferenceDialogFragmentCompat": "androidx/preference/EditTextPreferenceDialogFragmentCompat",
+      "android/support/v4/graphics/drawable/DrawableWrapperApi19$DrawableWrapperStateKitKat": "androidx/graphics/drawable/DrawableWrapperApi19$DrawableWrapperStateKitKat",
+      "android/support/v17/leanback/transition/TransitionHelperKitkat$CustomChangeBounds": "androidx/leanback/transition/TransitionHelperKitkat$CustomChangeBounds",
+      "android/support/v4/app/FragmentManagerImpl$StartEnterTransitionListener": "androidx/app/FragmentManagerImpl$StartEnterTransitionListener",
+      "android/support/v4/view/LayoutInflaterCompat$LayoutInflaterCompatBaseImpl": "androidx/view/LayoutInflaterCompat$LayoutInflaterCompatBaseImpl",
+      "android/support/v7/view/SupportMenuInflater$MenuState": "androidx/view/SupportMenuInflater$MenuState",
+      "android/support/v17/leanback/app/GuidedStepFragment": "androidx/leanback/app/GuidedStepFragment",
+      "android/support/v17/leanback/widget/PagingIndicator": "androidx/leanback/widget/PagingIndicator",
+      "android/support/v7/app/MediaRouteControllerDialog$MediaRouterCallback": "androidx/app/MediaRouteControllerDialog$MediaRouterCallback",
+      "android/support/design/widget/CoordinatorLayout$DefaultBehavior": "androidx/design/widget/CoordinatorLayout$DefaultBehavior",
+      "android/support/v4/os/LocaleListInterface": "androidx/os/LocaleListInterface",
+      "android/support/v4/hardware/fingerprint/FingerprintManagerCompat$AuthenticationCallback": "androidx/hardware/fingerprint/FingerprintManagerCompat$AuthenticationCallback",
+      "android/support/v17/leanback/widget/Parallax$FloatProperty": "androidx/leanback/widget/Parallax$FloatProperty",
+      "android/support/v4/app/NotificationCompat$Style": "androidx/app/NotificationCompat$Style",
+      "android/support/v4/app/FrameMetricsAggregator$FrameMetricsApi24Impl": "androidx/app/FrameMetricsAggregator$FrameMetricsApi24Impl",
+      "android/support/v17/leanback/app/HeadersSupportFragment$OnHeaderClickedListener": "androidx/leanback/app/HeadersSupportFragment$OnHeaderClickedListener",
+      "android/support/v7/preference/PreferenceManager$OnNavigateToScreenListener": "androidx/preference/PreferenceManager$OnNavigateToScreenListener",
+      "android/support/v7/widget/AdapterHelper": "androidx/widget/AdapterHelper",
+      "android/support/v17/leanback/transition/SlideKitkat$CalculateSlideHorizontal": "androidx/leanback/transition/SlideKitkat$CalculateSlideHorizontal",
+      "android/support/v4/graphics/ColorUtils": "androidx/graphics/ColorUtils",
+      "android/support/v7/widget/ActionMenuPresenter$OverflowMenuButton": "androidx/widget/ActionMenuPresenter$OverflowMenuButton",
+      "android/support/v7/widget/ListPopupWindow": "androidx/widget/ListPopupWindow",
+      "android/support/v4/media/MediaBrowserServiceCompatApi21$MediaBrowserServiceAdaptor": "androidx/media/MediaBrowserServiceCompatApi21$MediaBrowserServiceAdaptor",
+      "android/support/v17/leanback/widget/Row": "androidx/leanback/widget/Row",
+      "android/support/v17/leanback/widget/ShadowOverlayHelper": "androidx/leanback/widget/ShadowOverlayHelper",
+      "android/support/wear/internal/widget/drawer/MultiPagePresenter": "androidx/wear/internal/widget/drawer/MultiPagePresenter",
+      "android/support/animation/SpringForce": "androidx/animation/SpringForce",
+      "android/support/customtabs/ICustomTabsService$Stub$Proxy": "androidx/browser/customtabs/ICustomTabsService$Stub$Proxy",
+      "android/support/annotation/StringDef": "androidx/annotation/StringDef",
+      "android/support/v4/media/session/MediaControllerCompat$MediaControllerImpl": "androidx/media/session/MediaControllerCompat$MediaControllerImpl",
+      "android/support/design/widget/CircularBorderDrawableLollipop": "androidx/design/widget/CircularBorderDrawableLollipop",
+      "android/support/v17/leanback/widget/ControlBarPresenter$BoundData": "androidx/leanback/widget/ControlBarPresenter$BoundData",
+      "android/support/v7/widget/RecyclerView$Orientation": "androidx/widget/RecyclerView$Orientation",
+      "android/support/v7/media/MediaRouteProviderService$ReceiveHandler": "androidx/media/MediaRouteProviderService$ReceiveHandler",
+      "android/support/v4/view/ViewPropertyAnimatorListenerAdapter": "androidx/view/ViewPropertyAnimatorListenerAdapter",
+      "android/support/v7/graphics/Palette$Swatch": "androidx/graphics/Palette$Swatch",
+      "android/support/v17/leanback/widget/GridLayoutManager": "androidx/leanback/widget/GridLayoutManager",
+      "android/support/v17/leanback/media/PlaybackGlue": "androidx/leanback/media/PlaybackGlue",
+      "android/support/v17/leanback/widget/ShadowHelper$ShadowHelperApi21Impl": "androidx/leanback/widget/ShadowHelper$ShadowHelperApi21Impl",
+      "android/support/v4/view/ViewCompat": "androidx/view/ViewCompat",
+      "android/support/v17/leanback/widget/BackgroundHelper": "androidx/leanback/widget/BackgroundHelper",
+      "android/support/design/widget/AppBarLayout$OnOffsetChangedListener": "androidx/design/widget/AppBarLayout$OnOffsetChangedListener",
+      "android/support/v4/view/GestureDetectorCompat$GestureDetectorCompatImplJellybeanMr2": "androidx/view/GestureDetectorCompat$GestureDetectorCompatImplJellybeanMr2",
+      "android/support/v7/appcompat/R$attr": "androidx/appcompat/R$attr",
+      "android/support/transition/TransitionValuesMaps": "androidx/transition/TransitionValuesMaps",
+      "android/support/v4/view/ViewConfigurationCompat": "androidx/view/ViewConfigurationCompat",
+      "android/support/v4/media/MediaBrowserServiceCompat$ServiceCallbacks": "androidx/media/MediaBrowserServiceCompat$ServiceCallbacks",
+      "android/support/v4/widget/AutoSizeableTextView": "androidx/widget/AutoSizeableTextView",
+      "android/support/v4/view/ViewCompat$LayerType": "androidx/view/ViewCompat$LayerType",
+      "android/support/v17/leanback/widget/DividerRow": "androidx/leanback/widget/DividerRow",
+      "android/support/v7/widget/ActionMenuPresenter$SavedState": "androidx/widget/ActionMenuPresenter$SavedState",
+      "android/support/design/internal/NavigationMenu": "androidx/design/internal/NavigationMenu",
+      "android/support/v7/preference/Preference$OnPreferenceChangeInternalListener": "androidx/preference/Preference$OnPreferenceChangeInternalListener",
+      "android/support/v4/app/JobIntentService": "androidx/app/JobIntentService",
+      "android/support/v4/app/Fragment": "androidx/app/Fragment",
+      "android/support/v17/leanback/app/BrowseSupportFragment$BackStackListener": "androidx/leanback/app/BrowseSupportFragment$BackStackListener",
+      "android/support/v7/widget/RecyclerView$OnChildAttachStateChangeListener": "androidx/widget/RecyclerView$OnChildAttachStateChangeListener",
+      "android/support/v17/leanback/widget/PlaybackSeekDataProvider": "androidx/leanback/widget/PlaybackSeekDataProvider",
+      "android/support/content/ContentPager": "androidx/content/ContentPager",
+      "android/support/v4/util/MapCollections$MapIterator": "androidx/util/MapCollections$MapIterator",
+      "android/support/app/recommendation/ContentRecommendation": "androidx/app/recommendation/ContentRecommendation",
+      "android/support/v7/app/ActionBar$OnNavigationListener": "androidx/app/ActionBar$OnNavigationListener",
+      "android/support/v4/media/session/MediaSessionCompat$MediaSessionImplBase": "androidx/media/session/MediaSessionCompat$MediaSessionImplBase",
+      "android/support/transition/Styleable$ChangeTransform": "androidx/transition/Styleable$ChangeTransform",
+      "android/support/v7/media/SystemMediaRouteProvider$LegacyImpl$DefaultRouteController": "androidx/media/SystemMediaRouteProvider$LegacyImpl$DefaultRouteController",
+      "android/support/v4/net/DatagramSocketWrapper$DatagramSocketImplWrapper": "androidx/net/DatagramSocketWrapper$DatagramSocketImplWrapper",
+      "android/support/v17/leanback/R$layout": "androidx/leanback/R$layout",
+      "android/support/v4/app/FragmentHostCallback": "androidx/app/FragmentHostCallback",
+      "android/support/v7/widget/StaggeredGridLayoutManager$AnchorInfo": "androidx/widget/StaggeredGridLayoutManager$AnchorInfo",
+      "android/support/v7/widget/RecyclerView$State": "androidx/widget/RecyclerView$State",
+      "android/support/v7/media/MediaRouterJellybean$Callback": "androidx/media/MediaRouterJellybean$Callback",
+      "android/support/v14/preference/MultiSelectListPreferenceDialogFragment": "androidx/preference/MultiSelectListPreferenceDialogFragment",
+      "android/support/v4/media/MediaBrowserServiceCompatApi21$ServiceCompatProxy": "androidx/media/MediaBrowserServiceCompatApi21$ServiceCompatProxy",
+      "android/support/v4/text/util/LinkifyCompat$LinkSpec": "androidx/text/util/LinkifyCompat$LinkSpec",
+      "android/support/v4/app/NotificationManagerCompat$ServiceConnectedEvent": "androidx/app/NotificationManagerCompat$ServiceConnectedEvent",
+      "android/support/v4/graphics/drawable/IconCompat": "androidx/graphics/drawable/IconCompat",
+      "android/support/v17/leanback/graphics/ColorOverlayDimmer": "androidx/leanback/graphics/ColorOverlayDimmer",
+      "android/support/v17/leanback/R$styleable": "androidx/leanback/R$styleable",
+      "android/support/v4/view/AccessibilityDelegateCompat": "androidx/view/AccessibilityDelegateCompat",
+      "android/support/v7/cardview/R$dimen": "androidx/cardview/R$dimen",
+      "android/support/wear/widget/SwipeDismissFrameLayout$Callback": "androidx/wear/widget/SwipeDismissFrameLayout$Callback",
+      "android/support/transition/ArcMotion": "androidx/transition/ArcMotion",
+      "android/support/mediacompat/R$layout": "androidx/mediacompat/R$layout",
+      "android/support/v4/view/ViewPager$ItemInfo": "androidx/view/ViewPager$ItemInfo",
+      "android/support/v4/app/FragmentActivity": "androidx/app/FragmentActivity",
+      "android/support/animation/FloatPropertyCompat": "androidx/animation/FloatPropertyCompat",
+      "android/support/v17/leanback/widget/ObjectAdapter$DataObserver": "androidx/leanback/widget/ObjectAdapter$DataObserver",
+      "android/support/design/widget/AppBarLayout$LayoutParams$ScrollFlags": "androidx/design/widget/AppBarLayout$LayoutParams$ScrollFlags",
+      "android/support/v7/widget/AbsActionBarView": "androidx/widget/AbsActionBarView",
+      "android/support/v4/media/app/NotificationCompat": "androidx/media/app/NotificationCompat",
+      "android/support/v17/leanback/media/PlayerAdapter": "androidx/leanback/media/PlayerAdapter",
+      "android/support/v17/leanback/widget/GridLayoutManager$SavedState": "androidx/leanback/widget/GridLayoutManager$SavedState",
+      "android/support/transition/Fade": "androidx/transition/Fade",
+      "android/support/v17/leanback/R$animator": "androidx/leanback/R$animator",
+      "android/support/v4/view/ViewCompat$ScrollAxis": "androidx/view/ViewCompat$ScrollAxis",
+      "android/support/v17/leanback/widget/GuidedActionEditText$NoPaddingDrawable": "androidx/leanback/widget/GuidedActionEditText$NoPaddingDrawable",
+      "android/support/v7/widget/GridLayout$Axis": "androidx/widget/GridLayout$Axis",
+      "android/support/v4/app/FragmentManagerImpl$AnimationListenerWrapper": "androidx/app/FragmentManagerImpl$AnimationListenerWrapper",
+      "android/support/v4/view/ViewParentCompat$ViewParentCompatBaseImpl": "androidx/view/ViewParentCompat$ViewParentCompatBaseImpl",
+      "android/support/v7/util/DiffUtil$Snake": "androidx/util/DiffUtil$Snake",
+      "android/support/v7/app/AppCompatDelegateImplV9$PanelFeatureState$SavedState": "androidx/app/AppCompatDelegateImplV9$PanelFeatureState$SavedState",
+      "android/support/v17/leanback/widget/DetailsOverviewRowPresenter$ViewHolder": "androidx/leanback/widget/DetailsOverviewRowPresenter$ViewHolder",
+      "android/support/v4/media/RatingCompat$Style": "androidx/media/RatingCompat$Style",
+      "android/support/v7/widget/GridLayout$Spec": "androidx/widget/GridLayout$Spec",
+      "android/support/constraint/solver/widgets/ConstraintAnchor": "androidx/constraint/solver/widgets/ConstraintAnchor",
+      "android/support/v17/leanback/widget/FullWidthDetailsOverviewSharedElementHelper$TransitionTimeOutRunnable": "androidx/leanback/widget/FullWidthDetailsOverviewSharedElementHelper$TransitionTimeOutRunnable",
+      "android/support/graphics/drawable/animated/BuildConfig": "androidx/graphics/drawable/animated/BuildConfig",
+      "android/support/v4/view/LayoutInflaterFactory": "androidx/view/LayoutInflaterFactory",
+      "android/support/v7/preference/internal/package-info": "androidx/preference/internal/package-info",
+      "android/support/v17/leanback/app/BrowseFragment$MainFragmentAdapterProvider": "androidx/leanback/app/BrowseFragment$MainFragmentAdapterProvider",
+      "android/support/v7/app/OverlayListView$OverlayObject$OnAnimationEndListener": "androidx/app/OverlayListView$OverlayObject$OnAnimationEndListener",
+      "android/support/v7/app/AlertController$RecycleListView": "androidx/app/AlertController$RecycleListView",
+      "android/support/v4/view/ViewCompat$ViewCompatApi18Impl": "androidx/view/ViewCompat$ViewCompatApi18Impl",
+      "android/support/v4/text/TextUtilsCompat": "androidx/text/TextUtilsCompat",
+      "android/support/v17/leanback/widget/Action": "androidx/leanback/widget/Action",
+      "android/support/v7/widget/RecyclerView$ItemAnimator$AdapterChanges": "androidx/widget/RecyclerView$ItemAnimator$AdapterChanges",
+      "android/support/v4/media/session/MediaControllerCompat$MediaControllerExtraData": "androidx/media/session/MediaControllerCompat$MediaControllerExtraData",
+      "android/support/v4/media/session/MediaControllerCompat$TransportControlsBase": "androidx/media/session/MediaControllerCompat$TransportControlsBase",
+      "android/support/v13/view/inputmethod/InputConnectionCompat$InputConnectionCompatImpl": "androidx/view/inputmethod/InputConnectionCompat$InputConnectionCompatImpl",
+      "android/support/v4/media/ParceledListSliceAdapterApi21": "androidx/media/ParceledListSliceAdapterApi21",
+      "android/support/v17/leanback/widget/ItemBridgeAdapter": "androidx/leanback/widget/ItemBridgeAdapter",
+      "android/support/v7/appcompat/BuildConfig": "androidx/appcompat/BuildConfig",
+      "android/support/v13/view/inputmethod/InputConnectionCompat": "androidx/view/inputmethod/InputConnectionCompat",
+      "android/support/v4/widget/ResourceCursorAdapter": "androidx/widget/ResourceCursorAdapter",
+      "android/support/v7/view/WindowCallbackWrapper": "androidx/view/WindowCallbackWrapper",
+      "android/support/v7/widget/AdapterHelper$UpdateOp": "androidx/widget/AdapterHelper$UpdateOp",
+      "android/support/v4/content/res/FontResourcesParserCompat$FontFileResourceEntry": "androidx/content/res/FontResourcesParserCompat$FontFileResourceEntry",
+      "android/support/v4/app/NavUtils": "androidx/app/NavUtils",
+      "android/support/v4/internal/view/SupportMenuItem": "androidx/internal/view/SupportMenuItem",
+      "android/support/v7/widget/FastScroller$DragState": "androidx/widget/FastScroller$DragState",
+      "android/support/annotation/Size": "androidx/annotation/Size",
+      "android/support/wear/widget/drawer/WearableNavigationDrawerView$OnItemSelectedListener": "androidx/wear/widget/drawer/WearableNavigationDrawerView$OnItemSelectedListener",
+      "android/support/transition/WindowIdImpl": "androidx/transition/WindowIdImpl",
+      "android/support/v7/media/RemoteControlClientCompat$LegacyImpl": "androidx/media/RemoteControlClientCompat$LegacyImpl",
+      "android/support/transition/Slide$CalculateSlideVertical": "androidx/transition/Slide$CalculateSlideVertical",
+      "android/support/v17/leanback/app/VideoFragment": "androidx/leanback/app/VideoFragment",
+      "android/support/v4/widget/NestedScrollView$OnScrollChangeListener": "androidx/widget/NestedScrollView$OnScrollChangeListener",
+      "android/support/v7/media/MediaRouter$CallbackFlags": "androidx/media/MediaRouter$CallbackFlags",
+      "android/support/customtabs/IPostMessageService": "androidx/browser/customtabs/IPostMessageService",
+      "android/support/v4/util/AtomicFile": "androidx/util/AtomicFile",
+      "android/support/v4/provider/DocumentsContractApi19": "androidx/provider/DocumentsContractApi19",
+      "android/support/v17/leanback/widget/picker/Picker$ViewHolder": "androidx/leanback/widget/picker/Picker$ViewHolder",
+      "android/support/v4/widget/AutoScrollHelper": "androidx/widget/AutoScrollHelper",
+      "android/support/v4/widget/DrawerLayout$LockMode": "androidx/widget/DrawerLayout$LockMode",
+      "android/support/v17/leanback/transition/SlideKitkat$CalculateSlideVertical": "androidx/leanback/transition/SlideKitkat$CalculateSlideVertical",
+      "android/support/v4/os/BuildCompat": "androidx/os/BuildCompat",
+      "android/support/wear/ambient/SharedLibraryVersion$PresenceHolder": "androidx/wear/ambient/SharedLibraryVersion$PresenceHolder",
+      "android/support/transition/ObjectAnimatorUtilsImpl": "androidx/transition/ObjectAnimatorUtilsImpl",
+      "android/support/v17/leanback/BuildConfig": "androidx/leanback/BuildConfig",
+      "android/support/v7/widget/SearchView$OnSuggestionListener": "androidx/widget/SearchView$OnSuggestionListener",
+      "android/support/design/internal/NavigationMenuPresenter": "androidx/design/internal/NavigationMenuPresenter",
+      "android/support/v17/leanback/widget/ShadowHelper$ShadowHelperVersionImpl": "androidx/leanback/widget/ShadowHelper$ShadowHelperVersionImpl",
+      "android/support/v7/media/RemotePlaybackClient$ItemActionCallback": "androidx/media/RemotePlaybackClient$ItemActionCallback",
+      "android/support/graphics/drawable/BuildConfig": "androidx/graphics/drawable/BuildConfig",
+      "android/support/annotation/Dimension": "androidx/annotation/Dimension",
+      "android/support/v13/view/DragStartHelper$OnDragStartListener": "androidx/view/DragStartHelper$OnDragStartListener",
+      "android/support/text/emoji/R$id": "androidx/text/emoji/R$id",
+      "android/support/wear/widget/CurvingLayoutCallback": "androidx/wear/widget/CurvingLayoutCallback",
+      "android/support/v17/leanback/app/RowsFragment": "androidx/leanback/app/RowsFragment",
+      "android/support/v7/widget/TooltipCompat": "androidx/widget/TooltipCompat",
+      "android/support/v7/widget/SnapHelper": "androidx/widget/SnapHelper",
+      "android/support/v7/mediarouter/R$layout": "androidx/mediarouter/R$layout",
+      "android/support/transition/R$id": "androidx/transition/R$id",
+      "android/support/v4/graphics/BitmapCompat$BitmapCompatBaseImpl": "androidx/graphics/BitmapCompat$BitmapCompatBaseImpl",
+      "android/support/v4/media/VolumeProviderCompatApi21$Delegate": "androidx/media/VolumeProviderCompatApi21$Delegate",
+      "android/support/v17/leanback/widget/SearchBar$SearchBarListener": "androidx/leanback/widget/SearchBar$SearchBarListener",
+      "android/support/v17/leanback/app/SearchFragment": "androidx/leanback/app/SearchFragment",
+      "android/support/v4/view/LayoutInflaterCompat": "androidx/view/LayoutInflaterCompat",
+      "android/support/v7/widget/CardViewApi21Impl": "androidx/widget/CardViewApi21Impl",
+      "android/support/v17/leanback/widget/VideoSurfaceView": "androidx/leanback/widget/VideoSurfaceView",
+      "android/support/transition/GhostViewImpl": "androidx/transition/GhostViewImpl",
+      "android/support/transition/ChangeBounds": "androidx/transition/ChangeBounds",
+      "android/support/v17/leanback/app/HeadersFragment$OnHeaderViewSelectedListener": "androidx/leanback/app/HeadersFragment$OnHeaderViewSelectedListener",
+      "android/support/media/tv/PreviewProgram": "androidx/media/tv/PreviewProgram",
+      "android/support/annotation/RequiresApi": "androidx/annotation/RequiresApi",
+      "android/support/app/recommendation/RecommendationExtender": "androidx/app/recommendation/RecommendationExtender",
+      "android/support/v17/leanback/app/ErrorSupportFragment": "androidx/leanback/app/ErrorSupportFragment",
+      "android/support/v17/leanback/media/MediaPlayerGlue": "androidx/leanback/media/MediaPlayerGlue",
+      "android/support/v4/media/session/MediaSessionCompatApi21$QueueItem": "androidx/media/session/MediaSessionCompatApi21$QueueItem",
+      "android/support/v17/leanback/widget/AbstractMediaItemPresenter": "androidx/leanback/widget/AbstractMediaItemPresenter",
+      "android/support/annotation/StyleableRes": "androidx/annotation/StyleableRes",
+      "android/support/v4/media/session/PlaybackStateCompat$CustomAction": "androidx/media/session/PlaybackStateCompat$CustomAction",
+      "android/support/wear/widget/drawer/WearableDrawerLayout$TopDrawerDraggerCallback": "androidx/wear/widget/drawer/WearableDrawerLayout$TopDrawerDraggerCallback",
+      "android/support/v17/leanback/app/PermissionHelper": "androidx/leanback/app/PermissionHelper",
+      "android/support/transition/R": "androidx/transition/R",
+      "android/support/v17/leanback/transition/SlideKitkat$SlideAnimatorListener": "androidx/leanback/transition/SlideKitkat$SlideAnimatorListener",
+      "android/support/v7/graphics/drawable/DrawerArrowDrawable": "androidx/graphics/drawable/DrawerArrowDrawable",
+      "android/support/v17/leanback/widget/ClassPresenterSelector": "androidx/leanback/widget/ClassPresenterSelector",
+      "android/support/v17/leanback/widget/ControlBarPresenter$OnControlSelectedListener": "androidx/leanback/widget/ControlBarPresenter$OnControlSelectedListener",
+      "android/support/v4/app/NotificationCompat$GroupAlertBehavior": "androidx/app/NotificationCompat$GroupAlertBehavior",
+      "android/support/text/emoji/widget/EmojiInputFilter$InitCallbackImpl": "androidx/text/emoji/widget/EmojiInputFilter$InitCallbackImpl",
+      "android/support/v7/appcompat/R$bool": "androidx/appcompat/R$bool",
+      "android/support/v4/widget/TextViewCompat$AutoSizeTextType": "androidx/widget/TextViewCompat$AutoSizeTextType",
+      "android/support/v4/app/NotificationCompat$BadgeIconType": "androidx/app/NotificationCompat$BadgeIconType",
+      "android/support/v4/app/NotificationCompat$BigTextStyle": "androidx/app/NotificationCompat$BigTextStyle",
+      "android/support/annotation/ColorInt": "androidx/annotation/ColorInt",
+      "android/support/text/emoji/EmojiSpan": "androidx/text/emoji/EmojiSpan",
+      "android/support/wear/widget/SwipeDismissFrameLayout$MyOnDismissedListener": "androidx/wear/widget/SwipeDismissFrameLayout$MyOnDismissedListener",
+      "android/support/percent/PercentRelativeLayout": "androidx/PercentRelativeLayout",
+      "android/support/text/emoji/widget/EmojiInputFilter": "androidx/text/emoji/widget/EmojiInputFilter",
+      "android/support/media/tv/TvContractCompat$PreviewPrograms": "androidx/media/tv/TvContractCompat$PreviewPrograms",
+      "android/support/v17/leanback/widget/FullWidthDetailsOverviewRowPresenter$ViewHolder": "androidx/leanback/widget/FullWidthDetailsOverviewRowPresenter$ViewHolder",
+      "android/support/v17/leanback/app/BrowseFragment$BackStackListener": "androidx/leanback/app/BrowseFragment$BackStackListener",
+      "android/support/text/emoji/EmojiProcessor$ProcessorSm": "androidx/text/emoji/EmojiProcessor$ProcessorSm",
+      "android/support/v4/view/ViewCompat$ViewCompatBaseImpl": "androidx/view/ViewCompat$ViewCompatBaseImpl",
+      "android/support/design/internal/BottomNavigationMenu": "androidx/design/internal/BottomNavigationMenu",
+      "android/support/v7/view/menu/BaseMenuWrapper": "androidx/view/menu/BaseMenuWrapper",
+      "android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory$DefaultRoundedBitmapDrawable": "androidx/graphics/drawable/RoundedBitmapDrawableFactory$DefaultRoundedBitmapDrawable",
+      "android/support/v17/leanback/widget/OnItemViewClickedListener": "androidx/leanback/widget/OnItemViewClickedListener",
+      "android/support/v4/app/ShareCompat": "androidx/app/ShareCompat",
+      "android/support/v4/app/DialogFragment": "androidx/app/DialogFragment",
+      "android/support/v4/os/ConfigurationCompat": "androidx/os/ConfigurationCompat",
+      "android/support/v4/graphics/drawable/DrawableCompat": "androidx/graphics/drawable/DrawableCompat",
+      "android/support/v17/leanback/widget/PlaybackTransportRowView": "androidx/leanback/widget/PlaybackTransportRowView",
+      "android/support/annotation/ColorRes": "androidx/annotation/ColorRes",
+      "android/support/v4/database/DatabaseUtilsCompat": "androidx/database/DatabaseUtilsCompat",
+      "android/support/v7/app/AppCompatDelegateImplBase$ActionBarDrawableToggleImpl": "androidx/app/AppCompatDelegateImplBase$ActionBarDrawableToggleImpl",
+      "android/support/v7/widget/ForwardingListener": "androidx/widget/ForwardingListener",
+      "android/support/annotation/NavigationRes": "androidx/annotation/NavigationRes",
+      "android/support/v4/widget/TextViewCompat$TextViewCompatApi18Impl": "androidx/widget/TextViewCompat$TextViewCompatApi18Impl",
+      "android/support/v4/media/session/MediaSessionCompat$MediaSessionImplBase$Command": "androidx/media/session/MediaSessionCompat$MediaSessionImplBase$Command",
+      "android/support/transition/Styleable$ArcMotion": "androidx/transition/Styleable$ArcMotion",
+      "android/support/v17/leanback/transition/TransitionHelper$TransitionHelperStubImpl": "androidx/leanback/transition/TransitionHelper$TransitionHelperStubImpl",
+      "android/support/text/emoji/widget/EmojiEditTextHelper$HelperInternal": "androidx/text/emoji/widget/EmojiEditTextHelper$HelperInternal",
+      "android/support/wear/widget/BezierSCurveInterpolator": "androidx/wear/widget/BezierSCurveInterpolator",
+      "android/support/v4/view/NestedScrollingParent": "androidx/view/NestedScrollingParent",
+      "android/support/v7/widget/ListPopupWindow$ListSelectorHider": "androidx/widget/ListPopupWindow$ListSelectorHider",
+      "android/support/transition/PathProperty": "androidx/transition/PathProperty",
+      "android/support/v4/provider/FontsContractCompat$FontInfo": "androidx/provider/FontsContractCompat$FontInfo",
+      "android/support/v17/leanback/widget/PlaybackControlsRow": "androidx/leanback/widget/PlaybackControlsRow",
+      "android/support/v17/leanback/widget/Presenter": "androidx/leanback/widget/Presenter",
+      "android/support/v17/leanback/app/VerticalGridFragment": "androidx/leanback/app/VerticalGridFragment",
+      "android/support/v4/app/LoaderManagerImpl$LoaderInfo": "androidx/app/LoaderManagerImpl$LoaderInfo",
+      "android/support/v7/widget/RoundRectDrawableWithShadow": "androidx/widget/RoundRectDrawableWithShadow",
+      "android/support/v7/app/AppCompatDelegateImplV9$PanelFeatureState": "androidx/app/AppCompatDelegateImplV9$PanelFeatureState",
+      "android/support/v7/media/SystemMediaRouteProvider$JellybeanImpl$UserRouteRecord": "androidx/media/SystemMediaRouteProvider$JellybeanImpl$UserRouteRecord",
+      "android/support/v4/view/accessibility/AccessibilityManagerCompat": "androidx/view/accessibility/AccessibilityManagerCompat",
+      "android/support/v4/view/ScrollingView": "androidx/view/ScrollingView",
+      "android/support/customtabs/CustomTabsIntent": "androidx/browser/customtabs/CustomTabsIntent",
+      "android/support/v17/leanback/transition/TransitionListener": "androidx/leanback/transition/TransitionListener",
+      "android/support/annotation/DimenRes": "androidx/annotation/DimenRes",
+      "android/support/v17/leanback/widget/Parallax$IntProperty": "androidx/leanback/widget/Parallax$IntProperty",
+      "android/support/v4/app/FragmentManagerImpl$AnimationOrAnimator": "androidx/app/FragmentManagerImpl$AnimationOrAnimator",
+      "android/support/v4/widget/TextViewCompat$TextViewCompatBaseImpl": "androidx/widget/TextViewCompat$TextViewCompatBaseImpl",
+      "android/support/v17/leanback/app/BrowseFragment$ExpandPreLayout": "androidx/leanback/app/BrowseFragment$ExpandPreLayout",
+      "android/support/v13/view/DragAndDropPermissionsCompat$BaseDragAndDropPermissionsCompatImpl": "androidx/view/DragAndDropPermissionsCompat$BaseDragAndDropPermissionsCompatImpl",
+      "android/support/multidex/MultiDexExtractor": "androidx/multidex/MultiDexExtractor",
+      "android/support/v17/leanback/widget/GuidedDatePickerAction$Builder": "androidx/leanback/widget/GuidedDatePickerAction$Builder",
+      "android/support/v7/widget/AppCompatRatingBar": "androidx/widget/AppCompatRatingBar",
+      "android/support/wear/R$string": "androidx/wear/R$string",
+      "android/support/v7/app/AppCompatDelegateImplN": "androidx/app/AppCompatDelegateImplN",
+      "android/support/v17/leanback/widget/GridLayoutManager$PendingMoveSmoothScroller": "androidx/leanback/widget/GridLayoutManager$PendingMoveSmoothScroller",
+      "android/support/v17/leanback/widget/GuidedActionItemContainer": "androidx/leanback/widget/GuidedActionItemContainer",
+      "android/support/transition/ObjectAnimatorUtils": "androidx/transition/ObjectAnimatorUtils",
+      "android/support/v4/view/accessibility/AccessibilityNodeInfoCompat$CollectionInfoCompat": "androidx/view/accessibility/AccessibilityNodeInfoCompat$CollectionInfoCompat",
+      "android/support/design/widget/CoordinatorLayout$DispatchChangeEvent": "androidx/design/widget/CoordinatorLayout$DispatchChangeEvent",
+      "android/support/annotation/HalfFloat": "androidx/annotation/HalfFloat",
+      "android/support/v4/view/ViewCompat$ViewCompatApi16Impl": "androidx/view/ViewCompat$ViewCompatApi16Impl",
+      "android/support/v7/text/AllCapsTransformationMethod": "androidx/text/AllCapsTransformationMethod",
+      "android/support/v4/app/FragmentController": "androidx/app/FragmentController",
+      "android/support/v17/leanback/app/SearchSupportFragment$SearchResultProvider": "androidx/leanback/app/SearchSupportFragment$SearchResultProvider",
+      "android/support/v17/leanback/widget/RoundedRectHelper$StubImpl": "androidx/leanback/widget/RoundedRectHelper$StubImpl",
+      "android/support/v4/graphics/drawable/RoundedBitmapDrawable": "androidx/graphics/drawable/RoundedBitmapDrawable",
+      "android/support/v4/widget/ScrollerCompat": "androidx/widget/ScrollerCompat",
+      "android/support/v17/leanback/widget/RoundedRectHelper$Api21Impl": "androidx/leanback/widget/RoundedRectHelper$Api21Impl",
+      "android/support/wear/internal/widget/drawer/SinglePagePresenter$Ui": "androidx/wear/internal/widget/drawer/SinglePagePresenter$Ui",
+      "android/support/v7/app/AppCompatDelegateImplV14$AutoNightModeManager": "androidx/app/AppCompatDelegateImplV14$AutoNightModeManager",
+      "android/support/v17/leanback/widget/ImeKeyMonitor$ImeKeyListener": "androidx/leanback/widget/ImeKeyMonitor$ImeKeyListener",
+      "android/support/v4/view/InputDeviceCompat": "androidx/view/InputDeviceCompat",
+      "android/support/wear/widget/CircularProgressLayoutController": "androidx/wear/widget/CircularProgressLayoutController",
+      "android/support/v17/leanback/widget/PlaybackTransportRowView$OnUnhandledKeyListener": "androidx/leanback/widget/PlaybackTransportRowView$OnUnhandledKeyListener",
+      "android/support/v4/view/ViewCompat$AccessibilityLiveRegion": "androidx/view/ViewCompat$AccessibilityLiveRegion",
+      "android/support/v13/app/FragmentCompat$FragmentCompatImpl": "androidx/app/FragmentCompat$FragmentCompatImpl",
+      "android/support/design/widget/Snackbar": "androidx/design/widget/Snackbar",
+      "android/support/v7/preference/PreferenceGroupAdapter": "androidx/preference/PreferenceGroupAdapter",
+      "android/support/v4/view/MenuItemCompat": "androidx/view/MenuItemCompat",
+      "android/support/v7/widget/ChildHelper$Bucket": "androidx/widget/ChildHelper$Bucket",
+      "android/support/v7/util/DiffUtil$DiffResult": "androidx/util/DiffUtil$DiffResult",
+      "android/support/v13/app/FragmentCompat$PermissionCompatDelegate": "androidx/app/FragmentCompat$PermissionCompatDelegate",
+      "android/support/v4/view/MotionEventCompat": "androidx/view/MotionEventCompat",
+      "android/support/v17/leanback/widget/PlaybackControlsRow$ClosedCaptioningAction": "androidx/leanback/widget/PlaybackControlsRow$ClosedCaptioningAction",
+      "android/support/v4/app/FragmentState": "androidx/app/FragmentState",
+      "android/support/v7/widget/CardView": "androidx/widget/CardView",
+      "android/support/v13/view/inputmethod/InputContentInfoCompat$InputContentInfoCompatApi25Impl": "androidx/view/inputmethod/InputContentInfoCompat$InputContentInfoCompatApi25Impl",
+      "android/support/v4/widget/PopupWindowCompat$PopupWindowCompatBaseImpl": "androidx/widget/PopupWindowCompat$PopupWindowCompatBaseImpl",
+      "android/support/media/tv/TvContractCompat$PreviewProgramColumns$Type": "androidx/media/tv/TvContractCompat$PreviewProgramColumns$Type",
+      "android/support/v4/media/session/MediaSessionCompat$OnActiveChangeListener": "androidx/media/session/MediaSessionCompat$OnActiveChangeListener",
+      "android/support/design/widget/BottomSheetBehavior": "androidx/design/widget/BottomSheetBehavior",
+      "android/support/customtabs/CustomTabsSession": "androidx/browser/customtabs/CustomTabsSession",
+      "android/support/v7/media/RemotePlaybackClient$StatusCallback": "androidx/media/RemotePlaybackClient$StatusCallback",
+      "android/support/v17/leanback/app/HeadersSupportFragment$OnHeaderViewSelectedListener": "androidx/leanback/app/HeadersSupportFragment$OnHeaderViewSelectedListener",
+      "android/support/v4/view/ViewPager$OnPageChangeListener": "androidx/view/ViewPager$OnPageChangeListener",
+      "android/support/v4/app/BackStackRecord$Op": "androidx/app/BackStackRecord$Op",
+      "android/support/media/ExifInterface$ExifAttribute": "androidx/media/ExifInterface$ExifAttribute",
+      "android/support/transition/ChangeBounds$ViewBounds": "androidx/transition/ChangeBounds$ViewBounds",
+      "android/support/v17/leanback/widget/NonOverlappingLinearLayoutWithForeground": "androidx/leanback/widget/NonOverlappingLinearLayoutWithForeground",
+      "android/support/v4/app/NotificationCompat$BigPictureStyle": "androidx/app/NotificationCompat$BigPictureStyle",
+      "android/support/design/widget/FloatingActionButton$ShadowDelegateImpl": "androidx/design/widget/FloatingActionButton$ShadowDelegateImpl",
+      "android/support/media/instantvideo/preload/InstantVideoPreloadManager$VideoPreloaderFactory": "androidx/media/instantvideo/preload/InstantVideoPreloadManager$VideoPreloaderFactory",
+      "android/support/v7/app/MediaRouteDialogHelper": "androidx/app/MediaRouteDialogHelper",
+      "android/support/media/ExifInterface$ExifTag": "androidx/media/ExifInterface$ExifTag",
+      "android/support/v17/leanback/widget/BaseCardView$InfoHeightAnimation": "androidx/leanback/widget/BaseCardView$InfoHeightAnimation",
+      "android/support/v13/view/inputmethod/EditorInfoCompat$EditorInfoCompatApi25Impl": "androidx/view/inputmethod/EditorInfoCompat$EditorInfoCompatApi25Impl",
+      "android/support/transition/TransitionSet$TransitionSetListener": "androidx/transition/TransitionSet$TransitionSetListener",
+      "android/support/v17/leanback/transition/SlideKitkat$CalculateSlide": "androidx/leanback/transition/SlideKitkat$CalculateSlide",
+      "android/support/v4/app/ActivityCompat$OnRequestPermissionsResultCallback": "androidx/app/ActivityCompat$OnRequestPermissionsResultCallback",
+      "android/support/v7/widget/AppCompatRadioButton": "androidx/widget/AppCompatRadioButton",
+      "android/support/v4/app/TaskStackBuilder$TaskStackBuilderApi16Impl": "androidx/app/TaskStackBuilder$TaskStackBuilderApi16Impl",
+      "android/support/v7/media/MediaRouter$RouteInfo$PlaybackVolume": "androidx/media/MediaRouter$RouteInfo$PlaybackVolume",
+      "android/support/v17/preference/LeanbackListPreferenceDialogFragment$AdapterMulti": "androidx/leanback/preference/LeanbackListPreferenceDialogFragment$AdapterMulti",
+      "android/support/v4/print/PrintHelper$PrintHelperApi19": "androidx/print/PrintHelper$PrintHelperApi19",
+      "android/support/v7/media/MediaRouter$RouteInfo$DeviceType": "androidx/media/MediaRouter$RouteInfo$DeviceType",
+      "android/support/v4/media/MediaBrowserCompat$SubscriptionCallback": "androidx/media/MediaBrowserCompat$SubscriptionCallback",
+      "android/support/v4/media/MediaBrowserCompat$MediaItem$Flags": "androidx/media/MediaBrowserCompat$MediaItem$Flags",
+      "android/support/v17/leanback/R$attr": "androidx/leanback/R$attr",
+      "android/support/v7/media/MediaRouteDescriptor$Builder": "androidx/media/MediaRouteDescriptor$Builder",
+      "android/support/v7/media/MediaRouteProviderService": "androidx/media/MediaRouteProviderService",
+      "android/support/v4/app/ActivityCompat$RequestPermissionsRequestCodeValidator": "androidx/app/ActivityCompat$RequestPermissionsRequestCodeValidator",
+      "android/support/v17/leanback/widget/ControlBar$OnChildFocusedListener": "androidx/leanback/widget/ControlBar$OnChildFocusedListener",
+      "android/support/v7/media/MediaRouterApi24$RouteInfo": "androidx/media/MediaRouterApi24$RouteInfo",
+      "android/support/v4/app/JobIntentService$WorkEnqueuer": "androidx/app/JobIntentService$WorkEnqueuer",
+      "android/support/v7/preference/DialogPreference$TargetFragment": "androidx/preference/DialogPreference$TargetFragment",
+      "android/support/wear/widget/BoxInsetLayout$LayoutParams": "androidx/wear/widget/BoxInsetLayout$LayoutParams",
+      "android/support/v4/util/SimpleArrayMap": "androidx/util/SimpleArrayMap",
+      "android/support/v4/widget/EdgeEffectCompat": "androidx/widget/EdgeEffectCompat",
+      "android/support/v13/app/FragmentCompat$FragmentCompatApi15Impl": "androidx/app/FragmentCompat$FragmentCompatApi15Impl",
+      "android/support/v4/print/PrintHelper$PrintHelperApi23": "androidx/print/PrintHelper$PrintHelperApi23",
+      "android/support/percent/R$styleable": "androidx/R$styleable",
+      "android/support/v4/print/PrintHelper$PrintHelperApi24": "androidx/print/PrintHelper$PrintHelperApi24",
+      "android/support/v4/content/res/FontResourcesParserCompat": "androidx/content/res/FontResourcesParserCompat",
+      "android/support/design/widget/SnackbarManager$SnackbarRecord": "androidx/design/widget/SnackbarManager$SnackbarRecord",
+      "android/support/v7/util/DiffUtil$Range": "androidx/util/DiffUtil$Range",
+      "android/support/v4/media/MediaBrowserCompat$MediaBrowserImpl": "androidx/media/MediaBrowserCompat$MediaBrowserImpl",
+      "android/support/v4/print/PrintHelper$PrintHelperApi20": "androidx/print/PrintHelper$PrintHelperApi20",
+      "android/support/v4/media/session/PlaybackStateCompat$RepeatMode": "androidx/media/session/PlaybackStateCompat$RepeatMode",
+      "android/support/v7/media/MediaRouteProviderProtocol": "androidx/media/MediaRouteProviderProtocol",
+      "android/support/transition/Transition$ArrayListManager": "androidx/transition/Transition$ArrayListManager",
+      "android/support/text/emoji/widget/EmojiButton": "androidx/text/emoji/widget/EmojiButton",
+      "android/support/v4/view/ActionProvider$VisibilityListener": "androidx/view/ActionProvider$VisibilityListener",
+      "android/support/v7/widget/AppCompatProgressBarHelper": "androidx/widget/AppCompatProgressBarHelper",
+      "android/support/v7/widget/LinearLayoutCompat": "androidx/widget/LinearLayoutCompat",
+      "android/support/v4/app/Fragment$OnStartEnterTransitionListener": "androidx/app/Fragment$OnStartEnterTransitionListener",
+      "android/support/v7/app/AppCompatDialogFragment": "androidx/app/AppCompatDialogFragment",
+      "android/support/graphics/drawable/Animatable2Compat": "androidx/graphics/drawable/Animatable2Compat",
+      "android/support/design/widget/TabLayout$PagerAdapterObserver": "androidx/design/widget/TabLayout$PagerAdapterObserver",
+      "android/support/v7/widget/StaggeredGridLayoutManager$LazySpanLookup$FullSpanItem": "androidx/widget/StaggeredGridLayoutManager$LazySpanLookup$FullSpanItem",
+      "android/support/v4/content/pm/ShortcutInfoCompat": "androidx/content/pm/ShortcutInfoCompat",
+      "android/support/v4/view/ScaleGestureDetectorCompat": "androidx/view/ScaleGestureDetectorCompat",
+      "android/support/v17/leanback/widget/ShadowHelperApi21$ShadowImpl": "androidx/leanback/widget/ShadowHelperApi21$ShadowImpl",
+      "android/support/v7/appcompat/R$id": "androidx/appcompat/R$id",
+      "android/support/v17/leanback/transition/Scale": "androidx/leanback/transition/Scale",
+      "android/support/v7/widget/ViewBoundsCheck": "androidx/widget/ViewBoundsCheck",
+      "android/support/design/widget/BottomSheetBehavior$State": "androidx/design/widget/BottomSheetBehavior$State",
+      "android/support/v7/app/ActionBarDrawerToggle$JellybeanMr2Delegate": "androidx/app/ActionBarDrawerToggle$JellybeanMr2Delegate",
+      "android/support/v7/internal/widget/PreferenceImageView": "androidx/internal/widget/PreferenceImageView",
+      "android/support/content/ContentPager$CursorCache": "androidx/content/ContentPager$CursorCache",
+      "android/support/v4/media/session/MediaSessionCompat$MediaSessionImplBase$MessageHandler": "androidx/media/session/MediaSessionCompat$MediaSessionImplBase$MessageHandler",
+      "android/support/v4/app/NotificationCompat$Action$Extender": "androidx/app/NotificationCompat$Action$Extender",
+      "android/support/v7/widget/Toolbar$OnMenuItemClickListener": "androidx/widget/Toolbar$OnMenuItemClickListener",
+      "android/support/multidex/MultiDex$V4": "androidx/multidex/MultiDex$V4",
+      "android/support/v4/app/ActionBarDrawerToggle": "androidx/app/ActionBarDrawerToggle",
+      "android/support/v7/media/RemoteControlClientCompat": "androidx/media/RemoteControlClientCompat",
+      "android/support/v4/media/app/NotificationCompat$MediaStyle": "androidx/media/app/NotificationCompat$MediaStyle",
+      "android/support/v17/leanback/transition/TransitionEpicenterCallback": "androidx/leanback/transition/TransitionEpicenterCallback",
+      "android/support/v4/content/LocalBroadcastManager$ReceiverRecord": "androidx/content/LocalBroadcastManager$ReceiverRecord",
+      "android/support/v7/view/menu/BaseMenuPresenter": "androidx/view/menu/BaseMenuPresenter",
+      "android/support/v4/view/accessibility/AccessibilityManagerCompat$AccessibilityStateChangeListener": "androidx/view/accessibility/AccessibilityManagerCompat$AccessibilityStateChangeListener",
+      "android/support/design/widget/TabLayout$AdapterChangeListener": "androidx/design/widget/TabLayout$AdapterChangeListener",
+      "android/support/v7/widget/RecyclerView$RecyclerListener": "androidx/widget/RecyclerView$RecyclerListener",
+      "android/support/media/tv/ChannelLogoUtils": "androidx/media/tv/ChannelLogoUtils",
+      "android/support/v4/media/session/PlaybackStateCompat$CustomAction$Builder": "androidx/media/session/PlaybackStateCompat$CustomAction$Builder",
+      "android/support/v17/leanback/widget/GuidedActionAdapter": "androidx/leanback/widget/GuidedActionAdapter",
+      "android/support/v17/leanback/widget/ItemBridgeAdapter$Wrapper": "androidx/leanback/widget/ItemBridgeAdapter$Wrapper",
+      "android/support/v17/leanback/widget/DetailsOverviewRow$Listener": "androidx/leanback/widget/DetailsOverviewRow$Listener",
+      "android/support/annotation/InterpolatorRes": "androidx/annotation/InterpolatorRes",
+      "android/support/v4/widget/CursorAdapter$ChangeObserver": "androidx/widget/CursorAdapter$ChangeObserver",
+      "android/support/v4/widget/SimpleCursorAdapter$CursorToStringConverter": "androidx/widget/SimpleCursorAdapter$CursorToStringConverter",
+      "android/support/v7/widget/helper/ItemTouchUIUtilImpl$BaseImpl": "androidx/widget/helper/ItemTouchUIUtilImpl$BaseImpl",
+      "android/support/v4/provider/RawDocumentFile": "androidx/provider/RawDocumentFile",
+      "android/support/text/emoji/widget/EmojiExtractTextLayout$ButtonOnclickListener": "androidx/text/emoji/widget/EmojiExtractTextLayout$ButtonOnclickListener",
+      "android/support/v7/view/menu/MenuItemWrapperJB$ActionProviderWrapperJB": "androidx/view/menu/MenuItemWrapperJB$ActionProviderWrapperJB",
+      "android/support/media/tv/TvContractCompat$RecordedPrograms": "androidx/media/tv/TvContractCompat$RecordedPrograms",
+      "android/support/v17/leanback/widget/picker/Picker$PickerValueListener": "androidx/leanback/widget/picker/Picker$PickerValueListener",
+      "android/support/v4/media/session/IMediaControllerCallback$Stub": "androidx/media/session/IMediaControllerCallback$Stub",
+      "android/support/v7/preference/PreferenceManager": "androidx/preference/PreferenceManager",
+      "android/support/transition/GhostViewApi21$Creator": "androidx/transition/GhostViewApi21$Creator",
+      "android/support/v7/widget/ActionMenuView$ActionMenuChildView": "androidx/widget/ActionMenuView$ActionMenuChildView",
+      "android/support/v17/leanback/app/GuidedStepSupportFragment$DummyFragment": "androidx/leanback/app/GuidedStepSupportFragment$DummyFragment",
+      "android/support/v4/app/BaseFragmentActivityApi14": "androidx/app/BaseFragmentActivityApi14",
+      "android/support/v14/preference/SwitchPreference": "androidx/preference/SwitchPreference",
+      "android/support/v4/app/BaseFragmentActivityApi16": "androidx/app/BaseFragmentActivityApi16",
+      "android/support/v7/preference/PreferenceFragmentCompat$ScrollToPreferenceObserver": "androidx/preference/PreferenceFragmentCompat$ScrollToPreferenceObserver",
+      "android/support/v4/graphics/PaintCompat": "androidx/graphics/PaintCompat",
+      "android/support/v4/media/AudioAttributesCompat": "androidx/media/AudioAttributesCompat",
+      "android/support/v4/media/session/MediaSessionCompat$QueueItem": "androidx/media/session/MediaSessionCompat$QueueItem",
+      "android/support/transition/Transition$AnimationInfo": "androidx/transition/Transition$AnimationInfo",
+      "android/support/v7/app/MediaRouteControllerDialog$VolumeChangeListener": "androidx/app/MediaRouteControllerDialog$VolumeChangeListener",
+      "android/support/v4/widget/DrawerLayout$ViewDragCallback": "androidx/widget/DrawerLayout$ViewDragCallback",
+      "android/support/design/widget/FloatingActionButtonImpl$ResetElevationAnimation": "androidx/design/widget/FloatingActionButtonImpl$ResetElevationAnimation",
+      "android/support/design/widget/TabLayout$OnTabSelectedListener": "androidx/design/widget/TabLayout$OnTabSelectedListener",
+      "android/support/annotation/VisibleForTesting": "androidx/annotation/VisibleForTesting",
+      "android/support/v4/app/NotificationCompatSideChannelService$NotificationSideChannelStub": "androidx/app/NotificationCompatSideChannelService$NotificationSideChannelStub",
+      "android/support/annotation/RawRes": "androidx/annotation/RawRes",
+      "android/support/design/R$anim": "androidx/design/R$anim",
+      "android/support/transition/Transition$MatchOrder": "androidx/transition/Transition$MatchOrder",
+      "android/support/v7/widget/RecyclerView$SimpleOnItemTouchListener": "androidx/widget/RecyclerView$SimpleOnItemTouchListener",
+      "android/support/transition/AnimatorUtilsImpl": "androidx/transition/AnimatorUtilsImpl",
+      "android/support/v4/view/OnApplyWindowInsetsListener": "androidx/view/OnApplyWindowInsetsListener",
+      "android/support/wear/widget/SwipeDismissLayout": "androidx/wear/widget/SwipeDismissLayout",
+      "android/support/mediacompat/R$color": "androidx/mediacompat/R$color",
+      "android/support/v7/preference/PreferenceManager$PreferenceComparisonCallback": "androidx/preference/PreferenceManager$PreferenceComparisonCallback",
+      "android/support/v7/widget/ActionMenuView$OnMenuItemClickListener": "androidx/widget/ActionMenuView$OnMenuItemClickListener",
+      "android/support/v7/widget/TooltipCompat$BaseViewCompatImpl": "androidx/widget/TooltipCompat$BaseViewCompatImpl",
+      "android/support/wear/widget/drawer/WearableNavigationDrawerView$NavigationStyle": "androidx/wear/widget/drawer/WearableNavigationDrawerView$NavigationStyle",
+      "android/support/transition/PatternPathMotion": "androidx/transition/PatternPathMotion",
+      "android/support/v7/palette/BuildConfig": "androidx/palette/BuildConfig",
+      "android/support/transition/Styleable$TransitionTarget": "androidx/transition/Styleable$TransitionTarget",
+      "android/support/mediacompat/R": "androidx/mediacompat/R",
+      "android/support/v7/media/MediaRouterJellybeanMr1$Callback": "androidx/media/MediaRouterJellybeanMr1$Callback",
+      "android/support/v17/leanback/widget/ControlBarPresenter$OnControlClickedListener": "androidx/leanback/widget/ControlBarPresenter$OnControlClickedListener",
+      "android/support/v4/view/LayoutInflaterCompat$Factory2Wrapper": "androidx/view/LayoutInflaterCompat$Factory2Wrapper",
+      "android/support/v7/widget/GridLayoutManager$LayoutParams": "androidx/widget/GridLayoutManager$LayoutParams",
+      "android/support/v7/widget/GridLayout": "androidx/widget/GridLayout",
+      "android/support/v4/media/MediaBrowserServiceCompat$ServiceBinderImpl": "androidx/media/MediaBrowserServiceCompat$ServiceBinderImpl",
+      "android/support/v7/widget/AppCompatCheckBox": "androidx/widget/AppCompatCheckBox",
+      "android/support/v4/view/GestureDetectorCompat$GestureDetectorCompatImpl": "androidx/view/GestureDetectorCompat$GestureDetectorCompatImpl",
+      "android/support/v17/preference/BaseLeanbackPreferenceFragment": "androidx/leanback/preference/BaseLeanbackPreferenceFragment",
+      "android/support/v17/leanback/app/GuidedStepRootLayout": "androidx/leanback/app/GuidedStepRootLayout",
+      "android/support/v4/app/FragmentManager$FragmentLifecycleCallbacks": "androidx/app/FragmentManager$FragmentLifecycleCallbacks",
+      "android/support/v4/media/MediaMetadataCompatApi21": "androidx/media/MediaMetadataCompatApi21",
+      "android/support/v7/app/MediaRouterThemeHelper": "androidx/app/MediaRouterThemeHelper",
+      "android/support/v4/util/MapCollections$EntrySet": "androidx/util/MapCollections$EntrySet",
+      "android/support/v7/widget/ViewBoundsCheck$ViewBounds": "androidx/widget/ViewBoundsCheck$ViewBounds",
+      "android/support/design/widget/HeaderScrollingViewBehavior": "androidx/design/widget/HeaderScrollingViewBehavior",
+      "android/support/v7/app/ActionBarDrawerToggle$IcsDelegate": "androidx/app/ActionBarDrawerToggle$IcsDelegate",
+      "android/support/v4/media/session/ParcelableVolumeInfo": "androidx/media/session/ParcelableVolumeInfo",
+      "android/support/v17/leanback/widget/GuidedAction$Builder": "androidx/leanback/widget/GuidedAction$Builder",
+      "android/support/v7/view/menu/CascadingMenuPopup": "androidx/view/menu/CascadingMenuPopup",
+      "android/support/v7/view/menu/MenuPopup": "androidx/view/menu/MenuPopup",
+      "android/support/v7/app/AlertDialog": "androidx/app/AlertDialog",
+      "android/support/v7/widget/RecyclerView$SavedState": "androidx/widget/RecyclerView$SavedState",
+      "android/support/wear/widget/drawer/WearableActionDrawerView$TitleViewHolder": "androidx/wear/widget/drawer/WearableActionDrawerView$TitleViewHolder",
+      "android/support/v13/view/inputmethod/InputContentInfoCompat$InputContentInfoCompatBaseImpl": "androidx/view/inputmethod/InputContentInfoCompat$InputContentInfoCompatBaseImpl",
+      "android/support/v17/leanback/app/VideoSupportFragmentGlueHost": "androidx/leanback/app/VideoSupportFragmentGlueHost",
+      "android/support/text/emoji/EmojiProcessor": "androidx/text/emoji/EmojiProcessor",
+      "android/support/v17/leanback/widget/ActionPresenterSelector": "androidx/leanback/widget/ActionPresenterSelector",
+      "android/support/v13/view/inputmethod/EditorInfoCompat$EditorInfoCompatImpl": "androidx/view/inputmethod/EditorInfoCompat$EditorInfoCompatImpl",
+      "android/support/text/emoji/widget/EmojiExtractTextLayout": "androidx/text/emoji/widget/EmojiExtractTextLayout",
+      "android/support/v17/leanback/widget/GuidedAction": "androidx/leanback/widget/GuidedAction",
+      "android/support/v17/leanback/util/StateMachine$Condition": "androidx/leanback/util/StateMachine$Condition",
+      "android/support/text/emoji/flatbuffer/Table": "androidx/text/emoji/flatbuffer/Table",
+      "android/support/v4/os/OperationCanceledException": "androidx/os/OperationCanceledException",
+      "android/support/v7/media/RegisteredMediaRouteProvider$PrivateHandler": "androidx/media/RegisteredMediaRouteProvider$PrivateHandler",
+      "android/support/v7/widget/RecyclerView$Adapter": "androidx/widget/RecyclerView$Adapter",
+      "android/support/v13/BuildConfig": "androidx/BuildConfig",
+      "android/support/v7/util/ListUpdateCallback": "androidx/util/ListUpdateCallback",
+      "android/support/v4/media/MediaDescriptionCompatApi21": "androidx/media/MediaDescriptionCompatApi21",
+      "android/support/v4/view/ViewCompat$FocusRealDirection": "androidx/view/ViewCompat$FocusRealDirection",
+      "android/support/v4/media/session/MediaControllerCompat$Callback": "androidx/media/session/MediaControllerCompat$Callback",
+      "android/support/v4/media/MediaDescriptionCompatApi23": "androidx/media/MediaDescriptionCompatApi23",
+      "android/support/v4/view/accessibility/AccessibilityEventCompat": "androidx/view/accessibility/AccessibilityEventCompat",
+      "android/support/text/emoji/FontRequestEmojiCompatConfig$FontProviderHelper": "androidx/text/emoji/FontRequestEmojiCompatConfig$FontProviderHelper",
+      "android/support/v4/app/NotificationCompat$Action$Builder": "androidx/app/NotificationCompat$Action$Builder",
+      "android/support/v4/view/accessibility/AccessibilityNodeProviderCompat$AccessibilityNodeProviderApi16": "androidx/view/accessibility/AccessibilityNodeProviderCompat$AccessibilityNodeProviderApi16",
+      "android/support/annotation/AnimatorRes": "androidx/annotation/AnimatorRes",
+      "android/support/v17/leanback/widget/GuidanceStylist": "androidx/leanback/widget/GuidanceStylist",
+      "android/support/v17/leanback/widget/StaticShadowHelper$ShadowHelperStubImpl": "androidx/leanback/widget/StaticShadowHelper$ShadowHelperStubImpl",
+      "android/support/v4/view/accessibility/AccessibilityNodeProviderCompat$AccessibilityNodeProviderApi19": "androidx/view/accessibility/AccessibilityNodeProviderCompat$AccessibilityNodeProviderApi19",
+      "android/support/v7/util/ThreadUtil": "androidx/util/ThreadUtil",
+      "android/support/content/InMemoryCursor$ObserverRelay": "androidx/content/InMemoryCursor$ObserverRelay",
+      "android/support/wear/R$drawable": "androidx/wear/R$drawable",
+      "android/support/constraint/ConstraintLayout$LayoutParams": "androidx/constraint/ConstraintLayout$LayoutParams",
+      "android/support/v7/widget/StaggeredGridLayoutManager$LazySpanLookup": "androidx/widget/StaggeredGridLayoutManager$LazySpanLookup",
+      "android/support/transition/BuildConfig": "androidx/transition/BuildConfig",
+      "android/support/v17/leanback/app/SearchFragment$ExternalQuery": "androidx/leanback/app/SearchFragment$ExternalQuery",
+      "android/support/media/tv/TvContractCompat$Programs$Genres": "androidx/media/tv/TvContractCompat$Programs$Genres",
+      "android/support/v13/view/inputmethod/InputConnectionCompat$OnCommitContentListener": "androidx/view/inputmethod/InputConnectionCompat$OnCommitContentListener",
+      "android/support/content/ContentPager$CursorDisposition": "androidx/content/ContentPager$CursorDisposition",
+      "android/support/v4/view/AsyncLayoutInflater$OnInflateFinishedListener": "androidx/view/AsyncLayoutInflater$OnInflateFinishedListener",
+      "android/support/v4/media/session/MediaControllerCompat$MediaControllerImplBase": "androidx/media/session/MediaControllerCompat$MediaControllerImplBase",
+      "android/support/v4/util/PatternsCompat": "androidx/util/PatternsCompat",
+      "android/support/v4/media/MediaBrowserServiceCompat$ServiceCallbacksCompat": "androidx/media/MediaBrowserServiceCompat$ServiceCallbacksCompat",
+      "android/support/v7/widget/AppCompatCompoundButtonHelper$DirectSetButtonDrawableInterface": "androidx/widget/AppCompatCompoundButtonHelper$DirectSetButtonDrawableInterface",
+      "android/support/v17/leanback/media/PlaybackGlueHost": "androidx/leanback/media/PlaybackGlueHost",
+      "android/support/v4/content/ModernAsyncTask": "androidx/content/ModernAsyncTask",
+      "android/support/v4/view/ViewCompat$ViewCompatApi15Impl": "androidx/view/ViewCompat$ViewCompatApi15Impl",
+      "android/support/v4/os/ResultReceiver$MyResultReceiver": "androidx/os/ResultReceiver$MyResultReceiver",
+      "android/support/v7/widget/helper/ItemTouchHelper$Callback": "androidx/widget/helper/ItemTouchHelper$Callback",
+      "android/support/v17/leanback/app/BrowseFragment$MainFragmentRowsAdapterProvider": "androidx/leanback/app/BrowseFragment$MainFragmentRowsAdapterProvider",
+      "android/support/annotation/Nullable": "androidx/annotation/Nullable",
+      "android/support/v4/os/IResultReceiver$Stub": "androidx/os/IResultReceiver$Stub",
+      "android/support/v7/widget/Toolbar$ExpandedActionViewMenuPresenter": "androidx/widget/Toolbar$ExpandedActionViewMenuPresenter",
+      "android/support/v17/leanback/widget/picker/PickerUtility": "androidx/leanback/widget/picker/PickerUtility",
+      "android/support/v4/view/ViewPager$MyAccessibilityDelegate": "androidx/view/ViewPager$MyAccessibilityDelegate",
+      "android/support/v4/app/FragmentTransaction$Transit": "androidx/app/FragmentTransaction$Transit",
+      "android/support/v7/widget/DefaultItemAnimator": "androidx/widget/DefaultItemAnimator",
+      "android/support/v4/view/PagerAdapter": "androidx/widget/PagerAdapter",
+      "android/support/v7/media/MediaRouteProviderService$PrivateHandler": "androidx/media/MediaRouteProviderService$PrivateHandler",
+      "android/support/v4/media/session/IMediaControllerCallback$Stub$Proxy": "androidx/media/session/IMediaControllerCallback$Stub$Proxy",
+      "android/support/v4/media/RatingCompat": "androidx/media/RatingCompat",
+      "android/support/v7/widget/GridLayout$Assoc": "androidx/widget/GridLayout$Assoc",
+      "android/support/v17/leanback/app/DetailsSupportFragment$SetSelectionRunnable": "androidx/leanback/app/DetailsSupportFragment$SetSelectionRunnable",
+      "android/support/v7/util/DiffUtil$Callback": "androidx/util/DiffUtil$Callback",
+      "android/support/v7/widget/ActivityChooserModel$ActivityChooserModelClient": "androidx/widget/ActivityChooserModel$ActivityChooserModelClient",
+      "android/support/v7/widget/PagerSnapHelper": "androidx/widget/PagerSnapHelper",
+      "android/support/v17/leanback/widget/GridLayoutManager$LayoutParams": "androidx/leanback/widget/GridLayoutManager$LayoutParams",
+      "android/support/percent/R": "androidx/R",
+      "android/support/transition/Styleable$Fade": "androidx/transition/Styleable$Fade",
+      "android/support/design/widget/FloatingActionButtonLollipop": "androidx/design/widget/FloatingActionButtonLollipop",
+      "android/support/v17/leanback/widget/ActionPresenterSelector$OneLineActionPresenter": "androidx/leanback/widget/ActionPresenterSelector$OneLineActionPresenter",
+      "android/support/transition/ViewUtilsApi22": "androidx/transition/ViewUtilsApi22",
+      "android/support/v7/widget/LinearLayoutCompat$LayoutParams": "androidx/widget/LinearLayoutCompat$LayoutParams",
+      "android/support/transition/ViewUtilsApi21": "androidx/transition/ViewUtilsApi21",
+      "android/support/v17/leanback/app/BrandedFragment": "androidx/leanback/app/BrandedFragment",
+      "android/support/text/emoji/bundled/BundledEmojiCompatConfig$BundledMetadataLoader": "androidx/text/emoji/bundled/BundledEmojiCompatConfig$BundledMetadataLoader",
+      "android/support/v17/preference/R": "androidx/leanback/preference/R",
+      "android/support/v4/content/res/TypedArrayUtils": "androidx/content/res/TypedArrayUtils",
+      "android/support/v7/app/ActionBar$LayoutParams": "androidx/app/ActionBar$LayoutParams",
+      "android/support/transition/Visibility$DisappearListener": "androidx/transition/Visibility$DisappearListener",
+      "android/support/v7/view/menu/MenuView$ItemView": "androidx/view/menu/MenuView$ItemView",
+      "android/support/text/emoji/flatbuffer/FlatBufferBuilder": "androidx/text/emoji/flatbuffer/FlatBufferBuilder",
+      "android/support/v17/leanback/app/BaseRowFragment$LateSelectionObserver": "androidx/leanback/app/BaseRowFragment$LateSelectionObserver",
+      "android/support/wear/widget/SwipeDismissFrameLayout$MyOnSwipeProgressChangedListener": "androidx/wear/widget/SwipeDismissFrameLayout$MyOnSwipeProgressChangedListener",
+      "android/support/v4/content/PermissionChecker$PermissionResult": "androidx/content/PermissionChecker$PermissionResult",
+      "android/support/v7/media/MediaRouter$ControlRequestCallback": "androidx/media/MediaRouter$ControlRequestCallback",
+      "android/support/wear/widget/drawer/WearableNavigationDrawerView$WearableNavigationDrawerAdapter": "androidx/wear/widget/drawer/WearableNavigationDrawerView$WearableNavigationDrawerAdapter",
+      "android/support/v13/view/inputmethod/InputContentInfoCompat": "androidx/view/inputmethod/InputContentInfoCompat",
+      "android/support/transition/ViewUtilsApi19": "androidx/transition/ViewUtilsApi19",
+      "android/support/transition/ViewUtilsApi18": "androidx/transition/ViewUtilsApi18",
+      "android/support/v7/widget/AppCompatDrawableManager$InflateDelegate": "androidx/widget/AppCompatDrawableManager$InflateDelegate",
+      "android/support/v7/preference/R$styleable": "androidx/preference/R$styleable",
+      "android/support/v13/view/DragAndDropPermissionsCompat": "androidx/view/DragAndDropPermissionsCompat",
+      "android/support/v4/content/res/FontResourcesParserCompat$FontFamilyFilesResourceEntry": "androidx/content/res/FontResourcesParserCompat$FontFamilyFilesResourceEntry",
+      "android/support/transition/ViewUtilsApi14": "androidx/transition/ViewUtilsApi14",
+      "android/support/v7/view/menu/MenuItemWrapperICS": "androidx/view/menu/MenuItemWrapperICS",
+      "android/support/design/widget/CollapsingToolbarLayout": "androidx/design/widget/CollapsingToolbarLayout",
+      "android/support/dynamicanimation/BuildConfig": "androidx/dynamicanimation/BuildConfig",
+      "android/support/v4/util/ArrayMap": "androidx/util/ArrayMap",
+      "android/support/v17/leanback/widget/ParallaxTarget$PropertyValuesHolderTarget": "androidx/leanback/widget/ParallaxTarget$PropertyValuesHolderTarget",
+      "android/support/wear/widget/drawer/WearableActionDrawerView$ActionListAdapter": "androidx/wear/widget/drawer/WearableActionDrawerView$ActionListAdapter",
+      "android/support/v4/media/MediaBrowserCompat$MediaBrowserServiceCallbackImpl": "androidx/media/MediaBrowserCompat$MediaBrowserServiceCallbackImpl",
+      "android/support/wear/widget/SimpleAnimatorListener": "androidx/wear/widget/SimpleAnimatorListener",
+      "android/support/v17/leanback/widget/FacetProviderAdapter": "androidx/leanback/widget/FacetProviderAdapter",
+      "android/support/v4/widget/SlidingPaneLayout$SlidingPanelLayoutImplJBMR1": "androidx/widget/SlidingPaneLayout$SlidingPanelLayoutImplJBMR1",
+      "android/support/v7/widget/SearchView": "androidx/widget/SearchView",
+      "android/support/v4/media/session/MediaSessionCompat$MediaSessionImplBase$MediaSessionStub": "androidx/media/session/MediaSessionCompat$MediaSessionImplBase$MediaSessionStub",
+      "android/support/v7/widget/TooltipCompat$ViewCompatImpl": "androidx/widget/TooltipCompat$ViewCompatImpl",
+      "android/support/v13/view/DragAndDropPermissionsCompat$DragAndDropPermissionsCompatImpl": "androidx/view/DragAndDropPermissionsCompat$DragAndDropPermissionsCompatImpl",
+      "android/support/customtabs/IPostMessageService$Stub$Proxy": "androidx/browser/customtabs/IPostMessageService$Stub$Proxy",
+      "android/support/v17/leanback/widget/ItemAlignment$Axis": "androidx/leanback/widget/ItemAlignment$Axis",
+      "android/support/v7/widget/RecyclerView$ViewHolder": "androidx/widget/RecyclerView$ViewHolder",
+      "android/support/v7/widget/GapWorker": "androidx/widget/GapWorker",
+      "android/support/v7/media/MediaItemStatus": "androidx/media/MediaItemStatus",
+      "android/support/transition/Explode": "androidx/transition/Explode",
+      "android/support/v17/leanback/widget/GuidedActionAdapter$ActionEditListener": "androidx/leanback/widget/GuidedActionAdapter$ActionEditListener",
+      "android/support/v4/text/BidiFormatter": "androidx/text/BidiFormatter",
+      "android/support/v7/widget/CardViewApi17Impl": "androidx/widget/CardViewApi17Impl",
+      "android/support/v7/media/MediaRouter$RouteInfo$PlaybackType": "androidx/media/MediaRouter$RouteInfo$PlaybackType",
+      "android/support/v4/view/ViewGroupCompat$ViewGroupCompatBaseImpl": "androidx/view/ViewGroupCompat$ViewGroupCompatBaseImpl",
+      "android/support/v4/view/ViewParentCompat$ViewParentCompatApi21Impl": "androidx/view/ViewParentCompat$ViewParentCompatApi21Impl",
+      "android/support/v7/widget/StaggeredGridLayoutManager": "androidx/widget/StaggeredGridLayoutManager",
+      "android/support/v4/widget/FocusStrategy": "androidx/widget/FocusStrategy",
+      "android/support/customtabs/ICustomTabsCallback$Stub$Proxy": "androidx/browser/customtabs/ICustomTabsCallback$Stub$Proxy",
+      "android/support/v7/media/MediaRouteProvider": "androidx/media/MediaRouteProvider",
+      "android/support/v4/net/DatagramSocketWrapper": "androidx/net/DatagramSocketWrapper",
+      "android/support/design/widget/SnackbarManager": "androidx/design/widget/SnackbarManager",
+      "android/support/v17/leanback/media/MediaControllerGlue": "androidx/leanback/media/MediaControllerGlue",
+      "android/support/v7/widget/PopupMenu$OnMenuItemClickListener": "androidx/widget/PopupMenu$OnMenuItemClickListener",
+      "android/support/v7/widget/helper/ItemTouchUIUtilImpl$Api21Impl": "androidx/widget/helper/ItemTouchUIUtilImpl$Api21Impl",
+      "android/support/text/emoji/widget/EmojiTextViewHelper": "androidx/text/emoji/widget/EmojiTextViewHelper",
+      "android/support/v4/media/session/MediaSessionCompat$Callback": "androidx/media/session/MediaSessionCompat$Callback",
+      "android/support/design/internal/NavigationMenuPresenter$NavigationMenuTextItem": "androidx/design/internal/NavigationMenuPresenter$NavigationMenuTextItem",
+      "android/support/v13/app/FragmentCompat$OnRequestPermissionsResultCallback": "androidx/app/FragmentCompat$OnRequestPermissionsResultCallback",
+      "android/support/design/widget/AppBarLayout$Behavior$DragCallback": "androidx/design/widget/AppBarLayout$Behavior$DragCallback",
+      "android/support/text/emoji/widget/EmojiEditTextHelper": "androidx/text/emoji/widget/EmojiEditTextHelper",
+      "android/support/v7/widget/AppCompatTextHelperV17": "androidx/widget/AppCompatTextHelperV17",
+      "android/support/v4/widget/SimpleCursorAdapter": "androidx/widget/SimpleCursorAdapter",
+      "android/support/mediacompat/R$integer": "androidx/mediacompat/R$integer",
+      "android/support/v17/leanback/widget/Grid$Provider": "androidx/leanback/widget/Grid$Provider",
+      "android/support/v13/view/ViewCompat": "androidx/view/ViewCompat",
+      "android/support/v14/preference/MultiSelectListPreference$SavedState": "androidx/preference/MultiSelectListPreference$SavedState",
+      "android/support/v4/view/NestedScrollingChild": "androidx/view/NestedScrollingChild",
+      "android/support/v7/preference/R$layout": "androidx/preference/R$layout",
+      "android/support/v4/app/FragmentTransition": "androidx/app/FragmentTransition",
+      "android/support/v4/app/NotificationCompat$Action$WearableExtender": "androidx/app/NotificationCompat$Action$WearableExtender",
+      "android/support/v17/preference/LeanbackPreferenceDialogFragment": "androidx/leanback/preference/LeanbackPreferenceDialogFragment",
+      "android/support/annotation/WorkerThread": "androidx/annotation/WorkerThread",
+      "android/support/v7/app/MediaRouteDialogFactory": "androidx/app/MediaRouteDialogFactory",
+      "android/support/v7/view/ActionMode": "androidx/view/ActionMode",
+      "android/support/v4/provider/FontsContractCompat$FontRequestCallback$FontRequestFailReason": "androidx/provider/FontsContractCompat$FontRequestCallback$FontRequestFailReason",
+      "android/support/v17/leanback/R$drawable": "androidx/leanback/R$drawable",
+      "android/support/annotation/StringRes": "androidx/annotation/StringRes",
+      "android/support/v4/app/RemoteInputCompatBase$RemoteInput": "androidx/app/RemoteInputCompatBase$RemoteInput",
+      "android/support/v13/app/FragmentTabHost$TabInfo": "androidx/app/FragmentTabHost$TabInfo",
+      "android/support/v7/app/ActionBarDrawerToggle$ToolbarCompatDelegate": "androidx/app/ActionBarDrawerToggle$ToolbarCompatDelegate",
+      "android/support/v7/widget/PopupMenu$OnDismissListener": "androidx/widget/PopupMenu$OnDismissListener",
+      "android/support/transition/AnimatorUtilsApi14$AnimatorPauseListenerCompat": "androidx/transition/AnimatorUtilsApi14$AnimatorPauseListenerCompat",
+      "android/support/v4/app/JobIntentService$CommandProcessor": "androidx/app/JobIntentService$CommandProcessor",
+      "android/support/v17/leanback/widget/VerticalGridView": "androidx/leanback/widget/VerticalGridView",
+      "android/support/v4/app/FragmentManager": "androidx/app/FragmentManager",
+      "android/support/v4/app/BackStackState": "androidx/app/BackStackState",
+      "android/support/text/emoji/R": "androidx/text/emoji/R",
+      "android/support/v4/media/session/MediaSessionCompatApi21$Callback": "androidx/media/session/MediaSessionCompatApi21$Callback",
+      "android/support/transition/ChangeScroll": "androidx/transition/ChangeScroll",
+      "android/support/v4/view/ViewCompat$LayoutDirectionMode": "androidx/view/ViewCompat$LayoutDirectionMode",
+      "android/support/v7/widget/AdapterHelper$Callback": "androidx/widget/AdapterHelper$Callback",
+      "android/support/wear/R$layout": "androidx/wear/R$layout",
+      "android/support/v17/leanback/widget/GuidedActionAdapter$ClickListener": "androidx/leanback/widget/GuidedActionAdapter$ClickListener",
+      "android/support/v4/media/session/MediaControllerCompat$MediaControllerImplApi21$ExtraCallback": "androidx/media/session/MediaControllerCompat$MediaControllerImplApi21$ExtraCallback",
+      "android/support/customtabs/ICustomTabsService$Stub": "androidx/browser/customtabs/ICustomTabsService$Stub",
+      "android/support/v17/leanback/widget/ListRowView": "androidx/leanback/widget/ListRowView",
+      "android/support/v7/widget/ActivityChooserView": "androidx/widget/ActivityChooserView",
+      "android/support/v4/os/TraceCompat": "androidx/os/TraceCompat",
+      "android/support/transition/ImageViewUtils": "androidx/transition/ImageViewUtils",
+      "android/support/v4/app/FragmentManager$OnBackStackChangedListener": "androidx/app/FragmentManager$OnBackStackChangedListener",
+      "android/support/v7/app/AppCompatDelegateImplV23$AppCompatWindowCallbackV23": "androidx/app/AppCompatDelegateImplV23$AppCompatWindowCallbackV23",
+      "android/support/transition/GhostViewApi14": "androidx/transition/GhostViewApi14",
+      "android/support/design/widget/SwipeDismissBehavior$SwipeDirection": "androidx/design/widget/SwipeDismissBehavior$SwipeDirection",
+      "android/support/v17/leanback/widget/SearchOrbView": "androidx/leanback/widget/SearchOrbView",
+      "android/support/v4/app/NotificationManagerCompat$NotifyTask": "androidx/app/NotificationManagerCompat$NotifyTask",
+      "android/support/v4/util/LruCache": "androidx/util/LruCache",
+      "android/support/v7/widget/ActionMenuView$LayoutParams": "androidx/widget/ActionMenuView$LayoutParams",
+      "android/support/v7/widget/helper/ItemTouchHelper$ViewDropHandler": "androidx/widget/helper/ItemTouchHelper$ViewDropHandler",
+      "android/support/v7/content/res/AppCompatColorStateListInflater": "androidx/content/res/AppCompatColorStateListInflater",
+      "android/support/v7/widget/ActionMenuPresenter$OpenOverflowRunnable": "androidx/widget/ActionMenuPresenter$OpenOverflowRunnable",
+      "android/support/v7/media/MediaRouter$GlobalMediaRouter$CallbackHandler": "androidx/media/MediaRouter$GlobalMediaRouter$CallbackHandler",
+      "android/support/design/widget/AppBarLayout$Behavior": "androidx/design/widget/AppBarLayout$Behavior",
+      "android/support/v4/widget/ExploreByTouchHelper": "androidx/widget/ExploreByTouchHelper",
+      "android/support/transition/PropertyValuesHolderUtilsImpl": "androidx/transition/PropertyValuesHolderUtilsImpl",
+      "android/support/v14/preference/PreferenceDialogFragment": "androidx/preference/PreferenceDialogFragment",
+      "android/support/transition/GhostViewApi21": "androidx/transition/GhostViewApi21",
+      "android/support/text/emoji/MetadataListReader$InputStreamOpenTypeReader": "androidx/text/emoji/MetadataListReader$InputStreamOpenTypeReader",
+      "android/support/transition/RectEvaluator": "androidx/transition/RectEvaluator",
+      "android/support/v7/preference/BuildConfig": "androidx/preference/BuildConfig",
+      "android/support/v4/view/VelocityTrackerCompat": "androidx/view/VelocityTrackerCompat",
+      "android/support/v7/widget/AppCompatAutoCompleteTextView": "androidx/widget/AppCompatAutoCompleteTextView",
+      "android/support/v7/media/MediaRouterJellybean$VolumeCallback": "androidx/media/MediaRouterJellybean$VolumeCallback",
+      "android/support/media/tv/WatchNextProgram": "androidx/media/tv/WatchNextProgram",
+      "android/support/wear/internal/widget/drawer/MultiPageUi$NavigationPagerAdapter": "androidx/wear/internal/widget/drawer/MultiPageUi$NavigationPagerAdapter",
+      "android/support/v4/widget/ExploreByTouchHelper$MyNodeProvider": "androidx/widget/ExploreByTouchHelper$MyNodeProvider",
+      "android/support/v7/widget/RecyclerView$AdapterDataObserver": "androidx/widget/RecyclerView$AdapterDataObserver",
+      "android/support/v4/media/session/IMediaSession": "androidx/media/session/IMediaSession",
+      "android/support/v17/preference/LeanbackSettingsRootView": "androidx/leanback/preference/LeanbackSettingsRootView",
+      "android/support/v7/widget/AppCompatButton": "androidx/widget/AppCompatButton",
+      "android/support/constraint/solver/widgets/ConstraintWidget$DimensionBehaviour": "androidx/constraint/solver/widgets/ConstraintWidget$DimensionBehaviour",
+      "android/support/graphics/drawable/AnimatedVectorDrawableCompat$AnimatedVectorDrawableDelegateState": "androidx/graphics/drawable/AnimatedVectorDrawableCompat$AnimatedVectorDrawableDelegateState",
+      "android/support/v17/leanback/app/DetailsSupportFragmentBackgroundController": "androidx/leanback/app/DetailsSupportFragmentBackgroundController",
+      "android/support/v17/leanback/widget/ThumbsBar": "androidx/leanback/widget/ThumbsBar",
+      "android/support/v4/media/session/MediaSessionCompat$Token": "androidx/media/session/MediaSessionCompat$Token",
+      "android/support/v7/mediarouter/R$integer": "androidx/mediarouter/R$integer",
+      "android/support/v7/app/WindowDecorActionBar": "androidx/app/WindowDecorActionBar",
+      "android/support/v4/app/Fragment$SavedState": "androidx/app/Fragment$SavedState",
+      "android/support/v17/leanback/widget/StaggeredGrid$Location": "androidx/leanback/widget/StaggeredGrid$Location",
+      "android/support/v17/leanback/widget/DiffCallback": "androidx/leanback/widget/DiffCallback",
+      "android/support/v4/os/ParcelableCompat": "androidx/os/ParcelableCompat",
+      "android/support/transition/ViewGroupUtils": "androidx/transition/ViewGroupUtils",
+      "android/support/v7/media/MediaRouter$GlobalMediaRouter$MediaSessionRecord": "androidx/media/MediaRouter$GlobalMediaRouter$MediaSessionRecord",
+      "android/support/v7/widget/ViewBoundsCheck$BoundFlags": "androidx/widget/ViewBoundsCheck$BoundFlags",
+      "android/support/wear/R": "androidx/wear/R",
+      "android/support/v17/leanback/app/DetailsSupportFragment$WaitEnterTransitionTimeout": "androidx/leanback/app/DetailsSupportFragment$WaitEnterTransitionTimeout",
+      "android/support/v4/widget/ViewDragHelper$Callback": "androidx/widget/ViewDragHelper$Callback",
+      "android/support/v7/widget/DropDownListView": "androidx/widget/DropDownListView",
+      "android/support/v17/leanback/transition/TransitionHelper$TransitionHelperVersionImpl": "androidx/leanback/transition/TransitionHelper$TransitionHelperVersionImpl",
+      "android/support/transition/TransitionUtils$MatrixEvaluator": "androidx/transition/TransitionUtils$MatrixEvaluator",
+      "android/support/v4/hardware/display/DisplayManagerCompat": "androidx/hardware/display/DisplayManagerCompat",
+      "android/support/media/tv/TvContractCompat$Channels$VideoResolution": "androidx/media/tv/TvContractCompat$Channels$VideoResolution",
+      "android/support/v4/app/NotificationCompatBuilder": "androidx/app/NotificationCompatBuilder",
+      "android/support/v4/media/session/MediaControllerCompat$TransportControls": "androidx/media/session/MediaControllerCompat$TransportControls",
+      "android/support/v7/view/menu/SubMenuBuilder": "androidx/view/menu/SubMenuBuilder",
+      "android/support/v4/media/session/MediaSessionCompatApi23$CallbackProxy": "androidx/media/session/MediaSessionCompatApi23$CallbackProxy",
+      "android/support/v7/recyclerview/R$id": "androidx/recyclerview/R$id",
+      "android/support/wear/widget/WearableLinearLayoutManager$LayoutCallback": "androidx/wear/widget/WearableLinearLayoutManager$LayoutCallback",
+      "android/support/v17/leanback/widget/PresenterSelector": "androidx/leanback/widget/PresenterSelector",
+      "android/support/v4/app/JobIntentService$JobServiceEngineImpl": "androidx/app/JobIntentService$JobServiceEngineImpl",
+      "android/support/v4/app/ActionBarDrawerToggle$DelegateProvider": "androidx/app/ActionBarDrawerToggle$DelegateProvider",
+      "android/support/v17/leanback/widget/GuidedActionsRelativeLayout": "androidx/leanback/widget/GuidedActionsRelativeLayout",
+      "android/support/v7/widget/RecyclerView$LayoutManager": "androidx/widget/RecyclerView$LayoutManager",
+      "android/support/v4/print/PrintHelper": "androidx/print/PrintHelper",
+      "android/support/v7/util/AsyncListUtil": "androidx/util/AsyncListUtil",
+      "android/support/transition/TransitionListenerAdapter": "androidx/transition/TransitionListenerAdapter",
+      "android/support/v7/view/menu/MenuItemWrapperICS$ActionProviderWrapper": "androidx/view/menu/MenuItemWrapperICS$ActionProviderWrapper",
+      "android/support/v7/app/TwilightManager": "androidx/app/TwilightManager",
+      "android/support/v17/leanback/transition/LeanbackTransitionHelper$LeanbackTransitionHelperDefault": "androidx/leanback/transition/LeanbackTransitionHelper$LeanbackTransitionHelperDefault",
+      "android/support/v4/app/NotificationCompat$InboxStyle": "androidx/app/NotificationCompat$InboxStyle",
+      "android/support/v14/preference/MultiSelectListPreference": "androidx/preference/MultiSelectListPreference",
+      "android/support/v13/view/inputmethod/EditorInfoCompat$EditorInfoCompatBaseImpl": "androidx/view/inputmethod/EditorInfoCompat$EditorInfoCompatBaseImpl",
+      "android/support/v7/preference/SeekBarPreference$SavedState": "androidx/preference/SeekBarPreference$SavedState",
+      "android/support/v17/leanback/widget/PlaybackControlsRowPresenter$BoundData": "androidx/leanback/widget/PlaybackControlsRowPresenter$BoundData",
+      "android/support/customtabs/CustomTabsSessionToken": "androidx/browser/customtabs/CustomTabsSessionToken",
+      "android/support/v4/media/session/MediaControllerCompat$PlaybackInfo": "androidx/media/session/MediaControllerCompat$PlaybackInfo",
+      "android/support/v17/leanback/app/DetailsBackgroundVideoHelper": "androidx/leanback/app/DetailsBackgroundVideoHelper",
+      "android/support/v17/leanback/app/RowsFragment$MainFragmentRowsAdapter": "androidx/leanback/app/RowsFragment$MainFragmentRowsAdapter",
+      "android/support/v7/app/OverlayListView$OverlayObject": "androidx/app/OverlayListView$OverlayObject",
+      "android/support/v4/provider/SingleDocumentFile": "androidx/provider/SingleDocumentFile",
+      "android/support/v7/app/AppCompatDelegateImplV9$ActionModeCallbackWrapperV9": "androidx/app/AppCompatDelegateImplV9$ActionModeCallbackWrapperV9",
+      "android/support/v4/widget/TintableCompoundButton": "androidx/widget/TintableCompoundButton",
+      "android/support/v7/graphics/Target$Builder": "androidx/graphics/Target$Builder",
+      "android/support/v17/leanback/widget/NonOverlappingFrameLayout": "androidx/leanback/widget/NonOverlappingFrameLayout",
+      "android/support/annotation/IntegerRes": "androidx/annotation/IntegerRes",
+      "android/support/media/tv/CollectionUtils": "androidx/media/tv/CollectionUtils",
+      "android/support/v4/provider/FontsContractCompat$FontRequestCallback": "androidx/provider/FontsContractCompat$FontRequestCallback",
+      "android/support/design/internal/BottomNavigationMenuView": "androidx/design/internal/BottomNavigationMenuView",
+      "android/support/v4/graphics/drawable/RoundedBitmapDrawable21": "androidx/graphics/drawable/RoundedBitmapDrawable21",
+      "android/support/design/widget/CoordinatorLayout$SavedState": "androidx/design/widget/CoordinatorLayout$SavedState",
+      "android/support/text/emoji/BuildConfig": "androidx/text/emoji/BuildConfig",
+      "android/support/media/tv/TvContractCompat": "androidx/media/tv/TvContractCompat",
+      "android/support/v7/media/RemoteControlClientCompat$PlaybackInfo": "androidx/media/RemoteControlClientCompat$PlaybackInfo",
+      "android/support/v7/app/AppCompatDelegateImplV9$ActionMenuPresenterCallback": "androidx/app/AppCompatDelegateImplV9$ActionMenuPresenterCallback",
+      "android/support/v17/leanback/widget/StreamingTextView$DottySpan": "androidx/leanback/widget/StreamingTextView$DottySpan",
+      "android/support/v7/view/menu/ActionMenuItemView$PopupCallback": "androidx/view/menu/ActionMenuItemView$PopupCallback",
+      "android/support/percent/PercentLayoutHelper": "androidx/PercentLayoutHelper",
+      "android/support/v17/leanback/transition/LeanbackTransitionHelper$LeanbackTransitionHelperVersion": "androidx/leanback/transition/LeanbackTransitionHelper$LeanbackTransitionHelperVersion",
+      "android/support/v17/leanback/widget/VerticalGridPresenter$ViewHolder": "androidx/leanback/widget/VerticalGridPresenter$ViewHolder",
+      "android/support/media/tv/BaseProgram": "androidx/media/tv/BaseProgram",
+      "android/support/v4/view/ViewGroupCompat": "androidx/view/ViewGroupCompat",
+      "android/support/media/instantvideo/preload/InstantVideoPreloadManager$AsyncTaskVideoPreloader": "androidx/media/instantvideo/preload/InstantVideoPreloadManager$AsyncTaskVideoPreloader",
+      "android/support/v4/widget/FocusStrategy$CollectionAdapter": "androidx/widget/FocusStrategy$CollectionAdapter",
+      "android/support/v7/util/SortedList$Callback": "androidx/util/SortedList$Callback",
+      "android/support/v17/leanback/widget/Parallax$PropertyMarkerValue": "androidx/leanback/widget/Parallax$PropertyMarkerValue",
+      "android/support/v4/text/TextDirectionHeuristicsCompat$AnyStrong": "androidx/text/TextDirectionHeuristicsCompat$AnyStrong",
+      "android/support/v14/preference/PreferenceFragment$OnPreferenceStartScreenCallback": "androidx/preference/PreferenceFragment$OnPreferenceStartScreenCallback",
+      "android/support/v17/leanback/widget/picker/PickerColumn": "androidx/leanback/widget/picker/PickerColumn",
+      "android/support/transition/TransitionPropagation": "androidx/transition/TransitionPropagation",
+      "android/support/v17/leanback/widget/WindowAlignment": "androidx/leanback/widget/WindowAlignment",
+      "android/support/v17/leanback/transition/TranslationAnimationCreator": "androidx/leanback/transition/TranslationAnimationCreator",
+      "android/support/v7/media/MediaRouterApi24": "androidx/media/MediaRouterApi24",
+      "android/support/transition/Styleable$Slide": "androidx/transition/Styleable$Slide",
+      "android/support/v7/view/menu/StandardMenuPopup": "androidx/view/menu/StandardMenuPopup",
+      "android/support/v4/widget/PopupMenuCompat": "androidx/widget/PopupMenuCompat",
+      "android/support/design/widget/CollapsingToolbarLayout$LayoutParams$CollapseMode": "androidx/design/widget/CollapsingToolbarLayout$LayoutParams$CollapseMode",
+      "android/support/v17/leanback/widget/ObjectAdapter": "androidx/leanback/widget/ObjectAdapter",
+      "android/support/v4/view/GestureDetectorCompat": "androidx/view/GestureDetectorCompat",
+      "android/support/v17/leanback/widget/Parallax$IntPropertyMarkerValue": "androidx/leanback/widget/Parallax$IntPropertyMarkerValue",
+      "android/support/v4/view/ViewCompat$ViewCompatApi26Impl": "androidx/view/ViewCompat$ViewCompatApi26Impl",
+      "android/support/v7/media/RemoteControlClientCompat$VolumeCallback": "androidx/media/RemoteControlClientCompat$VolumeCallback",
+      "android/support/design/widget/AnimationUtils": "androidx/design/widget/AnimationUtils",
+      "android/support/v4/view/ViewCompat$ScrollIndicators": "androidx/view/ViewCompat$ScrollIndicators",
+      "android/support/media/tv/WatchNextProgram$Builder": "androidx/media/tv/WatchNextProgram$Builder",
+      "android/support/v7/graphics/Palette$Filter": "androidx/graphics/Palette$Filter",
+      "android/support/annotation/BinderThread": "androidx/annotation/BinderThread",
+      "android/support/v17/leanback/widget/FocusHighlightHelper$HeaderItemFocusHighlight$HeaderFocusAnimator": "androidx/leanback/widget/FocusHighlightHelper$HeaderItemFocusHighlight$HeaderFocusAnimator",
+      "android/support/media/tv/Program$Builder": "androidx/media/tv/Program$Builder",
+      "android/support/v4/hardware/fingerprint/FingerprintManagerCompat$AuthenticationResult": "androidx/hardware/fingerprint/FingerprintManagerCompat$AuthenticationResult",
+      "android/support/v4/os/LocaleListCompat$LocaleListCompatApi24Impl": "androidx/os/LocaleListCompat$LocaleListCompatApi24Impl",
+      "android/support/v17/leanback/widget/GuidedActionAdapter$FocusListener": "androidx/leanback/widget/GuidedActionAdapter$FocusListener",
+      "android/support/v17/leanback/widget/ControlBarPresenter$ViewHolder": "androidx/leanback/widget/ControlBarPresenter$ViewHolder",
+      "android/support/v7/graphics/drawable/DrawerArrowDrawable$ArrowDirection": "androidx/graphics/drawable/DrawerArrowDrawable$ArrowDirection",
+      "android/support/v4/view/ViewPager$DecorView": "androidx/view/ViewPager$DecorView",
+      "android/support/v4/view/ViewParentCompat$ViewParentCompatApi19Impl": "androidx/view/ViewParentCompat$ViewParentCompatApi19Impl",
+      "android/support/v7/widget/StaggeredGridLayoutManager$Span": "androidx/widget/StaggeredGridLayoutManager$Span",
+      "android/support/wear/widget/ProgressDrawable": "androidx/wear/widget/ProgressDrawable",
+      "android/support/v7/media/MediaRouterJellybeanMr2$RouteInfo": "androidx/media/MediaRouterJellybeanMr2$RouteInfo",
+      "android/support/v4/graphics/PathParser$PathDataNode": "androidx/graphics/PathParser$PathDataNode",
+      "android/support/v17/leanback/graphics/ColorFilterCache": "androidx/leanback/graphics/ColorFilterCache",
+      "android/support/text/emoji/EmojiCompat$InitCallback": "androidx/text/emoji/EmojiCompat$InitCallback",
+      "android/support/v17/leanback/app/BrowseFragment$MainFragmentAdapterRegistry": "androidx/leanback/app/BrowseFragment$MainFragmentAdapterRegistry",
+      "android/support/transition/PropertyValuesHolderUtils": "androidx/transition/PropertyValuesHolderUtils",
+      "android/support/v7/widget/ActivityChooserModel$DefaultSorter": "androidx/widget/ActivityChooserModel$DefaultSorter",
+      "android/support/v7/app/AlertController$CheckedItemAdapter": "androidx/app/AlertController$CheckedItemAdapter",
+      "android/support/design/widget/TabItem": "androidx/design/widget/TabItem",
+      "android/support/v4/media/MediaBrowserCompatApi21$ConnectionCallbackProxy": "androidx/media/MediaBrowserCompatApi21$ConnectionCallbackProxy",
+      "android/support/content/LoaderQueryRunner": "androidx/content/LoaderQueryRunner",
+      "android/support/v17/leanback/app/BrowseSupportFragment$FragmentFactory": "androidx/leanback/app/BrowseSupportFragment$FragmentFactory",
+      "android/support/v7/app/AppCompatDelegateImplN$AppCompatWindowCallbackN": "androidx/app/AppCompatDelegateImplN$AppCompatWindowCallbackN",
+      "android/support/wear/widget/drawer/WearableDrawerLayout$DrawerDraggerCallback": "androidx/wear/widget/drawer/WearableDrawerLayout$DrawerDraggerCallback",
+      "android/support/content/BuildConfig": "androidx/content/BuildConfig",
+      "android/support/v7/widget/OpReorderer": "androidx/widget/OpReorderer",
+      "android/support/media/instantvideo/preload/InstantVideoPreloadManager": "androidx/media/instantvideo/preload/InstantVideoPreloadManager",
+      "android/support/v4/view/ViewPropertyAnimatorCompat": "androidx/view/ViewPropertyAnimatorCompat",
+      "android/support/v13/app/FragmentCompat$FragmentCompatApi23Impl": "androidx/app/FragmentCompat$FragmentCompatApi23Impl",
+      "android/support/v4/media/MediaBrowserServiceCompat$MediaBrowserServiceImplApi23": "androidx/media/MediaBrowserServiceCompat$MediaBrowserServiceImplApi23",
+      "android/support/v4/internal/view/SupportSubMenu": "androidx/internal/view/SupportSubMenu",
+      "android/support/text/emoji/EmojiProcessor$Action": "androidx/text/emoji/EmojiProcessor$Action",
+      "android/support/v4/media/MediaBrowserServiceCompat$MediaBrowserServiceImplApi21": "androidx/media/MediaBrowserServiceCompat$MediaBrowserServiceImplApi21",
+      "android/support/v17/leanback/widget/DetailsOverviewLogoPresenter$ViewHolder": "androidx/leanback/widget/DetailsOverviewLogoPresenter$ViewHolder",
+      "android/support/v7/widget/OrientationHelper": "androidx/widget/OrientationHelper",
+      "android/support/v4/app/FragmentTransition$FragmentContainerTransition": "androidx/app/FragmentTransition$FragmentContainerTransition",
+      "android/support/multidex/BuildConfig": "androidx/multidex/BuildConfig",
+      "android/support/v4/media/MediaBrowserServiceCompat$MediaBrowserServiceImplApi26": "androidx/media/MediaBrowserServiceCompat$MediaBrowserServiceImplApi26",
+      "android/support/v7/preference/PreferenceViewHolder": "androidx/preference/PreferenceViewHolder",
+      "android/support/graphics/drawable/VectorDrawableCommon": "androidx/graphics/drawable/VectorDrawableCommon",
+      "android/support/v4/view/animation/FastOutLinearInInterpolator": "androidx/view/animation/FastOutLinearInInterpolator",
+      "android/support/v4/media/MediaBrowserServiceCompatApi26$MediaBrowserServiceAdaptor": "androidx/media/MediaBrowserServiceCompatApi26$MediaBrowserServiceAdaptor",
+      "android/support/v7/view/SupportActionModeWrapper": "androidx/view/SupportActionModeWrapper",
+      "android/support/v7/app/AppCompatDelegateImplV11": "androidx/app/AppCompatDelegateImplV11",
+      "android/support/v7/app/AppCompatDelegateImplV14": "androidx/app/AppCompatDelegateImplV14",
+      "android/support/v4/graphics/TypefaceCompatApi26Impl": "androidx/graphics/TypefaceCompatApi26Impl",
+      "android/support/v17/leanback/media/PlaybackGlueHost$PlayerCallback": "androidx/leanback/media/PlaybackGlueHost$PlayerCallback",
+      "android/support/design/widget/BottomNavigationView$SavedState": "androidx/design/widget/BottomNavigationView$SavedState",
+      "android/support/v7/preference/R$attr": "androidx/preference/R$attr",
+      "android/support/text/emoji/R$styleable": "androidx/text/emoji/R$styleable",
+      "android/support/v4/view/animation/LinearOutSlowInInterpolator": "androidx/view/animation/LinearOutSlowInInterpolator",
+      "android/support/customtabs/BuildConfig": "androidx/browser/customtabs/BuildConfig",
+      "android/support/v7/widget/LinearLayoutManager$LayoutState": "androidx/widget/LinearLayoutManager$LayoutState",
+      "android/support/wear/widget/BoxInsetLayout": "androidx/wear/widget/BoxInsetLayout",
+      "android/support/media/instantvideo/BuildConfig": "androidx/media/instantvideo/BuildConfig",
+      "android/support/design/widget/HeaderBehavior": "androidx/design/widget/HeaderBehavior",
+      "android/support/v17/leanback/widget/PageRow": "androidx/leanback/widget/PageRow",
+      "android/support/v7/widget/ViewStubCompat": "androidx/widget/ViewStubCompat",
+      "android/support/v7/widget/SearchView$AutoCompleteTextViewReflector": "androidx/widget/SearchView$AutoCompleteTextViewReflector",
+      "android/support/v17/leanback/widget/OnChildLaidOutListener": "androidx/leanback/widget/OnChildLaidOutListener",
+      "android/support/v7/app/TwilightManager$TwilightState": "androidx/app/TwilightManager$TwilightState",
+      "android/support/v7/widget/FastScroller": "androidx/widget/FastScroller",
+      "android/support/design/widget/CoordinatorLayout$ViewElevationComparator": "androidx/design/widget/CoordinatorLayout$ViewElevationComparator",
+      "android/support/transition/TransitionManager": "androidx/transition/TransitionManager",
+      "android/support/v7/widget/AppCompatDrawableManager": "androidx/widget/AppCompatDrawableManager",
+      "android/support/animation/DynamicAnimation$OnAnimationUpdateListener": "androidx/animation/DynamicAnimation$OnAnimationUpdateListener",
+      "android/support/v4/graphics/TypefaceCompat": "androidx/graphics/TypefaceCompat",
+      "android/support/v17/preference/LeanbackPreferenceFragment": "androidx/leanback/preference/LeanbackPreferenceFragment",
+      "android/support/v17/leanback/widget/ParallaxTarget": "androidx/leanback/widget/ParallaxTarget",
+      "android/support/annotation/StyleRes": "androidx/annotation/StyleRes",
+      "android/support/v17/leanback/widget/PlaybackControlsRow$ThumbsDownAction": "androidx/leanback/widget/PlaybackControlsRow$ThumbsDownAction",
+      "android/support/v17/leanback/media/PlaybackTransportControlGlue$SeekUiClient": "androidx/leanback/media/PlaybackTransportControlGlue$SeekUiClient",
+      "android/support/v7/media/SystemMediaRouteProvider$JellybeanImpl$SystemRouteRecord": "androidx/media/SystemMediaRouteProvider$JellybeanImpl$SystemRouteRecord",
+      "android/support/v17/leanback/widget/ShadowHelper$ShadowHelperStubImpl": "androidx/leanback/widget/ShadowHelper$ShadowHelperStubImpl",
+      "android/support/animation/DynamicAnimation$ViewProperty": "androidx/animation/DynamicAnimation$ViewProperty",
+      "android/support/v7/app/AppCompatDelegateImplV23": "androidx/app/AppCompatDelegateImplV23",
+      "android/support/v4/app/NotificationManagerCompat": "androidx/app/NotificationManagerCompat",
+      "android/support/v4/media/session/MediaControllerCompat": "androidx/media/session/MediaControllerCompat",
+      "android/support/v17/leanback/widget/GuidedDatePickerAction": "androidx/leanback/widget/GuidedDatePickerAction",
+      "android/support/v4/widget/CircularProgressDrawable$ProgressDrawableSize": "androidx/widget/CircularProgressDrawable$ProgressDrawableSize",
+      "android/support/v17/leanback/widget/Presenter$ViewHolderTask": "androidx/leanback/widget/Presenter$ViewHolderTask",
+      "android/support/text/emoji/bundled/BundledEmojiCompatConfig": "androidx/text/emoji/bundled/BundledEmojiCompatConfig",
+      "android/support/content/ContentPager$CursorView": "androidx/content/ContentPager$CursorView",
+      "android/support/transition/TranslationAnimationCreator$TransitionPositionListener": "androidx/transition/TranslationAnimationCreator$TransitionPositionListener",
+      "android/support/v7/recyclerview/R$styleable": "androidx/recyclerview/R$styleable",
+      "android/support/v17/leanback/widget/PersistentFocusWrapper": "androidx/leanback/widget/PersistentFocusWrapper",
+      "android/support/v4/view/accessibility/AccessibilityWindowInfoCompat": "androidx/view/accessibility/AccessibilityWindowInfoCompat",
+      "android/support/v17/leanback/widget/PlaybackControlsRow$RewindAction": "androidx/leanback/widget/PlaybackControlsRow$RewindAction",
+      "android/support/v7/widget/AppCompatPopupWindow": "androidx/widget/AppCompatPopupWindow",
+      "android/support/coreui/BuildConfig": "androidx/coreui/BuildConfig",
+      "android/support/mediacompat/R$id": "androidx/mediacompat/R$id",
+      "android/support/v17/leanback/widget/ItemAlignmentFacet": "androidx/leanback/widget/ItemAlignmentFacet",
+      "android/support/v4/view/GestureDetectorCompat$GestureDetectorCompatImplBase$GestureHandler": "androidx/view/GestureDetectorCompat$GestureDetectorCompatImplBase$GestureHandler",
+      "android/support/v17/leanback/widget/GuidedAction$BuilderBase": "androidx/leanback/widget/GuidedAction$BuilderBase",
+      "android/support/v17/leanback/widget/RoundedRectHelper$Impl": "androidx/leanback/widget/RoundedRectHelper$Impl",
+      "android/support/v7/media/MediaRouterJellybean$CallbackProxy": "androidx/media/MediaRouterJellybean$CallbackProxy",
+      "android/support/design/widget/CollapsingToolbarLayout$LayoutParams": "androidx/design/widget/CollapsingToolbarLayout$LayoutParams",
+      "android/support/v7/preference/DialogPreference": "androidx/preference/DialogPreference",
+      "android/support/v7/preference/EditTextPreference": "androidx/preference/EditTextPreference",
+      "android/support/v4/media/MediaBrowserCompatApi23$ItemCallbackProxy": "androidx/media/MediaBrowserCompatApi23$ItemCallbackProxy",
+      "android/support/v4/text/util/LinkifyCompat$LinkifyMask": "androidx/text/util/LinkifyCompat$LinkifyMask",
+      "android/support/v7/widget/helper/ItemTouchHelper$SimpleCallback": "androidx/widget/helper/ItemTouchHelper$SimpleCallback",
+      "android/support/wear/ambient/AmbientMode$AmbientCallbackProvider": "androidx/wear/ambient/AmbientMode$AmbientCallbackProvider",
+      "android/support/v7/widget/CardViewImpl": "androidx/widget/CardViewImpl",
+      "android/support/animation/FloatValueHolder": "androidx/animation/FloatValueHolder",
+      "android/support/transition/MatrixUtils": "androidx/transition/MatrixUtils",
+      "android/support/v14/preference/PreferenceFragment$OnPreferenceStartFragmentCallback": "androidx/preference/PreferenceFragment$OnPreferenceStartFragmentCallback",
+      "android/support/v4/widget/SlidingPaneLayout$PanelSlideListener": "androidx/widget/SlidingPaneLayout$PanelSlideListener",
+      "android/support/v7/util/SortedList": "androidx/util/SortedList",
+      "android/support/v4/view/AccessibilityDelegateCompat$AccessibilityDelegateBaseImpl": "androidx/view/AccessibilityDelegateCompat$AccessibilityDelegateBaseImpl",
+      "android/support/v7/app/MediaRouteControllerDialog$FetchArtTask": "androidx/app/MediaRouteControllerDialog$FetchArtTask",
+      "android/support/compat/R$drawable": "androidx/compat/R$drawable",
+      "android/support/transition/Visibility$VisibilityInfo": "androidx/transition/Visibility$VisibilityInfo",
+      "android/support/v7/widget/ThemedSpinnerAdapter": "androidx/widget/ThemedSpinnerAdapter",
+      "android/support/v17/leanback/widget/GuidanceStylingRelativeLayout": "androidx/leanback/widget/GuidanceStylingRelativeLayout",
+      "android/support/v7/media/MediaRouter$ProviderInfo": "androidx/media/MediaRouter$ProviderInfo",
+      "android/support/v7/media/MediaControlIntent": "androidx/media/MediaControlIntent",
+      "android/support/v17/leanback/widget/ListRow": "androidx/leanback/widget/ListRow",
+      "android/support/v7/view/menu/ListMenuPresenter$MenuAdapter": "androidx/view/menu/ListMenuPresenter$MenuAdapter",
+      "android/support/v4/app/FrameMetricsAggregator$FrameMetricsBaseImpl": "androidx/app/FrameMetricsAggregator$FrameMetricsBaseImpl",
+      "android/support/design/widget/TabLayout$SlidingTabStrip": "androidx/design/widget/TabLayout$SlidingTabStrip",
+      "android/support/content/ContentPager$ContentCallback": "androidx/content/ContentPager$ContentCallback",
+      "android/support/v4/media/MediaBrowserServiceCompat$MediaBrowserServiceImplBase": "androidx/media/MediaBrowserServiceCompat$MediaBrowserServiceImplBase",
+      "android/support/v7/util/TileList": "androidx/util/TileList",
+      "android/support/v7/widget/ActionBarBackgroundDrawable": "androidx/widget/ActionBarBackgroundDrawable",
+      "android/support/v4/widget/CursorFilter$CursorFilterClient": "androidx/widget/CursorFilter$CursorFilterClient",
+      "android/support/v17/leanback/database/CursorMapper": "androidx/leanback/database/CursorMapper",
+      "android/support/v17/leanback/transition/LeanbackTransitionHelper$LeanbackTransitionHelperKitKatImpl": "androidx/leanback/transition/LeanbackTransitionHelper$LeanbackTransitionHelperKitKatImpl",
+      "android/support/v7/cardview/R$styleable": "androidx/cardview/R$styleable",
+      "android/support/v17/leanback/transition/TransitionHelperApi21": "androidx/leanback/transition/TransitionHelperApi21",
+      "android/support/v7/widget/ViewInfoStore": "androidx/widget/ViewInfoStore",
+      "android/support/design/widget/BaseTransientBottomBar$BaseCallback$DismissEvent": "androidx/design/widget/BaseTransientBottomBar$BaseCallback$DismissEvent",
+      "android/support/percent/PercentLayoutHelper$PercentLayoutInfo": "androidx/PercentLayoutHelper$PercentLayoutInfo",
+      "android/support/v7/app/AppCompatCallback": "androidx/app/AppCompatCallback",
+      "android/support/wear/R$style": "androidx/wear/R$style",
+      "android/support/v17/leanback/widget/ItemAlignmentFacetHelper": "androidx/leanback/widget/ItemAlignmentFacetHelper",
+      "android/support/v7/view/menu/MenuItemImpl": "androidx/view/menu/MenuItemImpl",
+      "android/support/v4/content/ContextCompat": "androidx/content/ContextCompat",
+      "android/support/percent/PercentLayoutHelper$PercentMarginLayoutParams": "androidx/PercentLayoutHelper$PercentMarginLayoutParams",
+      "android/support/v7/widget/LayoutState": "androidx/widget/LayoutState",
+      "android/support/animation/AnimationHandler$AnimationFrameCallback": "androidx/animation/AnimationHandler$AnimationFrameCallback",
+      "android/support/v17/leanback/widget/SingleRow": "androidx/leanback/widget/SingleRow",
+      "android/support/v4/content/ModernAsyncTask$InternalHandler": "androidx/content/ModernAsyncTask$InternalHandler",
+      "android/support/transition/ImageViewUtilsImpl": "androidx/transition/ImageViewUtilsImpl",
+      "android/support/v7/widget/RtlSpacingHelper": "androidx/widget/RtlSpacingHelper",
+      "android/support/v4/content/PermissionChecker": "androidx/content/PermissionChecker",
+      "android/support/v7/preference/PreferenceCategory": "androidx/preference/PreferenceCategory",
+      "android/support/annotation/IntDef": "androidx/annotation/IntDef",
+      "android/support/v4/app/BundleCompat$BundleCompatBaseImpl": "androidx/app/BundleCompat$BundleCompatBaseImpl",
+      "android/support/v7/app/MediaRouteExpandCollapseButton": "androidx/app/MediaRouteExpandCollapseButton",
+      "android/support/v17/leanback/animation/LogAccelerateInterpolator": "androidx/leanback/animation/LogAccelerateInterpolator",
+      "android/support/v13/app/FragmentPagerAdapter": "androidx/app/FragmentPagerAdapter",
+      "android/support/content/InMemoryCursor": "androidx/content/InMemoryCursor",
+      "android/support/v17/leanback/widget/FullWidthDetailsOverviewRowPresenter$ActionsItemBridgeAdapter": "androidx/leanback/widget/FullWidthDetailsOverviewRowPresenter$ActionsItemBridgeAdapter",
+      "android/support/v7/widget/AppCompatTextViewAutoSizeHelper": "androidx/widget/AppCompatTextViewAutoSizeHelper",
+      "android/support/multidex/instrumentation/BuildConfig": "androidx/multidex/instrumentation/BuildConfig",
+      "android/support/v7/preference/PreferenceGroup": "androidx/preference/PreferenceGroup",
+      "android/support/v7/media/RegisteredMediaRouteProvider$Connection": "androidx/media/RegisteredMediaRouteProvider$Connection",
+      "android/support/v4/os/ParcelableCompatCreatorCallbacks": "androidx/os/ParcelableCompatCreatorCallbacks",
+      "android/support/v4/media/MediaMetadataCompat$BitmapKey": "androidx/media/MediaMetadataCompat$BitmapKey",
+      "android/support/v7/app/MediaRouteActionProvider$MediaRouterCallback": "androidx/app/MediaRouteActionProvider$MediaRouterCallback",
+      "android/support/v7/view/ViewPropertyAnimatorCompatSet": "androidx/view/ViewPropertyAnimatorCompatSet",
+      "android/support/v7/widget/AppCompatMultiAutoCompleteTextView": "androidx/widget/AppCompatMultiAutoCompleteTextView",
+      "android/support/v17/leanback/widget/GridLayoutManager$GridLinearSmoothScroller": "androidx/leanback/widget/GridLayoutManager$GridLinearSmoothScroller",
+      "android/support/design/internal/NavigationMenuView": "androidx/design/internal/NavigationMenuView",
+      "android/support/v17/leanback/app/SearchSupportFragment": "androidx/leanback/app/SearchSupportFragment",
+      "android/support/v7/media/SystemMediaRouteProvider$Api24Impl": "androidx/media/SystemMediaRouteProvider$Api24Impl",
+      "android/support/v4/content/FileProvider$PathStrategy": "androidx/content/FileProvider$PathStrategy",
+      "android/support/v17/leanback/util/StateMachine": "androidx/leanback/util/StateMachine",
+      "android/support/v7/widget/AppCompatCheckedTextView": "androidx/widget/AppCompatCheckedTextView",
+      "android/support/v4/media/session/PlaybackStateCompat$Builder": "androidx/media/session/PlaybackStateCompat$Builder",
+      "android/support/v17/leanback/widget/DetailsParallaxDrawable": "androidx/leanback/widget/DetailsParallaxDrawable",
+      "android/support/v17/leanback/widget/GuidedDatePickerAction$BuilderBase": "androidx/leanback/widget/GuidedDatePickerAction$BuilderBase",
+      "android/support/v17/leanback/widget/OnItemViewSelectedListener": "androidx/leanback/widget/OnItemViewSelectedListener",
+      "android/support/v7/widget/ShareActionProvider$OnShareTargetSelectedListener": "androidx/widget/ShareActionProvider$OnShareTargetSelectedListener",
+      "android/support/v4/app/JobIntentService$CompatWorkItem": "androidx/app/JobIntentService$CompatWorkItem",
+      "android/support/v7/preference/MultiSelectListPreferenceDialogFragmentCompat": "androidx/preference/MultiSelectListPreferenceDialogFragmentCompat",
+      "android/support/v17/leanback/widget/OnActionClickedListener": "androidx/leanback/widget/OnActionClickedListener",
+      "android/support/design/widget/SwipeDismissBehavior$OnDismissListener": "androidx/design/widget/SwipeDismissBehavior$OnDismissListener",
+      "android/support/design/R$id": "androidx/design/R$id",
+      "android/support/content/ContentPager$Stats": "androidx/content/ContentPager$Stats",
+      "android/support/v17/leanback/widget/SinglePresenterSelector": "androidx/leanback/widget/SinglePresenterSelector",
+      "android/support/v7/widget/GridLayout$Arc": "androidx/widget/GridLayout$Arc",
+      "android/support/v17/leanback/widget/BaseGridView$OnKeyInterceptListener": "androidx/leanback/widget/BaseGridView$OnKeyInterceptListener",
+      "android/support/customtabs/CustomTabsService$Relation": "androidx/browser/customtabs/CustomTabsService$Relation",
+      "android/support/v4/content/WakefulBroadcastReceiver": "androidx/content/WakefulBroadcastReceiver",
+      "android/support/v4/os/LocaleHelper": "androidx/os/LocaleHelper",
+      "android/support/v7/app/ActionBar$NavigationMode": "androidx/app/ActionBar$NavigationMode",
+      "android/support/v7/media/RemotePlaybackClient$ActionReceiver": "androidx/media/RemotePlaybackClient$ActionReceiver",
+      "android/support/v7/view/menu/CascadingMenuPopup$HorizPosition": "androidx/view/menu/CascadingMenuPopup$HorizPosition",
+      "android/support/v13/view/inputmethod/EditorInfoCompat": "androidx/view/inputmethod/EditorInfoCompat",
+      "android/support/v17/leanback/widget/Grid": "androidx/leanback/widget/Grid",
+      "android/support/annotation/TransitionRes": "androidx/annotation/TransitionRes",
+      "android/support/v4/media/VolumeProviderCompat": "androidx/media/VolumeProviderCompat",
+      "android/support/multidex/MultiDexApplication": "androidx/multidex/MultiDexApplication",
+      "android/support/text/emoji/EmojiCompat$MetadataRepoLoader": "androidx/text/emoji/EmojiCompat$MetadataRepoLoader",
+      "android/support/design/widget/TextInputLayout": "androidx/design/widget/TextInputLayout",
+      "android/support/v4/view/ViewCompat$ViewCompatApi24Impl": "androidx/view/ViewCompat$ViewCompatApi24Impl",
+      "android/support/v4/provider/FontRequest": "androidx/provider/FontRequest",
+      "android/support/v4/media/session/MediaButtonReceiver$MediaButtonConnectionCallback": "androidx/media/session/MediaButtonReceiver$MediaButtonConnectionCallback",
+      "android/support/v7/widget/RecyclerView$SmoothScroller$ScrollVectorProvider": "androidx/widget/RecyclerView$SmoothScroller$ScrollVectorProvider",
+      "android/support/v4/media/session/MediaSessionCompat$MediaSessionImpl": "androidx/media/session/MediaSessionCompat$MediaSessionImpl",
+      "android/support/v4/content/ModernAsyncTask$Status": "androidx/content/ModernAsyncTask$Status",
+      "android/support/v7/widget/AppCompatDrawableManager$ColorFilterLruCache": "androidx/widget/AppCompatDrawableManager$ColorFilterLruCache",
+      "android/support/multidex/MultiDex": "androidx/multidex/MultiDex",
+      "android/support/wear/ambient/WearableControllerProvider": "androidx/wear/ambient/WearableControllerProvider",
+      "android/support/v17/leanback/widget/ForegroundHelper": "androidx/leanback/widget/ForegroundHelper",
+      "android/support/v4/app/DialogFragment$DialogStyle": "androidx/app/DialogFragment$DialogStyle",
+      "android/support/v4/graphics/TypefaceCompatApi24Impl": "androidx/graphics/TypefaceCompatApi24Impl",
+      "android/support/v7/preference/R": "androidx/preference/R",
+      "android/support/v7/app/ActionBar$Tab": "androidx/app/ActionBar$Tab",
+      "android/support/transition/CircularPropagation": "androidx/transition/CircularPropagation",
+      "android/support/v7/app/ResourcesFlusher": "androidx/app/ResourcesFlusher",
+      "android/support/text/emoji/MetadataListReader$OffsetInfo": "androidx/text/emoji/MetadataListReader$OffsetInfo",
+      "android/support/v4/app/ActivityOptionsCompat$ActivityOptionsCompatApi24Impl": "androidx/app/ActivityOptionsCompat$ActivityOptionsCompatApi24Impl",
+      "android/support/v4/content/SharedPreferencesCompat$EditorCompat": "androidx/content/SharedPreferencesCompat$EditorCompat",
+      "android/support/v7/widget/RecyclerView$OnFlingListener": "androidx/widget/RecyclerView$OnFlingListener",
+      "android/support/animation/AnimationHandler$AnimationCallbackDispatcher": "androidx/animation/AnimationHandler$AnimationCallbackDispatcher",
+      "android/support/v7/widget/ResourcesWrapper": "androidx/widget/ResourcesWrapper",
+      "android/support/v17/leanback/widget/BrowseFrameLayout$OnChildFocusListener": "androidx/leanback/widget/BrowseFrameLayout$OnChildFocusListener",
+      "android/support/v7/widget/AppCompatEditText": "androidx/widget/AppCompatEditText",
+      "android/support/media/tv/TvContractCompat$WatchNextPrograms": "androidx/media/tv/TvContractCompat$WatchNextPrograms",
+      "android/support/v4/widget/ListPopupWindowCompat": "androidx/widget/ListPopupWindowCompat",
+      "android/support/v7/preference/PreferenceFragmentCompat$OnPreferenceDisplayDialogCallback": "androidx/preference/PreferenceFragmentCompat$OnPreferenceDisplayDialogCallback",
+      "android/support/v7/util/DiffUtil$PostponedUpdate": "androidx/util/DiffUtil$PostponedUpdate",
+      "android/support/transition/TransitionManager$MultiListener": "androidx/transition/TransitionManager$MultiListener",
+      "android/support/v4/view/ViewPropertyAnimatorCompat$ViewPropertyAnimatorListenerApi14": "androidx/view/ViewPropertyAnimatorCompat$ViewPropertyAnimatorListenerApi14",
+      "android/support/v17/leanback/widget/ViewsStateBundle": "androidx/leanback/widget/ViewsStateBundle",
+      "android/support/v4/provider/FontsContractCompat$TypefaceResult": "androidx/provider/FontsContractCompat$TypefaceResult",
+      "android/support/v4/widget/DrawerLayout$State": "androidx/widget/DrawerLayout$State",
+      "android/support/v4/widget/SwipeRefreshLayout$OnRefreshListener": "androidx/widget/SwipeRefreshLayout$OnRefreshListener",
+      "android/support/media/ExifInterface$Rational": "androidx/media/ExifInterface$Rational",
+      "android/support/v4/view/ViewPager$PagerObserver": "androidx/view/ViewPager$PagerObserver",
+      "android/support/design/widget/StateListAnimator": "androidx/design/widget/StateListAnimator",
+      "android/support/v4/media/MediaBrowserCompat$CallbackHandler": "androidx/media/MediaBrowserCompat$CallbackHandler",
+      "android/support/customtabs/CustomTabsClient": "androidx/browser/customtabs/CustomTabsClient",
+      "android/support/v4/media/MediaBrowserServiceCompatApi26$ServiceCompatProxy": "androidx/media/MediaBrowserServiceCompatApi26$ServiceCompatProxy",
+      "android/support/transition/Slide$CalculateSlideHorizontal": "androidx/transition/Slide$CalculateSlideHorizontal",
+      "android/support/v17/leanback/media/PlaybackBannerControlGlue$SPEED": "androidx/leanback/media/PlaybackBannerControlGlue$SPEED",
+      "android/support/annotation/CallSuper": "androidx/annotation/CallSuper",
+      "android/support/design/widget/Snackbar$Callback": "androidx/design/widget/Snackbar$Callback",
+      "android/support/v4/app/ActivityManagerCompat": "androidx/app/ActivityManagerCompat",
+      "android/support/v17/leanback/widget/RoundedRectHelperApi21": "androidx/leanback/widget/RoundedRectHelperApi21",
+      "android/support/v7/graphics/Palette": "androidx/graphics/palette/Palette",
+      "android/support/wear/R$color": "androidx/wear/R$color",
+      "android/support/v17/leanback/widget/ItemBridgeAdapter$OnFocusChangeListener": "androidx/leanback/widget/ItemBridgeAdapter$OnFocusChangeListener",
+      "android/support/v7/media/MediaRouteProviderService$ProviderCallback": "androidx/media/MediaRouteProviderService$ProviderCallback",
+      "android/support/media/ExifInterface": "androidx/media/ExifInterface",
+      "android/support/v4/app/FragmentManagerImpl": "androidx/app/FragmentManagerImpl",
+      "android/support/v13/app/FragmentTabHost$DummyTabFactory": "androidx/app/FragmentTabHost$DummyTabFactory",
+      "android/support/app/recommendation/ContentRecommendation$Builder": "androidx/app/recommendation/ContentRecommendation$Builder",
+      "android/support/v17/leanback/widget/RowHeaderView": "androidx/leanback/widget/RowHeaderView",
+      "android/support/v4/widget/DrawerLayout$EdgeGravity": "androidx/widget/DrawerLayout$EdgeGravity",
+      "android/support/v7/view/menu/MenuWrapperICS": "androidx/view/menu/MenuWrapperICS",
+      "android/support/v7/widget/RoundRectDrawableWithShadow$RoundRectHelper": "androidx/widget/RoundRectDrawableWithShadow$RoundRectHelper",
+      "android/support/v4/media/session/MediaControllerCompat$Callback$MessageHandler": "androidx/media/session/MediaControllerCompat$Callback$MessageHandler",
+      "android/support/v4/text/ICUCompat": "androidx/text/ICUCompat",
+      "android/support/v17/leanback/widget/PlaybackSeekDataProvider$ResultCallback": "androidx/leanback/widget/PlaybackSeekDataProvider$ResultCallback",
+      "android/support/v7/widget/ViewUtils": "androidx/widget/ViewUtils",
+      "android/support/v7/appcompat/R$string": "androidx/appcompat/R$string",
+      "android/support/constraint/ConstraintLayout": "androidx/constraint/ConstraintLayout",
+      "android/support/v4/view/ViewPager$SimpleOnPageChangeListener": "androidx/view/ViewPager$SimpleOnPageChangeListener",
+      "android/support/wear/internal/widget/drawer/MultiPageUi": "androidx/wear/internal/widget/drawer/MultiPageUi",
+      "android/support/v17/leanback/media/PlayerAdapter$Callback": "androidx/leanback/media/PlayerAdapter$Callback",
+      "android/support/design/widget/BaseTransientBottomBar$ContentViewCallback": "androidx/design/widget/BaseTransientBottomBar$ContentViewCallback",
+      "android/support/annotation/RestrictTo$Scope": "androidx/annotation/RestrictTo$Scope",
+      "android/support/v4/content/AsyncTaskLoader": "androidx/content/AsyncTaskLoader",
+      "android/support/v17/leanback/widget/ParallaxEffect": "androidx/leanback/widget/ParallaxEffect",
+      "android/support/v17/leanback/app/BackgroundManager$DrawableWrapper": "androidx/leanback/app/BackgroundManager$DrawableWrapper",
+      "android/support/v4/app/ListFragment": "androidx/app/ListFragment",
+      "android/support/design/widget/DirectedAcyclicGraph": "androidx/widget/DirectedAcyclicGraph",
+      "android/support/design/widget/AppBarLayout": "androidx/design/widget/AppBarLayout",
+      "android/support/v7/media/RemoteControlClientCompat$JellybeanImpl": "androidx/media/RemoteControlClientCompat$JellybeanImpl",
+      "android/support/design/widget/ViewGroupUtils": "androidx/widget/ViewGroupUtils",
+      "android/support/design/R$styleable": "androidx/design/R$styleable",
+      "android/support/animation/DynamicAnimation$OnAnimationEndListener": "androidx/animation/DynamicAnimation$OnAnimationEndListener",
+      "android/support/media/tv/TvContractUtils": "androidx/media/tv/TvContractUtils",
+      "android/support/v4/app/JobIntentService$JobWorkEnqueuer": "androidx/app/JobIntentService$JobWorkEnqueuer",
+      "android/support/percent/PercentLayoutHelper$PercentLayoutParams": "androidx/PercentLayoutHelper$PercentLayoutParams",
+      "android/support/v4/util/Pair": "androidx/util/Pair",
+      "android/support/text/emoji/appcompat/BuildConfig": "androidx/text/emoji/appcompat/BuildConfig",
+      "android/support/v17/leanback/transition/LeanbackTransitionHelperKitKat": "androidx/leanback/transition/LeanbackTransitionHelperKitKat",
+      "android/support/v13/view/DragStartHelper": "androidx/view/DragStartHelper",
+      "android/support/v7/widget/ListViewCompat": "androidx/widget/ListViewCompat",
+      "android/support/v7/preference/CheckBoxPreference$Listener": "androidx/preference/CheckBoxPreference$Listener",
+      "android/support/v7/app/AppCompatDelegate$NightMode": "androidx/app/AppCompatDelegate$NightMode",
+      "android/support/v17/leanback/transition/SlideNoPropagation": "androidx/leanback/transition/SlideNoPropagation",
+      "android/support/v7/media/MediaRouteProviderService$ClientRecord": "androidx/media/MediaRouteProviderService$ClientRecord",
+      "android/support/graphics/drawable/VectorDrawableCompat$VectorDrawableCompatState": "androidx/graphics/drawable/VectorDrawableCompat$VectorDrawableCompatState",
+      "android/support/text/emoji/R$layout": "androidx/text/emoji/R$layout",
+      "android/support/v4/media/session/MediaSessionCompat$SessionFlags": "androidx/media/session/MediaSessionCompat$SessionFlags",
+      "android/support/v7/preference/PreferenceRecyclerViewAccessibilityDelegate": "androidx/preference/PreferenceRecyclerViewAccessibilityDelegate",
+      "android/support/transition/AnimatorUtils": "androidx/transition/AnimatorUtils",
+      "android/support/v17/leanback/system/Settings": "androidx/leanback/system/Settings",
+      "android/support/v4/app/FragmentActivity$NonConfigurationInstances": "androidx/app/FragmentActivity$NonConfigurationInstances",
+      "android/support/v17/leanback/widget/GuidedActionAdapter$EditListener": "androidx/leanback/widget/GuidedActionAdapter$EditListener",
+      "android/support/v7/media/MediaRouteProvider$Callback": "androidx/media/MediaRouteProvider$Callback",
+      "android/support/v7/app/AppCompatDelegateImplV9$PanelMenuPresenterCallback": "androidx/app/AppCompatDelegateImplV9$PanelMenuPresenterCallback",
+      "android/support/constraint/ConstraintSet$Constraint": "androidx/constraint/ConstraintSet$Constraint",
+      "android/support/wear/R$dimen": "androidx/wear/R$dimen"
+    },
+    "fields": {
+      "android/support/v4/view/AbsSavedState": {
+        "androidx/view/AbsSavedState": [
+          "CREATOR",
+          "EMPTY_STATE"
+        ]
+      },
+      "android/support/v4/view/PagerTabStrip": {
+        "androidx/widget/PagerTabStrip": [
+          "TAG",
+          "TAB_PADDING",
+          "TAB_SPACING",
+          "FULL_UNDERLINE_HEIGHT",
+          "MIN_PADDING_BOTTOM",
+          "MIN_STRIP_HEIGHT",
+          "MIN_TEXT_SPACING",
+          "INDICATOR_HEIGHT"
+        ]
+      },
+      "android/support/media/tv/TvContractCompat$Channels": {
+        "androidx/media/tv/TvContractCompat$Channels": [
+          "VIDEO_FORMAT_4320P",
+          "VIDEO_RESOLUTION_HD",
+          "VIDEO_RESOLUTION_ED",
+          "VIDEO_FORMAT_720P",
+          "COLUMN_TRANSIENT",
+          "VIDEO_FORMAT_TO_RESOLUTION_MAP",
+          "COLUMN_DISPLAY_NAME",
+          "VIDEO_FORMAT_240P",
+          "COLUMN_VIDEO_FORMAT",
+          "COLUMN_APP_LINK_TEXT",
+          "COLUMN_APP_LINK_POSTER_ART_URI",
+          "VIDEO_FORMAT_360P",
+          "TYPE_OTHER",
+          "COLUMN_NETWORK_AFFILIATION",
+          "TYPE_PAL",
+          "SERVICE_TYPE_OTHER",
+          "VIDEO_FORMAT_480P",
+          "VIDEO_FORMAT_480I",
+          "CONTENT_URI",
+          "TYPE_CMMB",
+          "TYPE_ATSC_C",
+          "TYPE_ATSC_T",
+          "VIDEO_RESOLUTION_FHD",
+          "COLUMN_APP_LINK_INTENT_URI",
+          "VIDEO_FORMAT_2160P",
+          "TYPE_SECAM",
+          "COLUMN_INTERNAL_PROVIDER_FLAG2",
+          "COLUMN_INTERNAL_PROVIDER_FLAG1",
+          "COLUMN_INTERNAL_PROVIDER_FLAG4",
+          "COLUMN_INTERNAL_PROVIDER_FLAG3",
+          "TYPE_T_DMB",
+          "CONTENT_TYPE",
+          "COLUMN_TRANSPORT_STREAM_ID",
+          "VIDEO_FORMAT_576P",
+          "VIDEO_FORMAT_576I",
+          "COLUMN_LOCKED",
+          "COLUMN_SERVICE_TYPE",
+          "TYPE_ISDB_S",
+          "TYPE_ISDB_T",
+          "TYPE_ISDB_C",
+          "COLUMN_DESCRIPTION",
+          "TYPE_DVB_S2",
+          "TYPE_DVB_T2",
+          "TYPE_DVB_SH",
+          "TYPE_DTMB",
+          "COLUMN_INPUT_ID",
+          "VIDEO_RESOLUTION_SD",
+          "COLUMN_VERSION_NUMBER",
+          "COLUMN_SEARCHABLE",
+          "COLUMN_SERVICE_ID",
+          "TYPE_1SEG",
+          "TYPE_DVB_C2",
+          "TYPE_DVB_C",
+          "TYPE_DVB_H",
+          "TYPE_DVB_S",
+          "TYPE_DVB_T",
+          "CONTENT_ITEM_TYPE",
+          "SERVICE_TYPE_AUDIO",
+          "TYPE_ATSC_M_H",
+          "TYPE_NTSC",
+          "COLUMN_BROWSABLE",
+          "TYPE_ISDB_TB",
+          "COLUMN_INTERNAL_PROVIDER_DATA",
+          "COLUMN_DISPLAY_NUMBER",
+          "COLUMN_SYSTEM_APPROVED",
+          "VIDEO_FORMAT_1080I",
+          "VIDEO_FORMAT_1080P",
+          "COLUMN_TYPE",
+          "VIDEO_RESOLUTION_UHD",
+          "COLUMN_APP_LINK_ICON_URI",
+          "TYPE_S_DMB",
+          "COLUMN_APP_LINK_COLOR",
+          "SERVICE_TYPE_AUDIO_VIDEO",
+          "TYPE_PREVIEW",
+          "COLUMN_ORIGINAL_NETWORK_ID",
+          "COLUMN_INTERNAL_PROVIDER_ID"
+        ]
+      },
+      "android/support/v7/widget/ViewBoundsCheck": {
+        "androidx/widget/ViewBoundsCheck": [
+          "FLAG_CVS_EQ_PVE",
+          "FLAG_CVS_EQ_PVS",
+          "GT",
+          "LT",
+          "EQ",
+          "FLAG_CVS_GT_PVS",
+          "FLAG_CVS_GT_PVE",
+          "FLAG_CVE_EQ_PVE",
+          "FLAG_CVE_EQ_PVS",
+          "MASK",
+          "CVS_PVE_POS",
+          "CVE_PVE_POS",
+          "FLAG_CVS_LT_PVE",
+          "FLAG_CVS_LT_PVS",
+          "CVS_PVS_POS",
+          "FLAG_CVE_LT_PVS",
+          "FLAG_CVE_LT_PVE",
+          "CVE_PVS_POS",
+          "FLAG_CVE_GT_PVS",
+          "FLAG_CVE_GT_PVE"
+        ]
+      },
+      "android/support/constraint/ConstraintLayout$LayoutParams": {
+        "androidx/constraint/ConstraintLayout$LayoutParams": [
+          "topToTop",
+          "goneTopMargin",
+          "verticalWeight",
+          "horizontalDimensionFixed",
+          "goneBottomMargin",
+          "matchConstraintMaxHeight",
+          "resolveGoneLeftMargin",
+          "leftMargin",
+          "MATCH_CONSTRAINT",
+          "resolvedRightToLeft",
+          "needsBaseline",
+          "startToEnd",
+          "TOP",
+          "horizontalChainStyle",
+          "guidePercent",
+          "CHAIN_SPREAD",
+          "topMargin",
+          "goneStartMargin",
+          "goneRightMargin",
+          "UNSET",
+          "HORIZONTAL",
+          "dimensionRatioValue",
+          "MATCH_CONSTRAINT_SPREAD",
+          "END",
+          "guideBegin",
+          "matchConstraintMaxWidth",
+          "verticalDimensionFixed",
+          "resolvedRightToRight",
+          "BASELINE",
+          "START",
+          "resolvedHorizontalBias",
+          "bottomToBottom",
+          "MATCH_CONSTRAINT_WRAP",
+          "startToStart",
+          "RIGHT",
+          "orientation",
+          "matchConstraintDefaultHeight",
+          "guideEnd",
+          "bottomToTop",
+          "CHAIN_PACKED",
+          "isGuideline",
+          "dimensionRatioSide",
+          "dimensionRatio",
+          "goneLeftMargin",
+          "matchConstraintMinWidth",
+          "PARENT_ID",
+          "endToStart",
+          "LEFT",
+          "horizontalBias",
+          "leftToLeft",
+          "BOTTOM",
+          "resolveGoneRightMargin",
+          "leftToRight",
+          "verticalBias",
+          "goneEndMargin",
+          "VERTICAL",
+          "rightMargin",
+          "rightToRight",
+          "resolvedLeftToLeft",
+          "topToBottom",
+          "endToEnd",
+          "matchConstraintDefaultWidth",
+          "matchConstraintMinHeight",
+          "height",
+          "CHAIN_SPREAD_INSIDE",
+          "widget",
+          "resolvedLeftToRight",
+          "baselineToBaseline",
+          "rightToLeft",
+          "verticalChainStyle",
+          "editorAbsoluteY",
+          "editorAbsoluteX",
+          "bottomMargin",
+          "width",
+          "horizontalWeight"
+        ]
+      },
+      "android/support/v4/media/session/PlaybackStateCompat": {
+        "androidx/media/session/PlaybackStateCompat": [
+          "STATE_NONE",
+          "ACTION_SET_SHUFFLE_MODE",
+          "ACTION_SKIP_TO_QUEUE_ITEM",
+          "ERROR_CODE_CONTENT_ALREADY_PLAYING",
+          "STATE_ERROR",
+          "SHUFFLE_MODE_NONE",
+          "CREATOR",
+          "STATE_SKIPPING_TO_PREVIOUS",
+          "PLAYBACK_POSITION_UNKNOWN",
+          "REPEAT_MODE_INVALID",
+          "REPEAT_MODE_GROUP",
+          "ACTION_SET_REPEAT_MODE",
+          "ACTION_SKIP_TO_PREVIOUS",
+          "ACTION_PREPARE",
+          "ERROR_CODE_SKIP_LIMIT_REACHED",
+          "REPEAT_MODE_NONE",
+          "ACTION_PREPARE_FROM_URI",
+          "STATE_PLAYING",
+          "STATE_SKIPPING_TO_NEXT",
+          "ACTION_FAST_FORWARD",
+          "ERROR_CODE_END_OF_QUEUE",
+          "ERROR_CODE_PARENTAL_CONTROL_RESTRICTED",
+          "STATE_BUFFERING",
+          "ACTION_REWIND",
+          "KEYCODE_MEDIA_PAUSE",
+          "ACTION_PLAY_FROM_MEDIA_ID",
+          "STATE_FAST_FORWARDING",
+          "ACTION_STOP",
+          "KEYCODE_MEDIA_PLAY",
+          "ACTION_PLAY",
+          "ACTION_SET_CAPTIONING_ENABLED",
+          "ERROR_CODE_NOT_SUPPORTED",
+          "STATE_STOPPED",
+          "ACTION_PLAY_PAUSE",
+          "SHUFFLE_MODE_ALL",
+          "ERROR_CODE_AUTHENTICATION_EXPIRED",
+          "ERROR_CODE_APP_ERROR",
+          "ACTION_SEEK_TO",
+          "ERROR_CODE_CONCURRENT_STREAM_LIMIT",
+          "REPEAT_MODE_ALL",
+          "ERROR_CODE_NOT_AVAILABLE_IN_REGION",
+          "ERROR_CODE_ACTION_ABORTED",
+          "STATE_CONNECTING",
+          "ACTION_SKIP_TO_NEXT",
+          "SHUFFLE_MODE_INVALID",
+          "ACTION_PLAY_FROM_SEARCH",
+          "ERROR_CODE_UNKNOWN_ERROR",
+          "ACTION_PAUSE",
+          "ACTION_PLAY_FROM_URI",
+          "REPEAT_MODE_ONE",
+          "ACTION_SET_RATING",
+          "ACTION_PREPARE_FROM_SEARCH",
+          "STATE_PAUSED",
+          "SHUFFLE_MODE_GROUP",
+          "STATE_REWINDING",
+          "ACTION_PREPARE_FROM_MEDIA_ID",
+          "ERROR_CODE_PREMIUM_ACCOUNT_REQUIRED",
+          "STATE_SKIPPING_TO_QUEUE_ITEM",
+          "ACTION_SET_SHUFFLE_MODE_ENABLED"
+        ]
+      },
+      "android/support/v7/appcompat/R$styleable": {
+        "androidx/appcompat/R$styleable": [
+          "MenuItem_android_title",
+          "Toolbar_android_gravity",
+          "ActionMode_titleTextStyle",
+          "ActionBar_popupTheme",
+          "MenuView_android_itemTextAppearance",
+          "SearchView_goIcon",
+          "MenuItem_actionLayout",
+          "Toolbar_navigationIcon",
+          "ColorStateListItem_android_alpha",
+          "AppCompatTheme_android_windowAnimationStyle",
+          "LinearLayoutCompat_android_orientation",
+          "AppCompatTheme_windowActionBar",
+          "MenuItem_showAsAction",
+          "ColorStateListItem_android_color",
+          "ActivityChooserView_initialActivityCount",
+          "AppCompatSeekBar_tickMarkTint",
+          "CompoundButton_android_button",
+          "MenuGroup_android_orderInCategory",
+          "PopupWindow_overlapAnchor",
+          "ViewBackgroundHelper_backgroundTint",
+          "PopupWindow",
+          "Toolbar_contentInsetEndWithActions",
+          "MenuItem_android_titleCondensed",
+          "TextAppearance",
+          "MenuItem_android_id",
+          "LinearLayoutCompat_measureWithLargestChild",
+          "AppCompatTextView_autoSizeTextType",
+          "AppCompatTheme_windowMinWidthMajor",
+          "DrawerArrowToggle_color",
+          "AppCompatTheme",
+          "SwitchCompat_trackTintMode",
+          "AppCompatTheme_windowActionBarOverlay",
+          "DrawerArrowToggle_spinBars",
+          "LinearLayoutCompat",
+          "AppCompatTextHelper",
+          "ActionMode_subtitleTextStyle",
+          "Toolbar_titleTextAppearance",
+          "ActivityChooserView",
+          "LinearLayoutCompat_divider",
+          "MenuItem_android_alphabeticShortcut",
+          "Toolbar_subtitleTextColor",
+          "AppCompatImageView_tint",
+          "AppCompatTheme_windowFixedHeightMajor",
+          "LinearLayoutCompat_android_baselineAlignedChildIndex",
+          "AppCompatTheme_windowMinWidthMinor",
+          "SearchView_suggestionRowLayout",
+          "ListPopupWindow_android_dropDownHorizontalOffset",
+          "ActionBar_subtitleTextStyle",
+          "Toolbar_titleMarginEnd",
+          "Toolbar_titleMarginTop",
+          "LinearLayoutCompat_Layout",
+          "TextAppearance_android_textColor",
+          "Toolbar_subtitleTextAppearance",
+          "ActionBar_displayOptions",
+          "Toolbar_title",
+          "Spinner_android_entries",
+          "MenuItem_numericModifiers",
+          "RecycleListView",
+          "AppCompatTextHelper_android_drawableEnd",
+          "SearchView_searchHintIcon",
+          "Toolbar_collapseIcon",
+          "AppCompatImageView",
+          "MenuItem_android_icon",
+          "ActionBar_contentInsetStart",
+          "MenuItem_android_onClick",
+          "SearchView_searchIcon",
+          "MenuItem_actionViewClass",
+          "MenuGroup_android_enabled",
+          "Toolbar_subtitle",
+          "MenuGroup_android_id",
+          "TextAppearance_android_fontFamily",
+          "ViewBackgroundHelper_android_background",
+          "TextAppearance_android_textColorHint",
+          "LinearLayoutCompat_android_baselineAligned",
+          "MenuItem_contentDescription",
+          "SearchView_voiceIcon",
+          "ActionBar_background",
+          "ActionMenuItemView",
+          "SwitchCompat_switchMinWidth",
+          "AppCompatTheme_windowFixedWidthMajor",
+          "ActionMenuItemView_android_minWidth",
+          "AlertDialog_buttonPanelSideLayout",
+          "SearchView_defaultQueryHint",
+          "MenuItem_android_numericShortcut",
+          "ActionBar_homeAsUpIndicator",
+          "AppCompatTextHelper_android_drawableTop",
+          "DrawerArrowToggle_arrowHeadLength",
+          "TextAppearance_android_shadowRadius",
+          "Toolbar_titleMargins",
+          "SwitchCompat",
+          "ActionBar_height",
+          "LinearLayoutCompat_Layout_android_layout_gravity",
+          "AlertDialog_multiChoiceItemLayout",
+          "CompoundButton_buttonTint",
+          "SearchView_android_imeOptions",
+          "MenuGroup",
+          "ActionBar_customNavigationLayout",
+          "Toolbar_navigationContentDescription",
+          "Toolbar_popupTheme",
+          "View",
+          "ActionBar",
+          "SwitchCompat_android_textOff",
+          "MenuGroup_android_menuCategory",
+          "MenuItem_tooltipText",
+          "AppCompatTextView",
+          "Spinner",
+          "ViewStubCompat_android_inflatedId",
+          "Spinner_popupTheme",
+          "SearchView_closeIcon",
+          "TextAppearance_textAllCaps",
+          "SwitchCompat_trackTint",
+          "Toolbar_logoDescription",
+          "MenuView_android_itemBackground",
+          "TextAppearance_android_textSize",
+          "SearchView_queryBackground",
+          "MenuItem_android_checked",
+          "SearchView_commitIcon",
+          "LinearLayoutCompat_Layout_android_layout_weight",
+          "ViewStubCompat_android_id",
+          "AppCompatTextView_autoSizePresetSizes",
+          "ActionBar_hideOnContentScroll",
+          "Toolbar_contentInsetStartWithNavigation",
+          "AppCompatTextHelper_android_drawableBottom",
+          "PopupWindow_android_popupBackground",
+          "Toolbar_buttonGravity",
+          "AlertDialog",
+          "TextAppearance_android_textStyle",
+          "SwitchCompat_thumbTintMode",
+          "MenuItem_android_checkable",
+          "AppCompatTheme_windowFixedWidthMinor",
+          "TextAppearance_android_textColorLink",
+          "Toolbar_titleMarginStart",
+          "RecycleListView_paddingBottomNoButtons",
+          "ActionMode_closeItemLayout",
+          "Toolbar",
+          "Toolbar_collapseContentDescription",
+          "MenuItem_android_menuCategory",
+          "AppCompatTextHelper_android_textAppearance",
+          "View_theme",
+          "MenuItem_iconTintMode",
+          "Toolbar_contentInsetLeft",
+          "Toolbar_contentInsetStart",
+          "LinearLayoutCompat_android_weightSum",
+          "SwitchCompat_android_textOn",
+          "AppCompatSeekBar",
+          "MenuItem_actionProviderClass",
+          "Toolbar_titleMargin",
+          "AlertDialog_singleChoiceItemLayout",
+          "Toolbar_contentInsetRight",
+          "LinearLayoutCompat_showDividers",
+          "SwitchCompat_android_thumb",
+          "AlertDialog_showTitle",
+          "TextAppearance_android_shadowDy",
+          "TextAppearance_android_shadowDx",
+          "AppCompatTheme_windowActionModeOverlay",
+          "MenuItem_android_visible",
+          "MenuView",
+          "SearchView",
+          "MenuItem",
+          "SearchView_queryHint",
+          "SwitchCompat_thumbTint",
+          "SwitchCompat_thumbTextPadding",
+          "AlertDialog_listLayout",
+          "ActionBar_subtitle",
+          "AlertDialog_android_layout",
+          "ListPopupWindow_android_dropDownVerticalOffset",
+          "Toolbar_titleMarginBottom",
+          "AppCompatSeekBar_android_thumb",
+          "ListPopupWindow",
+          "ButtonBarLayout_allowStacking",
+          "MenuGroup_android_checkableBehavior",
+          "SwitchCompat_switchPadding",
+          "LinearLayoutCompat_android_gravity",
+          "AppCompatTheme_windowNoTitle",
+          "ActionBar_icon",
+          "AppCompatTextView_autoSizeMinTextSize",
+          "Toolbar_logo",
+          "ViewStubCompat_android_layout",
+          "MenuItem_android_enabled",
+          "MenuItem_iconTint",
+          "AppCompatTextHelper_android_drawableRight",
+          "AppCompatTheme_android_windowIsFloating",
+          "Spinner_android_popupBackground",
+          "TextAppearance_android_shadowColor",
+          "Toolbar_maxButtonHeight",
+          "TextAppearance_android_typeface",
+          "DrawerArrowToggle_drawableSize",
+          "DrawerArrowToggle_barLength",
+          "CompoundButton",
+          "ActionMode_height",
+          "DrawerArrowToggle_arrowShaftLength",
+          "DrawerArrowToggle_gapBetweenBars",
+          "SearchView_android_focusable",
+          "ActionMode",
+          "AppCompatTextHelper_android_drawableStart",
+          "SearchView_android_maxWidth",
+          "ActivityChooserView_expandActivityOverflowButtonDrawable",
+          "ActionMode_background",
+          "ActionBar_backgroundSplit",
+          "SwitchCompat_track",
+          "MenuItem_alphabeticModifiers",
+          "TextAppearance_fontFamily",
+          "DrawerArrowToggle_thickness",
+          "AppCompatTextHelper_android_drawableLeft",
+          "ActionBar_contentInsetEnd",
+          "Spinner_android_dropDownWidth",
+          "ColorStateListItem_alpha",
+          "LinearLayoutCompat_dividerPadding",
+          "ViewStubCompat",
+          "View_android_theme",
+          "ActionBar_backgroundStacked",
+          "SearchView_android_inputType",
+          "AlertDialog_listItemLayout",
+          "AppCompatTheme_panelBackground",
+          "AppCompatImageView_srcCompat",
+          "ColorStateListItem",
+          "AppCompatTheme_windowFixedHeightMinor",
+          "MenuView_preserveIconSpacing",
+          "ActionBar_logo",
+          "AppCompatSeekBar_tickMarkTintMode",
+          "SwitchCompat_showText",
+          "AppCompatTextView_autoSizeStepGranularity",
+          "Toolbar_contentInsetEnd",
+          "SearchView_submitBackground",
+          "MenuView_subMenuArrow",
+          "CompoundButton_buttonTintMode",
+          "MenuItem_android_orderInCategory",
+          "ViewBackgroundHelper_backgroundTintMode",
+          "SwitchCompat_switchTextAppearance",
+          "ActionBar_titleTextStyle",
+          "Toolbar_titleTextColor",
+          "MenuGroup_android_visible",
+          "SwitchCompat_splitTrack",
+          "ButtonBarLayout",
+          "AppCompatSeekBar_tickMark",
+          "Spinner_android_prompt",
+          "AppCompatTextView_autoSizeMaxTextSize",
+          "ActionBar_elevation",
+          "ActionBarLayout_android_layout_gravity",
+          "DrawerArrowToggle",
+          "ActionBar_title",
+          "AppCompatImageView_tintMode",
+          "SearchView_layout",
+          "ViewBackgroundHelper",
+          "RecycleListView_paddingTopNoTitle",
+          "ActionBarLayout",
+          "SearchView_iconifiedByDefault"
+        ]
+      },
+      "android/support/text/emoji/appcompat/BuildConfig": {
+        "androidx/text/emoji/appcompat/BuildConfig": [
+          "DEBUG",
+          "APPLICATION_ID",
+          "FLAVOR",
+          "VERSION_CODE",
+          "BUILD_TYPE",
+          "VERSION_NAME"
+        ]
+      },
+      "android/support/v17/leanback/widget/FocusHighlight": {
+        "androidx/leanback/widget/FocusHighlight": [
+          "ZOOM_FACTOR_SMALL",
+          "ZOOM_FACTOR_XSMALL",
+          "ZOOM_FACTOR_NONE",
+          "ZOOM_FACTOR_LARGE",
+          "ZOOM_FACTOR_MEDIUM"
+        ]
+      },
+      "android/support/v4/media/session/MediaSessionCompat$MediaSessionImplBase$Command": {
+        "androidx/media/session/MediaSessionCompat$MediaSessionImplBase$Command": [
+          "extras",
+          "command",
+          "stub"
+        ]
+      },
+      "android/support/v7/appcompat/R$attr": {
+        "androidx/appcompat/R$attr": [
+          "checkboxStyle",
+          "colorButtonNormal",
+          "editTextStyle",
+          "actionDropDownStyle",
+          "colorControlNormal",
+          "colorAccent",
+          "actionBarSize",
+          "listMenuViewStyle",
+          "buttonStyle",
+          "colorControlHighlight",
+          "panelMenuListTheme",
+          "seekBarStyle",
+          "actionModeStyle",
+          "alertDialogStyle",
+          "isLightTheme",
+          "ratingBarStyle",
+          "radioButtonStyle",
+          "actionBarTabTextStyle",
+          "alertDialogCenterButtons",
+          "actionBarStyle",
+          "listPopupWindowStyle",
+          "actionBarPopupTheme",
+          "actionOverflowButtonStyle",
+          "spinnerStyle",
+          "popupMenuStyle",
+          "colorPrimaryDark",
+          "drawerArrowStyle",
+          "actionBarWidgetTheme",
+          "alpha",
+          "actionBarTabStyle",
+          "imageButtonStyle",
+          "colorControlActivated",
+          "toolbarStyle",
+          "homeAsUpIndicator",
+          "toolbarNavigationButtonStyle",
+          "colorPrimary",
+          "actionBarTheme",
+          "actionOverflowMenuStyle",
+          "dropDownListViewStyle",
+          "actionModePopupWindowStyle",
+          "actionModeShareDrawable",
+          "dialogTheme",
+          "alertDialogTheme",
+          "searchViewStyle",
+          "colorSwitchThumbNormal",
+          "actionBarTabBarStyle",
+          "textColorSearchUrl",
+          "switchStyle",
+          "autoCompleteTextViewStyle"
+        ]
+      },
+      "android/support/v7/widget/StaggeredGridLayoutManager$Span": {
+        "androidx/widget/StaggeredGridLayoutManager$Span": [
+          "INVALID_LINE"
+        ]
+      },
+      "android/support/v4/app/NotificationManagerCompat": {
+        "androidx/app/NotificationManagerCompat": [
+          "OP_POST_NOTIFICATION",
+          "sEnabledNotificationListeners",
+          "IMPORTANCE_HIGH",
+          "SIDE_CHANNEL_RETRY_MAX_COUNT",
+          "TAG",
+          "SETTING_ENABLED_NOTIFICATION_LISTENERS",
+          "sSideChannelManager",
+          "sLock",
+          "IMPORTANCE_DEFAULT",
+          "SIDE_CHANNEL_RETRY_BASE_INTERVAL_MS",
+          "EXTRA_USE_SIDE_CHANNEL",
+          "IMPORTANCE_NONE",
+          "sEnabledNotificationListenersLock",
+          "ACTION_BIND_SIDE_CHANNEL",
+          "CHECK_OP_NO_THROW",
+          "sEnabledNotificationListenerPackages",
+          "IMPORTANCE_LOW",
+          "IMPORTANCE_MAX",
+          "MAX_SIDE_CHANNEL_SDK_VERSION",
+          "IMPORTANCE_MIN",
+          "IMPORTANCE_UNSPECIFIED"
+        ]
+      },
+      "android/support/v4/util/LruCache": {
+        "androidx/util/LruCache": [
+          "map",
+          "hitCount",
+          "missCount",
+          "maxSize",
+          "putCount",
+          "createCount",
+          "evictionCount",
+          "size"
+        ]
+      },
+      "android/support/v4/content/FileProvider": {
+        "androidx/content/FileProvider": [
+          "META_DATA_FILE_PROVIDER_PATHS",
+          "TAG_EXTERNAL_CACHE",
+          "TAG_FILES_PATH",
+          "TAG_EXTERNAL_FILES",
+          "ATTR_PATH",
+          "ATTR_NAME",
+          "DEVICE_ROOT",
+          "sCache",
+          "COLUMNS",
+          "TAG_EXTERNAL",
+          "TAG_ROOT_PATH",
+          "TAG_CACHE_PATH"
+        ]
+      },
+      "android/support/v4/media/AudioAttributesCompat": {
+        "androidx/media/AudioAttributesCompat": [
+          "FLAG_LOW_LATENCY",
+          "FLAG_DEEP_BUFFER",
+          "USAGE_NOTIFICATION",
+          "SUPPRESSIBLE_USAGES",
+          "SUPPRESSIBLE_CALL",
+          "USAGE_NOTIFICATION_COMMUNICATION_INSTANT",
+          "USAGE_NOTIFICATION_RINGTONE",
+          "USAGE_NOTIFICATION_EVENT",
+          "USAGE_VIRTUAL_SOURCE",
+          "FLAG_BYPASS_MUTE",
+          "USAGE_ASSISTANCE_SONIFICATION",
+          "CONTENT_TYPE_SPEECH",
+          "FLAG_BYPASS_INTERRUPTION_POLICY",
+          "SDK_USAGES",
+          "USAGE_GAME",
+          "USAGE_UNKNOWN",
+          "USAGE_NOTIFICATION_COMMUNICATION_DELAYED",
+          "FLAG_ALL_PUBLIC",
+          "FLAG_SCO",
+          "FLAG_SECURE",
+          "FLAG_ALL",
+          "USAGE_VOICE_COMMUNICATION",
+          "USAGE_VOICE_COMMUNICATION_SIGNALLING",
+          "USAGE_MEDIA",
+          "USAGE_ALARM",
+          "CONTENT_TYPE_MOVIE",
+          "TAG",
+          "FLAG_HW_AV_SYNC",
+          "sForceLegacyBehavior",
+          "USAGE_ASSISTANCE_NAVIGATION_GUIDANCE",
+          "FLAG_BEACON",
+          "CONTENT_TYPE_UNKNOWN",
+          "USAGE_ASSISTANT",
+          "FLAG_AUDIBILITY_ENFORCED",
+          "FLAG_HW_HOTWORD",
+          "USAGE_NOTIFICATION_COMMUNICATION_REQUEST",
+          "USAGE_ASSISTANCE_ACCESSIBILITY",
+          "CONTENT_TYPE_MUSIC",
+          "SUPPRESSIBLE_NOTIFICATION",
+          "CONTENT_TYPE_SONIFICATION"
+        ]
+      },
+      "android/support/v7/mediarouter/R$id": {
+        "androidx/mediarouter/R$id": [
+          "mr_custom_control",
+          "mr_art",
+          "mr_name",
+          "mr_control_subtitle",
+          "mr_control_title",
+          "mr_control_playback_ctrl",
+          "mr_chooser_title",
+          "mr_group_expand_collapse",
+          "mr_close",
+          "mr_chooser_list",
+          "mr_volume_group_list",
+          "mr_expandable_area",
+          "mr_chooser_route_desc",
+          "mr_chooser_route_icon",
+          "mr_control_divider",
+          "volume_item_container",
+          "mr_chooser_route_name",
+          "mr_volume_control",
+          "mr_dialog_area",
+          "mr_default_control",
+          "mr_volume_slider",
+          "mr_volume_item_icon",
+          "mr_media_main_control",
+          "mr_playback_control",
+          "mr_control_title_container"
+        ]
+      },
+      "android/support/text/emoji/flatbuffer/Table": {
+        "androidx/text/emoji/flatbuffer/Table": [
+          "UTF8_CHARSET",
+          "bb",
+          "bb_pos",
+          "UTF8_DECODER",
+          "CHAR_BUFFER"
+        ]
+      },
+      "android/support/v17/leanback/R$id": {
+        "androidx/leanback/R$id": [
+          "paused",
+          "content_container",
+          "guidedactions_item_title",
+          "container_list",
+          "lb_parallax_source",
+          "description_dock",
+          "guidedactions_list",
+          "browse_grid_dock",
+          "details_overview_image",
+          "scale_frame",
+          "details_overview_right_panel",
+          "playback_progress",
+          "guidedactions_sub_list_background",
+          "guidedactions_root2",
+          "mediaItemDetails",
+          "button_start",
+          "title_badge",
+          "lb_search_text_editor",
+          "bottom_spacer",
+          "lb_slide_transition_value",
+          "info_field",
+          "mediaItemActionsContainer",
+          "thumbs_row",
+          "controls_container",
+          "guidedactions_list_background2",
+          "guidedactions_item_content",
+          "lb_action_button",
+          "guidedactions_item_icon",
+          "guidedactions_item_checkmark",
+          "lb_control_closed_captioning",
+          "browse_grid",
+          "title_orb",
+          "details_root",
+          "guidedactions_list_background",
+          "total_time",
+          "controls_card",
+          "lb_control_more_actions",
+          "lb_control_shuffle",
+          "mediaRowSeparator",
+          "image",
+          "lb_control_fast_rewind",
+          "row_header",
+          "controls_card_right_panel",
+          "description",
+          "background_imagein",
+          "title_text",
+          "details_fragment_root",
+          "playback_fragment_background",
+          "browse_frame",
+          "title",
+          "current_time",
+          "browse_headers_dock",
+          "guidedactions_sub_list",
+          "details_overview_actions_background",
+          "background_imageout",
+          "guidedactions_item_chevron",
+          "mediaItemDuration",
+          "lb_control_skip_previous",
+          "browse_container_dock",
+          "lb_control_skip_next",
+          "mediaListHeader",
+          "main_image",
+          "lb_control_thumbs_down",
+          "mediaItemNumberViewFlipper",
+          "details_overview",
+          "actionIcon",
+          "guidedactions_item_description",
+          "guidance_container",
+          "guidedstep_background",
+          "guidedactions_activator_item",
+          "transport_row",
+          "playback_controls_dock",
+          "page_container",
+          "lb_shadow_focused",
+          "icon",
+          "guidedactions_content",
+          "playing",
+          "lb_control_high_quality",
+          "guidance_description",
+          "details_frame",
+          "guidance_icon",
+          "control_bar",
+          "page_indicator",
+          "lb_search_bar_speech_orb",
+          "details_rows_dock",
+          "row_header_description",
+          "lb_control_thumbs_up",
+          "secondary_controls_dock",
+          "button",
+          "lb_shadow_impl",
+          "lb_focus_animator",
+          "spacer",
+          "lb_search_bar_badge",
+          "initial",
+          "lb_control_repeat",
+          "guidedactions_content2",
+          "guidance_title",
+          "message",
+          "action_fragment_root",
+          "lb_shadow_normal",
+          "guidedactions_root",
+          "lb_details_description_title",
+          "guidance_breadcrumb",
+          "action_fragment",
+          "background_container",
+          "lb_row_container_header_dock",
+          "details_background_view",
+          "browse_title_group",
+          "main_icon",
+          "video_surface_container",
+          "lb_search_frame",
+          "picker",
+          "lb_control_play_pause",
+          "lb_details_description_body",
+          "controls_dock",
+          "lb_control_picture_in_picture",
+          "lb_results_frame",
+          "grid_frame",
+          "lb_control_fast_forward",
+          "details_overview_description",
+          "mediaItemRow",
+          "mediaItemName",
+          "content_fragment",
+          "details_overview_actions",
+          "more_actions_dock",
+          "fade_out_edge",
+          "label",
+          "action_fragment_background",
+          "browse_headers",
+          "foreground_container",
+          "transitionPosition",
+          "lb_search_bar",
+          "error_frame",
+          "guidedactions_list2",
+          "lb_details_description_subtitle",
+          "lb_search_bar_items",
+          "mediaRowSelector",
+          "bar3",
+          "bar2",
+          "bar1",
+          "logo",
+          "search_orb",
+          "row_content",
+          "guidedstep_background_view_root"
+        ]
+      },
+      "android/support/v17/leanback/widget/picker/Picker$ViewHolder": {
+        "androidx/leanback/widget/picker/Picker$ViewHolder": [
+          "textView",
+          "itemView"
+        ]
+      },
+      "android/support/v4/media/MediaMetadataCompat": {
+        "androidx/media/MediaMetadataCompat": [
+          "METADATA_KEY_COMPILATION",
+          "METADATA_KEY_ART",
+          "METADATA_KEY_NUM_TRACKS",
+          "METADATA_KEY_ALBUM",
+          "METADATA_TYPE_TEXT",
+          "METADATA_TYPE_LONG",
+          "METADATA_KEY_TRACK_NUMBER",
+          "METADATA_KEY_ARTIST",
+          "METADATA_KEY_DOWNLOAD_STATUS",
+          "METADATA_KEY_AUTHOR",
+          "METADATA_KEY_DATE",
+          "METADATA_KEY_DISPLAY_TITLE",
+          "METADATA_KEY_DISPLAY_DESCRIPTION",
+          "METADATA_KEY_DURATION",
+          "METADATA_KEY_YEAR",
+          "METADATA_TYPE_BITMAP",
+          "METADATA_KEY_USER_RATING",
+          "METADATA_KEY_ALBUM_ART_URI",
+          "METADATA_KEY_MEDIA_URI",
+          "METADATA_KEY_MEDIA_ID",
+          "PREFERRED_BITMAP_ORDER",
+          "CREATOR",
+          "METADATA_KEY_BT_FOLDER_TYPE",
+          "METADATA_KEY_TITLE",
+          "METADATA_KEY_DISPLAY_ICON_URI",
+          "PREFERRED_URI_ORDER",
+          "METADATA_TYPE_RATING",
+          "METADATA_KEY_WRITER",
+          "PREFERRED_DESCRIPTION_ORDER",
+          "TAG",
+          "METADATA_KEY_COMPOSER",
+          "METADATA_KEY_ALBUM_ART",
+          "METADATA_KEY_GENRE",
+          "METADATA_KEY_ART_URI",
+          "METADATA_KEY_DISPLAY_ICON",
+          "METADATA_KEY_ALBUM_ARTIST",
+          "METADATA_KEY_ADVERTISEMENT",
+          "METADATA_KEYS_TYPE",
+          "METADATA_KEY_RATING",
+          "METADATA_KEY_DISPLAY_SUBTITLE",
+          "METADATA_KEY_DISC_NUMBER"
+        ]
+      },
+      "android/support/transition/Fade": {
+        "androidx/transition/Fade": [
+          "PROPNAME_TRANSITION_ALPHA",
+          "IN",
+          "LOG_TAG",
+          "OUT"
+        ]
+      },
+      "android/support/v17/leanback/R$layout": {
+        "androidx/leanback/R$layout": [
+          "lb_rows_fragment",
+          "lb_video_surface",
+          "lb_headers_fragment",
+          "lb_image_card_view_themed_badge_left",
+          "lb_search_orb",
+          "lb_details_fragment",
+          "lb_guidedstep_fragment",
+          "lb_image_card_view",
+          "lb_details_overview",
+          "lb_playback_controls",
+          "lb_guidedactions_datepicker_item",
+          "lb_image_card_view_themed_title",
+          "lb_picker_column",
+          "lb_playback_transport_controls_row",
+          "lb_image_card_view_themed_badge_right",
+          "lb_row_media_item",
+          "lb_row_container",
+          "lb_search_bar",
+          "lb_shadow",
+          "lb_error_fragment",
+          "lb_guidedbuttonactions",
+          "lb_browse_fragment",
+          "lb_guidedactions_item",
+          "lb_browse_title",
+          "lb_list_row",
+          "lb_section_header",
+          "lb_image_card_view_themed_content",
+          "lb_action_1_line",
+          "lb_media_list_header",
+          "lb_media_item_number_view_flipper",
+          "lb_vertical_grid_fragment",
+          "lb_divider",
+          "lb_details_description",
+          "lb_playback_now_playing_bars",
+          "lb_row_media_item_action",
+          "lb_fullwidth_details_overview",
+          "lb_picker",
+          "lb_guidedstep_background",
+          "lb_guidance",
+          "lb_picker_item",
+          "lb_speech_orb",
+          "lb_vertical_grid",
+          "lb_control_button_primary",
+          "lb_search_fragment",
+          "lb_playback_fragment",
+          "lb_picker_separator",
+          "lb_onboarding_fragment",
+          "lb_playback_controls_row",
+          "lb_row_header",
+          "lb_list_row_hovercard",
+          "lb_control_bar",
+          "lb_header",
+          "lb_guidedactions",
+          "lb_action_2_lines",
+          "lb_control_button_secondary",
+          "lb_title_view",
+          "lb_fullwidth_details_overview_logo"
+        ]
+      },
+      "android/support/coreui/BuildConfig": {
+        "androidx/coreui/BuildConfig": [
+          "APPLICATION_ID",
+          "DEBUG",
+          "FLAVOR",
+          "VERSION_CODE",
+          "VERSION_NAME",
+          "BUILD_TYPE"
+        ]
+      },
+      "android/support/v4/app/NotificationCompat": {
+        "androidx/app/NotificationCompat": [
+          "EXTRA_BACKGROUND_IMAGE_URI",
+          "FLAG_FOREGROUND_SERVICE",
+          "CATEGORY_SYSTEM",
+          "EXTRA_REMOTE_INPUT_HISTORY",
+          "EXTRA_LARGE_ICON",
+          "EXTRA_SHOW_WHEN",
+          "EXTRA_COMPACT_ACTIONS",
+          "CATEGORY_ERROR",
+          "GROUP_ALERT_CHILDREN",
+          "BADGE_ICON_LARGE",
+          "PRIORITY_DEFAULT",
+          "EXTRA_PEOPLE",
+          "STREAM_DEFAULT",
+          "VISIBILITY_PRIVATE",
+          "FLAG_ONLY_ALERT_ONCE",
+          "CATEGORY_EMAIL",
+          "DEFAULT_VIBRATE",
+          "CATEGORY_PROGRESS",
+          "FLAG_ONGOING_EVENT",
+          "EXTRA_PROGRESS_MAX",
+          "EXTRA_TITLE",
+          "CATEGORY_PROMO",
+          "EXTRA_SELF_DISPLAY_NAME",
+          "VISIBILITY_SECRET",
+          "PRIORITY_LOW",
+          "EXTRA_LARGE_ICON_BIG",
+          "CATEGORY_TRANSPORT",
+          "DEFAULT_SOUND",
+          "CATEGORY_CALL",
+          "EXTRA_SMALL_ICON",
+          "CATEGORY_RECOMMENDATION",
+          "EXTRA_PROGRESS",
+          "PRIORITY_HIGH",
+          "EXTRA_CONVERSATION_TITLE",
+          "GROUP_ALERT_SUMMARY",
+          "EXTRA_INFO_TEXT",
+          "BADGE_ICON_NONE",
+          "EXTRA_BIG_TEXT",
+          "EXTRA_TEXT_LINES",
+          "EXTRA_PICTURE",
+          "PRIORITY_MIN",
+          "FLAG_HIGH_PRIORITY",
+          "COLOR_DEFAULT",
+          "PRIORITY_MAX",
+          "EXTRA_TEMPLATE",
+          "FLAG_LOCAL_ONLY",
+          "CATEGORY_ALARM",
+          "EXTRA_SUMMARY_TEXT",
+          "BADGE_ICON_SMALL",
+          "DEFAULT_ALL",
+          "CATEGORY_STATUS",
+          "CATEGORY_EVENT",
+          "GROUP_ALERT_ALL",
+          "EXTRA_SHOW_CHRONOMETER",
+          "DEFAULT_LIGHTS",
+          "VISIBILITY_PUBLIC",
+          "CATEGORY_SOCIAL",
+          "CATEGORY_MESSAGE",
+          "FLAG_INSISTENT",
+          "CATEGORY_REMINDER",
+          "EXTRA_AUDIO_CONTENTS_URI",
+          "CATEGORY_SERVICE",
+          "EXTRA_TITLE_BIG",
+          "FLAG_AUTO_CANCEL",
+          "EXTRA_MEDIA_SESSION",
+          "EXTRA_MESSAGES",
+          "EXTRA_TEXT",
+          "FLAG_SHOW_LIGHTS",
+          "FLAG_GROUP_SUMMARY",
+          "FLAG_NO_CLEAR",
+          "EXTRA_SUB_TEXT",
+          "EXTRA_PROGRESS_INDETERMINATE"
+        ]
+      },
+      "android/support/content/BuildConfig": {
+        "androidx/content/BuildConfig": [
+          "FLAVOR",
+          "APPLICATION_ID",
+          "DEBUG",
+          "VERSION_NAME",
+          "BUILD_TYPE",
+          "VERSION_CODE"
+        ]
+      },
+      "android/support/animation/AnimationHandler": {
+        "androidx/animation/AnimationHandler": [
+          "sAnimatorHandler",
+          "FRAME_DELAY_MS"
+        ]
+      },
+      "android/support/media/ExifInterface": {
+        "androidx/media/ExifInterface": [
+          "FLAG_FLASH_RED_EYE_SUPPORTED",
+          "sTagSetForCompatibility",
+          "TAG_CUSTOM_RENDERED",
+          "TAG_SENSITIVITY_TYPE",
+          "GAIN_CONTROL_HIGH_GAIN_UP",
+          "TAG_FOCAL_PLANE_Y_RESOLUTION",
+          "LIGHT_SOURCE_OTHER",
+          "TAG_GPS_TIMESTAMP",
+          "TAG_COMPRESSION",
+          "IFD_INTEROPERABILITY_TAGS",
+          "IFD_FORMAT_DOUBLE",
+          "TAG_PIXEL_Y_DIMENSION",
+          "TAG_GPS_IMG_DIRECTION_REF",
+          "JPEG_INTERCHANGE_FORMAT_LENGTH_TAG",
+          "FLIPPED_ROTATION_ORDER",
+          "LIGHT_SOURCE_WARM_WHITE_FLUORESCENT",
+          "LIGHT_SOURCE_UNKNOWN",
+          "WHITE_BALANCE_MANUAL",
+          "TAG_SUBSEC_TIME",
+          "FILE_SOURCE_DSC",
+          "IFD_FORMAT_SSHORT",
+          "MARKER",
+          "LONGITUDE_WEST",
+          "IMAGE_TYPE_UNKNOWN",
+          "METERING_MODE_SPOT",
+          "IMAGE_TYPE_CR2",
+          "TAG_GPS_DOP",
+          "MAX_THUMBNAIL_SIZE",
+          "SENSOR_TYPE_TWO_CHIP",
+          "TAG_GPS_TRACK",
+          "SENSITIVITY_TYPE_REI_AND_ISO",
+          "SENSOR_TYPE_ONE_CHIP",
+          "GPS_MEASUREMENT_NO_DIFFERENTIAL",
+          "ORIENTATION_ROTATE_90",
+          "TAG_WHITE_POINT",
+          "PEF_SIGNATURE",
+          "TAG_GPS_IMG_DIRECTION",
+          "ORIGINAL_RESOLUTION_IMAGE",
+          "METERING_MODE_CENTER_WEIGHT_AVERAGE",
+          "TAG_ORF_CAMERA_SETTINGS_IFD_POINTER",
+          "TAG_ISO_SPEED",
+          "IMAGE_TYPE_DNG",
+          "GAIN_CONTROL_NONE",
+          "LONGITUDE_EAST",
+          "IFD_TYPE_ORF_MAKER_NOTE",
+          "TAG_DEVICE_SETTING_DESCRIPTION",
+          "TAG_BRIGHTNESS_VALUE",
+          "TAG_GPS_MAP_DATUM",
+          "METERING_MODE_MULTI_SPOT",
+          "TAG_RW2_ISO",
+          "TAG_ORF_PREVIEW_IMAGE_START",
+          "LIGHT_SOURCE_FLUORESCENT",
+          "COLOR_SPACE_S_RGB",
+          "IMAGE_TYPE_SRW",
+          "FLAG_FLASH_RETURN_LIGHT_DETECTED",
+          "SUBJECT_DISTANCE_RANGE_DISTANT_VIEW",
+          "TAG_ISO_SPEED_LATITUDE_YYY",
+          "RAF_INFO_SIZE",
+          "TAG",
+          "ORIENTATION_NORMAL",
+          "TAG_SUB_IFD_POINTER",
+          "LIGHT_SOURCE_TUNGSTEN",
+          "RESOLUTION_UNIT_CENTIMETERS",
+          "ALTITUDE_ABOVE_SEA_LEVEL",
+          "BYTE_ALIGN_MM",
+          "METERING_MODE_OTHER",
+          "TAG_EXIF_IFD_POINTER",
+          "BYTE_ALIGN_II",
+          "TAG_GPS_TRACK_REF",
+          "SHARPNESS_NORMAL",
+          "FILE_SOURCE_OTHER",
+          "TAG_OECF",
+          "SENSOR_TYPE_COLOR_SEQUENTIAL",
+          "sFormatter",
+          "FLAG_FLASH_MODE_COMPULSORY_FIRING",
+          "GPS_SPEED_KNOTS",
+          "DATA_DEFLATE_ZIP",
+          "TAG_DNG_VERSION",
+          "IMAGE_TYPE_ARW",
+          "TAG_RELATED_SOUND_FILE",
+          "TAG_BODY_SERIAL_NUMBER",
+          "EXPOSURE_PROGRAM_NOT_DEFINED",
+          "SIGNATURE_CHECK_SIZE",
+          "JPEG_INTERCHANGE_FORMAT_TAG",
+          "TAG_RW2_SENSOR_RIGHT_BORDER",
+          "TAG_CAMARA_OWNER_NAME",
+          "TAG_SPATIAL_FREQUENCY_RESPONSE",
+          "COLOR_SPACE_UNCALIBRATED",
+          "EXPOSURE_PROGRAM_NORMAL",
+          "EXPOSURE_PROGRAM_SHUTTER_PRIORITY",
+          "TAG_THUMBNAIL_LENGTH",
+          "TAG_TRANSFER_FUNCTION",
+          "IMAGE_TYPE_RW2",
+          "TAG_RW2_SENSOR_LEFT_BORDER",
+          "TAG_CFA_PATTERN",
+          "TAG_THUMBNAIL_IMAGE_LENGTH",
+          "TAG_GPS_H_POSITIONING_ERROR",
+          "sGpsTimestampPattern",
+          "PEF_MAKER_NOTE_SKIP_SIZE",
+          "IFD_FORMAT_STRING",
+          "TAG_SUBJECT_DISTANCE",
+          "TAG_GPS_SPEED_REF",
+          "SENSITIVITY_TYPE_SOS_AND_REI_AND_ISO",
+          "TAG_ORIENTATION",
+          "TAG_PHOTOMETRIC_INTERPRETATION",
+          "LIGHT_SOURCE_DAYLIGHT",
+          "TAG_MODEL",
+          "IFD_TYPE_GPS",
+          "LIGHT_SOURCE_SHADE",
+          "Y_CB_CR_POSITIONING_CO_SITED",
+          "TAG_RAF_IMAGE_SIZE",
+          "IFD_FORMAT_BYTES_PER_FORMAT",
+          "SENSITIVITY_TYPE_SOS_AND_REI",
+          "TAG_Y_RESOLUTION",
+          "TAG_GPS_DIFFERENTIAL",
+          "EXPOSURE_MODE_AUTO",
+          "MARKER_SOS",
+          "MARKER_SOI",
+          "IFD_TYPE_PREVIEW",
+          "IMAGE_TYPE_RAF",
+          "IFD_FORMAT_SBYTE",
+          "ORIENTATION_FLIP_VERTICAL",
+          "TAG_LENS_SERIAL_NUMBER",
+          "TAG_FLASHPIX_VERSION",
+          "EXIF_ASCII_PREFIX",
+          "IFD_TYPE_ORF_IMAGE_PROCESSING",
+          "TAG_ORF_IMAGE_PROCESSING_IFD_POINTER",
+          "DATA_LOSSY_JPEG",
+          "TAG_GPS_SPEED",
+          "TAG_USER_COMMENT",
+          "TAG_SUBSEC_TIME_ORIGINAL",
+          "TAG_THUMBNAIL_IMAGE_WIDTH",
+          "IFD_FORMAT_UNDEFINED",
+          "RW2_SIGNATURE",
+          "ORF_MAKER_NOTE_HEADER_2",
+          "ORF_MAKER_NOTE_HEADER_1",
+          "TAG_WHITE_BALANCE",
+          "TAG_ISO_SPEED_LATITUDE_ZZZ",
+          "TAG_PHOTOGRAPHIC_SENSITIVITY",
+          "SCENE_CAPTURE_TYPE_LANDSCAPE",
+          "MARKER_COM",
+          "TAG_HAS_THUMBNAIL",
+          "TAG_EXPOSURE_TIME",
+          "TAG_GPS_SATELLITES",
+          "TAG_COLOR_SPACE",
+          "TAG_GPS_LATITUDE_REF",
+          "IMAGE_TYPE_JPEG",
+          "START_CODE",
+          "TAG_APERTURE_VALUE",
+          "TAG_BITS_PER_SAMPLE",
+          "TAG_DIGITAL_ZOOM_RATIO",
+          "METERING_MODE_PATTERN",
+          "TAG_SHUTTER_SPEED_VALUE",
+          "DATA_PACK_BITS_COMPRESSED",
+          "sExifPointerTagMap",
+          "EXPOSURE_PROGRAM_APERTURE_PRIORITY",
+          "TAG_SAMPLES_PER_PIXEL",
+          "TAG_FILE_SOURCE",
+          "LIGHT_SOURCE_DAY_WHITE_FLUORESCENT",
+          "IFD_EXIF_TAGS",
+          "TAG_GPS_INFO_IFD_POINTER",
+          "TAG_GAMMA",
+          "IMAGE_TYPE_ORF",
+          "TAG_GAIN_CONTROL",
+          "IFD_TYPE_EXIF",
+          "TAG_SUBJECT_AREA",
+          "FORMAT_CHUNKY",
+          "ASCII",
+          "ORF_IMAGE_PROCESSING_TAGS",
+          "TAG_ORF_THUMBNAIL_IMAGE",
+          "IMAGE_TYPE_PEF",
+          "IFD_TYPE_ORF_CAMERA_SETTINGS",
+          "EXPOSURE_MODE_MANUAL",
+          "TAG_GPS_DATESTAMP",
+          "IFD_FORMAT_SRATIONAL",
+          "TAG_SUBJECT_LOCATION",
+          "FILE_SOURCE_REFLEX_SCANNER",
+          "TAG_LENS_SPECIFICATION",
+          "LIGHT_SOURCE_DAYLIGHT_FLUORESCENT",
+          "TAG_COMPONENTS_CONFIGURATION",
+          "TAG_REFERENCE_BLACK_WHITE",
+          "IMAGE_TYPE_NEF",
+          "GAIN_CONTROL_HIGH_GAIN_DOWN",
+          "LIGHT_SOURCE_ISO_STUDIO_TUNGSTEN",
+          "ORIENTATION_TRANSVERSE",
+          "IFD_FORMAT_URATIONAL",
+          "EXPOSURE_PROGRAM_LANDSCAPE_MODE",
+          "IMAGE_TYPE_NRW",
+          "TAG_FLASH",
+          "RAF_SIGNATURE",
+          "EXPOSURE_PROGRAM_ACTION",
+          "TAG_PLANAR_CONFIGURATION",
+          "LIGHT_SOURCE_CLOUDY_WEATHER",
+          "FLAG_FLASH_FIRED",
+          "EXPOSURE_MODE_AUTO_BRACKET",
+          "ORIENTATION_TRANSPOSE",
+          "REDUCED_RESOLUTION_IMAGE",
+          "WHITE_BALANCE_AUTO",
+          "TAG_FOCAL_PLANE_RESOLUTION_UNIT",
+          "PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO",
+          "DATA_UNCOMPRESSED",
+          "MARKER_EOI",
+          "TAG_SUBSEC_TIME_DIGITIZED",
+          "TAG_THUMBNAIL_DATA",
+          "TAG_GPS_DEST_BEARING_REF",
+          "EXPOSURE_PROGRAM_PORTRAIT_MODE",
+          "TAG_GPS_ALTITUDE",
+          "DATA_JPEG",
+          "TAG_GPS_DEST_LATITUDE_REF",
+          "ALTITUDE_BELOW_SEA_LEVEL",
+          "TAG_F_NUMBER",
+          "SUBJECT_DISTANCE_RANGE_UNKNOWN",
+          "TAG_SPECTRAL_SENSITIVITY",
+          "SCENE_CAPTURE_TYPE_NIGHT",
+          "TAG_PIXEL_X_DIMENSION",
+          "TAG_DEFAULT_CROP_SIZE",
+          "LIGHT_SOURCE_D75",
+          "LIGHT_SOURCE_D65",
+          "GPS_MEASUREMENT_INTERRUPTED",
+          "TAG_GPS_DEST_DISTANCE",
+          "LIGHT_SOURCE_D50",
+          "LIGHT_SOURCE_D55",
+          "ORIENTATION_ROTATE_180",
+          "SATURATION_NORMAL",
+          "TAG_THUMBNAIL_OFFSET",
+          "RESOLUTION_UNIT_INCHES",
+          "ORF_CAMERA_SETTINGS_TAGS",
+          "IFD_FORMAT_BYTE",
+          "TAG_LENS_MAKE",
+          "JPEG_SIGNATURE",
+          "EXPOSURE_PROGRAM_MANUAL",
+          "EXPOSURE_PROGRAM_CREATIVE",
+          "SUBJECT_DISTANCE_RANGE_CLOSE_VIEW",
+          "TAG_MAX_APERTURE_VALUE",
+          "DEBUG",
+          "METERING_MODE_AVERAGE",
+          "RENDERED_PROCESS_NORMAL",
+          "LIGHT_SOURCE_STANDARD_LIGHT_B",
+          "LIGHT_SOURCE_STANDARD_LIGHT_C",
+          "LIGHT_SOURCE_STANDARD_LIGHT_A",
+          "TAG_ISO_SPEED_RATINGS",
+          "TAG_STANDARD_OUTPUT_SENSITIVITY",
+          "PHOTOMETRIC_INTERPRETATION_YCBCR",
+          "TAG_ARTIST",
+          "IFD_TYPE_THUMBNAIL",
+          "PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO",
+          "FLAG_FLASH_RETURN_LIGHT_NOT_DETECTED",
+          "TAG_FOCAL_PLANE_X_RESOLUTION",
+          "TAG_LIGHT_SOURCE",
+          "MARKER_SOF1",
+          "MARKER_SOF2",
+          "MARKER_SOF0",
+          "MARKER_SOF9",
+          "MARKER_SOF7",
+          "MARKER_SOF5",
+          "MARKER_SOF6",
+          "MARKER_SOF3",
+          "sExifTagMapsForWriting",
+          "SHARPNESS_SOFT",
+          "TAG_SCENE_TYPE",
+          "TAG_GPS_DEST_LONGITUDE",
+          "sNonZeroTimePattern",
+          "GPS_DIRECTION_MAGNETIC",
+          "EXIF_TAGS",
+          "TAG_GPS_AREA_INFORMATION",
+          "FLAG_FLASH_MODE_COMPULSORY_SUPPRESSION",
+          "IFD_THUMBNAIL_TAGS",
+          "IFD_FORMAT_ULONG",
+          "TAG_INTEROPERABILITY_IFD_POINTER",
+          "TAG_SUBFILE_TYPE",
+          "TAG_RW2_SENSOR_TOP_BORDER",
+          "TAG_INTEROPERABILITY_INDEX",
+          "GPS_MEASUREMENT_3D",
+          "GPS_DIRECTION_TRUE",
+          "GPS_MEASUREMENT_2D",
+          "sExifTagMapsForReading",
+          "TAG_JPEG_INTERCHANGE_FORMAT",
+          "LATITUDE_SOUTH",
+          "PHOTOMETRIC_INTERPRETATION_RGB",
+          "BITS_PER_SAMPLE_RGB",
+          "CONTRAST_HARD",
+          "SHARPNESS_HARD",
+          "LATITUDE_NORTH",
+          "TAG_CONTRAST",
+          "ORIENTATION_FLIP_HORIZONTAL",
+          "TAG_GPS_LONGITUDE",
+          "TAG_COMPRESSED_BITS_PER_PIXEL",
+          "TAG_METERING_MODE",
+          "BITS_PER_SAMPLE_GREYSCALE_1",
+          "BITS_PER_SAMPLE_GREYSCALE_2",
+          "TAG_ROWS_PER_STRIP",
+          "GPS_DISTANCE_MILES",
+          "TAG_MAKER_NOTE",
+          "SUBJECT_DISTANCE_RANGE_MACRO",
+          "GAIN_CONTROL_LOW_GAIN_DOWN",
+          "SATURATION_HIGH",
+          "SCENE_TYPE_DIRECTLY_PHOTOGRAPHED",
+          "LIGHT_SOURCE_WHITE_FLUORESCENT",
+          "TAG_SHARPNESS",
+          "GPS_DISTANCE_KILOMETERS",
+          "TAG_GPS_LATITUDE",
+          "TAG_RW2_SENSOR_BOTTOM_BORDER",
+          "WHITEBALANCE_AUTO",
+          "TAG_SCENE_CAPTURE_TYPE",
+          "TAG_STRIP_BYTE_COUNTS",
+          "TAG_GPS_LONGITUDE_REF",
+          "GPS_MEASUREMENT_IN_PROGRESS",
+          "ORIENTATION_UNDEFINED",
+          "RAF_OFFSET_TO_JPEG_IMAGE_OFFSET",
+          "TAG_SUBJECT_DISTANCE_RANGE",
+          "ROTATION_ORDER",
+          "TAG_GPS_STATUS",
+          "ORF_MAKER_NOTE_HEADER_1_SIZE",
+          "TAG_GPS_DEST_BEARING",
+          "TAG_Y_CB_CR_COEFFICIENTS",
+          "TAG_MAKE",
+          "IFD_TYPE_PEF",
+          "TAG_RESOLUTION_UNIT",
+          "TAG_IMAGE_DESCRIPTION",
+          "SENSOR_TYPE_NOT_DEFINED",
+          "ORF_MAKER_NOTE_HEADER_2_SIZE",
+          "IFD_FORMAT_IFD",
+          "TAG_SATURATION",
+          "TAG_FOCAL_LENGTH_IN_35MM_FILM",
+          "TAG_FOCAL_LENGTH",
+          "SENSITIVITY_TYPE_SOS_AND_ISO",
+          "TAG_EXIF_VERSION",
+          "TAG_ORF_PREVIEW_IMAGE_LENGTH",
+          "GPS_MEASUREMENT_DIFFERENTIAL_CORRECTED",
+          "IFD_FORMAT_SINGLE",
+          "CONTRAST_NORMAL",
+          "TAG_PRIMARY_CHROMATICITIES",
+          "TAG_LENS_MODEL",
+          "TAG_IMAGE_LENGTH",
+          "TAG_RW2_JPG_FROM_RAW",
+          "SATURATION_LOW",
+          "TAG_SOFTWARE",
+          "IFD_TIFF_TAGS",
+          "SENSITIVITY_TYPE_ISO_SPEED",
+          "TAG_GPS_PROCESSING_METHOD",
+          "TAG_X_RESOLUTION",
+          "LIGHT_SOURCE_COOL_WHITE_FLUORESCENT",
+          "DATA_HUFFMAN_COMPRESSED",
+          "SENSOR_TYPE_THREE_CHIP",
+          "TAG_IMAGE_WIDTH",
+          "METERING_MODE_PARTIAL",
+          "SENSOR_TYPE_COLOR_SEQUENTIAL_LINEAR",
+          "LIGHT_SOURCE_FINE_WEATHER",
+          "TAG_EXPOSURE_INDEX",
+          "TAG_STRIP_OFFSETS",
+          "TAG_EXPOSURE_PROGRAM",
+          "TAG_IMAGE_UNIQUE_ID",
+          "IFD_FORMAT_USHORT",
+          "TAG_GPS_VERSION_ID",
+          "CONTRAST_SOFT",
+          "TAG_DATETIME_DIGITIZED",
+          "EXIF_POINTER_TAGS",
+          "FLAG_FLASH_NO_FLASH_FUNCTION",
+          "IFD_FORMAT_NAMES",
+          "ORF_MAKER_NOTE_TAGS",
+          "TAG_EXPOSURE_BIAS_VALUE",
+          "TAG_GPS_DEST_LONGITUDE_REF",
+          "IFD_OFFSET",
+          "ORF_SIGNATURE_1",
+          "ORF_SIGNATURE_2",
+          "METERING_MODE_UNKNOWN",
+          "GPS_SPEED_KILOMETERS_PER_HOUR",
+          "FORMAT_PLANAR",
+          "MARKER_SOF10",
+          "MARKER_SOF11",
+          "MARKER_SOF13",
+          "MARKER_SOF14",
+          "MARKER_SOF15",
+          "IFD_FORMAT_SLONG",
+          "TAG_COPYRIGHT",
+          "TAG_GPS_DEST_DISTANCE_REF",
+          "TAG_GPS_MEASURE_MODE",
+          "Y_CB_CR_POSITIONING_CENTERED",
+          "TAG_Y_CB_CR_POSITIONING",
+          "GPS_DISTANCE_NAUTICAL_MILES",
+          "WHITEBALANCE_MANUAL",
+          "GAIN_CONTROL_LOW_GAIN_UP",
+          "IDENTIFIER_EXIF_APP1",
+          "DATA_JPEG_COMPRESSED",
+          "SCENE_CAPTURE_TYPE_PORTRAIT",
+          "GPS_SPEED_MILES_PER_HOUR",
+          "IFD_TYPE_PRIMARY",
+          "TAG_Y_CB_CR_SUB_SAMPLING",
+          "TAG_GPS_ALTITUDE_REF",
+          "SENSITIVITY_TYPE_SOS",
+          "TAG_SENSING_METHOD",
+          "TAG_ORF_ASPECT_FRAME",
+          "TAG_NEW_SUBFILE_TYPE",
+          "TAG_EXPOSURE_MODE",
+          "ORIENTATION_ROTATE_270",
+          "IFD_GPS_TAGS",
+          "RAF_JPEG_LENGTH_VALUE_SIZE",
+          "TAG_FLASH_ENERGY",
+          "TAG_RECOMMENDED_EXPOSURE_INDEX",
+          "SENSOR_TYPE_TRILINEAR",
+          "SCENE_CAPTURE_TYPE_STANDARD",
+          "PEF_TAGS",
+          "SENSITIVITY_TYPE_UNKNOWN",
+          "TAG_DATETIME",
+          "TAG_JPEG_INTERCHANGE_FORMAT_LENGTH",
+          "LIGHT_SOURCE_FLASH",
+          "FLAG_FLASH_MODE_AUTO",
+          "MARKER_APP1",
+          "SENSITIVITY_TYPE_REI",
+          "TAG_DATETIME_ORIGINAL",
+          "TAG_GPS_DEST_LATITUDE",
+          "FILE_SOURCE_TRANSPARENT_SCANNER",
+          "RENDERED_PROCESS_CUSTOM",
+          "IFD_TYPE_INTEROPERABILITY"
+        ]
+      },
+      "android/support/v4/widget/ViewDragHelper": {
+        "androidx/widget/ViewDragHelper": [
+          "DIRECTION_HORIZONTAL",
+          "TAG",
+          "INVALID_POINTER",
+          "EDGE_SIZE",
+          "EDGE_LEFT",
+          "STATE_IDLE",
+          "STATE_DRAGGING",
+          "EDGE_ALL",
+          "DIRECTION_ALL",
+          "EDGE_RIGHT",
+          "sInterpolator",
+          "EDGE_TOP",
+          "STATE_SETTLING",
+          "BASE_SETTLE_DURATION",
+          "EDGE_BOTTOM",
+          "MAX_SETTLE_DURATION",
+          "DIRECTION_VERTICAL"
+        ]
+      },
+      "android/support/design/widget/NavigationView": {
+        "androidx/design/widget/NavigationView": [
+          "EMPTY_STATE_SET",
+          "CHECKED_STATE_SET",
+          "PRESENTER_NAVIGATION_VIEW_ID",
+          "DISABLED_STATE_SET"
+        ]
+      },
+      "android/support/customtabs/ICustomTabsCallback$Stub": {
+        "androidx/browser/customtabs/ICustomTabsCallback$Stub": [
+          "TRANSACTION_onMessageChannelReady",
+          "TRANSACTION_onPostMessage",
+          "TRANSACTION_onNavigationEvent",
+          "TRANSACTION_onRelationshipValidationResult",
+          "TRANSACTION_extraCallback",
+          "DESCRIPTOR"
+        ]
+      },
+      "android/support/v7/media/MediaRouterJellybeanMr1$ActiveScanWorkaround": {
+        "androidx/media/MediaRouterJellybeanMr1$ActiveScanWorkaround": [
+          "WIFI_DISPLAY_SCAN_INTERVAL"
+        ]
+      },
+      "android/support/v17/leanback/widget/GridLayoutManager$LayoutParams": {
+        "androidx/leanback/widget/GridLayoutManager$LayoutParams": [
+          "bottomMargin",
+          "leftMargin",
+          "topMargin",
+          "width",
+          "height",
+          "rightMargin"
+        ]
+      },
+      "android/support/v7/cardview/R$styleable": {
+        "androidx/cardview/R$styleable": [
+          "CardView_contentPaddingRight",
+          "CardView",
+          "CardView_android_minWidth",
+          "CardView_cardCornerRadius",
+          "CardView_contentPaddingLeft",
+          "CardView_cardBackgroundColor",
+          "CardView_contentPadding",
+          "CardView_contentPaddingBottom",
+          "CardView_cardElevation",
+          "CardView_cardUseCompatPadding",
+          "CardView_contentPaddingTop",
+          "CardView_android_minHeight",
+          "CardView_cardPreventCornerOverlap",
+          "CardView_cardMaxElevation"
+        ]
+      },
+      "android/support/v17/leanback/widget/PlaybackControlsRow$ThumbsAction": {
+        "androidx/leanback/widget/PlaybackControlsRow$ThumbsAction": [
+          "INDEX_OUTLINE",
+          "OUTLINE",
+          "SOLID",
+          "INDEX_SOLID"
+        ]
+      },
+      "android/support/v4/media/session/IMediaSession$Stub": {
+        "androidx/media/session/IMediaSession$Stub": [
+          "TRANSACTION_playFromUri",
+          "TRANSACTION_playFromSearch",
+          "TRANSACTION_getRepeatMode",
+          "TRANSACTION_previous",
+          "TRANSACTION_getPlaybackState",
+          "TRANSACTION_adjustVolume",
+          "TRANSACTION_setShuffleMode",
+          "TRANSACTION_setVolumeTo",
+          "TRANSACTION_prepareFromSearch",
+          "TRANSACTION_removeQueueItemAt",
+          "TRANSACTION_playFromMediaId",
+          "TRANSACTION_getPackageName",
+          "TRANSACTION_sendCommand",
+          "TRANSACTION_isTransportControlEnabled",
+          "TRANSACTION_pause",
+          "DESCRIPTOR",
+          "TRANSACTION_rewind",
+          "TRANSACTION_getRatingType",
+          "TRANSACTION_prepareFromUri",
+          "TRANSACTION_getMetadata",
+          "TRANSACTION_addQueueItemAt",
+          "TRANSACTION_getQueueTitle",
+          "TRANSACTION_getLaunchPendingIntent",
+          "TRANSACTION_getQueue",
+          "TRANSACTION_registerCallbackListener",
+          "TRANSACTION_getShuffleMode",
+          "TRANSACTION_seekTo",
+          "TRANSACTION_prepareFromMediaId",
+          "TRANSACTION_setShuffleModeEnabledRemoved",
+          "TRANSACTION_removeQueueItem",
+          "TRANSACTION_setRepeatMode",
+          "TRANSACTION_getFlags",
+          "TRANSACTION_prepare",
+          "TRANSACTION_isShuffleModeEnabledRemoved",
+          "TRANSACTION_addQueueItem",
+          "TRANSACTION_isCaptioningEnabled",
+          "TRANSACTION_setCaptioningEnabled",
+          "TRANSACTION_rateWithExtras",
+          "TRANSACTION_stop",
+          "TRANSACTION_getVolumeAttributes",
+          "TRANSACTION_next",
+          "TRANSACTION_skipToQueueItem",
+          "TRANSACTION_rate",
+          "TRANSACTION_unregisterCallbackListener",
+          "TRANSACTION_play",
+          "TRANSACTION_getTag",
+          "TRANSACTION_sendMediaButton",
+          "TRANSACTION_sendCustomAction",
+          "TRANSACTION_fastForward",
+          "TRANSACTION_getExtras"
+        ]
+      },
+      "android/support/v4/provider/FontsContractCompat$Columns": {
+        "androidx/provider/FontsContractCompat$Columns": [
+          "TTC_INDEX",
+          "FILE_ID",
+          "RESULT_CODE",
+          "WEIGHT",
+          "ITALIC",
+          "VARIATION_SETTINGS",
+          "RESULT_CODE_OK",
+          "RESULT_CODE_MALFORMED_QUERY",
+          "RESULT_CODE_FONT_UNAVAILABLE",
+          "RESULT_CODE_FONT_NOT_FOUND"
+        ]
+      },
+      "android/support/v4/media/MediaBrowserProtocol": {
+        "androidx/media/MediaBrowserProtocol": [
+          "DATA_CALLING_UID",
+          "EXTRA_MESSENGER_BINDER",
+          "DATA_ROOT_HINTS",
+          "CLIENT_MSG_UNREGISTER_CALLBACK_MESSENGER",
+          "EXTRA_SERVICE_VERSION",
+          "SERVICE_VERSION_1",
+          "DATA_OPTIONS",
+          "CLIENT_MSG_DISCONNECT",
+          "EXTRA_SESSION_BINDER",
+          "SERVICE_VERSION_CURRENT",
+          "CLIENT_VERSION_CURRENT",
+          "EXTRA_CLIENT_VERSION",
+          "CLIENT_VERSION_1",
+          "CLIENT_MSG_GET_MEDIA_ITEM",
+          "DATA_MEDIA_ITEM_LIST",
+          "DATA_MEDIA_SESSION_TOKEN",
+          "SERVICE_MSG_ON_CONNECT",
+          "DATA_RESULT_RECEIVER",
+          "CLIENT_MSG_SEND_CUSTOM_ACTION",
+          "CLIENT_MSG_CONNECT",
+          "SERVICE_MSG_ON_CONNECT_FAILED",
+          "SERVICE_MSG_ON_LOAD_CHILDREN",
+          "DATA_MEDIA_ITEM_ID",
+          "DATA_CALLBACK_TOKEN",
+          "DATA_SEARCH_QUERY",
+          "DATA_CUSTOM_ACTION_EXTRAS",
+          "CLIENT_MSG_SEARCH",
+          "DATA_SEARCH_EXTRAS",
+          "CLIENT_MSG_ADD_SUBSCRIPTION",
+          "DATA_CUSTOM_ACTION",
+          "CLIENT_MSG_REGISTER_CALLBACK_MESSENGER",
+          "DATA_PACKAGE_NAME",
+          "CLIENT_MSG_REMOVE_SUBSCRIPTION"
+        ]
+      },
+      "android/support/v17/leanback/R$color": {
+        "androidx/leanback/R$color": [
+          "lb_error_background_color_opaque",
+          "lb_playback_controls_background_dark",
+          "lb_default_brand_color_dark",
+          "lb_error_background_color_translucent",
+          "lb_background_protection",
+          "lb_speech_orb_recording",
+          "lb_default_brand_color",
+          "lb_page_indicator_dot",
+          "lb_search_bar_hint",
+          "lb_playback_progress_color_no_theme",
+          "lb_speech_orb_not_recording",
+          "lb_search_bar_text_speech_mode",
+          "lb_view_dim_mask_color",
+          "lb_playback_controls_background_light",
+          "lb_playback_media_row_highlight_color",
+          "lb_default_search_color",
+          "lb_playback_icon_highlight_no_theme",
+          "lb_search_bar_hint_speech_mode",
+          "lb_page_indicator_arrow_shadow",
+          "lb_speech_orb_not_recording_icon",
+          "lb_speech_orb_not_recording_pulsed",
+          "lb_search_bar_text",
+          "lb_page_indicator_arrow_background"
+        ]
+      },
+      "android/support/transition/ViewGroupUtilsApi18": {
+        "androidx/transition/ViewGroupUtilsApi18": [
+          "sSuppressLayoutMethod",
+          "TAG",
+          "sSuppressLayoutMethodFetched"
+        ]
+      },
+      "android/support/v4/text/TextDirectionHeuristicsCompat": {
+        "androidx/text/TextDirectionHeuristicsCompat": [
+          "STATE_UNKNOWN",
+          "RTL",
+          "LOCALE",
+          "ANYRTL_LTR",
+          "LTR",
+          "FIRSTSTRONG_RTL",
+          "FIRSTSTRONG_LTR",
+          "STATE_TRUE",
+          "STATE_FALSE"
+        ]
+      },
+      "android/support/v7/widget/ListViewCompat": {
+        "androidx/widget/ListViewCompat": [
+          "INVALID_POSITION",
+          "NO_POSITION",
+          "STATE_SET_NOTHING"
+        ]
+      },
+      "android/support/v17/leanback/app/GuidedStepSupportFragment": {
+        "androidx/leanback/app/GuidedStepSupportFragment": [
+          "SLIDE_FROM_BOTTOM",
+          "SLIDE_FROM_SIDE",
+          "EXTRA_BUTTON_ACTION_PREFIX",
+          "TAG_LEAN_BACK_ACTIONS_FRAGMENT",
+          "ENTRY_NAME_ENTRANCE",
+          "DEBUG",
+          "UI_STYLE_DEFAULT",
+          "UI_STYLE_REPLACE",
+          "IS_FRAMEWORK_FRAGMENT",
+          "UI_STYLE_ACTIVITY_ROOT",
+          "EXTRA_UI_STYLE",
+          "UI_STYLE_ENTRANCE",
+          "entranceTransitionType",
+          "TAG",
+          "ENTRY_NAME_REPLACE",
+          "EXTRA_ACTION_PREFIX"
+        ]
+      },
+      "android/support/v7/media/MediaRouter$GlobalMediaRouter$CallbackHandler": {
+        "androidx/media/MediaRouter$GlobalMediaRouter$CallbackHandler": [
+          "MSG_TYPE_MASK",
+          "MSG_PROVIDER_REMOVED",
+          "MSG_ROUTE_SELECTED",
+          "MSG_ROUTE_VOLUME_CHANGED",
+          "MSG_PROVIDER_ADDED",
+          "MSG_TYPE_ROUTE",
+          "MSG_ROUTE_REMOVED",
+          "MSG_ROUTE_UNSELECTED",
+          "MSG_TYPE_PROVIDER",
+          "MSG_PROVIDER_CHANGED",
+          "MSG_ROUTE_ADDED",
+          "MSG_ROUTE_PRESENTATION_DISPLAY_CHANGED",
+          "MSG_ROUTE_CHANGED"
+        ]
+      },
+      "android/support/v7/view/menu/ActionMenuItem": {
+        "androidx/view/menu/ActionMenuItem": [
+          "EXCLUSIVE",
+          "CHECKED",
+          "ENABLED",
+          "CHECKABLE",
+          "NO_ICON",
+          "HIDDEN"
+        ]
+      },
+      "android/support/v4/app/NotificationCompat$WearableExtender": {
+        "androidx/app/NotificationCompat$WearableExtender": [
+          "SCREEN_TIMEOUT_LONG",
+          "KEY_FLAGS",
+          "KEY_DISMISSAL_ID",
+          "SIZE_LARGE",
+          "KEY_GRAVITY",
+          "KEY_CONTENT_ICON",
+          "FLAG_START_SCROLL_BOTTOM",
+          "DEFAULT_FLAGS",
+          "DEFAULT_CONTENT_ICON_GRAVITY",
+          "FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE",
+          "UNSET_ACTION_INDEX",
+          "DEFAULT_GRAVITY",
+          "FLAG_HINT_SHOW_BACKGROUND_ONLY",
+          "SIZE_XSMALL",
+          "KEY_CUSTOM_CONTENT_HEIGHT",
+          "FLAG_HINT_AVOID_BACKGROUND_CLIPPING",
+          "SIZE_DEFAULT",
+          "KEY_BACKGROUND",
+          "FLAG_BIG_PICTURE_AMBIENT",
+          "SCREEN_TIMEOUT_SHORT",
+          "SIZE_FULL_SCREEN",
+          "KEY_ACTIONS",
+          "EXTRA_WEARABLE_EXTENSIONS",
+          "KEY_CONTENT_ACTION_INDEX",
+          "FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY",
+          "KEY_CONTENT_ICON_GRAVITY",
+          "KEY_DISPLAY_INTENT",
+          "SIZE_SMALL",
+          "KEY_CUSTOM_SIZE_PRESET",
+          "KEY_HINT_SCREEN_TIMEOUT",
+          "FLAG_HINT_HIDE_ICON",
+          "KEY_BRIDGE_TAG",
+          "SIZE_MEDIUM",
+          "KEY_PAGES"
+        ]
+      },
+      "android/support/design/R$styleable": {
+        "androidx/design/R$styleable": [
+          "TabLayout_tabSelectedTextColor",
+          "BottomSheetBehavior_Layout_behavior_peekHeight",
+          "TextInputLayout_counterOverflowTextAppearance",
+          "BottomNavigationView_itemTextColor",
+          "ScrimInsetsFrameLayout_insetForeground",
+          "TabItem_android_icon",
+          "CoordinatorLayout",
+          "TextInputLayout_counterTextAppearance",
+          "TabLayout_tabMode",
+          "TextInputLayout_hintAnimationEnabled",
+          "TextInputLayout_passwordToggleTint",
+          "NavigationView_android_fitsSystemWindows",
+          "AppBarLayout_android_background",
+          "BottomSheetBehavior_Layout",
+          "FloatingActionButton_fabSize",
+          "TextInputLayout_android_hint",
+          "FloatingActionButton_elevation",
+          "TabItem_android_text",
+          "CollapsingToolbarLayout_expandedTitleTextAppearance",
+          "TextInputLayout_hintTextAppearance",
+          "BottomNavigationView_elevation",
+          "BottomNavigationView_itemIconTint",
+          "CollapsingToolbarLayout_collapsedTitleTextAppearance",
+          "TabLayout_tabPaddingEnd",
+          "TextInputLayout",
+          "TextInputLayout_counterEnabled",
+          "TextInputLayout_passwordToggleContentDescription",
+          "NavigationView_itemBackground",
+          "CollapsingToolbarLayout_expandedTitleMarginBottom",
+          "AppBarLayout_android_keyboardNavigationCluster",
+          "FloatingActionButton_Behavior_Layout",
+          "NavigationView_itemTextAppearance",
+          "TabLayout_tabBackground",
+          "TabLayout_tabContentStart",
+          "ScrollingViewBehavior_Layout",
+          "TabItem_android_layout",
+          "TextInputLayout_hintEnabled",
+          "CollapsingToolbarLayout_scrimVisibleHeightTrigger",
+          "TabLayout_tabPaddingTop",
+          "CoordinatorLayout_Layout_layout_anchorGravity",
+          "CoordinatorLayout_statusBarBackground",
+          "NavigationView",
+          "CollapsingToolbarLayout_expandedTitleMarginEnd",
+          "CoordinatorLayout_Layout_layout_anchor",
+          "TabLayout_tabMinWidth",
+          "TabLayout_tabPaddingStart",
+          "TextInputLayout_errorEnabled",
+          "TabLayout_tabMaxWidth",
+          "TabLayout_tabTextColor",
+          "CollapsingToolbarLayout_collapsedTitleGravity",
+          "BottomNavigationView",
+          "TabLayout_tabTextAppearance",
+          "NavigationView_itemIconTint",
+          "TabLayout_tabIndicatorColor",
+          "TabLayout",
+          "TabLayout_tabIndicatorHeight",
+          "NavigationView_headerLayout",
+          "CoordinatorLayout_Layout",
+          "BottomNavigationView_menu",
+          "CollapsingToolbarLayout_Layout_layout_collapseParallaxMultiplier",
+          "NavigationView_menu",
+          "TextInputLayout_passwordToggleDrawable",
+          "ScrimInsetsFrameLayout",
+          "CoordinatorLayout_Layout_layout_insetEdge",
+          "CollapsingToolbarLayout_titleEnabled",
+          "ForegroundLinearLayout",
+          "TabLayout_tabGravity",
+          "CollapsingToolbarLayout_contentScrim",
+          "ForegroundLinearLayout_android_foreground",
+          "CollapsingToolbarLayout_expandedTitleGravity",
+          "TextInputLayout_errorTextAppearance",
+          "TabLayout_tabPaddingBottom",
+          "CollapsingToolbarLayout",
+          "AppBarLayout",
+          "FloatingActionButton_pressedTranslationZ",
+          "FloatingActionButton_Behavior_Layout_behavior_autoHide",
+          "FloatingActionButton_useCompatPadding",
+          "NavigationView_android_maxWidth",
+          "CollapsingToolbarLayout_title",
+          "CollapsingToolbarLayout_Layout",
+          "SnackbarLayout_maxActionInlineWidth",
+          "ForegroundLinearLayout_android_foregroundGravity",
+          "AppBarLayout_android_touchscreenBlocksFocus",
+          "ScrollingViewBehavior_Layout_behavior_overlapTop",
+          "BottomSheetBehavior_Layout_behavior_skipCollapsed",
+          "CollapsingToolbarLayout_expandedTitleMarginTop",
+          "BottomNavigationView_itemBackground",
+          "FloatingActionButton_rippleColor",
+          "NavigationView_android_background",
+          "SnackbarLayout_elevation",
+          "TextInputLayout_android_textColorHint",
+          "NavigationView_itemTextColor",
+          "BottomSheetBehavior_Layout_behavior_hideable",
+          "AppBarLayout_Layout_layout_scrollInterpolator",
+          "FloatingActionButton_backgroundTint",
+          "AppBarLayout_Layout_layout_scrollFlags",
+          "CollapsingToolbarLayout_Layout_layout_collapseMode",
+          "TabLayout_tabPadding",
+          "FloatingActionButton_backgroundTintMode",
+          "CoordinatorLayout_Layout_layout_behavior",
+          "CoordinatorLayout_Layout_android_layout_gravity",
+          "AppBarLayout_elevation",
+          "ForegroundLinearLayout_foregroundInsidePadding",
+          "TabItem",
+          "AppBarLayout_expanded",
+          "FloatingActionButton",
+          "TextInputLayout_passwordToggleTintMode",
+          "SnackbarLayout_android_maxWidth",
+          "TextInputLayout_passwordToggleEnabled",
+          "CollapsingToolbarLayout_expandedTitleMargin",
+          "NavigationView_elevation",
+          "FloatingActionButton_borderWidth",
+          "CoordinatorLayout_Layout_layout_keyline",
+          "SnackbarLayout",
+          "CollapsingToolbarLayout_toolbarId",
+          "CoordinatorLayout_keylines",
+          "CollapsingToolbarLayout_scrimAnimationDuration",
+          "TextInputLayout_counterMaxLength",
+          "CoordinatorLayout_Layout_layout_dodgeInsetEdges",
+          "AppBarLayout_Layout",
+          "CollapsingToolbarLayout_statusBarScrim",
+          "CollapsingToolbarLayout_expandedTitleMarginStart"
+        ]
+      },
+      "android/support/text/emoji/MetadataListReader": {
+        "androidx/text/emoji/MetadataListReader": [
+          "META_TABLE_NAME",
+          "EMJI_TAG_DEPRECATED",
+          "EMJI_TAG"
+        ]
+      },
+      "android/support/v4/widget/DrawerLayout": {
+        "androidx/widget/DrawerLayout": [
+          "TAG",
+          "DRAWER_ELEVATION",
+          "SET_DRAWER_SHADOW_FROM_ELEVATION",
+          "STATE_DRAGGING",
+          "MIN_FLING_VELOCITY",
+          "TOUCH_SLOP_SENSITIVITY",
+          "LOCK_MODE_UNDEFINED",
+          "MIN_DRAWER_MARGIN",
+          "STATE_IDLE",
+          "THEME_ATTRS",
+          "LOCK_MODE_LOCKED_OPEN",
+          "ALLOW_EDGE_LOCK",
+          "CAN_HIDE_DESCENDANTS",
+          "CHILDREN_DISALLOW_INTERCEPT",
+          "PEEK_DELAY",
+          "LOCK_MODE_UNLOCKED",
+          "DEFAULT_SCRIM_COLOR",
+          "STATE_SETTLING",
+          "LOCK_MODE_LOCKED_CLOSED",
+          "LAYOUT_ATTRS"
+        ]
+      },
+      "android/support/v4/content/ModernAsyncTask": {
+        "androidx/content/ModernAsyncTask": [
+          "MESSAGE_POST_RESULT",
+          "MESSAGE_POST_PROGRESS",
+          "sDefaultExecutor",
+          "KEEP_ALIVE",
+          "sThreadFactory",
+          "sPoolWorkQueue",
+          "THREAD_POOL_EXECUTOR",
+          "CORE_POOL_SIZE",
+          "MAXIMUM_POOL_SIZE",
+          "sHandler",
+          "LOG_TAG"
+        ]
+      },
+      "android/support/v7/media/MediaControlIntent": {
+        "androidx/media/MediaControlIntent": [
+          "EXTRA_SESSION_STATUS_UPDATE_RECEIVER",
+          "CATEGORY_REMOTE_PLAYBACK",
+          "ACTION_START_SESSION",
+          "ACTION_PLAY",
+          "ACTION_SEEK",
+          "ERROR_UNSUPPORTED_OPERATION",
+          "ERROR_INVALID_ITEM_ID",
+          "ACTION_ENQUEUE",
+          "ACTION_SEND_MESSAGE",
+          "EXTRA_ITEM_CONTENT_POSITION",
+          "ERROR_UNKNOWN",
+          "ACTION_STOP",
+          "CATEGORY_LIVE_VIDEO",
+          "EXTRA_ITEM_STATUS_UPDATE_RECEIVER",
+          "EXTRA_ITEM_METADATA",
+          "EXTRA_SESSION_ID",
+          "EXTRA_MESSAGE",
+          "EXTRA_ITEM_HTTP_HEADERS",
+          "CATEGORY_LIVE_AUDIO",
+          "ACTION_END_SESSION",
+          "EXTRA_MESSAGE_RECEIVER",
+          "ACTION_GET_SESSION_STATUS",
+          "ACTION_REMOVE",
+          "EXTRA_ITEM_STATUS",
+          "EXTRA_ERROR_CODE",
+          "ACTION_GET_STATUS",
+          "ERROR_INVALID_SESSION_ID",
+          "EXTRA_SESSION_STATUS",
+          "EXTRA_ITEM_ID",
+          "ACTION_RESUME",
+          "ACTION_PAUSE"
+        ]
+      },
+      "android/support/design/widget/CoordinatorLayout$LayoutParams": {
+        "androidx/design/widget/CoordinatorLayout$LayoutParams": [
+          "keyline",
+          "topMargin",
+          "leftMargin",
+          "anchorGravity",
+          "bottomMargin",
+          "rightMargin",
+          "height",
+          "gravity",
+          "dodgeInsetEdges",
+          "insetEdge"
+        ]
+      },
+      "android/support/design/internal/NavigationMenuPresenter$NormalViewHolder": {
+        "androidx/design/internal/NavigationMenuPresenter$NormalViewHolder": [
+          "itemView"
+        ]
+      },
+      "android/support/v7/recyclerview/R$styleable": {
+        "androidx/recyclerview/R$styleable": [
+          "RecyclerView_fastScrollHorizontalThumbDrawable",
+          "RecyclerView_fastScrollVerticalTrackDrawable",
+          "RecyclerView_android_descendantFocusability",
+          "RecyclerView_spanCount",
+          "RecyclerView_stackFromEnd",
+          "RecyclerView_reverseLayout",
+          "RecyclerView_fastScrollVerticalThumbDrawable",
+          "RecyclerView_layoutManager",
+          "RecyclerView_android_orientation",
+          "RecyclerView",
+          "RecyclerView_fastScrollEnabled",
+          "RecyclerView_fastScrollHorizontalTrackDrawable"
+        ]
+      },
+      "android/support/v4/app/ActionBarDrawerToggle": {
+        "androidx/app/ActionBarDrawerToggle": [
+          "TAG",
+          "ID_HOME",
+          "THEME_ATTRS",
+          "TOGGLE_DRAWABLE_OFFSET"
+        ]
+      },
+      "android/support/v7/widget/GridLayout$Axis": {
+        "androidx/widget/GridLayout$Axis": [
+          "backwardLinks",
+          "orderPreserved",
+          "trailingMargins",
+          "leadingMargins",
+          "locationsValid",
+          "groupBounds",
+          "hasWeights",
+          "groupBoundsValid",
+          "PENDING",
+          "arcsValid",
+          "trailingMarginsValid",
+          "arcs",
+          "locations",
+          "backwardLinksValid",
+          "leadingMarginsValid",
+          "maxIndex",
+          "horizontal",
+          "forwardLinksValid",
+          "definedCount",
+          "NEW",
+          "deltas",
+          "parentMax",
+          "parentMin",
+          "COMPLETE",
+          "hasWeightsValid",
+          "forwardLinks"
+        ]
+      },
+      "android/support/v4/app/BundleCompat$BundleCompatBaseImpl": {
+        "androidx/app/BundleCompat$BundleCompatBaseImpl": [
+          "sGetIBinderMethod",
+          "sGetIBinderMethodFetched",
+          "sPutIBinderMethodFetched",
+          "TAG",
+          "sPutIBinderMethod"
+        ]
+      },
+      "android/support/v7/app/AppCompatViewInflater": {
+        "androidx/app/AppCompatViewInflater": [
+          "sOnClickAttrs",
+          "sConstructorMap",
+          "sClassPrefixList",
+          "sConstructorSignature",
+          "LOG_TAG"
+        ]
+      },
+      "android/support/v17/leanback/widget/SearchBar": {
+        "androidx/leanback/widget/SearchBar": [
+          "FULL_RIGHT_VOLUME",
+          "DEBUG",
+          "DEFAULT_RATE",
+          "FULL_LEFT_VOLUME",
+          "DO_NOT_LOOP",
+          "DEFAULT_PRIORITY",
+          "TAG"
+        ]
+      },
+      "android/support/v7/appcompat/R$drawable": {
+        "androidx/appcompat/R$drawable": [
+          "abc_ic_ab_back_material",
+          "abc_text_select_handle_middle_mtrl_dark",
+          "abc_text_select_handle_left_mtrl_light",
+          "abc_textfield_activated_mtrl_alpha",
+          "abc_text_select_handle_left_mtrl_dark",
+          "abc_text_cursor_material",
+          "abc_text_select_handle_right_mtrl_light",
+          "abc_ratingbar_material",
+          "abc_ic_menu_share_mtrl_alpha",
+          "abc_ratingbar_small_material",
+          "abc_ic_menu_paste_mtrl_am_alpha",
+          "abc_textfield_search_default_mtrl_alpha",
+          "abc_menu_hardkey_panel_mtrl_mult",
+          "abc_text_select_handle_right_mtrl_dark",
+          "abc_dialog_material_background",
+          "abc_ratingbar_indicator_material",
+          "abc_btn_colored_material",
+          "abc_edit_text_material",
+          "abc_btn_borderless_material",
+          "abc_btn_check_material",
+          "abc_ab_share_pack_mtrl_alpha",
+          "abc_tab_indicator_material",
+          "abc_spinner_textfield_background_material",
+          "abc_ic_commit_search_api_mtrl_alpha",
+          "abc_textfield_default_mtrl_alpha",
+          "abc_switch_track_mtrl_alpha",
+          "abc_seekbar_thumb_material",
+          "abc_list_divider_mtrl_alpha",
+          "abc_spinner_mtrl_am_alpha",
+          "abc_btn_radio_material",
+          "abc_cab_background_top_material",
+          "abc_cab_background_top_mtrl_alpha",
+          "abc_text_select_handle_middle_mtrl_light",
+          "abc_vector_test",
+          "abc_btn_default_mtrl_shape",
+          "abc_switch_thumb_material",
+          "abc_seekbar_track_material",
+          "abc_popup_background_mtrl_mult",
+          "abc_seekbar_tick_mark_material",
+          "abc_ic_menu_copy_mtrl_am_alpha",
+          "abc_ic_menu_selectall_mtrl_alpha",
+          "abc_textfield_search_activated_mtrl_alpha",
+          "abc_cab_background_internal_bg",
+          "abc_ic_menu_cut_mtrl_alpha",
+          "abc_textfield_search_material"
+        ]
+      },
+      "android/support/v17/leanback/widget/GuidedActionsStylist": {
+        "androidx/leanback/widget/GuidedActionsStylist": [
+          "sGuidedActionItemAlignFacet",
+          "TAG",
+          "VIEW_TYPE_DEFAULT",
+          "VIEW_TYPE_DATE_PICKER"
+        ]
+      },
+      "android/support/v17/leanback/R$dimen": {
+        "androidx/leanback/R$dimen": [
+          "lb_details_v2_align_pos_for_description",
+          "lb_details_overview_image_margin_vertical",
+          "lb_search_browse_rows_align_top",
+          "lb_material_shadow_normal_z",
+          "lb_details_v2_left",
+          "lb_details_v2_description_margin_top",
+          "lb_playback_controls_child_margin_bigger",
+          "lb_details_v2_actions_height",
+          "lb_playback_transport_thumbs_height",
+          "lb_details_rows_align_top",
+          "lb_control_icon_width",
+          "lb_page_indicator_dot_radius",
+          "lb_playback_controls_z",
+          "lb_error_under_image_baseline_margin",
+          "lb_browse_rows_margin_start",
+          "lb_playback_transport_thumbs_margin",
+          "lb_playback_transport_hero_thumbs_width",
+          "lb_details_description_under_subtitle_baseline_margin",
+          "lb_rounded_rect_corner_radius",
+          "lb_search_orb_unfocused_z",
+          "lb_playback_transport_progressbar_active_bar_height",
+          "picker_item_height",
+          "lb_page_indicator_arrow_shadow_offset",
+          "lb_page_indicator_arrow_gap",
+          "lb_details_description_title_baseline",
+          "lb_details_v2_blank_height",
+          "lb_search_bar_height",
+          "lb_playback_controls_child_margin_default",
+          "lb_details_v2_logo_margin_start",
+          "lb_browse_selected_row_top_padding",
+          "lb_details_cover_drawable_parallax_movement",
+          "lb_playback_transport_hero_thumbs_height",
+          "lb_details_overview_height_small",
+          "lb_material_shadow_focused_z",
+          "lb_playback_transport_thumbs_width",
+          "lb_browse_rows_margin_top",
+          "lb_action_padding_horizontal",
+          "lb_page_indicator_arrow_radius",
+          "lb_details_description_body_line_spacing",
+          "lb_browse_expanded_selected_row_top_padding",
+          "lb_browse_header_select_scale",
+          "lb_details_v2_align_pos_for_actions",
+          "lb_action_with_icon_padding_start",
+          "lb_page_indicator_arrow_shadow_radius",
+          "lb_playback_transport_progressbar_bar_height",
+          "lb_playback_controls_child_margin_biggest",
+          "lb_playback_controls_padding_bottom",
+          "lb_browse_expanded_row_no_hovercard_bottom_padding",
+          "lb_browse_header_select_duration",
+          "lb_playback_other_rows_center_to_bottom",
+          "lb_details_overview_image_margin_horizontal",
+          "lb_search_orb_focused_z",
+          "lb_error_under_message_baseline_margin",
+          "lb_playback_major_fade_translate_y",
+          "lb_page_indicator_dot_gap",
+          "lb_details_overview_height_large",
+          "lb_details_overview_actions_fade_size",
+          "lb_action_with_icon_padding_end",
+          "lb_details_description_title_line_spacing",
+          "lb_playback_minor_fade_translate_y",
+          "lb_playback_transport_progressbar_active_radius",
+          "lb_details_description_under_title_baseline_margin"
+        ]
+      },
+      "android/support/v7/appcompat/R$layout": {
+        "androidx/appcompat/R$layout": [
+          "abc_screen_toolbar",
+          "abc_screen_simple",
+          "abc_action_mode_close_item_material",
+          "abc_dialog_title_material",
+          "abc_list_menu_item_checkbox",
+          "abc_activity_chooser_view",
+          "abc_search_dropdown_item_icons_2line",
+          "support_simple_spinner_dropdown_item",
+          "abc_list_menu_item_radio",
+          "abc_popup_menu_header_item_layout",
+          "abc_list_menu_item_layout",
+          "tooltip",
+          "abc_action_menu_layout",
+          "abc_list_menu_item_icon",
+          "abc_action_menu_item_layout",
+          "abc_action_bar_title_item",
+          "abc_popup_menu_item_layout",
+          "abc_activity_chooser_view_list_item",
+          "abc_search_view",
+          "abc_expanded_menu_layout",
+          "abc_screen_simple_overlay_action_mode"
+        ]
+      },
+      "android/support/v7/appcompat/R$id": {
+        "androidx/appcompat/R$id": [
+          "submit_area",
+          "split_action_bar",
+          "scrollIndicatorUp",
+          "search_button",
+          "contentPanel",
+          "title",
+          "action_mode_close_button",
+          "custom",
+          "search_edit_frame",
+          "decor_content_parent",
+          "action_bar_title",
+          "submenuarrow",
+          "search_voice_btn",
+          "title_template",
+          "action_bar",
+          "action_bar_subtitle",
+          "alertTitle",
+          "default_activity_button",
+          "search_close_btn",
+          "textSpacerNoTitle",
+          "action_mode_bar_stub",
+          "titleDividerNoCustom",
+          "action_bar_container",
+          "parentPanel",
+          "shortcut",
+          "icon",
+          "search_src_text",
+          "activity_chooser_view_content",
+          "search_plate",
+          "customPanel",
+          "edit_query",
+          "action_menu_presenter",
+          "spacer",
+          "scrollIndicatorDown",
+          "scrollView",
+          "message",
+          "buttonPanel",
+          "topPanel",
+          "list_item",
+          "action_context_bar",
+          "search_go_btn",
+          "textSpacerNoButtons",
+          "search_mag_icon",
+          "action_bar_activity_content",
+          "expand_activities_button",
+          "image"
+        ]
+      },
+      "android/support/v7/widget/Toolbar$LayoutParams": {
+        "androidx/widget/Toolbar$LayoutParams": [
+          "gravity",
+          "EXPANDED",
+          "width",
+          "SYSTEM",
+          "leftMargin",
+          "CUSTOM",
+          "bottomMargin",
+          "rightMargin",
+          "topMargin",
+          "height"
+        ]
+      },
+      "android/support/v4/app/NotificationCompatJellybean": {
+        "androidx/app/NotificationCompatJellybean": [
+          "KEY_LABEL",
+          "sActionIntentField",
+          "KEY_ACTION_INTENT",
+          "sActionClass",
+          "sExtrasLock",
+          "KEY_REMOTE_INPUTS",
+          "KEY_ALLOWED_DATA_TYPES",
+          "sActionsLock",
+          "KEY_CHOICES",
+          "sExtrasField",
+          "EXTRA_DATA_ONLY_REMOTE_INPUTS",
+          "sActionTitleField",
+          "sActionIconField",
+          "KEY_EXTRAS",
+          "KEY_TITLE",
+          "sActionsAccessFailed",
+          "TAG",
+          "KEY_ICON",
+          "EXTRA_ALLOW_GENERATED_REPLIES",
+          "KEY_ALLOW_FREE_FORM_INPUT",
+          "KEY_DATA_ONLY_REMOTE_INPUTS",
+          "sExtrasFieldAccessFailed",
+          "KEY_RESULT_KEY",
+          "sActionsField"
+        ]
+      },
+      "android/support/v7/widget/GridLayout": {
+        "androidx/widget/GridLayout": [
+          "RIGHT",
+          "DEFAULT_COUNT",
+          "DEFAULT_ORIENTATION",
+          "ROW_ORDER_PRESERVED",
+          "TOP",
+          "LEADING",
+          "START",
+          "BASELINE",
+          "UNDEFINED",
+          "ALIGN_BOUNDS",
+          "CENTER",
+          "MAX_SIZE",
+          "ALIGNMENT_MODE",
+          "COLUMN_COUNT",
+          "ROW_COUNT",
+          "CAN_STRETCH",
+          "USE_DEFAULT_MARGINS",
+          "FILL",
+          "HORIZONTAL",
+          "DEFAULT_CONTAINER_MARGIN",
+          "LOG_PRINTER",
+          "ALIGN_MARGINS",
+          "VERTICAL",
+          "DEFAULT_ALIGNMENT_MODE",
+          "COLUMN_ORDER_PRESERVED",
+          "ORIENTATION",
+          "LEFT",
+          "TRAILING",
+          "DEFAULT_ORDER_PRESERVED",
+          "BOTTOM",
+          "END",
+          "DEFAULT_USE_DEFAULT_MARGINS",
+          "INFLEXIBLE",
+          "UNINITIALIZED_HASH",
+          "UNDEFINED_ALIGNMENT",
+          "NO_PRINTER"
+        ]
+      },
+      "android/support/wear/widget/BoxInsetLayout$LayoutParams": {
+        "androidx/wear/widget/BoxInsetLayout$LayoutParams": [
+          "BOX_ALL",
+          "BOX_LEFT",
+          "BOX_TOP",
+          "gravity",
+          "height",
+          "topMargin",
+          "rightMargin",
+          "bottomMargin",
+          "boxedEdges",
+          "BOX_RIGHT",
+          "width",
+          "leftMargin",
+          "BOX_NONE",
+          "BOX_BOTTOM"
+        ]
+      },
+      "android/support/design/widget/TabLayout": {
+        "androidx/design/widget/TabLayout": [
+          "GRAVITY_FILL",
+          "TAB_MIN_WIDTH_MARGIN",
+          "DEFAULT_HEIGHT_WITH_TEXT_ICON",
+          "ANIMATION_DURATION",
+          "INVALID_WIDTH",
+          "FIXED_WRAP_GUTTER_MIN",
+          "MOTION_NON_ADJACENT_OFFSET",
+          "DEFAULT_HEIGHT",
+          "MODE_FIXED",
+          "SELECTED_STATE_SET",
+          "MODE_SCROLLABLE",
+          "EMPTY_STATE_SET",
+          "sTabPool",
+          "GRAVITY_CENTER",
+          "DEFAULT_GAP_TEXT_ICON"
+        ]
+      },
+      "android/support/v4/view/MotionEventCompat": {
+        "androidx/view/MotionEventCompat": [
+          "ACTION_SCROLL",
+          "AXIS_RUDDER",
+          "AXIS_BRAKE",
+          "AXIS_TOOL_MINOR",
+          "AXIS_GENERIC_3",
+          "AXIS_GENERIC_4",
+          "AXIS_GENERIC_5",
+          "AXIS_GENERIC_6",
+          "AXIS_GENERIC_7",
+          "AXIS_GENERIC_8",
+          "AXIS_GENERIC_9",
+          "AXIS_GENERIC_1",
+          "AXIS_GENERIC_2",
+          "AXIS_DISTANCE",
+          "AXIS_ORIENTATION",
+          "AXIS_LTRIGGER",
+          "AXIS_HSCROLL",
+          "ACTION_POINTER_DOWN",
+          "AXIS_TILT",
+          "AXIS_WHEEL",
+          "AXIS_Y",
+          "AXIS_Z",
+          "AXIS_X",
+          "AXIS_SCROLL",
+          "AXIS_TOUCH_MAJOR",
+          "ACTION_HOVER_ENTER",
+          "AXIS_GAS",
+          "ACTION_HOVER_MOVE",
+          "ACTION_HOVER_EXIT",
+          "AXIS_THROTTLE",
+          "AXIS_HAT_X",
+          "AXIS_HAT_Y",
+          "AXIS_SIZE",
+          "ACTION_POINTER_INDEX_SHIFT",
+          "AXIS_RTRIGGER",
+          "AXIS_RELATIVE_X",
+          "AXIS_RELATIVE_Y",
+          "AXIS_RY",
+          "AXIS_RZ",
+          "AXIS_RX",
+          "AXIS_TOUCH_MINOR",
+          "ACTION_POINTER_INDEX_MASK",
+          "BUTTON_PRIMARY",
+          "AXIS_PRESSURE",
+          "AXIS_TOOL_MAJOR",
+          "AXIS_GENERIC_13",
+          "AXIS_GENERIC_12",
+          "AXIS_GENERIC_11",
+          "AXIS_GENERIC_10",
+          "AXIS_GENERIC_16",
+          "AXIS_GENERIC_15",
+          "AXIS_GENERIC_14",
+          "ACTION_POINTER_UP",
+          "ACTION_MASK",
+          "AXIS_VSCROLL"
+        ]
+      },
+      "android/support/v4/view/PagerTitleStrip": {
+        "androidx/widget/PagerTitleStrip": [
+          "ATTRS",
+          "SIDE_ALPHA",
+          "TEXT_ATTRS",
+          "TEXT_SPACING"
+        ]
+      },
+      "android/support/v7/preference/BuildConfig": {
+        "androidx/preference/BuildConfig": [
+          "APPLICATION_ID",
+          "FLAVOR",
+          "VERSION_CODE",
+          "DEBUG",
+          "VERSION_NAME",
+          "BUILD_TYPE"
+        ]
+      },
+      "android/support/v4/graphics/TypefaceCompatApi26Impl": {
+        "androidx/graphics/TypefaceCompatApi26Impl": [
+          "sAddFontFromAssetManager",
+          "sFreeze",
+          "TAG",
+          "RESOLVE_BY_FONT_TABLE",
+          "sFontFamily",
+          "ADD_FONT_FROM_ASSET_MANAGER_METHOD",
+          "sCreateFromFamiliesWithDefault",
+          "FONT_FAMILY_CLASS",
+          "ABORT_CREATION_METHOD",
+          "sFontFamilyCtor",
+          "CREATE_FROM_FAMILIES_WITH_DEFAULT_METHOD",
+          "sAbortCreation",
+          "ADD_FONT_FROM_BUFFER_METHOD",
+          "sAddFontFromBuffer",
+          "FREEZE_METHOD"
+        ]
+      },
+      "android/support/mediacompat/BuildConfig": {
+        "androidx/mediacompat/BuildConfig": [
+          "DEBUG",
+          "APPLICATION_ID",
+          "FLAVOR",
+          "VERSION_CODE",
+          "BUILD_TYPE",
+          "VERSION_NAME"
+        ]
+      },
+      "android/support/v7/app/AppCompatDelegate": {
+        "androidx/app/AppCompatDelegate": [
+          "sCompatVectorFromResourcesEnabled",
+          "MODE_NIGHT_FOLLOW_SYSTEM",
+          "MODE_NIGHT_YES",
+          "TAG",
+          "FEATURE_SUPPORT_ACTION_BAR_OVERLAY",
+          "sDefaultNightMode",
+          "MODE_NIGHT_NO",
+          "MODE_NIGHT_AUTO",
+          "FEATURE_ACTION_MODE_OVERLAY",
+          "FEATURE_SUPPORT_ACTION_BAR",
+          "MODE_NIGHT_UNSPECIFIED"
+        ]
+      },
+      "android/support/v7/cardview/BuildConfig": {
+        "androidx/cardview/BuildConfig": [
+          "VERSION_NAME",
+          "BUILD_TYPE",
+          "VERSION_CODE",
+          "DEBUG",
+          "APPLICATION_ID",
+          "FLAVOR"
+        ]
+      },
+      "android/support/v7/view/menu/ListMenuItemView": {
+        "androidx/view/menu/ListMenuItemView": [
+          "TAG"
+        ]
+      },
+      "android/support/media/instantvideo/preload/InstantVideoPreloadManager": {
+        "androidx/media/instantvideo/preload/InstantVideoPreloadManager": [
+          "DEBUG",
+          "sInstance",
+          "TAG",
+          "DEFAULT_MAX_VIDEO_COUNT"
+        ]
+      },
+      "android/support/graphics/drawable/AndroidResources": {
+        "androidx/graphics/drawable/AndroidResources": [
+          "STYLEABLE_ANIMATED_VECTOR_DRAWABLE_TARGET",
+          "STYLEABLE_VECTOR_DRAWABLE_CLIP_PATH_NAME",
+          "STYLEABLE_VECTOR_DRAWABLE_GROUP_NAME",
+          "STYLEABLE_ANIMATED_VECTOR_DRAWABLE_DRAWABLE",
+          "STYLEABLE_VECTOR_DRAWABLE_PATH_TRIM_PATH_END",
+          "STYLEABLE_PATH_INTERPOLATOR",
+          "STYLEABLE_PROPERTY_VALUES_HOLDER_PROPERTY_NAME",
+          "STYLEABLE_KEYFRAME_INTERPOLATOR",
+          "STYLEABLE_KEYFRAME_FRACTION",
+          "STYLEABLE_ANIMATOR_REPEAT_MODE",
+          "STYLEABLE_ANIMATED_VECTOR_DRAWABLE_TARGET_NAME",
+          "STYLEABLE_VECTOR_DRAWABLE_VIEWPORT_WIDTH",
+          "FAST_OUT_LINEAR_IN",
+          "STYLEABLE_VECTOR_DRAWABLE_PATH_STROKE_LINE_JOIN",
+          "STYLEABLE_VECTOR_DRAWABLE_PATH_PATH_DATA",
+          "STYLEABLE_VECTOR_DRAWABLE_TINT_MODE",
+          "STYLEABLE_VECTOR_DRAWABLE_TINT",
+          "STYLEABLE_VECTOR_DRAWABLE_PATH_NAME",
+          "STYLEABLE_KEYFRAME_VALUE_TYPE",
+          "STYLEABLE_PATH_INTERPOLATOR_PATH_DATA",
+          "STYLEABLE_ANIMATOR_START_OFFSET",
+          "STYLEABLE_VECTOR_DRAWABLE_GROUP",
+          "STYLEABLE_VECTOR_DRAWABLE_PATH_STROKE_COLOR",
+          "STYLEABLE_ANIMATED_VECTOR_DRAWABLE_TARGET_ANIMATION",
+          "STYLEABLE_VECTOR_DRAWABLE_GROUP_ROTATION",
+          "STYLEABLE_VECTOR_DRAWABLE_PATH_STROKE_WIDTH",
+          "STYLEABLE_PROPERTY_ANIMATOR_PROPERTY_Y_NAME",
+          "STYLEABLE_VECTOR_DRAWABLE_PATH_STROKE_ALPHA",
+          "STYLEABLE_ANIMATOR_INTERPOLATOR",
+          "STYLEABLE_VECTOR_DRAWABLE_WIDTH",
+          "STYLEABLE_ANIMATOR_SET_ORDERING",
+          "STYLEABLE_ANIMATED_VECTOR_DRAWABLE",
+          "STYLEABLE_ANIMATOR_DURATION",
+          "STYLEABLE_VECTOR_DRAWABLE_ALPHA",
+          "STYLEABLE_VECTOR_DRAWABLE_HEIGHT",
+          "STYLEABLE_VECTOR_DRAWABLE_PATH_FILL_ALPHA",
+          "STYLEABLE_VECTOR_DRAWABLE_PATH_STROKE_MITER_LIMIT",
+          "STYLEABLE_PATH_INTERPOLATOR_CONTROL_X_2",
+          "STYLEABLE_PATH_INTERPOLATOR_CONTROL_X_1",
+          "LINEAR_OUT_SLOW_IN",
+          "STYLEABLE_VECTOR_DRAWABLE_PATH_FILL_COLOR",
+          "STYLEABLE_PROPERTY_VALUES_HOLDER_VALUE_TO",
+          "STYLEABLE_VECTOR_DRAWABLE_PATH",
+          "STYLEABLE_ANIMATOR_REMOVE_BEFORE_M_RELEASE",
+          "STYLEABLE_PATH_INTERPOLATOR_CONTROL_Y_1",
+          "STYLEABLE_PATH_INTERPOLATOR_CONTROL_Y_2",
+          "FAST_OUT_SLOW_IN",
+          "STYLEABLE_ANIMATOR_VALUE_FROM",
+          "STYLEABLE_ANIMATOR_REPEAT_COUNT",
+          "STYLEABLE_ANIMATOR_SET",
+          "STYLEABLE_PROPERTY_ANIMATOR_PATH_DATA",
+          "STYLEABLE_ANIMATOR_VALUE_TO",
+          "STYLEABLE_VECTOR_DRAWABLE_VIEWPORT_HEIGHT",
+          "STYLEABLE_KEYFRAME",
+          "STYLEABLE_VECTOR_DRAWABLE_TYPE_ARRAY",
+          "STYLEABLE_ANIMATOR_VALUE_TYPE",
+          "STYLEABLE_PROPERTY_VALUES_HOLDER",
+          "STYLEABLE_VECTOR_DRAWABLE_GROUP_PIVOT_X",
+          "STYLEABLE_VECTOR_DRAWABLE_PATH_TRIM_PATH_OFFSET",
+          "STYLEABLE_VECTOR_DRAWABLE_GROUP_PIVOT_Y",
+          "STYLEABLE_VECTOR_DRAWABLE_GROUP_TRANSLATE_X",
+          "STYLEABLE_VECTOR_DRAWABLE_GROUP_TRANSLATE_Y",
+          "STYLEABLE_VECTOR_DRAWABLE_AUTO_MIRRORED",
+          "STYLEABLE_VECTOR_DRAWABLE_PATH_TRIM_PATH_START",
+          "STYLEABLE_KEYFRAME_VALUE",
+          "STYLEABLE_PROPERTY_VALUES_HOLDER_VALUE_TYPE",
+          "STYLEABLE_PROPERTY_ANIMATOR_PROPERTY_NAME",
+          "STYLEABLE_VECTOR_DRAWABLE_CLIP_PATH",
+          "STYLEABLE_VECTOR_DRAWABLE_NAME",
+          "STYLEABLE_PROPERTY_VALUES_HOLDER_VALUE_FROM",
+          "STYLEABLE_VECTOR_DRAWABLE_GROUP_SCALE_Y",
+          "STYLEABLE_VECTOR_DRAWABLE_GROUP_SCALE_X",
+          "STYLEABLE_ANIMATOR",
+          "STYLEABLE_VECTOR_DRAWABLE_CLIP_PATH_PATH_DATA",
+          "STYLEABLE_VECTOR_DRAWABLE_PATH_STROKE_LINE_CAP",
+          "STYLEABLE_VECTOR_DRAWABLE_PATH_TRIM_PATH_FILLTYPE",
+          "STYLEABLE_PROPERTY_ANIMATOR",
+          "STYLEABLE_PROPERTY_ANIMATOR_PROPERTY_X_NAME"
+        ]
+      },
+      "android/support/v7/app/ActionBar$LayoutParams": {
+        "androidx/app/ActionBar$LayoutParams": [
+          "gravity"
+        ]
+      },
+      "android/support/v7/widget/LinearLayoutCompat": {
+        "androidx/widget/LinearLayoutCompat": [
+          "VERTICAL_GRAVITY_COUNT",
+          "SHOW_DIVIDER_BEGINNING",
+          "HORIZONTAL",
+          "SHOW_DIVIDER_MIDDLE",
+          "SHOW_DIVIDER_NONE",
+          "INDEX_CENTER_VERTICAL",
+          "INDEX_TOP",
+          "INDEX_FILL",
+          "VERTICAL",
+          "INDEX_BOTTOM",
+          "SHOW_DIVIDER_END"
+        ]
+      },
+      "android/support/percent/PercentLayoutHelper$PercentMarginLayoutParams": {
+        "androidx/PercentLayoutHelper$PercentMarginLayoutParams": [
+          "topMargin",
+          "leftMargin",
+          "rightMargin",
+          "height",
+          "width",
+          "bottomMargin"
+        ]
+      },
+      "android/support/v17/leanback/widget/StreamingTextView": {
+        "androidx/leanback/widget/StreamingTextView": [
+          "STREAM_POSITION_PROPERTY",
+          "DOTS_FOR_STABLE",
+          "ANIMATE_DOTS_FOR_PENDING",
+          "STREAM_UPDATE_DELAY_MILLIS",
+          "DEBUG",
+          "TEXT_DOT_SCALE",
+          "DOTS_FOR_PENDING",
+          "SPLIT_PATTERN",
+          "TAG"
+        ]
+      },
+      "android/support/v7/view/SupportMenuInflater$MenuState": {
+        "androidx/view/SupportMenuInflater$MenuState": [
+          "itemListenerMethodName",
+          "itemActionViewClassName",
+          "itemContentDescription",
+          "itemEnabled",
+          "defaultItemChecked",
+          "itemCategoryOrder",
+          "itemTitle",
+          "groupCategory",
+          "itemIconTintMode",
+          "itemAdded",
+          "groupCheckable",
+          "itemNumericShortcut",
+          "groupVisible",
+          "itemNumericModifiers",
+          "itemTitleCondensed",
+          "defaultItemVisible",
+          "itemAlphabeticShortcut",
+          "itemShowAsAction",
+          "menu",
+          "defaultItemCheckable",
+          "itemActionProvider",
+          "itemCheckable",
+          "itemActionViewLayout",
+          "itemIconResId",
+          "itemVisible",
+          "itemTooltipText",
+          "defaultItemCategory",
+          "defaultGroupId",
+          "groupOrder",
+          "defaultItemId",
+          "itemActionProviderClassName",
+          "itemId",
+          "itemIconTintList",
+          "itemChecked",
+          "groupId",
+          "defaultItemEnabled",
+          "defaultItemOrder",
+          "itemAlphabeticModifiers",
+          "groupEnabled"
+        ]
+      },
+      "android/support/v4/view/InputDeviceCompat": {
+        "androidx/view/InputDeviceCompat": [
+          "SOURCE_CLASS_JOYSTICK",
+          "SOURCE_KEYBOARD",
+          "SOURCE_CLASS_MASK",
+          "SOURCE_CLASS_POINTER",
+          "SOURCE_ROTARY_ENCODER",
+          "SOURCE_TOUCHSCREEN",
+          "SOURCE_TOUCHPAD",
+          "SOURCE_STYLUS",
+          "SOURCE_CLASS_POSITION",
+          "SOURCE_UNKNOWN",
+          "SOURCE_DPAD",
+          "SOURCE_CLASS_BUTTON",
+          "SOURCE_JOYSTICK",
+          "SOURCE_ANY",
+          "SOURCE_TOUCH_NAVIGATION",
+          "SOURCE_TRACKBALL",
+          "SOURCE_MOUSE",
+          "SOURCE_HDMI",
+          "SOURCE_GAMEPAD",
+          "SOURCE_CLASS_TRACKBALL",
+          "SOURCE_CLASS_NONE"
+        ]
+      },
+      "android/support/v17/leanback/R$styleable": {
+        "androidx/leanback/R$styleable": [
+          "lbImageCardView_lbImageCardViewType",
+          "LeanbackTheme_browseRowsMarginTop",
+          "lbPlaybackControlsActionIcons_thumb_down_outline",
+          "lbSlide_android_interpolator",
+          "lbBaseCardView_cardType",
+          "lbResizingTextView_resizedPaddingAdjustmentBottom",
+          "lbBaseGridView_android_horizontalSpacing",
+          "lbResizingTextView_maintainLineSpacing",
+          "lbBaseGridView_verticalMargin",
+          "lbPlaybackControlsActionIcons_repeat_one",
+          "lbBaseGridView_horizontalMargin",
+          "lbHorizontalGridView_rowHeight",
+          "lbHorizontalGridView_numberOfRows",
+          "LeanbackTheme_browseRowsFadingEdgeLength",
+          "PagingIndicator_dotToDotGap",
+          "PagingIndicator_arrowColor",
+          "PagingIndicator",
+          "lbSearchOrbView_searchOrbColor",
+          "lbTimePicker_is24HourFormat",
+          "lbDatePicker",
+          "lbSearchOrbView_searchOrbBrightColor",
+          "lbDatePicker_android_maxDate",
+          "lbVerticalGridView_columnWidth",
+          "PagingIndicator_arrowRadius",
+          "lbTimePicker_useCurrentTime",
+          "LeanbackGuidedStepTheme",
+          "lbPlaybackControlsActionIcons_shuffle",
+          "lbBaseCardView_cardBackground",
+          "lbPlaybackControlsActionIcons",
+          "lbBaseCardView",
+          "lbPlaybackControlsActionIcons_play",
+          "lbSlide_lb_slideEdge",
+          "lbBaseCardView_extraVisibility",
+          "LeanbackTheme_overlayDimActiveLevel",
+          "lbResizingTextView_resizeTrigger",
+          "lbPlaybackControlsActionIcons_picture_in_picture",
+          "lbBaseGridView_android_gravity",
+          "lbBaseGridView_focusOutEnd",
+          "LeanbackTheme",
+          "lbPlaybackControlsActionIcons_thumb_up",
+          "PagingIndicator_dotBgColor",
+          "lbSearchOrbView_searchOrbIconColor",
+          "lbSearchOrbView",
+          "lbBaseGridView_focusOutFront",
+          "lbBaseCardView_infoVisibility",
+          "lbResizingTextView_resizedTextSize",
+          "lbPlaybackControlsActionIcons_skip_previous",
+          "PagingIndicator_dotToArrowGap",
+          "lbHorizontalGridView",
+          "lbDatePicker_datePickerFormat",
+          "lbVerticalGridView_numberOfColumns",
+          "lbSlide_android_duration",
+          "PagingIndicator_lbDotRadius",
+          "lbPlaybackControlsActionIcons_skip_next",
+          "lbSlide",
+          "lbSlide_android_startDelay",
+          "lbPlaybackControlsActionIcons_fast_forward",
+          "lbImageCardView",
+          "LeanbackTheme_browseRowsMarginStart",
+          "lbBaseCardView_Layout_layout_viewType",
+          "lbTimePicker",
+          "lbResizingTextView",
+          "lbImageCardView_infoAreaBackground",
+          "lbBaseCardView_Layout",
+          "lbPlaybackControlsActionIcons_repeat",
+          "lbPlaybackControlsActionIcons_rewind",
+          "lbBaseCardView_selectedAnimationDelay",
+          "lbBaseGridView_focusOutSideStart",
+          "lbBaseCardView_selectedAnimationDuration",
+          "lbBaseGridView_android_verticalSpacing",
+          "lbSearchOrbView_searchOrbIcon",
+          "lbPlaybackControlsActionIcons_pause",
+          "lbBaseGridView",
+          "PagingIndicator_arrowBgColor",
+          "lbVerticalGridView",
+          "lbDatePicker_android_minDate",
+          "lbPlaybackControlsActionIcons_thumb_up_outline",
+          "LeanbackTheme_overlayDimDimmedLevel",
+          "lbBaseGridView_focusOutSideEnd",
+          "lbPlaybackControlsActionIcons_high_quality",
+          "lbResizingTextView_resizedPaddingAdjustmentTop",
+          "lbBaseCardView_activatedAnimationDuration",
+          "lbPlaybackControlsActionIcons_closed_captioning",
+          "LeanbackTheme_overlayDimMaskColor",
+          "lbPlaybackControlsActionIcons_thumb_down",
+          "LeanbackGuidedStepTheme_guidedStepKeyline",
+          "lbBaseCardView_cardForeground"
+        ]
+      },
+      "android/support/v17/leanback/R$drawable": {
+        "androidx/leanback/R$drawable": [
+          "lb_ic_in_app_search",
+          "lb_text_dot_one",
+          "lb_ic_search_mic_out",
+          "lb_ic_search_mic",
+          "lb_background",
+          "lb_text_dot_two",
+          "lb_ic_nav_arrow",
+          "lb_ic_more"
+        ]
+      },
+      "android/support/v7/widget/RecyclerView$ViewHolder": {
+        "androidx/widget/RecyclerView$ViewHolder": [
+          "FLAG_NOT_RECYCLABLE",
+          "FLAG_INVALID",
+          "FULLUPDATE_PAYLOADS",
+          "FLAG_RETURNED_FROM_SCRAP",
+          "PENDING_ACCESSIBILITY_STATE_NOT_SET",
+          "FLAG_REMOVED",
+          "FLAG_TMP_DETACHED",
+          "FLAG_ADAPTER_POSITION_UNKNOWN",
+          "FLAG_SET_A11Y_ITEM_DELEGATE",
+          "itemView",
+          "FLAG_APPEARED_IN_PRE_LAYOUT",
+          "FLAG_ADAPTER_FULLUPDATE",
+          "FLAG_BOUNCED_FROM_HIDDEN_LIST",
+          "FLAG_IGNORE",
+          "FLAG_BOUND",
+          "FLAG_UPDATE",
+          "FLAG_MOVED"
+        ]
+      },
+      "android/support/v4/media/session/MediaSessionCompat$Token": {
+        "androidx/media/session/MediaSessionCompat$Token": [
+          "CREATOR"
+        ]
+      },
+      "android/support/v17/leanback/app/BrowseFragment$ExpandPreLayout": {
+        "androidx/leanback/app/BrowseFragment$ExpandPreLayout": [
+          "STATE_SECOND_DRAW",
+          "STATE_INIT",
+          "mainFragmentAdapter",
+          "STATE_FIRST_DRAW"
+        ]
+      },
+      "android/support/v13/app/FragmentTabHost$TabInfo": {
+        "androidx/app/FragmentTabHost$TabInfo": [
+          "fragment",
+          "args",
+          "tag",
+          "clss"
+        ]
+      },
+      "android/support/media/tv/Channel": {
+        "androidx/media/tv/Channel": [
+          "IS_SEARCHABLE",
+          "IS_SYSTEM_APPROVED",
+          "INVALID_INT_VALUE",
+          "INVALID_CHANNEL_ID",
+          "PROJECTION",
+          "IS_TRANSIENT",
+          "IS_BROWSABLE",
+          "IS_LOCKED"
+        ]
+      },
+      "android/support/v4/text/BidiFormatter$DirectionalityEstimator": {
+        "androidx/text/BidiFormatter$DirectionalityEstimator": [
+          "lastChar",
+          "DIR_TYPE_CACHE_SIZE",
+          "DIR_TYPE_CACHE",
+          "charIndex",
+          "isHtml",
+          "text",
+          "length"
+        ]
+      },
+      "android/support/v7/widget/AppCompatDrawableManager": {
+        "androidx/widget/AppCompatDrawableManager": [
+          "COLOR_FILTER_CACHE",
+          "TINT_COLOR_CONTROL_NORMAL",
+          "TAG",
+          "COLORFILTER_TINT_COLOR_CONTROL_NORMAL",
+          "COLORFILTER_COLOR_CONTROL_ACTIVATED",
+          "TINT_CHECKABLE_BUTTON_LIST",
+          "INSTANCE",
+          "TINT_COLOR_CONTROL_STATE_LIST",
+          "DEFAULT_MODE",
+          "DEBUG",
+          "COLORFILTER_COLOR_BACKGROUND_MULTIPLY",
+          "PLATFORM_VD_CLAZZ",
+          "SKIP_DRAWABLE_TAG"
+        ]
+      },
+      "android/support/media/tv/TvContractCompat": {
+        "androidx/media/tv/TvContractCompat": [
+          "PARAM_INPUT",
+          "PATH_CHANNEL",
+          "ACTION_WATCH_NEXT_PROGRAM_BROWSABLE_DISABLED",
+          "ACTION_REQUEST_CHANNEL_BROWSABLE",
+          "EXTRA_PREVIEW_PROGRAM_ID",
+          "PATH_RECORDED_PROGRAM",
+          "PATH_PASSTHROUGH",
+          "PARAM_CHANNEL",
+          "AUTHORITY",
+          "PARAM_START_TIME",
+          "PARAM_BROWSABLE_ONLY",
+          "EXTRA_DATA_TYPE",
+          "EXTRA_DEFAULT_VALUE",
+          "PARAM_END_TIME",
+          "EXTRA_WATCH_NEXT_PROGRAM_ID",
+          "PARAM_CANONICAL_GENRE",
+          "METHOD_GET_COLUMNS",
+          "PATH_PROGRAM",
+          "METHOD_ADD_COLUMN",
+          "PATH_PREVIEW_PROGRAM",
+          "PATH_WATCH_NEXT_PROGRAM",
+          "EXTRA_COLUMN_NAME",
+          "ACTION_PREVIEW_PROGRAM_BROWSABLE_DISABLED",
+          "PERMISSION_READ_TV_LISTINGS",
+          "EXTRA_EXISTING_COLUMN_NAMES",
+          "ACTION_PREVIEW_PROGRAM_ADDED_TO_WATCH_NEXT",
+          "EXTRA_PACKAGE_NAME",
+          "ACTION_CHANNEL_BROWSABLE_REQUESTED",
+          "EXTRA_CHANNEL_ID",
+          "ACTION_INITIALIZE_PROGRAMS"
+        ]
+      },
+      "android/support/v17/leanback/widget/GridLayoutManager$PendingMoveSmoothScroller": {
+        "androidx/leanback/widget/GridLayoutManager$PendingMoveSmoothScroller": [
+          "TARGET_UNDEFINED"
+        ]
+      },
+      "android/support/v7/media/RemoteControlClientCompat$PlaybackInfo": {
+        "androidx/media/RemoteControlClientCompat$PlaybackInfo": [
+          "volumeMax",
+          "playbackType",
+          "volume",
+          "volumeHandling",
+          "playbackStream"
+        ]
+      },
+      "android/support/v4/util/ArraySet": {
+        "androidx/util/ArraySet": [
+          "OBJECT",
+          "TAG",
+          "sTwiceBaseCacheSize",
+          "CACHE_SIZE",
+          "sBaseCacheSize",
+          "sTwiceBaseCache",
+          "BASE_SIZE",
+          "INT",
+          "sBaseCache",
+          "DEBUG"
+        ]
+      },
+      "android/support/v17/leanback/app/OnboardingFragment": {
+        "androidx/leanback/app/OnboardingFragment": [
+          "DESCRIPTION_START_DELAY_MS",
+          "TAG",
+          "HEADER_ANIMATION_DURATION_MS",
+          "HEADER_APPEAR_DELAY_MS",
+          "KEY_LOGO_ANIMATION_FINISHED",
+          "HEADER_DISAPPEAR_INTERPOLATOR",
+          "DEBUG",
+          "HEADER_APPEAR_INTERPOLATOR",
+          "KEY_ENTER_ANIMATION_FINISHED",
+          "KEY_CURRENT_PAGE_INDEX",
+          "sSlideDistance",
+          "LOGO_SPLASH_PAUSE_DURATION_MS",
+          "SLIDE_DISTANCE"
+        ]
+      },
+      "android/support/v17/leanback/R$string": {
+        "androidx/leanback/R$string": [
+          "lb_control_display_fast_forward_multiplier",
+          "lb_playback_controls_repeat_one",
+          "lb_playback_controls_thumb_up_outline",
+          "lb_playback_controls_thumb_up",
+          "lb_playback_controls_repeat_none",
+          "lb_guidedaction_finish_title",
+          "lb_control_display_rewind_multiplier",
+          "lb_playback_controls_closed_captioning_disable",
+          "lb_playback_controls_repeat_all",
+          "lb_playback_controls_pause",
+          "lb_playback_controls_thumb_down",
+          "lb_playback_controls_shuffle_enable",
+          "lb_playback_controls_rewind_multiplier",
+          "lb_guidedaction_continue_title",
+          "lb_playback_controls_shuffle_disable",
+          "lb_media_player_error",
+          "lb_playback_controls_hidden",
+          "lb_playback_controls_play",
+          "lb_guidedactions_item_unselected_description_text_alpha",
+          "lb_playback_controls_skip_next",
+          "lb_playback_controls_fast_forward",
+          "lb_playback_controls_high_quality_enable",
+          "lb_search_bar_hint_speech",
+          "lb_search_bar_hint_with_title_speech",
+          "lb_playback_controls_closed_captioning_enable",
+          "lb_playback_controls_shown",
+          "lb_guidedactions_item_disabled_description_text_alpha",
+          "lb_search_bar_hint",
+          "lb_playback_controls_fast_forward_multiplier",
+          "lb_playback_controls_picture_in_picture",
+          "lb_playback_controls_thumb_down_outline",
+          "lb_playback_controls_high_quality_disable",
+          "lb_playback_controls_rewind",
+          "lb_playback_controls_more_actions",
+          "lb_guidedactions_item_unselected_text_alpha",
+          "lb_guidedactions_item_disabled_text_alpha",
+          "lb_playback_controls_skip_previous",
+          "lb_search_bar_hint_with_title"
+        ]
+      },
+      "android/support/v7/preference/PreferenceInflater": {
+        "androidx/preference/PreferenceInflater": [
+          "EXTRA_TAG_NAME",
+          "INTENT_TAG_NAME",
+          "CONSTRUCTOR_MAP",
+          "TAG",
+          "CONSTRUCTOR_SIGNATURE"
+        ]
+      },
+      "android/support/constraint/R$styleable": {
+        "androidx/constraint/R$styleable": [
+          "ConstraintLayout_Layout_layout_constraintWidth_max",
+          "ConstraintLayout_Layout_layout_constraintWidth_min",
+          "ConstraintSet_layout_constraintDimensionRatio",
+          "ConstraintSet_android_transformPivotY",
+          "ConstraintSet_android_transformPivotX",
+          "ConstraintSet_layout_goneMarginRight",
+          "ConstraintLayout_Layout_layout_constraintGuide_percent",
+          "ConstraintLayout_Layout_layout_constraintLeft_toLeftOf",
+          "ConstraintSet_layout_goneMarginStart",
+          "ConstraintSet_android_orientation",
+          "ConstraintLayout_Layout_layout_constraintRight_toLeftOf",
+          "ConstraintSet_android_layout_marginBottom",
+          "ConstraintSet_layout_constraintBaseline_toBaselineOf",
+          "ConstraintSet_android_layout_height",
+          "ConstraintSet_layout_constraintRight_creator",
+          "ConstraintSet_layout_constraintWidth_max",
+          "ConstraintSet_layout_constraintEnd_toStartOf",
+          "ConstraintLayout_Layout_layout_constraintBottom_creator",
+          "ConstraintSet_android_visibility",
+          "ConstraintLayout_Layout_layout_constraintTop_toBottomOf",
+          "ConstraintSet_layout_constraintGuide_begin",
+          "ConstraintSet_layout_constraintWidth_min",
+          "ConstraintLayout_Layout_android_maxHeight",
+          "ConstraintSet_android_id",
+          "ConstraintSet_layout_constraintBottom_creator",
+          "ConstraintLayout_Layout_layout_optimizationLevel",
+          "ConstraintLayout_Layout_layout_constraintVertical_bias",
+          "ConstraintSet_layout_constraintHorizontal_bias",
+          "ConstraintSet_layout_constraintHeight_default",
+          "ConstraintSet",
+          "ConstraintLayout_Layout_layout_constraintBottom_toBottomOf",
+          "ConstraintLayout_Layout",
+          "ConstraintLayout_Layout_layout_constraintBaseline_creator",
+          "ConstraintLayout_Layout_layout_constraintVertical_chainStyle",
+          "ConstraintSet_android_rotationY",
+          "ConstraintSet_android_rotationX",
+          "ConstraintSet_android_alpha",
+          "ConstraintLayout_Layout_layout_constraintDimensionRatio",
+          "ConstraintLayout_Layout_android_orientation",
+          "ConstraintSet_layout_constraintLeft_creator",
+          "ConstraintSet_layout_goneMarginLeft",
+          "ConstraintSet_layout_constraintLeft_toLeftOf",
+          "ConstraintLayout_Layout_layout_constraintVertical_weight",
+          "ConstraintSet_layout_constraintStart_toStartOf",
+          "ConstraintLayout_Layout_layout_constraintHorizontal_chainStyle",
+          "ConstraintLayout_Layout_layout_constraintEnd_toEndOf",
+          "ConstraintSet_layout_editor_absoluteY",
+          "ConstraintSet_layout_editor_absoluteX",
+          "ConstraintLayout_Layout_layout_constraintEnd_toStartOf",
+          "ConstraintLayout_Layout_layout_constraintTop_toTopOf",
+          "ConstraintSet_android_layout_marginEnd",
+          "ConstraintLayout_Layout_layout_goneMarginLeft",
+          "ConstraintLayout_Layout_layout_constraintGuide_begin",
+          "ConstraintSet_layout_constraintGuide_end",
+          "ConstraintSet_android_translationZ",
+          "ConstraintSet_android_translationY",
+          "ConstraintSet_android_translationX",
+          "ConstraintSet_layout_constraintStart_toEndOf",
+          "ConstraintSet_layout_constraintRight_toRightOf",
+          "ConstraintSet_layout_constraintHeight_max",
+          "ConstraintSet_android_layout_marginTop",
+          "ConstraintSet_layout_constraintHeight_min",
+          "ConstraintSet_layout_constraintTop_creator",
+          "ConstraintLayout_Layout_layout_editor_absoluteX",
+          "ConstraintLayout_Layout_layout_editor_absoluteY",
+          "ConstraintLayout_Layout_layout_goneMarginTop",
+          "ConstraintSet_layout_goneMarginEnd",
+          "ConstraintSet_layout_constraintLeft_toRightOf",
+          "ConstraintLayout_Layout_layout_goneMarginBottom",
+          "ConstraintLayout_Layout_android_minHeight",
+          "ConstraintLayout_Layout_layout_constraintBaseline_toBaselineOf",
+          "ConstraintSet_android_layout_marginLeft",
+          "ConstraintSet_layout_constraintHorizontal_weight",
+          "ConstraintLayout_Layout_layout_goneMarginEnd",
+          "ConstraintSet_layout_constraintVertical_weight",
+          "ConstraintSet_layout_constraintBaseline_creator",
+          "ConstraintSet_layout_constraintVertical_bias",
+          "ConstraintSet_layout_goneMarginTop",
+          "ConstraintLayout_Layout_layout_constraintRight_creator",
+          "ConstraintSet_layout_constraintBottom_toTopOf",
+          "ConstraintLayout_Layout_layout_constraintWidth_default",
+          "ConstraintSet_android_layout_marginStart",
+          "ConstraintSet_android_layout_width",
+          "ConstraintSet_layout_constraintWidth_default",
+          "ConstraintLayout_Layout_layout_constraintBottom_toTopOf",
+          "ConstraintLayout_Layout_layout_constraintStart_toEndOf",
+          "ConstraintLayout_Layout_layout_constraintHorizontal_weight",
+          "ConstraintSet_android_layout_marginRight",
+          "ConstraintSet_layout_constraintBottom_toBottomOf",
+          "ConstraintSet_android_scaleX",
+          "ConstraintSet_android_scaleY",
+          "ConstraintLayout_Layout_layout_constraintRight_toRightOf",
+          "ConstraintSet_layout_constraintGuide_percent",
+          "ConstraintLayout_Layout_layout_constraintLeft_creator",
+          "ConstraintLayout_Layout_layout_constraintTop_creator",
+          "ConstraintSet_layout_constraintTop_toTopOf",
+          "ConstraintLayout_Layout_layout_goneMarginRight",
+          "ConstraintSet_layout_constraintEnd_toEndOf",
+          "ConstraintLayout_Layout_layout_constraintHeight_max",
+          "ConstraintLayout_Layout_constraintSet",
+          "ConstraintLayout_Layout_layout_constraintHeight_min",
+          "ConstraintLayout_Layout_layout_goneMarginStart",
+          "ConstraintSet_android_elevation",
+          "ConstraintSet_layout_constraintTop_toBottomOf",
+          "ConstraintLayout_Layout_layout_constraintLeft_toRightOf",
+          "ConstraintLayout_Layout_layout_constraintStart_toStartOf",
+          "ConstraintSet_layout_constraintHorizontal_chainStyle",
+          "ConstraintLayout_Layout_android_maxWidth",
+          "ConstraintLayout_Layout_layout_constraintHorizontal_bias",
+          "ConstraintLayout_Layout_layout_constraintHeight_default",
+          "ConstraintSet_layout_constraintRight_toLeftOf",
+          "ConstraintLayout_Layout_android_minWidth",
+          "ConstraintSet_layout_constraintVertical_chainStyle",
+          "ConstraintLayout_Layout_layout_constraintGuide_end",
+          "ConstraintSet_layout_goneMarginBottom"
+        ]
+      },
+      "android/support/v4/view/ViewPager": {
+        "androidx/widget/ViewPager": [
+          "SCROLL_STATE_DRAGGING",
+          "MIN_FLING_VELOCITY",
+          "LAYOUT_ATTRS",
+          "DRAW_ORDER_DEFAULT",
+          "DEFAULT_OFFSCREEN_PAGES",
+          "USE_CACHE",
+          "INVALID_POINTER",
+          "sPositionComparator",
+          "SCROLL_STATE_IDLE",
+          "TAG",
+          "DEBUG",
+          "MAX_SETTLE_DURATION",
+          "SCROLL_STATE_SETTLING",
+          "DEFAULT_GUTTER_SIZE",
+          "sInterpolator",
+          "MIN_DISTANCE_FOR_FLING",
+          "DRAW_ORDER_REVERSE",
+          "CLOSE_ENOUGH",
+          "DRAW_ORDER_FORWARD",
+          "COMPARATOR"
+        ]
+      },
+      "android/support/v4/media/session/MediaSessionCompat$MediaSessionImplBase$MessageHandler": {
+        "androidx/media/session/MediaSessionCompat$MediaSessionImplBase$MessageHandler": [
+          "MSG_CUSTOM_ACTION",
+          "MSG_SET_VOLUME",
+          "MSG_SET_SHUFFLE_MODE",
+          "MSG_PREPARE",
+          "MSG_COMMAND",
+          "MSG_REMOVE_QUEUE_ITEM",
+          "MSG_RATE_EXTRA",
+          "MSG_PREPARE_SEARCH",
+          "MSG_FAST_FORWARD",
+          "MSG_PREPARE_URI",
+          "MSG_PAUSE",
+          "MSG_PLAY_URI",
+          "MSG_STOP",
+          "MSG_SKIP_TO_ITEM",
+          "MSG_ADD_QUEUE_ITEM",
+          "MSG_RATE",
+          "MSG_PLAY",
+          "MSG_NEXT",
+          "MSG_REMOVE_QUEUE_ITEM_AT",
+          "MSG_SET_REPEAT_MODE",
+          "MSG_PREVIOUS",
+          "MSG_SEEK_TO",
+          "MSG_PLAY_SEARCH",
+          "MSG_PLAY_MEDIA_ID",
+          "MSG_MEDIA_BUTTON",
+          "KEYCODE_MEDIA_PAUSE",
+          "MSG_PREPARE_MEDIA_ID",
+          "KEYCODE_MEDIA_PLAY",
+          "MSG_ADD_QUEUE_ITEM_AT",
+          "MSG_SET_CAPTIONING_ENABLED",
+          "MSG_ADJUST_VOLUME",
+          "MSG_REWIND"
+        ]
+      },
+      "android/support/design/R$dimen": {
+        "androidx/design/R$dimen": [
+          "design_bottom_navigation_item_min_width",
+          "design_tab_scrollable_min_width",
+          "design_tab_text_size_2line",
+          "design_bottom_sheet_peek_height_min",
+          "design_fab_size_mini",
+          "design_snackbar_padding_vertical_2lines",
+          "design_snackbar_padding_vertical",
+          "design_bottom_navigation_item_max_width",
+          "design_bottom_navigation_active_text_size",
+          "design_navigation_separator_vertical_padding",
+          "design_navigation_icon_size",
+          "design_bottom_navigation_text_size",
+          "design_fab_image_size",
+          "design_bottom_navigation_height",
+          "design_bottom_navigation_margin",
+          "design_fab_size_normal",
+          "design_bottom_navigation_active_item_max_width",
+          "design_bottom_navigation_shadow_height"
+        ]
+      },
+      "android/support/multidex/MultiDex": {
+        "androidx/multidex/MultiDex": [
+          "installedApk",
+          "CODE_CACHE_SECONDARY_FOLDER_NAME",
+          "CODE_CACHE_NAME",
+          "VM_WITH_MULTIDEX_VERSION_MAJOR",
+          "TAG",
+          "NO_KEY_PREFIX",
+          "IS_VM_MULTIDEX_CAPABLE",
+          "VM_WITH_MULTIDEX_VERSION_MINOR",
+          "MIN_SDK_VERSION",
+          "MAX_SUPPORTED_SDK_VERSION",
+          "OLD_SECONDARY_FOLDER_NAME"
+        ]
+      },
+      "android/support/constraint/ConstraintSet$Constraint": {
+        "androidx/constraint/ConstraintSet$Constraint": [
+          "guidePercent",
+          "topToTop",
+          "guideEnd",
+          "heightDefault",
+          "topMargin",
+          "goneRightMargin",
+          "goneTopMargin",
+          "visibility",
+          "elevation",
+          "dimensionRatio",
+          "startToStart",
+          "widthMax",
+          "applyElevation",
+          "widthMin",
+          "verticalWeight",
+          "endToStart",
+          "bottomToTop",
+          "rotationY",
+          "rotationX",
+          "horizontalBias",
+          "translationY",
+          "translationZ",
+          "translationX",
+          "transformPivotX",
+          "transformPivotY",
+          "leftMargin",
+          "horizontalChainStyle",
+          "orientation",
+          "leftToRight",
+          "endMargin",
+          "verticalBias",
+          "heightMax",
+          "scaleX",
+          "scaleY",
+          "rightMargin",
+          "leftToLeft",
+          "heightMin",
+          "endToEnd",
+          "topToBottom",
+          "startMargin",
+          "goneLeftMargin",
+          "bottomToBottom",
+          "editorAbsoluteY",
+          "editorAbsoluteX",
+          "baselineToBaseline",
+          "horizontalWeight",
+          "guideBegin",
+          "bottomMargin",
+          "widthDefault",
+          "startToEnd",
+          "rightToRight",
+          "alpha",
+          "UNSET",
+          "goneStartMargin",
+          "rightToLeft",
+          "goneBottomMargin",
+          "verticalChainStyle",
+          "goneEndMargin"
+        ]
+      },
+      "android/support/compat/R$id": {
+        "androidx/compat/R$id": [
+          "right_icon",
+          "action_container",
+          "icon",
+          "notification_background",
+          "line1",
+          "line3",
+          "text",
+          "action_divider",
+          "time",
+          "action_text",
+          "right_side",
+          "notification_main_column",
+          "action_image",
+          "tag_transition_group",
+          "title",
+          "text2",
+          "actions",
+          "notification_main_column_container",
+          "info",
+          "chronometer"
+        ]
+      },
+      "android/support/v7/widget/helper/ItemTouchHelper": {
+        "androidx/widget/helper/ItemTouchHelper": [
+          "DOWN",
+          "DEBUG",
+          "PIXELS_PER_SECOND",
+          "ACTION_STATE_SWIPE",
+          "ANIMATION_TYPE_SWIPE_CANCEL",
+          "END",
+          "UP",
+          "START",
+          "ANIMATION_TYPE_SWIPE_SUCCESS",
+          "ANIMATION_TYPE_DRAG",
+          "LEFT",
+          "ACTIVE_POINTER_ID_NONE",
+          "RIGHT",
+          "TAG",
+          "ACTION_STATE_DRAG",
+          "ACTION_MODE_IDLE_MASK",
+          "ACTION_MODE_SWIPE_MASK",
+          "DIRECTION_FLAG_COUNT",
+          "ACTION_MODE_DRAG_MASK",
+          "ACTION_STATE_IDLE"
+        ]
+      },
+      "android/support/graphics/drawable/VectorDrawableCompat": {
+        "androidx/graphics/drawable/VectorDrawableCompat": [
+          "LINECAP_ROUND",
+          "SHAPE_VECTOR",
+          "LOGTAG",
+          "DEFAULT_TINT_MODE",
+          "SHAPE_CLIP_PATH",
+          "LINECAP_SQUARE",
+          "LINEJOIN_MITER",
+          "LINEJOIN_ROUND",
+          "LINEJOIN_BEVEL",
+          "LINECAP_BUTT",
+          "SHAPE_PATH",
+          "MAX_CACHED_BITMAP_SIZE",
+          "DBG_VECTOR_DRAWABLE",
+          "SHAPE_GROUP"
+        ]
+      },
+      "android/support/v14/preference/R$styleable": {
+        "androidx/preference/R$styleable": [
+          "SwitchPreference_switchTextOff",
+          "SwitchPreference_switchTextOn",
+          "SwitchPreference_disableDependentsState",
+          "PreferenceFragment",
+          "SwitchPreference_android_summaryOn",
+          "PreferenceFragment_android_dividerHeight",
+          "SwitchPreference_android_switchTextOff",
+          "SwitchPreference_android_summaryOff",
+          "PreferenceFragment_allowDividerAfterLastItem",
+          "SwitchPreference",
+          "SwitchPreference_summaryOff",
+          "SwitchPreference_summaryOn",
+          "SwitchPreference_android_switchTextOn",
+          "PreferenceFragment_android_divider",
+          "SwitchPreference_android_disableDependentsState",
+          "PreferenceFragment_android_layout"
+        ]
+      },
+      "android/support/v7/app/MediaRouteControllerDialog": {
+        "androidx/app/MediaRouteControllerDialog": [
+          "BUTTON_DISCONNECT_RES_ID",
+          "DEBUG",
+          "BUTTON_NEUTRAL_RES_ID",
+          "VOLUME_UPDATE_DELAY_MILLIS",
+          "CONNECTION_TIMEOUT_MILLIS",
+          "BUTTON_STOP_RES_ID",
+          "TAG"
+        ]
+      },
+      "android/support/v17/leanback/widget/picker/PickerUtility": {
+        "androidx/leanback/widget/picker/PickerUtility": [
+          "SUPPORTS_BEST_DATE_TIME_PATTERN"
+        ]
+      },
+      "android/support/wear/widget/drawer/WearableActionDrawerView$TitleViewHolder": {
+        "androidx/wear/widget/drawer/WearableActionDrawerView$TitleViewHolder": [
+          "textView",
+          "view"
+        ]
+      },
+      "android/support/customtabs/CustomTabsIntent": {
+        "androidx/browser/customtabs/CustomTabsIntent": [
+          "TOOLBAR_ACTION_BUTTON_ID",
+          "EXTRA_ACTION_BUTTON_BUNDLE",
+          "EXTRA_EXIT_ANIMATION_BUNDLE",
+          "EXTRA_MENU_ITEMS",
+          "EXTRA_CLOSE_BUTTON_ICON",
+          "startAnimationBundle",
+          "KEY_ICON",
+          "EXTRA_SESSION",
+          "EXTRA_DEFAULT_SHARE_MENU_ITEM",
+          "MAX_TOOLBAR_ITEMS",
+          "intent",
+          "EXTRA_SECONDARY_TOOLBAR_COLOR",
+          "KEY_ID",
+          "EXTRA_TOOLBAR_COLOR",
+          "SHOW_PAGE_TITLE",
+          "EXTRA_TITLE_VISIBILITY_STATE",
+          "EXTRA_TOOLBAR_ITEMS",
+          "EXTRA_TINT_ACTION_BUTTON",
+          "NO_TITLE",
+          "KEY_DESCRIPTION",
+          "EXTRA_ENABLE_INSTANT_APPS",
+          "EXTRA_REMOTEVIEWS_PENDINGINTENT",
+          "EXTRA_REMOTEVIEWS_CLICKED_ID",
+          "KEY_MENU_ITEM_TITLE",
+          "EXTRA_ENABLE_URLBAR_HIDING",
+          "KEY_PENDING_INTENT",
+          "EXTRA_REMOTEVIEWS",
+          "EXTRA_USER_OPT_OUT_FROM_CUSTOM_TABS",
+          "EXTRA_REMOTEVIEWS_VIEW_IDS"
+        ]
+      },
+      "android/support/wear/R$styleable": {
+        "androidx/wear/R$styleable": [
+          "PageIndicatorView_wsPageIndicatorDotFadeInDuration",
+          "CircularProgressLayout_strokeWidth",
+          "WearableActionDrawerView",
+          "WearableRecyclerView_circularScrollingGestureEnabled",
+          "PageIndicatorView_wsPageIndicatorDotColor",
+          "PageIndicatorView",
+          "WearableActionDrawerView_actionMenu",
+          "PageIndicatorView_wsPageIndicatorDotFadeOutDelay",
+          "PageIndicatorView_wsPageIndicatorDotRadius",
+          "PageIndicatorView_wsPageIndicatorDotRadiusSelected",
+          "RoundedDrawable",
+          "WearableNavigationDrawerView_navigationStyle",
+          "CircledImageView_img_horizontal_offset_percentage",
+          "CircularProgressLayout",
+          "CircledImageView_img_tint",
+          "WearableActionDrawerView_showOverflowInPeek",
+          "CircledImageView_background_border_cap",
+          "CircledImageView_img_circle_percentage",
+          "BoxInsetLayout_Layout",
+          "CircledImageView_background_radius_pressed_percent",
+          "WearableActionDrawerView_drawerTitle",
+          "WearableRecyclerView",
+          "PageIndicatorView_wsPageIndicatorDotColorSelected",
+          "CircledImageView_img_padding",
+          "WearableDrawerView_android_elevation",
+          "RoundedDrawable_backgroundColor",
+          "PageIndicatorView_wsPageIndicatorDotShadowDx",
+          "PageIndicatorView_wsPageIndicatorDotShadowDy",
+          "CircularProgressLayout_backgroundColor",
+          "CircledImageView_background_radius_pressed",
+          "WearableDrawerView",
+          "BoxInsetLayout_Layout_boxedEdges",
+          "RoundedDrawable_radius",
+          "CircularProgressLayout_indeterminate",
+          "WearableDrawerView_peekView",
+          "WearableDrawerView_android_background",
+          "WearableDrawerView_enableAutoPeek",
+          "CircledImageView",
+          "PageIndicatorView_wsPageIndicatorDotFadeOutDuration",
+          "CircledImageView_background_shadow_width",
+          "CircledImageView_android_src",
+          "CircledImageView_background_radius_percent",
+          "PageIndicatorView_wsPageIndicatorDotShadowRadius",
+          "WearableRecyclerView_scrollDegreesPerScreen",
+          "CircledImageView_background_color",
+          "CircledImageView_clip_dimen",
+          "CircledImageView_background_radius",
+          "PageIndicatorView_wsPageIndicatorDotShadowColor",
+          "RoundedDrawable_clipEnabled",
+          "RoundedDrawable_android_src",
+          "WearableRecyclerView_bezelWidth",
+          "CircularProgressLayout_colorSchemeColors",
+          "WearableDrawerView_drawerContent",
+          "CircledImageView_background_border_color",
+          "WearableNavigationDrawerView",
+          "PageIndicatorView_wsPageIndicatorDotSpacing",
+          "CircledImageView_background_border_width",
+          "PageIndicatorView_wsPageIndicatorDotFadeWhenIdle"
+        ]
+      },
+      "android/support/v17/leanback/widget/PlaybackControlsRow$ShuffleAction": {
+        "androidx/leanback/widget/PlaybackControlsRow$ShuffleAction": [
+          "INDEX_ON",
+          "INDEX_OFF",
+          "ON",
+          "OFF"
+        ]
+      },
+      "android/support/v7/widget/TooltipCompatHandler": {
+        "androidx/widget/TooltipCompatHandler": [
+          "LONG_CLICK_HIDE_TIMEOUT_MS",
+          "sActiveHandler",
+          "HOVER_HIDE_TIMEOUT_MS",
+          "TAG",
+          "HOVER_HIDE_TIMEOUT_SHORT_MS"
+        ]
+      },
+      "android/support/v7/appcompat/BuildConfig": {
+        "androidx/appcompat/BuildConfig": [
+          "APPLICATION_ID",
+          "FLAVOR",
+          "DEBUG",
+          "VERSION_CODE",
+          "VERSION_NAME",
+          "BUILD_TYPE"
+        ]
+      },
+      "android/support/v4/media/AudioAttributesCompat$AudioManagerHidden": {
+        "androidx/media/AudioAttributesCompat$AudioManagerHidden": [
+          "STREAM_TTS",
+          "STREAM_BLUETOOTH_SCO",
+          "STREAM_SYSTEM_ENFORCED",
+          "STREAM_ACCESSIBILITY"
+        ]
+      },
+      "android/support/v17/leanback/widget/CursorObjectAdapter": {
+        "androidx/leanback/widget/CursorObjectAdapter": [
+          "CACHE_SIZE"
+        ]
+      },
+      "android/support/v7/mediarouter/R$dimen": {
+        "androidx/mediarouter/R$dimen": [
+          "mr_controller_volume_group_list_padding_top",
+          "mr_controller_volume_group_list_max_height",
+          "mr_controller_volume_group_list_item_height",
+          "mr_controller_volume_group_list_item_icon_size",
+          "mr_dialog_fixed_width_major",
+          "mr_dialog_fixed_width_minor"
+        ]
+      },
+      "android/support/transition/Transition": {
+        "androidx/transition/Transition": [
+          "MATCH_INSTANCE",
+          "MATCH_LAST",
+          "MATCH_FIRST",
+          "sRunningAnimators",
+          "MATCH_ID_STR",
+          "DBG",
+          "MATCH_INSTANCE_STR",
+          "MATCH_ITEM_ID",
+          "DEFAULT_MATCH_ORDER",
+          "MATCH_NAME_STR",
+          "MATCH_NAME",
+          "STRAIGHT_PATH_MOTION",
+          "MATCH_ITEM_ID_STR",
+          "MATCH_ID",
+          "LOG_TAG"
+        ]
+      },
+      "android/support/design/widget/CoordinatorLayout": {
+        "androidx/widget/CoordinatorLayout": [
+          "EVENT_VIEW_REMOVED",
+          "sConstructors",
+          "TOP_SORTED_CHILDREN_COMPARATOR",
+          "TYPE_ON_TOUCH",
+          "sRectPool",
+          "CONSTRUCTOR_PARAMS",
+          "TAG",
+          "EVENT_NESTED_SCROLL",
+          "WIDGET_PACKAGE_NAME",
+          "EVENT_PRE_DRAW",
+          "TYPE_ON_INTERCEPT"
+        ]
+      },
+      "android/support/constraint/ConstraintSet": {
+        "androidx/constraint/ConstraintSet": [
+          "CHAIN_SPREAD_INSIDE",
+          "UNUSED",
+          "GONE_LEFT_MARGIN",
+          "WIDTH_MAX",
+          "DIMENSION_RATIO",
+          "WIDTH_MIN",
+          "GUIDE_BEGIN",
+          "START_TO_START",
+          "ORIENTATION",
+          "RIGHT_TO_LEFT",
+          "VERTICAL",
+          "GUIDE_PERCENT",
+          "GONE_END_MARGIN",
+          "CHAIN_SPREAD",
+          "LAYOUT_HEIGHT",
+          "HORIZONTAL_STYLE",
+          "TOP_MARGIN",
+          "TRANSLATION_X",
+          "TRANSLATION_Z",
+          "TRANSLATION_Y",
+          "ELEVATION",
+          "EDITOR_ABSOLUTE_Y",
+          "EDITOR_ABSOLUTE_X",
+          "END_TO_START",
+          "DEBUG",
+          "MATCH_CONSTRAINT",
+          "BOTTOM_TO_TOP",
+          "RIGHT_MARGIN",
+          "TRANSFORM_PIVOT_Y",
+          "TRANSFORM_PIVOT_X",
+          "WRAP_CONTENT",
+          "BASELINE",
+          "HORIZONTAL_BIAS",
+          "SCALE_Y",
+          "SCALE_X",
+          "VISIBILITY_FLAGS",
+          "CHAIN_PACKED",
+          "VERTICAL_STYLE",
+          "mapToConstant",
+          "HORIZONTAL_GUIDELINE",
+          "LEFT",
+          "START_MARGIN",
+          "PARENT_ID",
+          "GUIDE_END",
+          "END_TO_END",
+          "TOP",
+          "LAYOUT_VISIBILITY",
+          "GONE_BOTTOM_MARGIN",
+          "LEFT_MARGIN",
+          "RIGHT",
+          "GONE_TOP_MARGIN",
+          "VERTICAL_GUIDELINE",
+          "START",
+          "GONE_RIGHT_MARGIN",
+          "TAG",
+          "GONE_START_MARGIN",
+          "HEIGHT_MIN",
+          "MATCH_CONSTRAINT_WRAP",
+          "UNSET",
+          "HEIGHT_MAX",
+          "END",
+          "BASELINE_TO_BASELINE",
+          "LAYOUT_WIDTH",
+          "VERTICAL_BIAS",
+          "HORIZONTAL",
+          "MATCH_CONSTRAINT_SPREAD",
+          "ALPHA",
+          "TOP_TO_TOP",
+          "RIGHT_TO_RIGHT",
+          "TOP_TO_BOTTOM",
+          "BOTTOM",
+          "LEFT_TO_LEFT",
+          "VIEW_ID",
+          "INVISIBLE",
+          "VERTICAL_WEIGHT",
+          "VISIBLE",
+          "HEIGHT_DEFAULT",
+          "WIDTH_DEFAULT",
+          "LEFT_TO_RIGHT",
+          "END_MARGIN",
+          "BOTTOM_MARGIN",
+          "HORIZONTAL_WEIGHT",
+          "START_TO_END",
+          "BOTTOM_TO_BOTTOM",
+          "GONE",
+          "ROTATION_X",
+          "ROTATION_Y"
+        ]
+      },
+      "android/support/v4/app/FrameMetricsAggregator$FrameMetricsApi24Impl": {
+        "androidx/app/FrameMetricsAggregator$FrameMetricsApi24Impl": [
+          "NANOS_PER_MS",
+          "NANOS_ROUNDING_VALUE",
+          "sHandlerThread",
+          "sHandler"
+        ]
+      },
+      "android/support/v4/view/GestureDetectorCompat$GestureDetectorCompatImplBase": {
+        "androidx/view/GestureDetectorCompat$GestureDetectorCompatImplBase": [
+          "LONGPRESS_TIMEOUT",
+          "TAP_TIMEOUT",
+          "DOUBLE_TAP_TIMEOUT",
+          "LONG_PRESS",
+          "TAP",
+          "SHOW_PRESS"
+        ]
+      },
+      "android/support/multidex/MultiDexExtractor": {
+        "androidx/multidex/MultiDexExtractor": [
+          "KEY_DEX_TIME",
+          "MAX_EXTRACT_ATTEMPTS",
+          "TAG",
+          "DEX_PREFIX",
+          "DEX_SUFFIX",
+          "EXTRACTED_NAME_EXT",
+          "KEY_CRC",
+          "KEY_DEX_CRC",
+          "NO_VALUE",
+          "LOCK_FILENAME",
+          "KEY_TIME_STAMP",
+          "EXTRACTED_SUFFIX",
+          "BUFFER_SIZE",
+          "KEY_DEX_NUMBER",
+          "PREFS_FILE"
+        ]
+      },
+      "android/support/v4/view/accessibility/AccessibilityNodeInfoCompat$AccessibilityActionCompat": {
+        "androidx/view/accessibility/AccessibilityNodeInfoCompat$AccessibilityActionCompat": [
+          "ACTION_FOCUS",
+          "ACTION_SCROLL_BACKWARD",
+          "ACTION_SCROLL_DOWN",
+          "ACTION_CONTEXT_CLICK",
+          "ACTION_CLEAR_FOCUS",
+          "ACTION_ACCESSIBILITY_FOCUS",
+          "ACTION_PASTE",
+          "ACTION_SET_PROGRESS",
+          "ACTION_EXPAND",
+          "ACTION_NEXT_HTML_ELEMENT",
+          "ACTION_SCROLL_UP",
+          "ACTION_SCROLL_FORWARD",
+          "ACTION_CLEAR_SELECTION",
+          "ACTION_LONG_CLICK",
+          "ACTION_SET_TEXT",
+          "ACTION_NEXT_AT_MOVEMENT_GRANULARITY",
+          "ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY",
+          "ACTION_SHOW_ON_SCREEN",
+          "ACTION_COLLAPSE",
+          "ACTION_COPY",
+          "ACTION_SCROLL_LEFT",
+          "ACTION_PREVIOUS_HTML_ELEMENT",
+          "ACTION_SET_SELECTION",
+          "ACTION_CLEAR_ACCESSIBILITY_FOCUS",
+          "ACTION_SCROLL_TO_POSITION",
+          "ACTION_DISMISS",
+          "ACTION_CLICK",
+          "ACTION_CUT",
+          "ACTION_SCROLL_RIGHT",
+          "ACTION_SELECT"
+        ]
+      },
+      "android/support/v4/graphics/drawable/DrawableWrapperApi21": {
+        "androidx/graphics/drawable/DrawableWrapperApi21": [
+          "sIsProjectedDrawableMethod",
+          "TAG"
+        ]
+      },
+      "android/support/v7/media/MediaRouter$RouteInfo": {
+        "androidx/media/MediaRouter$RouteInfo": [
+          "PRESENTATION_DISPLAY_ID_NONE",
+          "SYSTEM_MEDIA_ROUTE_PROVIDER_PACKAGE_NAME",
+          "PLAYBACK_TYPE_REMOTE",
+          "CHANGE_VOLUME",
+          "DEVICE_TYPE_BLUETOOTH",
+          "CHANGE_PRESENTATION_DISPLAY",
+          "CHANGE_GENERAL",
+          "CONNECTION_STATE_DISCONNECTED",
+          "DEVICE_TYPE_TV",
+          "PLAYBACK_VOLUME_VARIABLE",
+          "CONNECTION_STATE_CONNECTING",
+          "PLAYBACK_TYPE_LOCAL",
+          "DEVICE_TYPE_UNKNOWN",
+          "CONNECTION_STATE_CONNECTED",
+          "PLAYBACK_VOLUME_FIXED",
+          "DEVICE_TYPE_SPEAKER"
+        ]
+      },
+      "android/support/v17/leanback/widget/picker/TimePicker": {
+        "androidx/leanback/widget/picker/TimePicker": [
+          "TAG",
+          "HOURS_IN_HALF_DAY",
+          "AM_INDEX",
+          "PM_INDEX"
+        ]
+      },
+      "android/support/v7/widget/RecyclerView": {
+        "androidx/widget/RecyclerView": [
+          "LAYOUT_MANAGER_CONSTRUCTOR_SIGNATURE",
+          "TRACE_ON_DATA_SET_CHANGE_LAYOUT_TAG",
+          "NO_ID",
+          "NO_POSITION",
+          "DISPATCH_TEMP_DETACH",
+          "TRACE_SCROLL_TAG",
+          "SCROLL_STATE_DRAGGING",
+          "TRACE_PREFETCH_TAG",
+          "INVALID_POINTER",
+          "MAX_SCROLL_DURATION",
+          "TOUCH_SLOP_PAGING",
+          "TOUCH_SLOP_DEFAULT",
+          "VERBOSE_TRACING",
+          "TRACE_CREATE_VIEW_TAG",
+          "ALLOW_SIZE_IN_UNSPECIFIED_SPEC",
+          "FORCE_ABS_FOCUS_SEARCH_DIRECTION",
+          "TRACE_BIND_VIEW_TAG",
+          "HORIZONTAL",
+          "ALLOW_THREAD_GAP_WORK",
+          "DEBUG",
+          "TRACE_NESTED_PREFETCH_TAG",
+          "SCROLL_STATE_SETTLING",
+          "POST_UPDATES_ON_ANIMATION",
+          "sQuinticInterpolator",
+          "VERTICAL",
+          "CLIP_TO_PADDING_ATTR",
+          "FOREVER_NS",
+          "IGNORE_DETACHED_FOCUSED_CHILD",
+          "INVALID_TYPE",
+          "SCROLL_STATE_IDLE",
+          "NESTED_SCROLLING_ATTRS",
+          "TAG",
+          "TRACE_ON_LAYOUT_TAG",
+          "FORCE_INVALIDATE_DISPLAY_LIST",
+          "TRACE_HANDLE_ADAPTER_UPDATES_TAG"
+        ]
+      },
+      "android/support/v4/view/PagerAdapter": {
+        "androidx/widget/PagerAdapter": [
+          "POSITION_NONE",
+          "POSITION_UNCHANGED"
+        ]
+      },
+      "android/support/compat/BuildConfig": {
+        "androidx/compat/BuildConfig": [
+          "FLAVOR",
+          "APPLICATION_ID",
+          "VERSION_CODE",
+          "DEBUG",
+          "VERSION_NAME",
+          "BUILD_TYPE"
+        ]
+      },
+      "android/support/v4/view/accessibility/AccessibilityNodeInfoCompat": {
+        "androidx/view/accessibility/AccessibilityNodeInfoCompat": [
+          "ACTION_PREVIOUS_HTML_ELEMENT",
+          "ACTION_ARGUMENT_SELECTION_END_INT",
+          "ACTION_SET_SELECTION",
+          "MOVEMENT_GRANULARITY_CHARACTER",
+          "MOVEMENT_GRANULARITY_LINE",
+          "ACTION_SET_TEXT",
+          "MOVEMENT_GRANULARITY_PARAGRAPH",
+          "ACTION_SELECT",
+          "FOCUS_ACCESSIBILITY",
+          "ACTION_DISMISS",
+          "ACTION_EXPAND",
+          "ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE",
+          "ACTION_CLEAR_ACCESSIBILITY_FOCUS",
+          "ACTION_CUT",
+          "ACTION_PASTE",
+          "ACTION_ARGUMENT_ROW_INT",
+          "FOCUS_INPUT",
+          "ACTION_ARGUMENT_HTML_ELEMENT_STRING",
+          "MOVEMENT_GRANULARITY_PAGE",
+          "ACTION_ARGUMENT_PROGRESS_VALUE",
+          "ACTION_CLEAR_FOCUS",
+          "MOVEMENT_GRANULARITY_WORD",
+          "ACTION_SCROLL_BACKWARD",
+          "ROLE_DESCRIPTION_KEY",
+          "ACTION_FOCUS",
+          "ACTION_NEXT_HTML_ELEMENT",
+          "ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN",
+          "ACTION_CLEAR_SELECTION",
+          "ACTION_ARGUMENT_COLUMN_INT",
+          "ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY",
+          "ACTION_NEXT_AT_MOVEMENT_GRANULARITY",
+          "ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT",
+          "ACTION_ACCESSIBILITY_FOCUS",
+          "ACTION_CLICK",
+          "ACTION_ARGUMENT_SELECTION_START_INT",
+          "ACTION_SCROLL_FORWARD",
+          "ACTION_COPY",
+          "ACTION_COLLAPSE",
+          "ACTION_LONG_CLICK"
+        ]
+      },
+      "android/support/v7/preference/R$styleable": {
+        "androidx/preference/R$styleable": [
+          "SeekBarPreference",
+          "CheckBoxPreference_summaryOn",
+          "DialogPreference_android_dialogLayout",
+          "Preference_summary",
+          "Preference_widgetLayout",
+          "CheckBoxPreference_android_disableDependentsState",
+          "Preference_android_layout",
+          "Preference_android_selectable",
+          "Preference_android_enabled",
+          "DialogPreference_dialogLayout",
+          "Preference_dependency",
+          "Preference_iconSpaceReserved",
+          "Preference_fragment",
+          "ListPreference_android_entries",
+          "PreferenceGroup",
+          "SeekBarPreference_seekBarIncrement",
+          "Preference_android_iconSpaceReserved",
+          "Preference_selectable",
+          "PreferenceFragmentCompat",
+          "Preference_allowDividerAbove",
+          "PreferenceImageView",
+          "SwitchPreferenceCompat_switchTextOn",
+          "DialogPreference_android_dialogMessage",
+          "Preference",
+          "CheckBoxPreference_summaryOff",
+          "Preference_shouldDisableView",
+          "Preference_title",
+          "Preference_enabled",
+          "SwitchPreferenceCompat_android_switchTextOff",
+          "DialogPreference_android_dialogIcon",
+          "ListPreference_entries",
+          "MultiSelectListPreference",
+          "SeekBarPreference_adjustable",
+          "PreferenceImageView_maxHeight",
+          "SwitchPreferenceCompat_android_summaryOn",
+          "PreferenceFragmentCompat_allowDividerAfterLastItem",
+          "SwitchPreferenceCompat_disableDependentsState",
+          "DialogPreference",
+          "DialogPreference_positiveButtonText",
+          "PreferenceFragmentCompat_android_layout",
+          "Preference_android_icon",
+          "SwitchPreferenceCompat_summaryOff",
+          "DialogPreference_dialogMessage",
+          "DialogPreference_android_negativeButtonText",
+          "MultiSelectListPreference_entries",
+          "ListPreference_android_entryValues",
+          "Preference_defaultValue",
+          "CheckBoxPreference_android_summaryOff",
+          "PreferenceFragmentCompat_android_divider",
+          "Preference_android_shouldDisableView",
+          "SwitchPreferenceCompat_switchTextOff",
+          "SwitchPreferenceCompat",
+          "Preference_allowDividerBelow",
+          "Preference_android_persistent",
+          "BackgroundStyle",
+          "ListPreference",
+          "Preference_android_widgetLayout",
+          "SwitchPreferenceCompat_android_summaryOff",
+          "SwitchPreferenceCompat_android_switchTextOn",
+          "Preference_android_order",
+          "MultiSelectListPreference_android_entries",
+          "Preference_layout",
+          "SwitchPreferenceCompat_android_disableDependentsState",
+          "SwitchPreferenceCompat_summaryOn",
+          "Preference_android_summary",
+          "DialogPreference_dialogTitle",
+          "Preference_android_dependency",
+          "DialogPreference_android_dialogTitle",
+          "Preference_persistent",
+          "MultiSelectListPreference_android_entryValues",
+          "Preference_android_key",
+          "DialogPreference_android_positiveButtonText",
+          "Preference_android_title",
+          "Preference_android_singleLineTitle",
+          "DialogPreference_negativeButtonText",
+          "MultiSelectListPreference_entryValues",
+          "PreferenceFragmentCompat_android_dividerHeight",
+          "CheckBoxPreference_android_summaryOn",
+          "SeekBarPreference_min",
+          "BackgroundStyle_android_selectableItemBackground",
+          "Preference_android_fragment",
+          "DialogPreference_dialogIcon",
+          "Preference_icon",
+          "CheckBoxPreference_disableDependentsState",
+          "Preference_key",
+          "CheckBoxPreference",
+          "SeekBarPreference_android_max",
+          "Preference_singleLineTitle",
+          "SeekBarPreference_showSeekBarValue",
+          "PreferenceImageView_maxWidth",
+          "Preference_android_defaultValue",
+          "ListPreference_entryValues",
+          "PreferenceGroup_orderingFromXml",
+          "Preference_order"
+        ]
+      },
+      "android/support/media/tv/TvContractCompat$RecordedPrograms": {
+        "androidx/media/tv/TvContractCompat$RecordedPrograms": [
+          "CONTENT_URI",
+          "COLUMN_RECORDING_DURATION_MILLIS",
+          "COLUMN_CHANNEL_ID",
+          "COLUMN_BROADCAST_GENRE",
+          "COLUMN_RECORDING_DATA_BYTES",
+          "COLUMN_END_TIME_UTC_MILLIS",
+          "CONTENT_TYPE",
+          "COLUMN_RECORDING_DATA_URI",
+          "COLUMN_RECORDING_EXPIRE_TIME_UTC_MILLIS",
+          "COLUMN_INPUT_ID",
+          "COLUMN_START_TIME_UTC_MILLIS",
+          "CONTENT_ITEM_TYPE"
+        ]
+      },
+      "android/support/v17/leanback/app/PlaybackSupportFragment": {
+        "androidx/leanback/app/PlaybackSupportFragment": [
+          "IDLE",
+          "ANIMATION_MULTIPLIER",
+          "ANIMATING",
+          "BG_LIGHT",
+          "START_FADE_OUT",
+          "TAG",
+          "BG_DARK",
+          "BG_NONE",
+          "BUNDLE_CONTROL_VISIBLE_ON_CREATEVIEW",
+          "DEBUG"
+        ]
+      },
+      "android/support/v17/leanback/widget/ShadowOverlayHelper$Options": {
+        "androidx/leanback/widget/ShadowOverlayHelper$Options": [
+          "DEFAULT",
+          "roundedCornerRadius",
+          "dynamicShadowUnfocusedZ",
+          "dynamicShadowFocusedZ"
+        ]
+      },
+      "android/support/v17/leanback/widget/ShadowOverlayHelper": {
+        "androidx/leanback/widget/ShadowOverlayHelper": [
+          "SHADOW_NONE",
+          "SHADOW_STATIC",
+          "SHADOW_DYNAMIC"
+        ]
+      },
+      "android/support/v4/media/MediaBrowserServiceCompat": {
+        "androidx/media/MediaBrowserServiceCompat": [
+          "EPSILON",
+          "RESULT_ERROR",
+          "DEBUG",
+          "RESULT_FLAG_ON_SEARCH_NOT_IMPLEMENTED",
+          "RESULT_FLAG_ON_LOAD_ITEM_NOT_IMPLEMENTED",
+          "KEY_MEDIA_ITEM",
+          "TAG",
+          "SERVICE_INTERFACE",
+          "KEY_SEARCH_RESULTS",
+          "RESULT_OK",
+          "RESULT_PROGRESS_UPDATE",
+          "RESULT_FLAG_OPTION_NOT_HANDLED"
+        ]
+      },
+      "android/support/v13/app/FragmentPagerAdapter": {
+        "androidx/app/FragmentPagerAdapter": [
+          "DEBUG",
+          "TAG"
+        ]
+      },
+      "android/support/design/widget/BaseTransientBottomBar$BaseCallback": {
+        "androidx/design/widget/BaseTransientBottomBar$BaseCallback": [
+          "DISMISS_EVENT_MANUAL",
+          "DISMISS_EVENT_SWIPE",
+          "DISMISS_EVENT_TIMEOUT",
+          "DISMISS_EVENT_ACTION",
+          "DISMISS_EVENT_CONSECUTIVE"
+        ]
+      },
+      "android/support/v7/widget/GapWorker": {
+        "androidx/widget/GapWorker": [
+          "sGapWorker",
+          "sTaskComparator"
+        ]
+      },
+      "android/support/v7/preference/PreferenceManager": {
+        "androidx/preference/PreferenceManager": [
+          "STORAGE_DEFAULT",
+          "KEY_HAS_SET_DEFAULT_VALUES",
+          "STORAGE_DEVICE_PROTECTED"
+        ]
+      },
+      "android/support/v4/widget/SlidingPaneLayout$LayoutParams": {
+        "androidx/widget/SlidingPaneLayout$LayoutParams": [
+          "width",
+          "ATTRS",
+          "height",
+          "dimPaint",
+          "leftMargin",
+          "weight",
+          "slideable",
+          "rightMargin",
+          "dimWhenOffset"
+        ]
+      },
+      "android/support/v17/leanback/widget/GuidedAction": {
+        "androidx/leanback/widget/GuidedAction": [
+          "ACTION_ID_CONTINUE",
+          "PF_ENABLED",
+          "ACTION_ID_YES",
+          "ACTION_ID_OK",
+          "ACTION_ID_NO",
+          "PF_INFO_ONLY",
+          "ACTION_ID_FINISH",
+          "TAG",
+          "EDITING_TITLE",
+          "EDITING_DESCRIPTION",
+          "PF_MULTI_lINE_DESCRIPTION",
+          "PF_CHECKED",
+          "DEFAULT_CHECK_SET_ID",
+          "ACTION_ID_CURRENT",
+          "CHECKBOX_CHECK_SET_ID",
+          "EDITING_NONE",
+          "PF_AUTORESTORE",
+          "EDITING_ACTIVATOR_VIEW",
+          "PF_HAS_NEXT",
+          "ACTION_ID_NEXT",
+          "ACTION_ID_CANCEL",
+          "PF_FOCUSABLE",
+          "NO_CHECK_SET"
+        ]
+      },
+      "android/support/media/tv/TvContractCompat$PreviewPrograms": {
+        "androidx/media/tv/TvContractCompat$PreviewPrograms": [
+          "COLUMN_CHANNEL_ID",
+          "CONTENT_ITEM_TYPE",
+          "CONTENT_URI",
+          "CONTENT_TYPE",
+          "COLUMN_WEIGHT"
+        ]
+      },
+      "android/support/v17/leanback/widget/BaseCardView": {
+        "androidx/leanback/widget/BaseCardView": [
+          "CARD_TYPE_INFO_OVER",
+          "CARD_TYPE_INVALID",
+          "DEBUG",
+          "CARD_TYPE_INFO_UNDER",
+          "CARD_TYPE_MAIN_ONLY",
+          "LB_PRESSED_STATE_SET",
+          "CARD_REGION_VISIBLE_ALWAYS",
+          "CARD_REGION_VISIBLE_SELECTED",
+          "TAG",
+          "CARD_TYPE_INFO_UNDER_WITH_EXTRA",
+          "CARD_REGION_VISIBLE_ACTIVATED"
+        ]
+      },
+      "android/support/v7/mediarouter/R$string": {
+        "androidx/mediarouter/R$string": [
+          "mr_user_route_category_name",
+          "mr_controller_no_info_available",
+          "mr_system_route_name",
+          "mr_controller_disconnect",
+          "mr_cast_button_disconnected",
+          "mr_controller_casting_screen",
+          "mr_button_content_description",
+          "mr_controller_no_media_selected",
+          "mr_controller_expand_group",
+          "mr_controller_play",
+          "mr_controller_stop",
+          "mr_cast_button_connected",
+          "mr_controller_pause",
+          "mr_controller_collapse_group",
+          "mr_cast_button_connecting",
+          "mr_controller_stop_casting"
+        ]
+      },
+      "android/support/multidex/BuildConfig": {
+        "androidx/multidex/BuildConfig": [
+          "DEBUG",
+          "APPLICATION_ID",
+          "FLAVOR",
+          "VERSION_NAME",
+          "BUILD_TYPE",
+          "VERSION_CODE"
+        ]
+      },
+      "android/support/v4/text/util/LinkifyCompat$LinkSpec": {
+        "androidx/text/util/LinkifyCompat$LinkSpec": [
+          "end",
+          "url",
+          "frameworkAddedSpan",
+          "start"
+        ]
+      },
+      "android/support/transition/ArcMotion": {
+        "androidx/transition/ArcMotion": [
+          "DEFAULT_MIN_ANGLE_DEGREES",
+          "DEFAULT_MAX_ANGLE_DEGREES",
+          "DEFAULT_MAX_TANGENT"
+        ]
+      },
+      "android/support/v17/leanback/widget/TitleViewAdapter": {
+        "androidx/leanback/widget/TitleViewAdapter": [
+          "BRANDING_VIEW_VISIBLE",
+          "FULL_VIEW_VISIBLE",
+          "SEARCH_VIEW_VISIBLE"
+        ]
+      },
+      "android/support/v17/leanback/app/DetailsFragment": {
+        "androidx/leanback/app/DetailsFragment": [
+          "EVT_ON_CREATE",
+          "EVT_SWITCH_TO_VIDEO",
+          "STATE_ENTRANCE_INIT",
+          "STATE_SWITCH_TO_VIDEO_IN_ON_CREATE",
+          "STATE_START",
+          "EVT_DETAILS_ROW_LOADED",
+          "STATE_ENTER_TRANSITION_INIT",
+          "STATE_ENTER_TRANSITION_CANCEL",
+          "COND_TRANSITION_NOT_SUPPORTED",
+          "STATE_ENTRANCE_ON_PREPARED",
+          "EVT_NO_ENTER_TRANSITION",
+          "EVT_ON_CREATEVIEW",
+          "STATE_ENTER_TRANSITION_PENDING",
+          "EVT_ONSTART",
+          "STATE_ON_SAFE_START",
+          "DEBUG",
+          "EVT_ENTER_TRANSIITON_DONE",
+          "STATE_SET_ENTRANCE_START_STATE",
+          "STATE_ENTRANCE_PERFORM",
+          "STATE_ENTRANCE_COMPLETE",
+          "STATE_ENTER_TRANSITION_COMPLETE",
+          "STATE_ENTER_TRANSITION_ADDLISTENER",
+          "TAG"
+        ]
+      },
+      "android/support/v7/widget/FastScroller": {
+        "androidx/widget/FastScroller": [
+          "ANIMATION_STATE_OUT",
+          "ANIMATION_STATE_IN",
+          "SHOW_DURATION_MS",
+          "STATE_VISIBLE",
+          "DRAG_NONE",
+          "STATE_HIDDEN",
+          "ANIMATION_STATE_FADING_IN",
+          "STATE_DRAGGING",
+          "HIDE_DELAY_AFTER_VISIBLE_MS",
+          "DRAG_X",
+          "DRAG_Y",
+          "HIDE_DELAY_AFTER_DRAGGING_MS",
+          "HIDE_DURATION_MS",
+          "SCROLLBAR_FULL_OPAQUE",
+          "ANIMATION_STATE_FADING_OUT",
+          "PRESSED_STATE_SET",
+          "EMPTY_STATE_SET"
+        ]
+      },
+      "android/support/v7/media/MediaRouteDescriptor": {
+        "androidx/media/MediaRouteDescriptor": [
+          "KEY_VOLUME",
+          "KEY_PLAYBACK_TYPE",
+          "KEY_SETTINGS_INTENT",
+          "KEY_ID",
+          "KEY_ENABLED",
+          "KEY_MAX_CLIENT_VERSION",
+          "KEY_ICON_URI",
+          "KEY_DESCRIPTION",
+          "KEY_PLAYBACK_STREAM",
+          "KEY_GROUP_MEMBER_IDS",
+          "KEY_CONNECTION_STATE",
+          "KEY_EXTRAS",
+          "KEY_VOLUME_HANDLING",
+          "KEY_PRESENTATION_DISPLAY_ID",
+          "KEY_MIN_CLIENT_VERSION",
+          "KEY_CONTROL_FILTERS",
+          "KEY_CAN_DISCONNECT",
+          "KEY_DEVICE_TYPE",
+          "KEY_NAME",
+          "KEY_VOLUME_MAX",
+          "KEY_CONNECTING"
+        ]
+      },
+      "android/support/design/widget/BottomNavigationView": {
+        "androidx/design/widget/BottomNavigationView": [
+          "DISABLED_STATE_SET",
+          "CHECKED_STATE_SET",
+          "EMPTY_STATE_SET",
+          "MENU_PRESENTER_ID"
+        ]
+      },
+      "android/support/multidex/instrumentation/BuildConfig": {
+        "androidx/multidex/instrumentation/BuildConfig": [
+          "VERSION_NAME",
+          "VERSION_CODE",
+          "APPLICATION_ID",
+          "BUILD_TYPE",
+          "FLAVOR",
+          "DEBUG"
+        ]
+      },
+      "android/support/wear/widget/drawer/WearableActionDrawerView": {
+        "androidx/wear/widget/drawer/WearableActionDrawerView": [
+          "TAG"
+        ]
+      },
+      "android/support/v17/leanback/media/PlaybackBaseControlGlue": {
+        "androidx/leanback/media/PlaybackBaseControlGlue": [
+          "ACTION_FAST_FORWARD",
+          "DEBUG",
+          "ACTION_CUSTOM_LEFT_FIRST",
+          "ACTION_SKIP_TO_PREVIOUS",
+          "TAG",
+          "ACTION_SKIP_TO_NEXT",
+          "ACTION_CUSTOM_RIGHT_FIRST",
+          "ACTION_PLAY_PAUSE",
+          "ACTION_REPEAT",
+          "ACTION_REWIND",
+          "ACTION_SHUFFLE"
+        ]
+      },
+      "android/support/v4/app/AppLaunchChecker": {
+        "androidx/app/AppLaunchChecker": [
+          "KEY_STARTED_FROM_LAUNCHER",
+          "SHARED_PREFS_NAME"
+        ]
+      },
+      "android/support/transition/Styleable$ArcMotion": {
+        "androidx/transition/Styleable$ArcMotion": [
+          "MAXIMUM_ANGLE",
+          "MINIMUM_VERTICAL_ANGLE",
+          "MINIMUM_HORIZONTAL_ANGLE"
+        ]
+      },
+      "android/support/v7/cardview/R$color": {
+        "androidx/cardview/R$color": [
+          "cardview_shadow_end_color",
+          "cardview_light_background",
+          "cardview_dark_background",
+          "cardview_shadow_start_color"
+        ]
+      },
+      "android/support/v17/leanback/app/BrowseFragment": {
+        "androidx/leanback/app/BrowseFragment": [
+          "TAG",
+          "EVT_SCREEN_DATA_READY",
+          "LB_HEADERS_BACKSTACK",
+          "STATE_ENTRANCE_PERFORM",
+          "IS_PAGE_ROW",
+          "ARG_TITLE",
+          "HEADERS_HIDDEN",
+          "HEADER_SHOW",
+          "EVT_MAIN_FRAGMENT_VIEW_CREATED",
+          "CURRENT_SELECTED_POSITION",
+          "DEBUG",
+          "EVT_HEADER_VIEW_CREATED",
+          "STATE_ENTRANCE_ON_PREPARED",
+          "HEADERS_ENABLED",
+          "ARG_HEADERS_STATE",
+          "STATE_ENTRANCE_ON_PREPARED_ON_CREATEVIEW",
+          "STATE_SET_ENTRANCE_START_STATE",
+          "HEADERS_DISABLED",
+          "HEADER_STACK_INDEX"
+        ]
+      },
+      "android/support/media/tv/TvContractCompat$ProgramColumns": {
+        "androidx/media/tv/TvContractCompat$ProgramColumns": [
+          "COLUMN_SHORT_DESCRIPTION",
+          "COLUMN_SEASON_DISPLAY_NUMBER",
+          "COLUMN_SEASON_TITLE",
+          "COLUMN_VIDEO_WIDTH",
+          "COLUMN_AUDIO_LANGUAGE",
+          "REVIEW_RATING_STYLE_STARS",
+          "COLUMN_EPISODE_TITLE",
+          "REVIEW_RATING_STYLE_THUMBS_UP_DOWN",
+          "COLUMN_INTERNAL_PROVIDER_FLAG2",
+          "COLUMN_INTERNAL_PROVIDER_FLAG3",
+          "COLUMN_INTERNAL_PROVIDER_FLAG4",
+          "COLUMN_INTERNAL_PROVIDER_FLAG1",
+          "COLUMN_TITLE",
+          "COLUMN_POSTER_ART_URI",
+          "COLUMN_VERSION_NUMBER",
+          "COLUMN_THUMBNAIL_URI",
+          "REVIEW_RATING_STYLE_PERCENTAGE",
+          "COLUMN_EPISODE_DISPLAY_NUMBER",
+          "COLUMN_SEARCHABLE",
+          "COLUMN_VIDEO_HEIGHT",
+          "COLUMN_CANONICAL_GENRE",
+          "COLUMN_CONTENT_RATING",
+          "COLUMN_REVIEW_RATING",
+          "COLUMN_REVIEW_RATING_STYLE",
+          "COLUMN_LONG_DESCRIPTION",
+          "COLUMN_INTERNAL_PROVIDER_DATA"
+        ]
+      },
+      "android/support/v4/graphics/PaintCompat": {
+        "androidx/graphics/PaintCompat": [
+          "TOFU_STRING",
+          "sRectThreadLocal",
+          "EM_STRING"
+        ]
+      },
+      "android/support/v4/view/accessibility/AccessibilityEventCompat": {
+        "androidx/view/accessibility/AccessibilityEventCompat": [
+          "CONTENT_CHANGE_TYPE_UNDEFINED",
+          "TYPE_ANNOUNCEMENT",
+          "TYPE_TOUCH_INTERACTION_END",
+          "CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION",
+          "TYPE_TOUCH_INTERACTION_START",
+          "TYPE_TOUCH_EXPLORATION_GESTURE_END",
+          "TYPE_VIEW_HOVER_ENTER",
+          "TYPE_VIEW_ACCESSIBILITY_FOCUSED",
+          "CONTENT_CHANGE_TYPE_TEXT",
+          "TYPE_WINDOW_CONTENT_CHANGED",
+          "TYPES_ALL_MASK",
+          "TYPE_TOUCH_EXPLORATION_GESTURE_START",
+          "TYPE_WINDOWS_CHANGED",
+          "TYPE_VIEW_CONTEXT_CLICKED",
+          "TYPE_VIEW_HOVER_EXIT",
+          "CONTENT_CHANGE_TYPE_SUBTREE",
+          "TYPE_ASSIST_READING_CONTEXT",
+          "TYPE_VIEW_TEXT_SELECTION_CHANGED",
+          "TYPE_GESTURE_DETECTION_START",
+          "TYPE_VIEW_SCROLLED",
+          "TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY",
+          "TYPE_GESTURE_DETECTION_END",
+          "TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED"
+        ]
+      },
+      "android/support/v17/leanback/R$attr": {
+        "androidx/leanback/R$attr": [
+          "guidedActionContentWidthWeightTwoPanels",
+          "onboardingTheme",
+          "playbackProgressPrimaryColor",
+          "guidedActionTitleMaxLines",
+          "guidedActionPressedAnimation",
+          "playbackMediaItemNumberViewFlipperLayout",
+          "imageCardViewStyle",
+          "playbackControlsActionIcons",
+          "browseTitleViewLayout",
+          "playbackControlsIconHighlightColor",
+          "guidedStepTheme",
+          "guidedActionTitleMinLines",
+          "defaultBrandColorDark",
+          "baseCardViewStyle",
+          "browseTitleViewStyle",
+          "guidedActionVerticalPadding",
+          "guidedActionDisabledChevronAlpha",
+          "searchOrbViewStyle",
+          "defaultBrandColor",
+          "guidedStepThemeFlag",
+          "rowHeaderStyle",
+          "guidedActionDescriptionMinLines",
+          "guidedActionUnpressedAnimation",
+          "guidedActionEnabledChevronAlpha"
+        ]
+      },
+      "android/support/media/ExifInterface$ExifTag": {
+        "androidx/media/ExifInterface$ExifTag": [
+          "number",
+          "primaryFormat",
+          "name",
+          "secondaryFormat"
+        ]
+      },
+      "android/support/v4/widget/ContentLoadingProgressBar": {
+        "androidx/widget/ContentLoadingProgressBar": [
+          "MIN_DELAY",
+          "MIN_SHOW_TIME"
+        ]
+      },
+      "android/support/transition/ViewGroupUtilsApi14": {
+        "androidx/transition/ViewGroupUtilsApi14": [
+          "TAG",
+          "LAYOUT_TRANSITION_CHANGING",
+          "sEmptyLayoutTransition",
+          "sCancelMethod",
+          "sLayoutSuppressedField",
+          "sCancelMethodFetched",
+          "sLayoutSuppressedFieldFetched"
+        ]
+      },
+      "android/support/design/internal/NavigationMenuPresenter$NavigationMenuAdapter": {
+        "androidx/design/internal/NavigationMenuPresenter$NavigationMenuAdapter": [
+          "STATE_ACTION_VIEWS",
+          "VIEW_TYPE_SEPARATOR",
+          "VIEW_TYPE_HEADER",
+          "VIEW_TYPE_SUBHEADER",
+          "VIEW_TYPE_NORMAL",
+          "STATE_CHECKED_ITEM"
+        ]
+      },
+      "android/support/v4/os/LocaleListHelper": {
+        "androidx/os/LocaleListHelper": [
+          "EN_LATN",
+          "LOCALE_EN_XA",
+          "sDefaultAdjustedLocaleList",
+          "sLock",
+          "sLastExplicitlySetLocaleList",
+          "sEmptyLocaleList",
+          "sEmptyList",
+          "NUM_PSEUDO_LOCALES",
+          "STRING_AR_XB",
+          "sDefaultLocaleList",
+          "sLastDefaultLocale",
+          "STRING_EN_XA",
+          "LOCALE_AR_XB"
+        ]
+      },
+      "android/support/v4/app/NotificationCompat$CarExtender": {
+        "androidx/app/NotificationCompat$CarExtender": [
+          "KEY_TIMESTAMP",
+          "EXTRA_LARGE_ICON",
+          "KEY_ON_READ",
+          "KEY_PARTICIPANTS",
+          "KEY_ON_REPLY",
+          "KEY_AUTHOR",
+          "KEY_REMOTE_INPUT",
+          "KEY_TEXT",
+          "KEY_MESSAGES",
+          "EXTRA_CAR_EXTENDER",
+          "EXTRA_COLOR",
+          "EXTRA_CONVERSATION"
+        ]
+      },
+      "android/support/wear/R$layout": {
+        "androidx/wear/R$layout": [
+          "ws_navigation_drawer_item_view",
+          "ws_single_page_nav_drawer_7_item",
+          "ws_single_page_nav_drawer_4_item",
+          "ws_action_drawer_item_view",
+          "ws_single_page_nav_drawer_2_item",
+          "ws_navigation_drawer_view",
+          "ws_single_page_nav_drawer_5_item",
+          "ws_wearable_drawer_view",
+          "ws_single_page_nav_drawer_3_item",
+          "ws_single_page_nav_drawer_peek_view",
+          "ws_action_drawer_title_view",
+          "ws_action_drawer_peek_view",
+          "ws_single_page_nav_drawer_1_item",
+          "ws_single_page_nav_drawer_6_item"
+        ]
+      },
+      "android/support/v4/content/pm/ActivityInfoCompat": {
+        "androidx/content/pm/ActivityInfoCompat": [
+          "CONFIG_UI_MODE"
+        ]
+      },
+      "android/support/v7/mediarouter/R$layout": {
+        "androidx/mediarouter/R$layout": [
+          "mr_chooser_dialog",
+          "mr_controller_material_dialog_b",
+          "mr_chooser_list_item",
+          "mr_controller_volume_item"
+        ]
+      },
+      "android/support/v7/app/AppCompatDelegateImplV9$PanelFeatureState": {
+        "androidx/app/AppCompatDelegateImplV9$PanelFeatureState": [
+          "x",
+          "y",
+          "frozenActionViewState",
+          "refreshDecorView",
+          "createdPanelView",
+          "frozenMenuState",
+          "qwertyMode",
+          "featureId",
+          "listPresenterContext",
+          "listMenuPresenter",
+          "decorView",
+          "isPrepared",
+          "wasLastOpen",
+          "isOpen",
+          "gravity",
+          "background",
+          "isHandled",
+          "windowAnimations",
+          "refreshMenuContent",
+          "shownPanelView",
+          "menu"
+        ]
+      },
+      "android/support/v4/graphics/TypefaceCompatBaseImpl": {
+        "androidx/graphics/TypefaceCompatBaseImpl": [
+          "TAG",
+          "CACHE_FILE_PREFIX"
+        ]
+      },
+      "android/support/media/tv/TvContractCompat$PreviewProgramColumns": {
+        "androidx/media/tv/TvContractCompat$PreviewProgramColumns": [
+          "COLUMN_DURATION_MILLIS",
+          "COLUMN_LIVE",
+          "COLUMN_OFFER_PRICE",
+          "AVAILABILITY_PURCHASED",
+          "COLUMN_INTERACTION_COUNT",
+          "INTERACTION_TYPE_FOLLOWERS",
+          "INTERACTION_TYPE_FANS",
+          "COLUMN_GENRE",
+          "INTERACTION_TYPE_LISTENS",
+          "AVAILABILITY_PAID_CONTENT",
+          "COLUMN_START_TIME_UTC_MILLIS",
+          "COLUMN_INTERACTION_TYPE",
+          "TYPE_MOVIE",
+          "TYPE_GAME",
+          "TYPE_ALBUM",
+          "TYPE_TRACK",
+          "COLUMN_POSTER_ART_ASPECT_RATIO",
+          "TYPE_EVENT",
+          "COLUMN_AVAILABILITY",
+          "COLUMN_BROWSABLE",
+          "COLUMN_INTENT_URI",
+          "COLUMN_LOGO_URI",
+          "TYPE_TV_SERIES",
+          "COLUMN_ITEM_COUNT",
+          "INTERACTION_TYPE_VIEWS",
+          "COLUMN_PREVIEW_AUDIO_URI",
+          "AVAILABILITY_FREE_WITH_SUBSCRIPTION",
+          "COLUMN_PREVIEW_VIDEO_URI",
+          "COLUMN_LAST_PLAYBACK_POSITION_MILLIS",
+          "COLUMN_END_TIME_UTC_MILLIS",
+          "ASPECT_RATIO_4_3",
+          "INTERACTION_TYPE_THUMBS",
+          "INTERACTION_TYPE_VIEWERS",
+          "COLUMN_TYPE",
+          "ASPECT_RATIO_3_2",
+          "COLUMN_AUTHOR",
+          "TYPE_TV_EPISODE",
+          "ASPECT_RATIO_1_1",
+          "ASPECT_RATIO_2_3",
+          "COLUMN_INTERNAL_PROVIDER_ID",
+          "ASPECT_RATIO_MOVIE_POSTER",
+          "TYPE_CHANNEL",
+          "COLUMN_LOGO_CONTENT_DESCRIPTION",
+          "AVAILABILITY_FREE",
+          "COLUMN_TRANSIENT",
+          "TYPE_STATION",
+          "INTERACTION_TYPE_LIKES",
+          "TYPE_TV_SEASON",
+          "AVAILABILITY_AVAILABLE",
+          "TYPE_PLAYLIST",
+          "ASPECT_RATIO_16_9",
+          "COLUMN_RELEASE_DATE",
+          "COLUMN_CONTENT_ID",
+          "TYPE_ARTIST",
+          "COLUMN_THUMBNAIL_ASPECT_RATIO",
+          "COLUMN_STARTING_PRICE",
+          "TYPE_CLIP"
+        ]
+      },
+      "android/support/wear/widget/BoxInsetLayout": {
+        "androidx/wear/widget/BoxInsetLayout": [
+          "FACTOR",
+          "DEFAULT_CHILD_GRAVITY"
+        ]
+      },
+      "android/support/v7/graphics/ColorCutQuantizer": {
+        "androidx/graphics/palette/ColorCutQuantizer": [
+          "LOG_TAG",
+          "COMPONENT_GREEN",
+          "VBOX_COMPARATOR_VOLUME",
+          "LOG_TIMINGS",
+          "COMPONENT_RED",
+          "COMPONENT_BLUE",
+          "QUANTIZE_WORD_WIDTH",
+          "QUANTIZE_WORD_MASK"
+        ]
+      },
+      "android/support/text/emoji/flatbuffer/Struct": {
+        "androidx/text/emoji/flatbuffer/Struct": [
+          "bb",
+          "bb_pos"
+        ]
+      },
+      "android/support/media/instantvideo/BuildConfig": {
+        "androidx/media/instantvideo/BuildConfig": [
+          "VERSION_CODE",
+          "DEBUG",
+          "FLAVOR",
+          "APPLICATION_ID",
+          "BUILD_TYPE",
+          "VERSION_NAME"
+        ]
+      },
+      "android/support/v4/media/session/MediaSessionCompat": {
+        "androidx/media/session/MediaSessionCompat": [
+          "TAG",
+          "EXTRA_BINDER",
+          "ARGUMENT_MEDIA_ATTRIBUTE_VALUE",
+          "ACTION_PREPARE_FROM_MEDIA_ID",
+          "ACTION_FLAG_AS_INAPPROPRIATE",
+          "ACTION_SET_CAPTIONING_ENABLED",
+          "MEDIA_ATTRIBUTE_PLAYLIST",
+          "sMaxBitmapSize",
+          "MAX_BITMAP_SIZE_IN_DP",
+          "FLAG_HANDLES_MEDIA_BUTTONS",
+          "ACTION_ARGUMENT_CAPTIONING_ENABLED",
+          "ACTION_SET_SHUFFLE_MODE",
+          "ACTION_ARGUMENT_EXTRAS",
+          "ACTION_SKIP_AD",
+          "ACTION_ARGUMENT_REPEAT_MODE",
+          "FLAG_HANDLES_TRANSPORT_CONTROLS",
+          "ACTION_ARGUMENT_QUERY",
+          "ACTION_ARGUMENT_MEDIA_ID",
+          "ACTION_PREPARE_FROM_SEARCH",
+          "FLAG_HANDLES_QUEUE_COMMANDS",
+          "ACTION_UNFOLLOW",
+          "ACTION_ARGUMENT_RATING",
+          "ACTION_ARGUMENT_URI",
+          "ACTION_PLAY_FROM_URI",
+          "MEDIA_ATTRIBUTE_ALBUM",
+          "ACTION_SET_RATING",
+          "ACTION_SET_REPEAT_MODE",
+          "ACTION_ARGUMENT_SHUFFLE_MODE",
+          "ACTION_FOLLOW",
+          "ACTION_PREPARE_FROM_URI",
+          "ACTION_PREPARE",
+          "ARGUMENT_MEDIA_ATTRIBUTE",
+          "MEDIA_ATTRIBUTE_ARTIST"
+        ]
+      },
+      "android/support/v17/leanback/widget/GridLayoutManager": {
+        "androidx/leanback/widget/GridLayoutManager": [
+          "PREV_ROW",
+          "NEXT_ITEM",
+          "DEBUG",
+          "DEFAULT_MAX_PENDING_MOVES",
+          "TRACE",
+          "MIN_MS_SMOOTH_SCROLL_MAIN_SCREEN",
+          "sTempRect",
+          "NEXT_ROW",
+          "TAG",
+          "PREV_ITEM",
+          "sTwoInts"
+        ]
+      },
+      "android/support/v14/preference/BuildConfig": {
+        "androidx/preference/BuildConfig": [
+          "APPLICATION_ID",
+          "DEBUG",
+          "VERSION_CODE",
+          "FLAVOR",
+          "BUILD_TYPE",
+          "VERSION_NAME"
+        ]
+      },
+      "android/support/v4/util/TimeUtils": {
+        "androidx/util/TimeUtils": [
+          "HUNDRED_DAY_FIELD_LEN",
+          "SECONDS_PER_MINUTE",
+          "sFormatSync",
+          "sFormatStr",
+          "SECONDS_PER_DAY",
+          "SECONDS_PER_HOUR"
+        ]
+      },
+      "android/support/v4/view/accessibility/AccessibilityWindowInfoCompat": {
+        "androidx/view/accessibility/AccessibilityWindowInfoCompat": [
+          "TYPE_APPLICATION",
+          "TYPE_SYSTEM",
+          "TYPE_ACCESSIBILITY_OVERLAY",
+          "TYPE_INPUT_METHOD",
+          "UNDEFINED",
+          "TYPE_SPLIT_SCREEN_DIVIDER"
+        ]
+      },
+      "android/support/v17/leanback/app/VerticalGridFragment": {
+        "androidx/leanback/app/VerticalGridFragment": [
+          "STATE_SET_ENTRANCE_START_STATE",
+          "EVT_ON_CREATEVIEW",
+          "STATE_ENTRANCE_ON_PREPARED",
+          "DEBUG",
+          "TAG"
+        ]
+      },
+      "android/support/v4/app/FrameMetricsAggregator": {
+        "androidx/app/FrameMetricsAggregator": [
+          "COMMAND_INDEX",
+          "SWAP_DURATION",
+          "SYNC_INDEX",
+          "ANIMATION_INDEX",
+          "LAYOUT_MEASURE_DURATION",
+          "INPUT_INDEX",
+          "TOTAL_INDEX",
+          "LAST_INDEX",
+          "LAYOUT_MEASURE_INDEX",
+          "DELAY_DURATION",
+          "COMMAND_DURATION",
+          "DBG",
+          "INPUT_DURATION",
+          "SWAP_INDEX",
+          "SYNC_DURATION",
+          "TOTAL_DURATION",
+          "TAG",
+          "ANIMATION_DURATION",
+          "DRAW_DURATION",
+          "DRAW_INDEX",
+          "EVERY_DURATION",
+          "DELAY_INDEX"
+        ]
+      },
+      "android/support/design/widget/FloatingActionButtonImpl": {
+        "androidx/design/widget/FloatingActionButtonImpl": [
+          "PRESSED_ENABLED_STATE_SET",
+          "PRESSED_ANIM_DURATION",
+          "PRESSED_ANIM_DELAY",
+          "FOCUSED_ENABLED_STATE_SET",
+          "ANIM_STATE_NONE",
+          "EMPTY_STATE_SET",
+          "ANIM_INTERPOLATOR",
+          "ENABLED_STATE_SET",
+          "ANIM_STATE_SHOWING",
+          "SHOW_HIDE_ANIM_DURATION",
+          "ANIM_STATE_HIDING"
+        ]
+      },
+      "android/support/transition/R$id": {
+        "androidx/transition/R$id": [
+          "transition_transform",
+          "ghost_view",
+          "transition_scene_layoutid_cache",
+          "save_image_matrix",
+          "save_scale_type",
+          "transition_current_scene",
+          "transition_layout_save",
+          "parent_matrix",
+          "transition_position",
+          "save_non_transition_alpha"
+        ]
+      },
+      "android/support/v7/app/ResourcesFlusher": {
+        "androidx/app/ResourcesFlusher": [
+          "sDrawableCacheField",
+          "sThemedResourceCacheClazz",
+          "sResourcesImplFieldFetched",
+          "sDrawableCacheFieldFetched",
+          "TAG",
+          "sThemedResourceCacheClazzFetched",
+          "sResourcesImplField",
+          "sThemedResourceCache_mUnthemedEntriesFieldFetched",
+          "sThemedResourceCache_mUnthemedEntriesField"
+        ]
+      },
+      "android/support/v4/app/RemoteInput": {
+        "androidx/app/RemoteInput": [
+          "EXTRA_RESULTS_DATA",
+          "TAG",
+          "RESULTS_CLIP_LABEL",
+          "EXTRA_DATA_TYPE_RESULTS_DATA"
+        ]
+      },
+      "android/support/media/tv/TvContractCompat$Programs$Genres": {
+        "androidx/media/tv/TvContractCompat$Programs$Genres": [
+          "EDUCATION",
+          "DELIMITER",
+          "LIFE_STYLE",
+          "SPORTS",
+          "GAMING",
+          "TECH_SCIENCE",
+          "DOUBLE_QUOTE",
+          "TRAVEL",
+          "CANONICAL_GENRES",
+          "MOVIES",
+          "FAMILY_KIDS",
+          "NEWS",
+          "ENTERTAINMENT",
+          "PREMIER",
+          "ARTS",
+          "EMPTY_STRING_ARRAY",
+          "DRAMA",
+          "MUSIC",
+          "COMMA",
+          "COMEDY",
+          "SHOPPING",
+          "ANIMAL_WILDLIFE"
+        ]
+      },
+      "android/support/v4/view/GravityCompat": {
+        "androidx/view/GravityCompat": [
+          "START",
+          "END",
+          "RELATIVE_LAYOUT_DIRECTION",
+          "RELATIVE_HORIZONTAL_GRAVITY_MASK"
+        ]
+      },
+      "android/support/design/R$color": {
+        "androidx/design/R$color": [
+          "design_fab_shadow_start_color",
+          "design_bottom_navigation_shadow_color",
+          "design_fab_stroke_end_outer_color",
+          "design_fab_shadow_mid_color",
+          "design_fab_stroke_end_inner_color",
+          "design_fab_shadow_end_color",
+          "design_fab_stroke_top_inner_color",
+          "design_fab_stroke_top_outer_color"
+        ]
+      },
+      "android/support/v4/view/LayoutInflaterCompat": {
+        "androidx/view/LayoutInflaterCompat": [
+          "sLayoutInflaterFactory2Field",
+          "TAG",
+          "sCheckedField",
+          "IMPL"
+        ]
+      },
+      "android/support/v13/view/inputmethod/EditorInfoCompat": {
+        "androidx/view/inputmethod/EditorInfoCompat": [
+          "IMPL",
+          "IME_FLAG_NO_PERSONALIZED_LEARNING",
+          "EMPTY_STRING_ARRAY",
+          "IME_FLAG_FORCE_ASCII"
+        ]
+      },
+      "android/support/v17/leanback/app/OnboardingSupportFragment": {
+        "androidx/leanback/app/OnboardingSupportFragment": [
+          "KEY_ENTER_ANIMATION_FINISHED",
+          "KEY_CURRENT_PAGE_INDEX",
+          "DEBUG",
+          "HEADER_DISAPPEAR_INTERPOLATOR",
+          "sSlideDistance",
+          "LOGO_SPLASH_PAUSE_DURATION_MS",
+          "HEADER_APPEAR_DELAY_MS",
+          "HEADER_ANIMATION_DURATION_MS",
+          "TAG",
+          "HEADER_APPEAR_INTERPOLATOR",
+          "DESCRIPTION_START_DELAY_MS",
+          "SLIDE_DISTANCE",
+          "KEY_LOGO_ANIMATION_FINISHED"
+        ]
+      },
+      "android/support/v4/app/BackStackRecord": {
+        "androidx/app/BackStackRecord": [
+          "TAG",
+          "OP_SET_PRIMARY_NAV",
+          "OP_DETACH",
+          "OP_NULL",
+          "OP_UNSET_PRIMARY_NAV",
+          "OP_REMOVE",
+          "OP_HIDE",
+          "OP_SHOW",
+          "OP_ADD",
+          "OP_REPLACE",
+          "OP_ATTACH"
+        ]
+      },
+      "android/support/v7/util/DiffUtil$DiffResult": {
+        "androidx/util/DiffUtil$DiffResult": [
+          "FLAG_MOVED_CHANGED",
+          "FLAG_IGNORE",
+          "FLAG_MASK",
+          "FLAG_OFFSET",
+          "FLAG_MOVED_NOT_CHANGED",
+          "FLAG_CHANGED",
+          "FLAG_NOT_CHANGED"
+        ]
+      },
+      "android/support/wear/widget/drawer/WearableDrawerView": {
+        "androidx/wear/widget/drawer/WearableDrawerView": [
+          "STATE_DRAGGING",
+          "STATE_SETTLING",
+          "STATE_IDLE"
+        ]
+      },
+      "android/support/wear/R$string": {
+        "androidx/wear/R$string": [
+          "ws_action_drawer_content_description",
+          "ws_navigation_drawer_content_description"
+        ]
+      },
+      "android/support/design/widget/SwipeDismissBehavior": {
+        "androidx/design/widget/SwipeDismissBehavior": [
+          "DEFAULT_ALPHA_START_DISTANCE",
+          "STATE_IDLE",
+          "STATE_DRAGGING",
+          "SWIPE_DIRECTION_START_TO_END",
+          "DEFAULT_ALPHA_END_DISTANCE",
+          "DEFAULT_DRAG_DISMISS_THRESHOLD",
+          "SWIPE_DIRECTION_ANY",
+          "STATE_SETTLING",
+          "SWIPE_DIRECTION_END_TO_START"
+        ]
+      },
+      "android/support/v17/leanback/widget/FullWidthDetailsOverviewSharedElementHelper": {
+        "androidx/leanback/widget/FullWidthDetailsOverviewSharedElementHelper": [
+          "DEFAULT_TIMEOUT",
+          "TAG",
+          "DEBUG"
+        ]
+      },
+      "android/support/transition/Styleable$TransitionTarget": {
+        "androidx/transition/Styleable$TransitionTarget": [
+          "EXCLUDE_CLASS",
+          "EXCLUDE_ID",
+          "TARGET_CLASS",
+          "EXCLUDE_NAME",
+          "TARGET_ID",
+          "TARGET_NAME"
+        ]
+      },
+      "android/support/v4/media/MediaDescriptionCompat": {
+        "androidx/media/MediaDescriptionCompat": [
+          "BT_FOLDER_TYPE_MIXED",
+          "CREATOR",
+          "STATUS_DOWNLOADED",
+          "STATUS_NOT_DOWNLOADED",
+          "EXTRA_BT_FOLDER_TYPE",
+          "BT_FOLDER_TYPE_GENRES",
+          "EXTRA_DOWNLOAD_STATUS",
+          "DESCRIPTION_KEY_MEDIA_URI",
+          "BT_FOLDER_TYPE_ARTISTS",
+          "DESCRIPTION_KEY_NULL_BUNDLE_FLAG",
+          "BT_FOLDER_TYPE_TITLES",
+          "STATUS_DOWNLOADING",
+          "BT_FOLDER_TYPE_YEARS",
+          "BT_FOLDER_TYPE_PLAYLISTS",
+          "BT_FOLDER_TYPE_ALBUMS"
+        ]
+      },
+      "android/support/v4/graphics/ColorUtils": {
+        "androidx/graphics/ColorUtils": [
+          "MIN_ALPHA_SEARCH_MAX_ITERATIONS",
+          "XYZ_KAPPA",
+          "MIN_ALPHA_SEARCH_PRECISION",
+          "TEMP_ARRAY",
+          "XYZ_WHITE_REFERENCE_Z",
+          "XYZ_WHITE_REFERENCE_Y",
+          "XYZ_WHITE_REFERENCE_X",
+          "XYZ_EPSILON"
+        ]
+      },
+      "android/support/v7/widget/AppCompatTextViewAutoSizeHelper": {
+        "androidx/widget/AppCompatTextViewAutoSizeHelper": [
+          "TAG",
+          "DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX",
+          "UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE",
+          "DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP",
+          "VERY_WIDE",
+          "sTextViewMethodByNameCache",
+          "DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP",
+          "TEMP_RECTF"
+        ]
+      },
+      "android/support/design/R$style": {
+        "androidx/design/R$style": [
+          "Widget_Design_ScrimInsetsFrameLayout",
+          "Widget_Design_TextInputLayout",
+          "Widget_Design_TabLayout",
+          "Widget_Design_CollapsingToolbar",
+          "Widget_Design_NavigationView",
+          "TextAppearance_Design_Tab",
+          "Widget_Design_CoordinatorLayout",
+          "Theme_Design_Light_BottomSheetDialog",
+          "Widget_Design_FloatingActionButton",
+          "Widget_Design_AppBarLayout",
+          "Widget_Design_BottomNavigationView",
+          "TextAppearance_Design_CollapsingToolbar_Expanded"
+        ]
+      },
+      "android/support/v7/widget/GridLayoutManager": {
+        "androidx/widget/GridLayoutManager": [
+          "TAG",
+          "DEFAULT_SPAN_COUNT",
+          "DEBUG"
+        ]
+      },
+      "android/support/v7/appcompat/R$dimen": {
+        "androidx/appcompat/R$dimen": [
+          "tooltip_precise_anchor_extra_offset",
+          "abc_dropdownitem_icon_width",
+          "tooltip_precise_anchor_threshold",
+          "tooltip_y_offset_non_touch",
+          "abc_config_prefDialogWidth",
+          "abc_dropdownitem_text_padding_left",
+          "abc_action_bar_stacked_max_height",
+          "abc_search_view_preferred_height",
+          "abc_cascading_menus_min_smallest_width",
+          "abc_action_bar_stacked_tab_max_width",
+          "tooltip_y_offset_touch",
+          "abc_search_view_preferred_width"
+        ]
+      },
+      "android/support/wear/internal/widget/drawer/SinglePageUi": {
+        "androidx/wear/internal/widget/drawer/SinglePageUi": [
+          "SINGLE_PAGE_LAYOUT_RES",
+          "SINGLE_PAGE_BUTTON_IDS"
+        ]
+      },
+      "android/support/v4/accessibilityservice/AccessibilityServiceInfoCompat": {
+        "androidx/accessibilityservice/AccessibilityServiceInfoCompat": [
+          "FLAG_REQUEST_TOUCH_EXPLORATION_MODE",
+          "CAPABILITY_CAN_FILTER_KEY_EVENTS",
+          "FLAG_INCLUDE_NOT_IMPORTANT_VIEWS",
+          "CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY",
+          "CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT",
+          "FEEDBACK_BRAILLE",
+          "FEEDBACK_ALL_MASK",
+          "FLAG_REPORT_VIEW_IDS",
+          "CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION",
+          "FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY",
+          "FLAG_REQUEST_FILTER_KEY_EVENTS"
+        ]
+      },
+      "android/support/graphics/drawable/AnimatorInflaterCompat": {
+        "androidx/graphics/drawable/AnimatorInflaterCompat": [
+          "MAX_NUM_POINTS",
+          "VALUE_TYPE_INT",
+          "VALUE_TYPE_FLOAT",
+          "VALUE_TYPE_COLOR",
+          "TAG",
+          "DBG_ANIMATOR_INFLATER",
+          "VALUE_TYPE_PATH",
+          "TOGETHER",
+          "VALUE_TYPE_UNDEFINED"
+        ]
+      },
+      "android/support/wear/R$id": {
+        "androidx/wear/R$id": [
+          "ws_navigation_drawer_item_icon",
+          "ws_drawer_view_peek_container",
+          "ws_navigation_drawer_item_text",
+          "ws_action_drawer_item_text",
+          "ws_drawer_view_peek_icon",
+          "ws_nav_drawer_icon_4",
+          "ws_nav_drawer_icon_5",
+          "ws_nav_drawer_icon_6",
+          "ws_nav_drawer_icon_0",
+          "ws_nav_drawer_icon_1",
+          "ws_nav_drawer_icon_2",
+          "ws_nav_drawer_icon_3",
+          "ws_navigation_drawer_page_indicator",
+          "ws_navigation_drawer_view_pager",
+          "ws_nav_drawer_text",
+          "ws_action_drawer_expand_icon",
+          "ws_action_drawer_item_icon",
+          "ws_action_drawer_title",
+          "ws_action_drawer_peek_action_icon"
+        ]
+      },
+      "android/support/v4/view/animation/LinearOutSlowInInterpolator": {
+        "androidx/view/animation/LinearOutSlowInInterpolator": [
+          "VALUES"
+        ]
+      },
+      "android/support/v4/graphics/TypefaceCompatApi24Impl": {
+        "androidx/graphics/TypefaceCompatApi24Impl": [
+          "FONT_FAMILY_CLASS",
+          "sFontFamily",
+          "TAG",
+          "sAddFontWeightStyle",
+          "sFontFamilyCtor",
+          "sCreateFromFamiliesWithDefault",
+          "ADD_FONT_WEIGHT_STYLE_METHOD",
+          "CREATE_FROM_FAMILIES_WITH_DEFAULT_METHOD"
+        ]
+      },
+      "android/support/v7/widget/AdapterHelper": {
+        "androidx/widget/AdapterHelper": [
+          "TAG",
+          "POSITION_TYPE_NEW_OR_LAID_OUT",
+          "DEBUG",
+          "POSITION_TYPE_INVISIBLE"
+        ]
+      },
+      "android/support/wear/BuildConfig": {
+        "androidx/wear/BuildConfig": [
+          "VERSION_CODE",
+          "DEBUG",
+          "FLAVOR",
+          "BUILD_TYPE",
+          "APPLICATION_ID",
+          "VERSION_NAME"
+        ]
+      },
+      "android/support/v4/media/session/ParcelableVolumeInfo": {
+        "androidx/media/session/ParcelableVolumeInfo": [
+          "audioStream",
+          "currentVolume",
+          "maxVolume",
+          "volumeType",
+          "CREATOR",
+          "controlType"
+        ]
+      },
+      "android/support/text/emoji/EmojiCompat": {
+        "androidx/text/emoji/EmojiCompat": [
+          "EDITOR_INFO_REPLACE_ALL_KEY",
+          "sInstanceLock",
+          "EMOJI_COUNT_UNLIMITED",
+          "LOAD_STATE_SUCCEEDED",
+          "EDITOR_INFO_METAVERSION_KEY",
+          "sInstance",
+          "REPLACE_STRATEGY_ALL",
+          "LOAD_STATE_FAILED",
+          "REPLACE_STRATEGY_NON_EXISTENT",
+          "LOAD_STATE_LOADING",
+          "REPLACE_STRATEGY_DEFAULT"
+        ]
+      },
+      "android/support/v7/graphics/Target": {
+        "androidx/graphics/palette/Target": [
+          "TARGET_MUTED_SATURATION",
+          "MUTED",
+          "MIN_NORMAL_LUMA",
+          "MAX_DARK_LUMA",
+          "VIBRANT",
+          "TARGET_LIGHT_LUMA",
+          "TARGET_NORMAL_LUMA",
+          "MAX_NORMAL_LUMA",
+          "WEIGHT_LUMA",
+          "INDEX_WEIGHT_LUMA",
+          "TARGET_VIBRANT_SATURATION",
+          "DARK_MUTED",
+          "DARK_VIBRANT",
+          "INDEX_MAX",
+          "INDEX_MIN",
+          "MAX_MUTED_SATURATION",
+          "MIN_LIGHT_LUMA",
+          "LIGHT_VIBRANT",
+          "LIGHT_MUTED",
+          "INDEX_TARGET",
+          "INDEX_WEIGHT_POP",
+          "TARGET_DARK_LUMA",
+          "WEIGHT_POPULATION",
+          "INDEX_WEIGHT_SAT",
+          "WEIGHT_SATURATION",
+          "MIN_VIBRANT_SATURATION"
+        ]
+      },
+      "android/support/v17/leanback/media/PlaybackControlGlue": {
+        "androidx/leanback/media/PlaybackControlGlue": [
+          "MSG_UPDATE_PLAYBACK_STATE",
+          "PLAYBACK_SPEED_INVALID",
+          "ACTION_REWIND",
+          "sHandler",
+          "ACTION_FAST_FORWARD",
+          "PLAYBACK_SPEED_FAST_L2",
+          "PLAYBACK_SPEED_FAST_L3",
+          "PLAYBACK_SPEED_FAST_L4",
+          "PLAYBACK_SPEED_FAST_L0",
+          "PLAYBACK_SPEED_FAST_L1",
+          "DEBUG",
+          "PLAYBACK_SPEED_PAUSED",
+          "UPDATE_PLAYBACK_STATE_DELAY_MS",
+          "PLAYBACK_SPEED_NORMAL",
+          "TAG",
+          "ACTION_SKIP_TO_NEXT",
+          "ACTION_SKIP_TO_PREVIOUS",
+          "ACTION_CUSTOM_LEFT_FIRST",
+          "ACTION_PLAY_PAUSE",
+          "NUMBER_OF_SEEK_SPEEDS",
+          "ACTION_CUSTOM_RIGHT_FIRST"
+        ]
+      },
+      "android/support/v7/widget/GridLayout$LayoutParams": {
+        "androidx/widget/GridLayout$LayoutParams": [
+          "bottomMargin",
+          "DEFAULT_SPAN",
+          "COLUMN_WEIGHT",
+          "leftMargin",
+          "GRAVITY",
+          "ROW_SPAN",
+          "ROW",
+          "COLUMN",
+          "topMargin",
+          "RIGHT_MARGIN",
+          "height",
+          "COLUMN_SPAN",
+          "rightMargin",
+          "width",
+          "ROW_WEIGHT",
+          "BOTTOM_MARGIN",
+          "DEFAULT_SPAN_SIZE",
+          "columnSpec",
+          "DEFAULT_ROW",
+          "DEFAULT_WIDTH",
+          "MARGIN",
+          "rowSpec",
+          "TOP_MARGIN",
+          "DEFAULT_HEIGHT",
+          "LEFT_MARGIN",
+          "DEFAULT_COLUMN",
+          "DEFAULT_MARGIN"
+        ]
+      },
+      "android/support/v17/leanback/app/DetailsSupportFragment": {
+        "androidx/leanback/app/DetailsSupportFragment": [
+          "EVT_ONSTART",
+          "STATE_ENTER_TRANSITION_CANCEL",
+          "DEBUG",
+          "STATE_SWITCH_TO_VIDEO_IN_ON_CREATE",
+          "STATE_ENTRANCE_COMPLETE",
+          "STATE_ENTER_TRANSITION_COMPLETE",
+          "STATE_ENTER_TRANSITION_ADDLISTENER",
+          "STATE_ENTRANCE_INIT",
+          "EVT_SWITCH_TO_VIDEO",
+          "STATE_START",
+          "STATE_ON_SAFE_START",
+          "STATE_ENTRANCE_PERFORM",
+          "EVT_ON_CREATEVIEW",
+          "STATE_SET_ENTRANCE_START_STATE",
+          "COND_TRANSITION_NOT_SUPPORTED",
+          "TAG",
+          "STATE_ENTER_TRANSITION_PENDING",
+          "EVT_ENTER_TRANSIITON_DONE",
+          "STATE_ENTRANCE_ON_PREPARED",
+          "EVT_ON_CREATE",
+          "EVT_DETAILS_ROW_LOADED",
+          "STATE_ENTER_TRANSITION_INIT",
+          "EVT_NO_ENTER_TRANSITION"
+        ]
+      },
+      "android/support/v17/leanback/app/ListRowDataAdapter": {
+        "androidx/leanback/app/ListRowDataAdapter": [
+          "ON_ITEM_RANGE_INSERTED",
+          "ON_ITEM_RANGE_CHANGED",
+          "ON_ITEM_RANGE_REMOVED",
+          "ON_CHANGED"
+        ]
+      },
+      "android/support/percent/PercentLayoutHelper$PercentLayoutInfo": {
+        "androidx/PercentLayoutHelper$PercentLayoutInfo": [
+          "topMarginPercent",
+          "endMarginPercent",
+          "aspectRatio",
+          "rightMarginPercent",
+          "heightPercent",
+          "leftMarginPercent",
+          "startMarginPercent",
+          "bottomMarginPercent",
+          "widthPercent"
+        ]
+      },
+      "android/support/v17/leanback/system/Settings": {
+        "androidx/leanback/system/Settings": [
+          "OUTLINE_CLIPPING_DISABLED",
+          "PREFER_STATIC_SHADOWS",
+          "DEBUG",
+          "sInstance",
+          "ACTION_PARTNER_CUSTOMIZATION",
+          "TAG"
+        ]
+      },
+      "android/support/v4/util/SimpleArrayMap": {
+        "androidx/util/SimpleArrayMap": [
+          "BASE_SIZE",
+          "CONCURRENT_MODIFICATION_EXCEPTIONS",
+          "DEBUG",
+          "TAG",
+          "CACHE_SIZE"
+        ]
+      },
+      "android/support/v4/widget/AutoScrollHelper": {
+        "androidx/widget/AutoScrollHelper": [
+          "EDGE_TYPE_OUTSIDE",
+          "NO_MIN",
+          "NO_MAX",
+          "HORIZONTAL",
+          "EDGE_TYPE_INSIDE_EXTEND",
+          "DEFAULT_MAXIMUM_EDGE",
+          "VERTICAL",
+          "EDGE_TYPE_INSIDE",
+          "RELATIVE_UNSPECIFIED",
+          "DEFAULT_MAXIMUM_VELOCITY_DIPS",
+          "DEFAULT_RAMP_DOWN_DURATION",
+          "DEFAULT_RELATIVE_VELOCITY",
+          "DEFAULT_ACTIVATION_DELAY",
+          "DEFAULT_MINIMUM_VELOCITY_DIPS",
+          "DEFAULT_RAMP_UP_DURATION",
+          "DEFAULT_EDGE_TYPE",
+          "DEFAULT_RELATIVE_EDGE"
+        ]
+      },
+      "android/support/v7/widget/ViewInfoStore$InfoRecord": {
+        "androidx/widget/ViewInfoStore$InfoRecord": [
+          "FLAG_PRE",
+          "postInfo",
+          "FLAG_APPEAR_AND_DISAPPEAR",
+          "FLAG_PRE_AND_POST",
+          "FLAG_DISAPPEARED",
+          "preInfo",
+          "flags",
+          "FLAG_APPEAR",
+          "FLAG_APPEAR_PRE_AND_POST",
+          "FLAG_POST",
+          "sPool"
+        ]
+      },
+      "android/support/design/widget/AnimationUtils": {
+        "androidx/design/widget/AnimationUtils": [
+          "LINEAR_OUT_SLOW_IN_INTERPOLATOR",
+          "LINEAR_INTERPOLATOR",
+          "FAST_OUT_LINEAR_IN_INTERPOLATOR",
+          "FAST_OUT_SLOW_IN_INTERPOLATOR",
+          "DECELERATE_INTERPOLATOR"
+        ]
+      },
+      "android/support/design/widget/SnackbarManager$SnackbarRecord": {
+        "androidx/design/widget/SnackbarManager$SnackbarRecord": [
+          "duration",
+          "callback",
+          "paused"
+        ]
+      },
+      "android/support/transition/ChangeScroll": {
+        "androidx/transition/ChangeScroll": [
+          "PROPNAME_SCROLL_X",
+          "PROPNAME_SCROLL_Y",
+          "PROPERTIES"
+        ]
+      },
+      "android/support/design/widget/FloatingActionButtonLollipop": {
+        "androidx/design/widget/FloatingActionButtonLollipop": [
+          "EMPTY_STATE_SET",
+          "ENABLED_STATE_SET",
+          "PRESSED_ENABLED_STATE_SET",
+          "ANIM_INTERPOLATOR",
+          "FOCUSED_ENABLED_STATE_SET"
+        ]
+      },
+      "android/support/v4/graphics/TypefaceCompat": {
+        "androidx/graphics/TypefaceCompat": [
+          "sTypefaceCompatImpl",
+          "TAG",
+          "sTypefaceCache"
+        ]
+      },
+      "android/support/v7/widget/LinearSmoothScroller": {
+        "androidx/widget/LinearSmoothScroller": [
+          "MILLISECONDS_PER_PX",
+          "SNAP_TO_ANY",
+          "SNAP_TO_END",
+          "DEBUG",
+          "TAG",
+          "MILLISECONDS_PER_INCH",
+          "TARGET_SEEK_SCROLL_DISTANCE_PX",
+          "TARGET_SEEK_EXTRA_SCROLL_RATIO",
+          "SNAP_TO_START"
+        ]
+      },
+      "android/support/transition/Styleable$ChangeTransform": {
+        "androidx/transition/Styleable$ChangeTransform": [
+          "REPARENT_WITH_OVERLAY",
+          "REPARENT"
+        ]
+      },
+      "android/support/animation/DynamicAnimation": {
+        "androidx/animation/DynamicAnimation": [
+          "MIN_VISIBLE_CHANGE_ALPHA",
+          "MIN_VISIBLE_CHANGE_ROTATION_DEGREES",
+          "UNSET",
+          "ROTATION_Y",
+          "ROTATION_X",
+          "ALPHA",
+          "Z",
+          "X",
+          "Y",
+          "SCROLL_Y",
+          "SCROLL_X",
+          "TRANSLATION_Z",
+          "TRANSLATION_X",
+          "TRANSLATION_Y",
+          "THRESHOLD_MULTIPLIER",
+          "ROTATION",
+          "MIN_VISIBLE_CHANGE_SCALE",
+          "SCALE_Y",
+          "SCALE_X",
+          "MIN_VISIBLE_CHANGE_PIXELS"
+        ]
+      },
+      "android/support/constraint/solver/widgets/ConstraintWidget$DimensionBehaviour": {
+        "androidx/constraint/solver/widgets/ConstraintWidget$DimensionBehaviour": [
+          "FIXED",
+          "MATCH_PARENT",
+          "WRAP_CONTENT",
+          "MATCH_CONSTRAINT"
+        ]
+      },
+      "android/support/v17/leanback/media/PlaybackBannerControlGlue": {
+        "androidx/leanback/media/PlaybackBannerControlGlue": [
+          "PLAYBACK_SPEED_PAUSED",
+          "ACTION_SKIP_TO_PREVIOUS",
+          "ACTION_SKIP_TO_NEXT",
+          "ACTION_CUSTOM_LEFT_FIRST",
+          "TAG",
+          "ACTION_PLAY_PAUSE",
+          "PLAYBACK_SPEED_NORMAL",
+          "ACTION_CUSTOM_RIGHT_FIRST",
+          "NUMBER_OF_SEEK_SPEEDS",
+          "ACTION_REWIND",
+          "PLAYBACK_SPEED_INVALID",
+          "PLAYBACK_SPEED_FAST_L1",
+          "PLAYBACK_SPEED_FAST_L0",
+          "PLAYBACK_SPEED_FAST_L4",
+          "PLAYBACK_SPEED_FAST_L3",
+          "PLAYBACK_SPEED_FAST_L2",
+          "ACTION_FAST_FORWARD"
+        ]
+      },
+      "android/support/transition/ChangeTransform": {
+        "androidx/transition/ChangeTransform": [
+          "PROPNAME_PARENT",
+          "TRANSLATIONS_PROPERTY",
+          "PROPNAME_INTERMEDIATE_PARENT_MATRIX",
+          "PROPNAME_TRANSFORMS",
+          "PROPNAME_INTERMEDIATE_MATRIX",
+          "sTransitionProperties",
+          "PROPNAME_MATRIX",
+          "PROPNAME_PARENT_MATRIX",
+          "NON_TRANSLATIONS_PROPERTY",
+          "SUPPORTS_VIEW_REMOVAL_SUPPRESSION"
+        ]
+      },
+      "android/support/v17/leanback/R$fraction": {
+        "androidx/leanback/R$fraction": [
+          "lb_focus_zoom_factor_xsmall",
+          "lb_focus_zoom_factor_medium",
+          "lb_browse_rows_scale",
+          "lb_search_orb_focused_zoom",
+          "lb_view_active_level",
+          "lb_focus_zoom_factor_large",
+          "lb_focus_zoom_factor_small",
+          "lb_browse_header_unselect_alpha",
+          "lb_view_dimmed_level",
+          "lb_search_bar_speech_orb_max_level_zoom"
+        ]
+      },
+      "android/support/v7/widget/LinearLayoutCompat$LayoutParams": {
+        "androidx/widget/LinearLayoutCompat$LayoutParams": [
+          "height",
+          "gravity",
+          "rightMargin",
+          "width",
+          "bottomMargin",
+          "leftMargin",
+          "topMargin",
+          "weight"
+        ]
+      },
+      "android/support/v7/preference/PreferenceViewHolder": {
+        "androidx/preference/PreferenceViewHolder": [
+          "itemView"
+        ]
+      },
+      "android/support/v7/util/MessageThreadUtil$SyncQueueItem": {
+        "androidx/util/MessageThreadUtil$SyncQueueItem": [
+          "sPoolLock",
+          "what",
+          "sPool",
+          "arg2",
+          "arg1",
+          "arg4",
+          "arg3",
+          "arg5",
+          "next",
+          "data"
+        ]
+      },
+      "android/support/v14/preference/PreferenceFragment": {
+        "androidx/preference/PreferenceFragment": [
+          "PREFERENCES_TAG",
+          "DIALOG_FRAGMENT_TAG",
+          "ARG_PREFERENCE_ROOT",
+          "MSG_BIND_PREFERENCES"
+        ]
+      },
+      "android/support/v7/gridlayout/R$styleable": {
+        "androidx/gridlayout/R$styleable": [
+          "GridLayout_Layout_android_layout_margin",
+          "GridLayout_Layout_layout_gravity",
+          "GridLayout_Layout_layout_column",
+          "GridLayout_Layout_layout_columnSpan",
+          "GridLayout_Layout_layout_row",
+          "GridLayout_Layout_layout_columnWeight",
+          "GridLayout_Layout_layout_rowWeight",
+          "GridLayout_Layout",
+          "GridLayout_Layout_android_layout_marginBottom",
+          "GridLayout_rowCount",
+          "GridLayout_columnCount",
+          "GridLayout_Layout_android_layout_marginRight",
+          "GridLayout",
+          "GridLayout_useDefaultMargins",
+          "GridLayout_rowOrderPreserved",
+          "GridLayout_columnOrderPreserved",
+          "GridLayout_Layout_android_layout_marginLeft",
+          "GridLayout_alignmentMode",
+          "GridLayout_Layout_layout_rowSpan",
+          "GridLayout_orientation",
+          "GridLayout_Layout_android_layout_marginTop"
+        ]
+      },
+      "android/support/v17/leanback/app/BackgroundManager": {
+        "androidx/leanback/app/BackgroundManager": [
+          "FULL_ALPHA",
+          "DEBUG",
+          "FRAGMENT_TAG",
+          "FADE_DURATION",
+          "CHANGE_BG_DELAY_MS",
+          "TAG"
+        ]
+      },
+      "android/support/customtabs/ICustomTabsService$Stub": {
+        "androidx/browser/customtabs/ICustomTabsService$Stub": [
+          "TRANSACTION_extraCommand",
+          "TRANSACTION_requestPostMessageChannel",
+          "TRANSACTION_postMessage",
+          "TRANSACTION_validateRelationship",
+          "TRANSACTION_warmup",
+          "TRANSACTION_updateVisuals",
+          "TRANSACTION_mayLaunchUrl",
+          "TRANSACTION_newSession",
+          "DESCRIPTOR"
+        ]
+      },
+      "android/support/v7/widget/Toolbar$SavedState": {
+        "androidx/widget/Toolbar$SavedState": [
+          "isOverflowOpen",
+          "expandedMenuItemId",
+          "CREATOR"
+        ]
+      },
+      "android/support/v17/leanback/widget/FullWidthDetailsOverviewRowPresenter": {
+        "androidx/leanback/widget/FullWidthDetailsOverviewRowPresenter": [
+          "ALIGN_MODE_START",
+          "sTmpRect",
+          "STATE_HALF",
+          "TAG",
+          "STATE_SMALL",
+          "sHandler",
+          "ALIGN_MODE_MIDDLE",
+          "STATE_FULL",
+          "DEBUG"
+        ]
+      },
+      "android/support/v4/app/FragmentTabHost$SavedState": {
+        "androidx/app/FragmentTabHost$SavedState": [
+          "CREATOR",
+          "curTab"
+        ]
+      },
+      "android/support/recommendation/BuildConfig": {
+        "androidx/recommendation/BuildConfig": [
+          "DEBUG",
+          "APPLICATION_ID",
+          "FLAVOR",
+          "VERSION_NAME",
+          "BUILD_TYPE",
+          "VERSION_CODE"
+        ]
+      },
+      "android/support/v4/app/ActivityOptionsCompat": {
+        "androidx/app/ActivityOptionsCompat": [
+          "EXTRA_USAGE_TIME_REPORT",
+          "EXTRA_USAGE_TIME_REPORT_PACKAGES"
+        ]
+      },
+      "android/support/v4/widget/NestedScrollView": {
+        "androidx/widget/NestedScrollView": [
+          "MAX_SCROLL_FACTOR",
+          "SCROLLVIEW_STYLEABLE",
+          "TAG",
+          "ANIMATED_SCROLL_GAP",
+          "INVALID_POINTER",
+          "ACCESSIBILITY_DELEGATE"
+        ]
+      },
+      "android/support/v4/app/BackStackRecord$Op": {
+        "androidx/app/BackStackRecord$Op": [
+          "enterAnim",
+          "fragment",
+          "popEnterAnim",
+          "exitAnim",
+          "popExitAnim",
+          "cmd"
+        ]
+      },
+      "android/support/v17/leanback/graphics/BoundsRule": {
+        "androidx/leanback/graphics/BoundsRule": [
+          "bottom",
+          "right",
+          "left",
+          "top"
+        ]
+      },
+      "android/support/v17/leanback/app/SearchSupportFragment": {
+        "androidx/leanback/app/SearchSupportFragment": [
+          "SPEECH_RECOGNITION_DELAY_MS",
+          "TAG",
+          "AUDIO_PERMISSION_REQUEST_CODE",
+          "ARG_QUERY",
+          "RESULTS_CHANGED",
+          "ARG_TITLE",
+          "EXTRA_LEANBACK_BADGE_PRESENT",
+          "ARG_PREFIX",
+          "QUERY_COMPLETE",
+          "DEBUG"
+        ]
+      },
+      "android/support/v7/util/SortedList": {
+        "androidx/util/SortedList": [
+          "INSERTION",
+          "DELETION",
+          "MIN_CAPACITY",
+          "INVALID_POSITION",
+          "CAPACITY_GROWTH",
+          "LOOKUP"
+        ]
+      },
+      "android/support/v4/view/ViewCompat": {
+        "androidx/view/ViewCompat": [
+          "IMPL",
+          "SCROLL_AXIS_VERTICAL",
+          "SCROLL_INDICATOR_END",
+          "MEASURED_HEIGHT_STATE_SHIFT",
+          "OVER_SCROLL_ALWAYS",
+          "SCROLL_INDICATOR_RIGHT",
+          "IMPORTANT_FOR_ACCESSIBILITY_AUTO",
+          "SCROLL_INDICATOR_START",
+          "LAYOUT_DIRECTION_LOCALE",
+          "ACCESSIBILITY_LIVE_REGION_ASSERTIVE",
+          "IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS",
+          "SCROLL_INDICATOR_TOP",
+          "MEASURED_STATE_MASK",
+          "SCROLL_INDICATOR_BOTTOM",
+          "LAYOUT_DIRECTION_LTR",
+          "OVER_SCROLL_IF_CONTENT_SCROLLS",
+          "SCROLL_AXIS_NONE",
+          "OVER_SCROLL_NEVER",
+          "ACCESSIBILITY_LIVE_REGION_POLITE",
+          "TYPE_TOUCH",
+          "MEASURED_STATE_TOO_SMALL",
+          "ACCESSIBILITY_LIVE_REGION_NONE",
+          "LAYOUT_DIRECTION_INHERIT",
+          "IMPORTANT_FOR_ACCESSIBILITY_NO",
+          "LAYER_TYPE_NONE",
+          "MEASURED_SIZE_MASK",
+          "SCROLL_INDICATOR_LEFT",
+          "TAG",
+          "SCROLL_AXIS_HORIZONTAL",
+          "TYPE_NON_TOUCH",
+          "LAYER_TYPE_HARDWARE",
+          "LAYER_TYPE_SOFTWARE",
+          "IMPORTANT_FOR_ACCESSIBILITY_YES",
+          "LAYOUT_DIRECTION_RTL"
+        ]
+      },
+      "android/support/v7/media/MediaRouter": {
+        "androidx/media/MediaRouter": [
+          "AVAILABILITY_FLAG_REQUIRE_MATCH",
+          "CALLBACK_FLAG_PERFORM_ACTIVE_SCAN",
+          "CALLBACK_FLAG_FORCE_DISCOVERY",
+          "UNSELECT_REASON_DISCONNECTED",
+          "AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE",
+          "UNSELECT_REASON_ROUTE_CHANGED",
+          "sGlobal",
+          "UNSELECT_REASON_UNKNOWN",
+          "TAG",
+          "CALLBACK_FLAG_REQUEST_DISCOVERY",
+          "DEBUG",
+          "UNSELECT_REASON_STOPPED",
+          "CALLBACK_FLAG_UNFILTERED_EVENTS"
+        ]
+      },
+      "android/support/v17/leanback/graphics/CompositeDrawable$ChildDrawable": {
+        "androidx/leanback/graphics/CompositeDrawable$ChildDrawable": [
+          "adjustedBounds",
+          "BOTTOM_FRACTION",
+          "RIGHT_FRACTION",
+          "BOTTOM_ABSOLUTE",
+          "RIGHT_ABSOLUTE",
+          "LEFT_ABSOLUTE",
+          "LEFT_FRACTION",
+          "TOP_FRACTION",
+          "TOP_ABSOLUTE"
+        ]
+      },
+      "android/support/v17/leanback/widget/PlaybackControlsRow$RepeatAction": {
+        "androidx/leanback/widget/PlaybackControlsRow$RepeatAction": [
+          "INDEX_NONE",
+          "INDEX_ALL",
+          "ALL",
+          "NONE",
+          "ONE",
+          "INDEX_ONE"
+        ]
+      },
+      "android/support/design/R$id": {
+        "androidx/design/R$id": [
+          "touch_outside",
+          "largeLabel",
+          "textinput_error",
+          "textinput_counter",
+          "snackbar_text",
+          "snackbar_action",
+          "icon",
+          "smallLabel",
+          "view_offset_helper",
+          "design_bottom_sheet",
+          "coordinator",
+          "design_menu_item_action_area_stub",
+          "design_menu_item_text"
+        ]
+      },
+      "android/support/v7/widget/ActivityChooserModel$HistoricalRecord": {
+        "androidx/widget/ActivityChooserModel$HistoricalRecord": [
+          "activity",
+          "weight",
+          "time"
+        ]
+      },
+      "android/support/media/tv/TvContractCompat$BaseTvColumns": {
+        "androidx/media/tv/TvContractCompat$BaseTvColumns": [
+          "COLUMN_PACKAGE_NAME"
+        ]
+      },
+      "android/support/v17/leanback/media/MediaPlayerGlue": {
+        "androidx/leanback/media/MediaPlayerGlue": [
+          "FAST_FORWARD_REWIND_STEP",
+          "TAG",
+          "REPEAT_ONE",
+          "FAST_FORWARD_REWIND_REPEAT_DELAY",
+          "NO_REPEAT",
+          "REPEAT_ALL"
+        ]
+      },
+      "android/support/text/emoji/EmojiProcessor$GlyphChecker": {
+        "androidx/text/emoji/EmojiProcessor$GlyphChecker": [
+          "PAINT_TEXT_SIZE",
+          "sStringBuilder"
+        ]
+      },
+      "android/support/v4/app/FragmentManager": {
+        "androidx/app/FragmentManager": [
+          "POP_BACK_STACK_INCLUSIVE"
+        ]
+      },
+      "android/support/v4/content/LocalBroadcastManager$ReceiverRecord": {
+        "androidx/content/LocalBroadcastManager$ReceiverRecord": [
+          "broadcasting",
+          "receiver",
+          "filter",
+          "dead"
+        ]
+      },
+      "android/support/percent/R$styleable": {
+        "androidx/R$styleable": [
+          "PercentLayout_Layout_layout_marginEndPercent",
+          "PercentLayout_Layout",
+          "PercentLayout_Layout_layout_marginBottomPercent",
+          "PercentLayout_Layout_layout_aspectRatio",
+          "PercentLayout_Layout_layout_widthPercent",
+          "PercentLayout_Layout_layout_heightPercent",
+          "PercentLayout_Layout_layout_marginLeftPercent",
+          "PercentLayout_Layout_layout_marginRightPercent",
+          "PercentLayout_Layout_layout_marginStartPercent",
+          "PercentLayout_Layout_layout_marginPercent",
+          "PercentLayout_Layout_layout_marginTopPercent"
+        ]
+      },
+      "android/support/v17/leanback/app/BrowseSupportFragment": {
+        "androidx/leanback/app/BrowseSupportFragment": [
+          "STATE_ENTRANCE_ON_PREPARED_ON_CREATEVIEW",
+          "STATE_SET_ENTRANCE_START_STATE",
+          "HEADERS_ENABLED",
+          "STATE_ENTRANCE_ON_PREPARED",
+          "HEADERS_HIDDEN",
+          "ARG_HEADERS_STATE",
+          "TAG",
+          "STATE_ENTRANCE_PERFORM",
+          "HEADER_SHOW",
+          "IS_PAGE_ROW",
+          "ARG_TITLE",
+          "LB_HEADERS_BACKSTACK",
+          "DEBUG",
+          "EVT_SCREEN_DATA_READY",
+          "CURRENT_SELECTED_POSITION",
+          "EVT_HEADER_VIEW_CREATED",
+          "EVT_MAIN_FRAGMENT_VIEW_CREATED",
+          "HEADERS_DISABLED",
+          "HEADER_STACK_INDEX"
+        ]
+      },
+      "android/support/v17/leanback/app/SearchFragment": {
+        "androidx/leanback/app/SearchFragment": [
+          "TAG",
+          "QUERY_COMPLETE",
+          "ARG_QUERY",
+          "RESULTS_CHANGED",
+          "ARG_TITLE",
+          "SPEECH_RECOGNITION_DELAY_MS",
+          "EXTRA_LEANBACK_BADGE_PRESENT",
+          "AUDIO_PERMISSION_REQUEST_CODE",
+          "DEBUG",
+          "ARG_PREFIX"
+        ]
+      },
+      "android/support/media/tv/BuildConfig": {
+        "androidx/media/tv/BuildConfig": [
+          "VERSION_NAME",
+          "VERSION_CODE",
+          "BUILD_TYPE",
+          "FLAVOR",
+          "APPLICATION_ID",
+          "DEBUG"
+        ]
+      },
+      "android/support/v4/graphics/drawable/IconCompat": {
+        "androidx/graphics/drawable/IconCompat": [
+          "ADAPTIVE_ICON_INSET_FACTOR",
+          "TYPE_RESOURCE",
+          "TYPE_BITMAP",
+          "ICON_DIAMETER_FACTOR",
+          "KEY_SHADOW_ALPHA",
+          "DEFAULT_VIEW_PORT_SCALE",
+          "AMBIENT_SHADOW_ALPHA",
+          "TYPE_DATA",
+          "BLUR_FACTOR",
+          "TYPE_URI",
+          "KEY_SHADOW_OFFSET_FACTOR",
+          "TYPE_ADAPTIVE_BITMAP"
+        ]
+      },
+      "android/support/v4/internal/view/SupportMenuItem": {
+        "androidx/internal/view/SupportMenuItem": [
+          "SHOW_AS_ACTION_ALWAYS",
+          "SHOW_AS_ACTION_NEVER",
+          "SHOW_AS_ACTION_WITH_TEXT",
+          "SHOW_AS_ACTION_IF_ROOM",
+          "SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW"
+        ]
+      },
+      "android/support/v7/cardview/R$dimen": {
+        "androidx/cardview/R$dimen": [
+          "cardview_compat_inset_shadow"
+        ]
+      },
+      "android/support/v4/widget/CompoundButtonCompat$CompoundButtonCompatBaseImpl": {
+        "androidx/widget/CompoundButtonCompat$CompoundButtonCompatBaseImpl": [
+          "sButtonDrawableField",
+          "sButtonDrawableFieldFetched",
+          "TAG"
+        ]
+      },
+      "android/support/v7/widget/StaggeredGridLayoutManager$LayoutParams": {
+        "androidx/widget/StaggeredGridLayoutManager$LayoutParams": [
+          "width",
+          "leftMargin",
+          "INVALID_SPAN_ID",
+          "bottomMargin",
+          "height",
+          "topMargin",
+          "rightMargin"
+        ]
+      },
+      "android/support/v17/leanback/widget/ItemAlignmentFacetHelper": {
+        "androidx/leanback/widget/ItemAlignmentFacetHelper": [
+          "sRect"
+        ]
+      },
+      "android/support/media/ExifInterface$ExifAttribute": {
+        "androidx/media/ExifInterface$ExifAttribute": [
+          "format",
+          "numberOfComponents",
+          "bytes"
+        ]
+      },
+      "android/support/compat/R$styleable": {
+        "androidx/compat/R$styleable": [
+          "FontFamily_fontProviderCerts",
+          "FontFamily_fontProviderAuthority",
+          "FontFamilyFont_fontWeight",
+          "FontFamilyFont_android_font",
+          "FontFamilyFont_android_fontStyle",
+          "FontFamilyFont_android_fontWeight",
+          "FontFamilyFont_fontStyle",
+          "FontFamilyFont",
+          "FontFamily_fontProviderFetchTimeout",
+          "FontFamily_fontProviderFetchStrategy",
+          "FontFamilyFont_font",
+          "FontFamily_fontProviderQuery",
+          "FontFamily",
+          "FontFamily_fontProviderPackage"
+        ]
+      },
+      "android/support/v7/widget/ButtonBarLayout": {
+        "androidx/widget/ButtonBarLayout": [
+          "PEEK_BUTTON_DP",
+          "ALLOW_STACKING_MIN_HEIGHT_DP"
+        ]
+      },
+      "android/support/v7/app/ActionBarDrawerToggleHoneycomb": {
+        "androidx/app/ActionBarDrawerToggleHoneycomb": [
+          "THEME_ATTRS",
+          "TAG"
+        ]
+      },
+      "android/support/v7/widget/RecyclerView$LayoutParams": {
+        "androidx/widget/RecyclerView$LayoutParams": [
+          "topMargin",
+          "bottomMargin",
+          "width",
+          "height",
+          "rightMargin",
+          "leftMargin"
+        ]
+      },
+      "android/support/v7/util/DiffUtil$Snake": {
+        "androidx/util/DiffUtil$Snake": [
+          "size",
+          "x",
+          "y",
+          "reverse",
+          "removal"
+        ]
+      },
+      "android/support/v4/print/PrintHelper": {
+        "androidx/print/PrintHelper": [
+          "COLOR_MODE_MONOCHROME",
+          "SCALE_MODE_FILL",
+          "ORIENTATION_PORTRAIT",
+          "SCALE_MODE_FIT",
+          "COLOR_MODE_COLOR",
+          "ORIENTATION_LANDSCAPE"
+        ]
+      },
+      "android/support/v4/widget/CompoundButtonCompat": {
+        "androidx/widget/CompoundButtonCompat": [
+          "IMPL"
+        ]
+      },
+      "android/support/v7/widget/DefaultItemAnimator$MoveInfo": {
+        "androidx/widget/DefaultItemAnimator$MoveInfo": [
+          "toY",
+          "toX",
+          "fromY",
+          "fromX",
+          "holder"
+        ]
+      },
+      "android/support/v7/widget/ActionMenuView": {
+        "androidx/widget/ActionMenuView": [
+          "MIN_CELL_SIZE",
+          "TAG",
+          "GENERATED_ITEM_PADDING"
+        ]
+      },
+      "android/support/v7/widget/GapWorker$Task": {
+        "androidx/widget/GapWorker$Task": [
+          "viewVelocity",
+          "position",
+          "view",
+          "immediate",
+          "distanceToItem"
+        ]
+      },
+      "android/support/v7/util/DiffUtil": {
+        "androidx/util/DiffUtil": [
+          "SNAKE_COMPARATOR"
+        ]
+      },
+      "android/support/v4/content/res/ResourcesCompat": {
+        "androidx/content/res/ResourcesCompat": [
+          "TAG"
+        ]
+      },
+      "android/support/v7/widget/ActionMenuView$LayoutParams": {
+        "androidx/widget/ActionMenuView$LayoutParams": [
+          "expandable",
+          "extraPixels",
+          "isOverflowButton",
+          "gravity",
+          "rightMargin",
+          "cellsUsed",
+          "expanded",
+          "leftMargin",
+          "preventEdgeOffset"
+        ]
+      },
+      "android/support/transition/Visibility": {
+        "androidx/transition/Visibility": [
+          "PROPNAME_PARENT",
+          "PROPNAME_VISIBILITY",
+          "MODE_OUT",
+          "PROPNAME_SCREEN_LOCATION",
+          "sTransitionProperties",
+          "MODE_IN"
+        ]
+      },
+      "android/support/v4/view/ViewPager$LayoutParams": {
+        "androidx/view/ViewPager$LayoutParams": [
+          "needsMeasure",
+          "height",
+          "width",
+          "childIndex",
+          "position",
+          "widthFactor",
+          "gravity",
+          "isDecor"
+        ]
+      },
+      "android/support/v17/preference/R$layout": {
+        "androidx/leanback/preference/R$layout": [
+          "leanback_list_preference_item_multi",
+          "leanback_list_preference_fragment",
+          "leanback_settings_fragment",
+          "leanback_list_preference_item_single",
+          "leanback_preferences_list",
+          "leanback_preference_fragment"
+        ]
+      },
+      "android/support/v4/widget/CircularProgressDrawable": {
+        "androidx/widget/CircularProgressDrawable": [
+          "ANIMATION_DURATION",
+          "CENTER_RADIUS",
+          "COLOR_CHANGE_OFFSET",
+          "ARROW_WIDTH",
+          "STROKE_WIDTH",
+          "MAX_PROGRESS_ARC",
+          "GROUP_FULL_ROTATION",
+          "MIN_PROGRESS_ARC",
+          "RING_ROTATION",
+          "DEFAULT",
+          "ARROW_WIDTH_LARGE",
+          "ARROW_HEIGHT_LARGE",
+          "CENTER_RADIUS_LARGE",
+          "COLORS",
+          "STROKE_WIDTH_LARGE",
+          "MATERIAL_INTERPOLATOR",
+          "ARROW_HEIGHT",
+          "SHRINK_OFFSET",
+          "LARGE",
+          "LINEAR_INTERPOLATOR"
+        ]
+      },
+      "android/support/v7/mediarouter/R$integer": {
+        "androidx/mediarouter/R$integer": [
+          "mr_controller_volume_group_list_animation_duration_ms",
+          "mr_controller_volume_group_list_fade_in_duration_ms",
+          "mr_controller_volume_group_list_fade_out_duration_ms"
+        ]
+      },
+      "android/support/v17/leanback/widget/ShadowOverlayContainer": {
+        "androidx/leanback/widget/ShadowOverlayContainer": [
+          "SHADOW_DYNAMIC",
+          "sTempRect",
+          "SHADOW_NONE",
+          "SHADOW_STATIC"
+        ]
+      },
+      "android/support/v4/widget/SwipeRefreshLayout": {
+        "androidx/widget/SwipeRefreshLayout": [
+          "LAYOUT_ATTRS",
+          "CIRCLE_DIAMETER_LARGE",
+          "ALPHA_ANIMATION_DURATION",
+          "MAX_ALPHA",
+          "ANIMATE_TO_START_DURATION",
+          "LOG_TAG",
+          "MAX_PROGRESS_ANGLE",
+          "ANIMATE_TO_TRIGGER_DURATION",
+          "DEFAULT",
+          "SCALE_DOWN_DURATION",
+          "CIRCLE_DIAMETER",
+          "INVALID_POINTER",
+          "DRAG_RATE",
+          "CIRCLE_BG_LIGHT",
+          "DECELERATE_INTERPOLATION_FACTOR",
+          "LARGE",
+          "DEFAULT_CIRCLE_TARGET",
+          "STARTING_PROGRESS_ALPHA"
+        ]
+      },
+      "android/support/annotation/Dimension": {
+        "androidx/annotation/Dimension": [
+          "DP",
+          "SP",
+          "PX"
+        ]
+      },
+      "android/support/v7/widget/AppCompatSpinner": {
+        "androidx/widget/AppCompatSpinner": [
+          "MAX_ITEMS_MEASURED",
+          "MODE_DIALOG",
+          "ATTRS_ANDROID_SPINNERMODE",
+          "TAG",
+          "MODE_THEME",
+          "MODE_DROPDOWN"
+        ]
+      },
+      "android/support/v13/view/inputmethod/InputConnectionCompat$InputContentInfoCompatBaseImpl": {
+        "androidx/view/inputmethod/InputConnectionCompat$InputContentInfoCompatBaseImpl": [
+          "COMMIT_CONTENT_RESULT_RECEIVER",
+          "COMMIT_CONTENT_OPTS_KEY",
+          "COMMIT_CONTENT_FLAGS_KEY",
+          "COMMIT_CONTENT_LINK_URI_KEY",
+          "COMMIT_CONTENT_DESCRIPTION_KEY",
+          "COMMIT_CONTENT_ACTION",
+          "COMMIT_CONTENT_CONTENT_URI_KEY"
+        ]
+      },
+      "android/support/v7/app/TwilightCalculator": {
+        "androidx/app/TwilightCalculator": [
+          "OBLIQUITY",
+          "ALTIDUTE_CORRECTION_CIVIL_TWILIGHT",
+          "UTC_2000",
+          "DEGREES_TO_RADIANS",
+          "NIGHT",
+          "sunrise",
+          "state",
+          "DAY",
+          "J0",
+          "C1",
+          "C2",
+          "C3",
+          "sunset",
+          "sInstance"
+        ]
+      },
+      "android/support/v7/widget/LinearLayoutManager$LayoutState": {
+        "androidx/widget/LinearLayoutManager$LayoutState": [
+          "TAG",
+          "SCROLLING_OFFSET_NaN",
+          "LAYOUT_START",
+          "ITEM_DIRECTION_TAIL",
+          "INVALID_LAYOUT",
+          "LAYOUT_END",
+          "ITEM_DIRECTION_HEAD"
+        ]
+      },
+      "android/support/wear/R$drawable": {
+        "androidx/wear/R$drawable": [
+          "ws_ic_more_horiz_24dp_wht",
+          "ws_ic_more_vert_24dp_wht"
+        ]
+      },
+      "android/support/v17/leanback/widget/ControlBarPresenter$BoundData": {
+        "androidx/leanback/widget/ControlBarPresenter$BoundData": [
+          "adapter",
+          "presenter"
+        ]
+      },
+      "android/support/v4/media/MediaBrowserServiceCompat$ConnectionRecord": {
+        "androidx/media/MediaBrowserServiceCompat$ConnectionRecord": [
+          "callbacks",
+          "pkg",
+          "subscriptions",
+          "rootHints",
+          "root"
+        ]
+      },
+      "android/support/v4/media/session/MediaSessionCompat$MediaSessionImplApi18": {
+        "androidx/media/session/MediaSessionCompat$MediaSessionImplApi18": [
+          "sIsMbrPendingIntentSupported"
+        ]
+      },
+      "android/support/v4/util/PatternsCompat": {
+        "androidx/util/PatternsCompat": [
+          "TLD_CHAR",
+          "USER_INFO",
+          "WEB_URL",
+          "AUTOLINK_WEB_URL",
+          "LABEL_CHAR",
+          "PATH_AND_QUERY",
+          "PUNYCODE_TLD",
+          "EMAIL_CHAR",
+          "STRICT_DOMAIN_NAME",
+          "STRICT_HOST_NAME",
+          "IP_ADDRESS",
+          "STRICT_TLD",
+          "WEB_URL_WITHOUT_PROTOCOL",
+          "PROTOCOL",
+          "EMAIL_ADDRESS",
+          "EMAIL_ADDRESS_LOCAL_PART",
+          "UCS_CHAR",
+          "TLD",
+          "IRI_LABEL",
+          "WEB_URL_WITH_PROTOCOL",
+          "AUTOLINK_EMAIL_ADDRESS",
+          "PORT_NUMBER",
+          "EMAIL_ADDRESS_DOMAIN",
+          "DOMAIN_NAME",
+          "HOST_NAME",
+          "IANA_TOP_LEVEL_DOMAINS",
+          "RELAXED_DOMAIN_NAME",
+          "WORD_BOUNDARY"
+        ]
+      },
+      "android/support/v17/leanback/widget/PlaybackControlsRow$ClosedCaptioningAction": {
+        "androidx/leanback/widget/PlaybackControlsRow$ClosedCaptioningAction": [
+          "INDEX_OFF",
+          "INDEX_ON",
+          "OFF",
+          "ON"
+        ]
+      },
+      "android/support/v4/widget/ExploreByTouchHelper": {
+        "androidx/widget/ExploreByTouchHelper": [
+          "INVALID_ID",
+          "NODE_ADAPTER",
+          "DEFAULT_CLASS_NAME",
+          "INVALID_PARENT_BOUNDS",
+          "HOST_ID",
+          "SPARSE_VALUES_ADAPTER"
+        ]
+      },
+      "android/support/v7/widget/OrientationHelper": {
+        "androidx/widget/OrientationHelper": [
+          "INVALID_SIZE",
+          "HORIZONTAL",
+          "VERTICAL"
+        ]
+      },
+      "android/support/animation/SpringForce": {
+        "androidx/animation/SpringForce": [
+          "DAMPING_RATIO_NO_BOUNCY",
+          "UNSET",
+          "DAMPING_RATIO_HIGH_BOUNCY",
+          "VELOCITY_THRESHOLD_MULTIPLIER",
+          "STIFFNESS_LOW",
+          "DAMPING_RATIO_LOW_BOUNCY",
+          "STIFFNESS_HIGH",
+          "STIFFNESS_MEDIUM",
+          "DAMPING_RATIO_MEDIUM_BOUNCY",
+          "STIFFNESS_VERY_LOW"
+        ]
+      },
+      "android/support/v7/view/menu/MenuAdapter": {
+        "androidx/view/menu/MenuAdapter": [
+          "ITEM_LAYOUT"
+        ]
+      },
+      "android/support/design/widget/ViewGroupUtils": {
+        "androidx/widget/ViewGroupUtils": [
+          "sMatrix",
+          "sRectF"
+        ]
+      },
+      "android/support/multidex/ZipUtil": {
+        "androidx/multidex/ZipUtil": [
+          "ENDSIG",
+          "ENDHDR",
+          "BUFFER_SIZE"
+        ]
+      },
+      "android/support/v7/media/MediaItemStatus": {
+        "androidx/media/MediaItemStatus": [
+          "KEY_EXTRAS",
+          "PLAYBACK_STATE_PLAYING",
+          "KEY_TIMESTAMP",
+          "KEY_CONTENT_POSITION",
+          "PLAYBACK_STATE_PENDING",
+          "PLAYBACK_STATE_FINISHED",
+          "PLAYBACK_STATE_BUFFERING",
+          "PLAYBACK_STATE_PAUSED",
+          "PLAYBACK_STATE_INVALIDATED",
+          "KEY_CONTENT_DURATION",
+          "PLAYBACK_STATE_ERROR",
+          "PLAYBACK_STATE_CANCELED",
+          "EXTRA_HTTP_RESPONSE_HEADERS",
+          "KEY_PLAYBACK_STATE",
+          "EXTRA_HTTP_STATUS_CODE"
+        ]
+      },
+      "android/support/v7/widget/RecyclerView$Recycler": {
+        "androidx/widget/RecyclerView$Recycler": [
+          "DEFAULT_CACHE_SIZE"
+        ]
+      },
+      "android/support/transition/ViewUtilsApi19": {
+        "androidx/transition/ViewUtilsApi19": [
+          "sSetTransitionAlphaMethod",
+          "sSetTransitionAlphaMethodFetched",
+          "TAG",
+          "sGetTransitionAlphaMethod",
+          "sGetTransitionAlphaMethodFetched"
+        ]
+      },
+      "android/support/v4/media/MediaBrowserCompat": {
+        "androidx/media/MediaBrowserCompat": [
+          "EXTRA_PAGE",
+          "DEBUG",
+          "EXTRA_MEDIA_ID",
+          "EXTRA_PAGE_SIZE",
+          "CUSTOM_ACTION_DOWNLOAD",
+          "CUSTOM_ACTION_REMOVE_DOWNLOADED_FILE",
+          "TAG",
+          "EXTRA_DOWNLOAD_PROGRESS"
+        ]
+      },
+      "android/support/v4/app/FragmentTransition$FragmentContainerTransition": {
+        "androidx/app/FragmentTransition$FragmentContainerTransition": [
+          "lastInTransaction",
+          "lastIn",
+          "lastInIsPop",
+          "firstOut",
+          "firstOutIsPop",
+          "firstOutTransaction"
+        ]
+      },
+      "android/support/v4/text/util/LinkifyCompat": {
+        "androidx/text/util/LinkifyCompat": [
+          "COMPARATOR",
+          "EMPTY_STRING"
+        ]
+      },
+      "android/support/v7/app/TwilightManager$TwilightState": {
+        "androidx/app/TwilightManager$TwilightState": [
+          "todaySunrise",
+          "todaySunset",
+          "yesterdaySunset",
+          "nextUpdate",
+          "tomorrowSunrise",
+          "isNight"
+        ]
+      },
+      "android/support/constraint/BuildConfig": {
+        "androidx/constraint/BuildConfig": [
+          "VERSION_NAME",
+          "DEBUG",
+          "FLAVOR",
+          "VERSION_CODE",
+          "APPLICATION_ID",
+          "BUILD_TYPE"
+        ]
+      },
+      "android/support/v4/content/res/FontResourcesParserCompat": {
+        "androidx/content/res/FontResourcesParserCompat": [
+          "NORMAL_WEIGHT",
+          "FETCH_STRATEGY_ASYNC",
+          "INFINITE_TIMEOUT_VALUE",
+          "DEFAULT_TIMEOUT_MILLIS",
+          "FETCH_STRATEGY_BLOCKING",
+          "ITALIC"
+        ]
+      },
+      "android/support/v7/widget/ListPopupWindow": {
+        "androidx/widget/ListPopupWindow": [
+          "sSetEpicenterBoundsMethod",
+          "WRAP_CONTENT",
+          "MATCH_PARENT",
+          "POSITION_PROMPT_BELOW",
+          "DEBUG",
+          "sGetMaxAvailableHeightMethod",
+          "INPUT_METHOD_FROM_FOCUSABLE",
+          "INPUT_METHOD_NEEDED",
+          "EXPAND_LIST_TIMEOUT",
+          "sClipToWindowEnabledMethod",
+          "POSITION_PROMPT_ABOVE",
+          "TAG",
+          "INPUT_METHOD_NOT_NEEDED"
+        ]
+      },
+      "android/support/v7/media/MediaRouteProviderProtocol": {
+        "androidx/media/MediaRouteProviderProtocol": [
+          "SERVICE_INTERFACE",
+          "CLIENT_MSG_RELEASE_ROUTE_CONTROLLER",
+          "CLIENT_DATA_ROUTE_ID",
+          "CLIENT_VERSION_CURRENT",
+          "CLIENT_VERSION_START",
+          "CLIENT_DATA_ROUTE_LIBRARY_GROUP",
+          "CLIENT_VERSION_1",
+          "CLIENT_VERSION_2",
+          "SERVICE_VERSION_CURRENT",
+          "SERVICE_MSG_CONTROL_REQUEST_SUCCEEDED",
+          "CLIENT_MSG_SELECT_ROUTE",
+          "CLIENT_MSG_UNREGISTER",
+          "CLIENT_MSG_UPDATE_ROUTE_VOLUME",
+          "SERVICE_MSG_REGISTERED",
+          "CLIENT_MSG_CREATE_ROUTE_CONTROLLER",
+          "CLIENT_MSG_ROUTE_CONTROL_REQUEST",
+          "CLIENT_DATA_UNSELECT_REASON",
+          "CLIENT_MSG_SET_DISCOVERY_REQUEST",
+          "SERVICE_MSG_CONTROL_REQUEST_FAILED",
+          "CLIENT_MSG_REGISTER",
+          "CLIENT_MSG_UNSELECT_ROUTE",
+          "SERVICE_MSG_GENERIC_FAILURE",
+          "SERVICE_MSG_GENERIC_SUCCESS",
+          "SERVICE_VERSION_1",
+          "CLIENT_DATA_VOLUME",
+          "SERVICE_DATA_ERROR",
+          "SERVICE_MSG_DESCRIPTOR_CHANGED",
+          "CLIENT_MSG_SET_ROUTE_VOLUME"
+        ]
+      },
+      "android/support/v4/app/Fragment": {
+        "androidx/app/Fragment": [
+          "CREATED",
+          "STARTED",
+          "ACTIVITY_CREATED",
+          "USE_DEFAULT_TRANSITION",
+          "RESUMED",
+          "sClassMap",
+          "INITIALIZING",
+          "STOPPED"
+        ]
+      },
+      "android/support/v7/app/AlertController$ButtonHandler": {
+        "androidx/app/AlertController$ButtonHandler": [
+          "MSG_DISMISS_DIALOG"
+        ]
+      },
+      "android/support/v4/text/BidiFormatter": {
+        "androidx/text/BidiFormatter": [
+          "DIR_LTR",
+          "DIR_UNKNOWN",
+          "DEFAULT_FLAGS",
+          "DEFAULT_LTR_INSTANCE",
+          "PDF",
+          "FLAG_STEREO_RESET",
+          "LRM_STRING",
+          "LRE",
+          "LRM",
+          "DEFAULT_TEXT_DIRECTION_HEURISTIC",
+          "DIR_RTL",
+          "DEFAULT_RTL_INSTANCE",
+          "RLM",
+          "RLE",
+          "EMPTY_STRING",
+          "RLM_STRING"
+        ]
+      },
+      "android/support/v17/leanback/widget/BaseGridView": {
+        "androidx/leanback/widget/BaseGridView": [
+          "WINDOW_ALIGN_NO_EDGE",
+          "SAVE_ALL_CHILD",
+          "WINDOW_ALIGN_BOTH_EDGE",
+          "FOCUS_SCROLL_PAGE",
+          "WINDOW_ALIGN_HIGH_EDGE",
+          "ITEM_ALIGN_OFFSET_PERCENT_DISABLED",
+          "WINDOW_ALIGN_LOW_EDGE",
+          "SAVE_ON_SCREEN_CHILD",
+          "SAVE_NO_CHILD",
+          "FOCUS_SCROLL_ALIGNED",
+          "WINDOW_ALIGN_OFFSET_PERCENT_DISABLED",
+          "FOCUS_SCROLL_ITEM",
+          "SAVE_LIMITED_CHILD"
+        ]
+      },
+      "android/support/v4/app/FragmentActivity": {
+        "androidx/app/FragmentActivity": [
+          "MSG_REALLY_STOPPED",
+          "NEXT_CANDIDATE_REQUEST_INDEX_TAG",
+          "MAX_NUM_PENDING_FRAGMENT_ACTIVITY_RESULTS",
+          "FRAGMENTS_TAG",
+          "TAG",
+          "ALLOCATED_REQUEST_INDICIES_TAG",
+          "MSG_RESUME_PENDING",
+          "REQUEST_FRAGMENT_WHO_TAG"
+        ]
+      },
+      "android/support/v7/gridlayout/R$dimen": {
+        "androidx/gridlayout/R$dimen": [
+          "default_gap"
+        ]
+      },
+      "android/support/content/ContentPager$Stats": {
+        "androidx/content/ContentPager$Stats": [
+          "EXTRA_COMPAT_PAGED",
+          "EXTRA_PROVIDER_PAGED",
+          "EXTRA_RESOLVED_QUERIES",
+          "EXTRA_TOTAL_QUERIES"
+        ]
+      },
+      "android/support/design/widget/BaseTransientBottomBar": {
+        "androidx/design/widget/BaseTransientBottomBar": [
+          "ANIMATION_DURATION",
+          "USE_OFFSET_API",
+          "LENGTH_SHORT",
+          "MSG_SHOW",
+          "MSG_DISMISS",
+          "LENGTH_LONG",
+          "sHandler",
+          "ANIMATION_FADE_DURATION",
+          "LENGTH_INDEFINITE"
+        ]
+      },
+      "android/support/v17/leanback/widget/ArrayObjectAdapter": {
+        "androidx/leanback/widget/ArrayObjectAdapter": [
+          "DEBUG",
+          "TAG"
+        ]
+      },
+      "android/support/v4/view/AsyncLayoutInflater$InflateRequest": {
+        "androidx/view/AsyncLayoutInflater$InflateRequest": [
+          "inflater",
+          "callback",
+          "view",
+          "parent",
+          "resid"
+        ]
+      },
+      "android/support/transition/Styleable": {
+        "androidx/transition/Styleable": [
+          "TRANSITION",
+          "VISIBILITY_TRANSITION",
+          "CHANGE_TRANSFORM",
+          "SLIDE",
+          "TRANSITION_MANAGER",
+          "CHANGE_BOUNDS",
+          "TRANSITION_SET",
+          "FADE",
+          "TRANSITION_TARGET",
+          "ARC_MOTION",
+          "PATTERN_PATH_MOTION"
+        ]
+      },
+      "android/support/design/widget/Snackbar$Callback": {
+        "androidx/design/widget/Snackbar$Callback": [
+          "DISMISS_EVENT_ACTION",
+          "DISMISS_EVENT_TIMEOUT",
+          "DISMISS_EVENT_SWIPE",
+          "DISMISS_EVENT_CONSECUTIVE",
+          "DISMISS_EVENT_MANUAL"
+        ]
+      },
+      "android/support/v4/content/ModernAsyncTask$Status": {
+        "androidx/content/ModernAsyncTask$Status": [
+          "PENDING",
+          "FINISHED",
+          "RUNNING"
+        ]
+      },
+      "android/support/wear/widget/drawer/WearableActionDrawerView$ActionListAdapter": {
+        "androidx/wear/widget/drawer/WearableActionDrawerView$ActionListAdapter": [
+          "TYPE_TITLE",
+          "TYPE_ACTION"
+        ]
+      },
+      "android/support/design/widget/AppBarLayout$Behavior": {
+        "androidx/design/widget/AppBarLayout$Behavior": [
+          "INVALID_POSITION",
+          "MAX_OFFSET_ANIMATION_DURATION"
+        ]
+      },
+      "android/support/v17/leanback/app/BaseSupportFragment": {
+        "androidx/leanback/app/BaseSupportFragment": [
+          "STATE_ENTRANCE_INIT",
+          "EVT_PREPARE_ENTRANCE",
+          "EVT_ON_CREATE",
+          "COND_TRANSITION_NOT_SUPPORTED",
+          "STATE_ENTRANCE_PERFORM",
+          "EVT_ENTRANCE_END",
+          "EVT_ON_CREATEVIEW",
+          "STATE_ENTRANCE_ON_PREPARED",
+          "STATE_ENTRANCE_ON_PREPARED_ON_CREATEVIEW",
+          "EVT_START_ENTRANCE",
+          "STATE_ENTRANCE_ON_ENDED",
+          "STATE_START",
+          "STATE_ENTRANCE_COMPLETE"
+        ]
+      },
+      "android/support/annotation/VisibleForTesting": {
+        "androidx/annotation/VisibleForTesting": [
+          "PROTECTED",
+          "PACKAGE_PRIVATE",
+          "NONE",
+          "PRIVATE"
+        ]
+      },
+      "android/support/v17/leanback/widget/Parallax$IntProperty": {
+        "androidx/leanback/widget/Parallax$IntProperty": [
+          "UNKNOWN_AFTER",
+          "UNKNOWN_BEFORE"
+        ]
+      },
+      "android/support/v7/media/SystemMediaRouteProvider$JellybeanImpl": {
+        "androidx/media/SystemMediaRouteProvider$JellybeanImpl": [
+          "LIVE_AUDIO_CONTROL_FILTERS",
+          "LIVE_VIDEO_CONTROL_FILTERS"
+        ]
+      },
+      "android/support/constraint/ConstraintLayout": {
+        "androidx/constraint/ConstraintLayout": [
+          "VERSION",
+          "ALLOWS_EMBEDDED",
+          "SIMPLE_LAYOUT",
+          "TAG"
+        ]
+      },
+      "android/support/v4/widget/TextViewCompat": {
+        "androidx/widget/TextViewCompat": [
+          "AUTO_SIZE_TEXT_TYPE_UNIFORM",
+          "IMPL",
+          "AUTO_SIZE_TEXT_TYPE_NONE"
+        ]
+      },
+      "android/support/content/ContentPager": {
+        "androidx/content/ContentPager": [
+          "DEBUG",
+          "CURSOR_DISPOSITION",
+          "EXTRA_TOTAL_COUNT",
+          "EXTRA_HONORED_ARGS",
+          "CURSOR_DISPOSITION_REPAGED",
+          "QUERY_ARG_LIMIT",
+          "QUERY_ARG_OFFSET",
+          "EXTRA_SUGGESTED_LIMIT",
+          "EXTRA_REQUESTED_LIMIT",
+          "CURSOR_DISPOSITION_PAGED",
+          "TAG",
+          "CURSOR_DISPOSITION_WRAPPED",
+          "DEFAULT_CURSOR_CACHE_SIZE",
+          "CURSOR_DISPOSITION_COPIED"
+        ]
+      },
+      "android/support/v13/app/FragmentTabHost$SavedState": {
+        "androidx/app/FragmentTabHost$SavedState": [
+          "curTab",
+          "CREATOR"
+        ]
+      },
+      "android/support/design/widget/CollapsingToolbarLayout$LayoutParams": {
+        "androidx/design/widget/CollapsingToolbarLayout$LayoutParams": [
+          "DEFAULT_PARALLAX_MULTIPLIER",
+          "bottomMargin",
+          "COLLAPSE_MODE_PARALLAX",
+          "COLLAPSE_MODE_PIN",
+          "COLLAPSE_MODE_OFF"
+        ]
+      },
+      "android/support/v7/app/WindowDecorActionBar": {
+        "androidx/app/WindowDecorActionBar": [
+          "FADE_OUT_DURATION_MS",
+          "FADE_IN_DURATION_MS",
+          "sShowInterpolator",
+          "INVALID_POSITION",
+          "sHideInterpolator",
+          "TAG"
+        ]
+      },
+      "android/support/v4/view/PointerIconCompat": {
+        "androidx/view/PointerIconCompat": [
+          "TYPE_ZOOM_IN",
+          "TYPE_HELP",
+          "TYPE_WAIT",
+          "TYPE_VERTICAL_TEXT",
+          "TYPE_GRAB",
+          "TYPE_NULL",
+          "TYPE_CELL",
+          "TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW",
+          "TYPE_ARROW",
+          "TYPE_TEXT",
+          "TYPE_HORIZONTAL_DOUBLE_ARROW",
+          "TYPE_HAND",
+          "TYPE_DEFAULT",
+          "TYPE_VERTICAL_DOUBLE_ARROW",
+          "TYPE_NO_DROP",
+          "TYPE_COPY",
+          "TYPE_ALL_SCROLL",
+          "TYPE_GRABBING",
+          "TYPE_ALIAS",
+          "TYPE_CROSSHAIR",
+          "TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW",
+          "TYPE_ZOOM_OUT",
+          "TYPE_CONTEXT_MENU"
+        ]
+      },
+      "android/support/v7/widget/GridLayout$Bounds": {
+        "androidx/widget/GridLayout$Bounds": [
+          "after",
+          "flexibility",
+          "before"
+        ]
+      },
+      "android/support/v4/internal/view/SupportMenu": {
+        "androidx/internal/view/SupportMenu": [
+          "USER_SHIFT",
+          "SUPPORTED_MODIFIERS_MASK",
+          "USER_MASK",
+          "CATEGORY_SHIFT",
+          "FLAG_KEEP_OPEN_ON_SUBMENU_OPENED",
+          "CATEGORY_MASK"
+        ]
+      },
+      "android/support/multidex/ZipUtil$CentralDirectory": {
+        "androidx/multidex/ZipUtil$CentralDirectory": [
+          "offset",
+          "size"
+        ]
+      },
+      "android/support/app/recommendation/ContentRecommendation": {
+        "androidx/app/recommendation/ContentRecommendation": [
+          "CONTENT_TYPE_SERIAL",
+          "CONTENT_TYPE_SPORTS",
+          "CONTENT_TYPE_APP",
+          "CONTENT_MATURITY_LOW",
+          "CONTENT_MATURITY_ALL",
+          "CONTENT_PRICING_PREORDER",
+          "INTENT_TYPE_BROADCAST",
+          "CONTENT_STATUS_AVAILABLE",
+          "CONTENT_PRICING_SUBSCRIPTION",
+          "CONTENT_TYPE_MAGAZINE",
+          "CONTENT_TYPE_VIDEO",
+          "CONTENT_MATURITY_MEDIUM",
+          "CONTENT_PRICING_PURCHASE",
+          "CONTENT_TYPE_RADIO",
+          "CONTENT_TYPE_WEBSITE",
+          "CONTENT_TYPE_MUSIC",
+          "INTENT_TYPE_ACTIVITY",
+          "CONTENT_STATUS_UNAVAILABLE",
+          "CONTENT_PRICING_FREE",
+          "CONTENT_TYPE_TRAILER",
+          "CONTENT_TYPE_GAME",
+          "CONTENT_MATURITY_HIGH",
+          "CONTENT_TYPE_NEWS",
+          "CONTENT_STATUS_READY",
+          "CONTENT_TYPE_MOVIE",
+          "CONTENT_STATUS_PENDING",
+          "INTENT_TYPE_SERVICE",
+          "CONTENT_TYPE_PODCAST",
+          "CONTENT_PRICING_RENTAL",
+          "CONTENT_TYPE_COMIC",
+          "CONTENT_TYPE_BOOK"
+        ]
+      },
+      "android/support/v4/media/app/NotificationCompat$MediaStyle": {
+        "androidx/media/app/NotificationCompat$MediaStyle": [
+          "MAX_MEDIA_BUTTONS",
+          "MAX_MEDIA_BUTTONS_IN_COMPACT"
+        ]
+      },
+      "android/support/v4/media/session/MediaControllerCompat$Callback$MessageHandler": {
+        "androidx/media/session/MediaControllerCompat$Callback$MessageHandler": [
+          "MSG_EVENT",
+          "MSG_UPDATE_QUEUE_TITLE",
+          "MSG_UPDATE_EXTRAS",
+          "MSG_UPDATE_METADATA",
+          "MSG_UPDATE_VOLUME",
+          "MSG_UPDATE_SHUFFLE_MODE",
+          "MSG_DESTROYED",
+          "MSG_SESSION_READY",
+          "MSG_UPDATE_REPEAT_MODE",
+          "MSG_UPDATE_CAPTIONING_ENABLED",
+          "MSG_UPDATE_QUEUE",
+          "MSG_UPDATE_PLAYBACK_STATE"
+        ]
+      },
+      "android/support/v7/widget/LinearLayoutManager": {
+        "androidx/widget/LinearLayoutManager": [
+          "TAG",
+          "MAX_SCROLL_FACTOR",
+          "VERTICAL",
+          "INVALID_OFFSET",
+          "DEBUG",
+          "HORIZONTAL"
+        ]
+      },
+      "android/support/percent/BuildConfig": {
+        "androidx/BuildConfig": [
+          "VERSION_NAME",
+          "BUILD_TYPE",
+          "VERSION_CODE",
+          "APPLICATION_ID",
+          "FLAVOR",
+          "DEBUG"
+        ]
+      },
+      "android/support/graphics/drawable/BuildConfig": {
+        "androidx/graphics/drawable/BuildConfig": [
+          "VERSION_NAME",
+          "DEBUG",
+          "APPLICATION_ID",
+          "BUILD_TYPE",
+          "VERSION_CODE",
+          "FLAVOR"
+        ]
+      },
+      "android/support/v4/view/ViewParentCompat": {
+        "androidx/view/ViewParentCompat": [
+          "IMPL",
+          "TAG"
+        ]
+      },
+      "android/support/v4/widget/DrawerLayout$LayoutParams": {
+        "androidx/widget/DrawerLayout$LayoutParams": [
+          "gravity",
+          "width",
+          "FLAG_IS_OPENED",
+          "openState",
+          "onScreen",
+          "isPeeking",
+          "leftMargin",
+          "bottomMargin",
+          "FLAG_IS_OPENING",
+          "topMargin",
+          "FLAG_IS_CLOSING",
+          "height",
+          "rightMargin"
+        ]
+      },
+      "android/support/v4/graphics/drawable/DrawableWrapperApi14": {
+        "androidx/graphics/drawable/DrawableWrapperApi14": [
+          "DEFAULT_TINT_MODE"
+        ]
+      },
+      "android/support/v7/widget/GridLayoutManager$LayoutParams": {
+        "androidx/widget/GridLayoutManager$LayoutParams": [
+          "bottomMargin",
+          "rightMargin",
+          "height",
+          "topMargin",
+          "width",
+          "INVALID_SPAN_ID",
+          "leftMargin"
+        ]
+      },
+      "android/support/v4/app/NotificationManagerCompat$SideChannelManager$ListenerRecord": {
+        "androidx/app/NotificationManagerCompat$SideChannelManager$ListenerRecord": [
+          "taskQueue",
+          "service",
+          "componentName",
+          "retryCount",
+          "bound"
+        ]
+      },
+      "android/support/v17/leanback/widget/picker/DatePicker": {
+        "androidx/leanback/widget/picker/DatePicker": [
+          "DATE_FORMAT",
+          "LOG_TAG",
+          "DATE_FIELDS"
+        ]
+      },
+      "android/support/v7/widget/DrawableUtils": {
+        "androidx/widget/DrawableUtils": [
+          "INSETS_NONE",
+          "VECTOR_DRAWABLE_CLAZZ_NAME",
+          "TAG",
+          "sInsetsClazz"
+        ]
+      },
+      "android/support/v4/media/session/MediaControllerCompat$PlaybackInfo": {
+        "androidx/media/session/MediaControllerCompat$PlaybackInfo": [
+          "PLAYBACK_TYPE_LOCAL",
+          "PLAYBACK_TYPE_REMOTE"
+        ]
+      },
+      "android/support/v17/leanback/widget/SearchOrbView$Colors": {
+        "androidx/leanback/widget/SearchOrbView$Colors": [
+          "sBrightnessAlpha",
+          "brightColor",
+          "iconColor",
+          "color"
+        ]
+      },
+      "android/support/v7/widget/LayoutState": {
+        "androidx/widget/LayoutState": [
+          "TAG",
+          "LAYOUT_END",
+          "INVALID_LAYOUT",
+          "ITEM_DIRECTION_HEAD",
+          "ITEM_DIRECTION_TAIL",
+          "LAYOUT_START"
+        ]
+      },
+      "android/support/v17/leanback/app/BaseFragment": {
+        "androidx/leanback/app/BaseFragment": [
+          "STATE_ENTRANCE_COMPLETE",
+          "EVT_ON_CREATEVIEW",
+          "COND_TRANSITION_NOT_SUPPORTED",
+          "STATE_ENTRANCE_ON_PREPARED_ON_CREATEVIEW",
+          "STATE_ENTRANCE_ON_ENDED",
+          "EVT_START_ENTRANCE",
+          "EVT_PREPARE_ENTRANCE",
+          "STATE_ENTRANCE_INIT",
+          "STATE_START",
+          "STATE_ENTRANCE_ON_PREPARED",
+          "EVT_ENTRANCE_END",
+          "STATE_ENTRANCE_PERFORM",
+          "EVT_ON_CREATE"
+        ]
+      },
+      "android/support/v7/widget/ActivityChooserView": {
+        "androidx/widget/ActivityChooserView": [
+          "LOG_TAG"
+        ]
+      },
+      "android/support/v17/leanback/widget/picker/PickerUtility$TimeConstant": {
+        "androidx/leanback/widget/picker/PickerUtility$TimeConstant": [
+          "ampm",
+          "minutes",
+          "locale",
+          "hours12",
+          "hours24"
+        ]
+      },
+      "android/support/design/widget/DrawableUtils": {
+        "androidx/design/widget/DrawableUtils": [
+          "LOG_TAG",
+          "sSetConstantStateMethod",
+          "sSetConstantStateMethodFetched"
+        ]
+      },
+      "android/support/v7/util/AsyncListUtil": {
+        "androidx/util/AsyncListUtil": [
+          "TAG",
+          "DEBUG"
+        ]
+      },
+      "android/support/v7/widget/ActivityChooserView$ActivityChooserViewAdapter": {
+        "androidx/widget/ActivityChooserView$ActivityChooserViewAdapter": [
+          "ITEM_VIEW_TYPE_COUNT",
+          "MAX_ACTIVITY_COUNT_DEFAULT",
+          "ITEM_VIEW_TYPE_ACTIVITY",
+          "ITEM_VIEW_TYPE_FOOTER",
+          "MAX_ACTIVITY_COUNT_UNLIMITED"
+        ]
+      },
+      "android/support/v17/leanback/widget/ControlBarPresenter": {
+        "androidx/leanback/widget/ControlBarPresenter": [
+          "sChildMarginDefault",
+          "MAX_CONTROLS",
+          "sControlIconWidth"
+        ]
+      },
+      "android/support/v4/app/FragmentTransaction": {
+        "androidx/app/FragmentTransaction": [
+          "TRANSIT_FRAGMENT_CLOSE",
+          "TRANSIT_EXIT_MASK",
+          "TRANSIT_FRAGMENT_OPEN",
+          "TRANSIT_UNSET",
+          "TRANSIT_FRAGMENT_FADE",
+          "TRANSIT_NONE",
+          "TRANSIT_ENTER_MASK"
+        ]
+      },
+      "android/support/design/widget/BottomSheetBehavior": {
+        "androidx/design/widget/BottomSheetBehavior": [
+          "STATE_COLLAPSED",
+          "HIDE_THRESHOLD",
+          "STATE_SETTLING",
+          "STATE_HIDDEN",
+          "STATE_DRAGGING",
+          "HIDE_FRICTION",
+          "STATE_EXPANDED",
+          "PEEK_HEIGHT_AUTO"
+        ]
+      },
+      "android/support/v17/leanback/app/VerticalGridSupportFragment": {
+        "androidx/leanback/app/VerticalGridSupportFragment": [
+          "DEBUG",
+          "STATE_ENTRANCE_ON_PREPARED",
+          "EVT_ON_CREATEVIEW",
+          "STATE_SET_ENTRANCE_START_STATE",
+          "TAG"
+        ]
+      },
+      "android/support/v7/media/MediaItemMetadata": {
+        "androidx/media/MediaItemMetadata": [
+          "KEY_ALBUM_TITLE",
+          "KEY_DURATION",
+          "KEY_YEAR",
+          "KEY_TRACK_NUMBER",
+          "KEY_AUTHOR",
+          "KEY_DISC_NUMBER",
+          "KEY_ALBUM_ARTIST",
+          "KEY_COMPOSER",
+          "KEY_ARTIST",
+          "KEY_ARTWORK_URI",
+          "KEY_TITLE"
+        ]
+      },
+      "android/support/v7/appcompat/R$bool": {
+        "androidx/appcompat/R$bool": [
+          "abc_config_showMenuShortcutsWhenKeyboardPresent",
+          "abc_action_bar_embed_tabs"
+        ]
+      },
+      "android/support/transition/ViewUtilsApi21": {
+        "androidx/transition/ViewUtilsApi21": [
+          "sTransformMatrixToGlobalMethodFetched",
+          "sTransformMatrixToLocalMethodFetched",
+          "TAG",
+          "sSetAnimationMatrixMethod",
+          "sTransformMatrixToGlobalMethod",
+          "sTransformMatrixToLocalMethod",
+          "sSetAnimationMatrixMethodFetched"
+        ]
+      },
+      "android/support/v4/util/Pair": {
+        "androidx/util/Pair": [
+          "first",
+          "second"
+        ]
+      },
+      "android/support/v7/preference/SeekBarPreference$SavedState": {
+        "androidx/preference/SeekBarPreference$SavedState": [
+          "min",
+          "max",
+          "CREATOR",
+          "seekBarValue"
+        ]
+      },
+      "android/support/v7/mediarouter/R$attr": {
+        "androidx/mediarouter/R$attr": [
+          "mediaRouteTheme",
+          "mediaRouteTvIconDrawable",
+          "mediaRouteButtonStyle",
+          "mediaRouteStopDrawable",
+          "mediaRoutePauseDrawable",
+          "mediaRouteSpeakerGroupIconDrawable",
+          "mediaRouteSpeakerIconDrawable",
+          "mediaRouteDefaultIconDrawable",
+          "mediaRoutePlayDrawable"
+        ]
+      },
+      "android/support/v7/view/SupportMenuInflater": {
+        "androidx/view/SupportMenuInflater": [
+          "XML_ITEM",
+          "XML_GROUP",
+          "LOG_TAG",
+          "ACTION_VIEW_CONSTRUCTOR_SIGNATURE",
+          "XML_MENU",
+          "ACTION_PROVIDER_CONSTRUCTOR_SIGNATURE",
+          "NO_ID"
+        ]
+      },
+      "android/support/v4/view/AsyncLayoutInflater$InflateThread": {
+        "androidx/view/AsyncLayoutInflater$InflateThread": [
+          "sInstance"
+        ]
+      },
+      "android/support/design/widget/CollapsingTextHelper": {
+        "androidx/design/widget/CollapsingTextHelper": [
+          "DEBUG_DRAW",
+          "USE_SCALING_TEXTURE",
+          "DEBUG_DRAW_PAINT"
+        ]
+      },
+      "android/support/v4/app/FragmentTabHost$TabInfo": {
+        "androidx/app/FragmentTabHost$TabInfo": [
+          "tag",
+          "fragment",
+          "args",
+          "clss"
+        ]
+      },
+      "android/support/v17/leanback/app/VideoFragment": {
+        "androidx/leanback/app/VideoFragment": [
+          "SURFACE_NOT_CREATED",
+          "SURFACE_CREATED"
+        ]
+      },
+      "android/support/text/emoji/bundled/BundledEmojiCompatConfig$InitRunnable": {
+        "androidx/text/emoji/bundled/BundledEmojiCompatConfig$InitRunnable": [
+          "FONT_NAME"
+        ]
+      },
+      "android/support/mediacompat/R$layout": {
+        "androidx/mediacompat/R$layout": [
+          "notification_template_big_media_custom",
+          "notification_template_media",
+          "notification_media_action",
+          "notification_template_big_media_narrow_custom",
+          "notification_template_media_custom",
+          "notification_template_big_media",
+          "notification_template_big_media_narrow"
+        ]
+      },
+      "android/support/v4/app/NotificationCompat$MessagingStyle$Message": {
+        "androidx/app/NotificationCompat$MessagingStyle$Message": [
+          "KEY_DATA_URI",
+          "KEY_EXTRAS_BUNDLE",
+          "KEY_DATA_MIME_TYPE",
+          "KEY_TIMESTAMP",
+          "KEY_SENDER",
+          "KEY_TEXT"
+        ]
+      },
+      "android/support/v4/media/session/MediaControllerCompat": {
+        "androidx/media/session/MediaControllerCompat": [
+          "COMMAND_GET_EXTRA_BINDER",
+          "COMMAND_REMOVE_QUEUE_ITEM_AT",
+          "COMMAND_ARGUMENT_INDEX",
+          "COMMAND_ARGUMENT_MEDIA_DESCRIPTION",
+          "COMMAND_ADD_QUEUE_ITEM_AT",
+          "COMMAND_ADD_QUEUE_ITEM",
+          "COMMAND_REMOVE_QUEUE_ITEM",
+          "TAG"
+        ]
+      },
+      "android/support/v4/text/TextDirectionHeuristicsCompat$AnyStrong": {
+        "androidx/text/TextDirectionHeuristicsCompat$AnyStrong": [
+          "INSTANCE_RTL",
+          "INSTANCE_LTR"
+        ]
+      },
+      "android/support/wear/widget/ProgressDrawable": {
+        "androidx/wear/widget/ProgressDrawable": [
+          "FULL_CIRCLE",
+          "LEVEL",
+          "CORRECTION_ANGLE",
+          "GROW_SHRINK_RATIO",
+          "MAX_LEVEL",
+          "sInterpolator",
+          "NUMBER_OF_SEGMENTS",
+          "STARTING_ANGLE",
+          "MAX_SWEEP",
+          "ANIMATION_DURATION",
+          "LEVELS_PER_SEGMENT"
+        ]
+      },
+      "android/support/v7/app/MediaRouteControllerDialog$FetchArtTask": {
+        "androidx/app/MediaRouteControllerDialog$FetchArtTask": [
+          "SHOW_ANIM_TIME_THRESHOLD_MILLIS"
+        ]
+      },
+      "android/support/v17/leanback/transition/SlideKitkat": {
+        "androidx/leanback/transition/SlideKitkat": [
+          "sCalculateRight",
+          "sCalculateStart",
+          "sAccelerate",
+          "sDecelerate",
+          "sCalculateTop",
+          "sCalculateEnd",
+          "sCalculateLeft",
+          "sCalculateBottom",
+          "TAG"
+        ]
+      },
+      "android/support/v17/leanback/widget/ImageCardView": {
+        "androidx/leanback/widget/ImageCardView": [
+          "CARD_TYPE_FLAG_CONTENT",
+          "ALPHA",
+          "CARD_TYPE_FLAG_ICON_RIGHT",
+          "CARD_TYPE_FLAG_TITLE",
+          "CARD_TYPE_FLAG_IMAGE_ONLY",
+          "CARD_TYPE_FLAG_ICON_LEFT"
+        ]
+      },
+      "android/support/v17/leanback/app/VideoSupportFragment": {
+        "androidx/leanback/app/VideoSupportFragment": [
+          "SURFACE_NOT_CREATED",
+          "SURFACE_CREATED"
+        ]
+      },
+      "android/support/v17/leanback/widget/PlaybackControlsRow$PlayPauseAction": {
+        "androidx/leanback/widget/PlaybackControlsRow$PlayPauseAction": [
+          "INDEX_PAUSE",
+          "PLAY",
+          "INDEX_PLAY",
+          "PAUSE"
+        ]
+      },
+      "android/support/v7/widget/SearchView$AutoCompleteTextViewReflector": {
+        "androidx/widget/SearchView$AutoCompleteTextViewReflector": [
+          "showSoftInputUnchecked",
+          "doAfterTextChanged",
+          "ensureImeVisible",
+          "doBeforeTextChanged"
+        ]
+      },
+      "android/support/text/emoji/R$styleable": {
+        "androidx/text/emoji/R$styleable": [
+          "EmojiExtractTextLayout",
+          "EmojiEditText_maxEmojiCount",
+          "EmojiExtractTextLayout_emojiReplaceStrategy",
+          "EmojiEditText"
+        ]
+      },
+      "android/support/v4/view/animation/PathInterpolatorApi14": {
+        "androidx/view/animation/PathInterpolatorApi14": [
+          "PRECISION"
+        ]
+      },
+      "android/support/v4/app/FragmentTransition": {
+        "androidx/app/FragmentTransition": [
+          "PLATFORM_IMPL",
+          "INVERSE_OPS",
+          "SUPPORT_IMPL"
+        ]
+      },
+      "android/support/v7/widget/GridLayout$Spec": {
+        "androidx/widget/GridLayout$Spec": [
+          "weight",
+          "alignment",
+          "UNDEFINED",
+          "startDefined",
+          "span",
+          "DEFAULT_WEIGHT"
+        ]
+      },
+      "android/support/v4/BuildConfig": {
+        "androidx/BuildConfig": [
+          "BUILD_TYPE",
+          "DEBUG",
+          "APPLICATION_ID",
+          "FLAVOR",
+          "VERSION_CODE",
+          "VERSION_NAME"
+        ]
+      },
+      "android/support/v4/app/INotificationSideChannel$Stub": {
+        "androidx/app/INotificationSideChannel$Stub": [
+          "TRANSACTION_notify",
+          "TRANSACTION_cancel",
+          "TRANSACTION_cancelAll",
+          "DESCRIPTOR"
+        ]
+      },
+      "android/support/v4/app/FragmentManagerImpl": {
+        "androidx/app/FragmentManagerImpl": [
+          "sAnimationListenerField",
+          "TAG",
+          "ANIM_DUR",
+          "ANIM_STYLE_FADE_EXIT",
+          "DEBUG",
+          "ACCELERATE_QUINT",
+          "TARGET_REQUEST_CODE_STATE_TAG",
+          "ANIM_STYLE_OPEN_EXIT",
+          "ANIM_STYLE_OPEN_ENTER",
+          "TARGET_STATE_TAG",
+          "USER_VISIBLE_HINT_TAG",
+          "ANIM_STYLE_FADE_ENTER",
+          "VIEW_STATE_TAG",
+          "DECELERATE_QUINT",
+          "DECELERATE_CUBIC",
+          "ANIM_STYLE_CLOSE_ENTER",
+          "ACCELERATE_CUBIC",
+          "ANIM_STYLE_CLOSE_EXIT"
+        ]
+      },
+      "android/support/v7/app/MediaRouteButton": {
+        "androidx/app/MediaRouteButton": [
+          "TAG",
+          "sRemoteIndicatorCache",
+          "CHECKED_STATE_SET",
+          "CHECKABLE_STATE_SET",
+          "CONTROLLER_FRAGMENT_TAG",
+          "CHOOSER_FRAGMENT_TAG"
+        ]
+      },
+      "android/support/v7/widget/VectorEnabledTintResources": {
+        "androidx/widget/VectorEnabledTintResources": [
+          "MAX_SDK_WHERE_REQUIRED"
+        ]
+      },
+      "android/support/v7/view/menu/MenuItemImpl": {
+        "androidx/view/menu/MenuItemImpl": [
+          "NO_ICON",
+          "HIDDEN",
+          "sEnterShortcutLabel",
+          "CHECKABLE",
+          "SHOW_AS_ACTION_MASK",
+          "sSpaceShortcutLabel",
+          "EXCLUSIVE",
+          "CHECKED",
+          "TAG",
+          "sDeleteShortcutLabel",
+          "IS_ACTION",
+          "ENABLED",
+          "sPrependShortcutLabel"
+        ]
+      },
+      "android/support/content/InMemoryCursor": {
+        "androidx/content/InMemoryCursor": [
+          "NUM_TYPES"
+        ]
+      },
+      "android/support/v4/app/FragmentPagerAdapter": {
+        "androidx/app/FragmentPagerAdapter": [
+          "DEBUG",
+          "TAG"
+        ]
+      },
+      "android/support/v4/text/ICUCompat": {
+        "androidx/text/ICUCompat": [
+          "TAG",
+          "sAddLikelySubtagsMethod",
+          "sGetScriptMethod"
+        ]
+      },
+      "android/support/v4/media/session/MediaControllerCompatApi21$PlaybackInfo": {
+        "androidx/media/session/MediaControllerCompatApi21$PlaybackInfo": [
+          "FLAG_SCO",
+          "STREAM_BLUETOOTH_SCO",
+          "STREAM_SYSTEM_ENFORCED"
+        ]
+      },
+      "android/support/text/emoji/R$id": {
+        "androidx/text/emoji/R$id": [
+          "inputExtractAccessories",
+          "inputExtractAction"
+        ]
+      },
+      "android/support/v17/leanback/R$animator": {
+        "androidx/leanback/R$animator": [
+          "lb_onboarding_page_indicator_fade_in",
+          "lb_onboarding_page_indicator_fade_out",
+          "lb_onboarding_start_button_fade_in",
+          "lb_playback_controls_fade_in",
+          "lb_playback_controls_fade_out",
+          "lb_onboarding_logo_enter",
+          "lb_onboarding_start_button_fade_out",
+          "lb_onboarding_page_indicator_enter",
+          "lb_playback_bg_fade_out",
+          "lb_onboarding_title_enter",
+          "lb_onboarding_description_enter",
+          "lb_playback_bg_fade_in",
+          "lb_onboarding_logo_exit"
+        ]
+      },
+      "android/support/transition/TransitionInflater": {
+        "androidx/transition/TransitionInflater": [
+          "CONSTRUCTOR_SIGNATURE",
+          "CONSTRUCTORS"
+        ]
+      },
+      "android/support/v4/media/RatingCompat": {
+        "androidx/media/RatingCompat": [
+          "RATING_THUMB_UP_DOWN",
+          "RATING_5_STARS",
+          "RATING_HEART",
+          "RATING_PERCENTAGE",
+          "CREATOR",
+          "RATING_4_STARS",
+          "RATING_NONE",
+          "RATING_NOT_RATED",
+          "TAG",
+          "RATING_3_STARS"
+        ]
+      },
+      "android/support/v17/leanback/transition/FadeAndShortSlide": {
+        "androidx/leanback/transition/FadeAndShortSlide": [
+          "sCalculateTopBottom",
+          "sCalculateStartEnd",
+          "sCalculateEnd",
+          "sCalculateBottom",
+          "PROPNAME_SCREEN_POSITION",
+          "sDecelerate",
+          "sCalculateStart",
+          "sCalculateTop"
+        ]
+      },
+      "android/support/design/R$attr": {
+        "androidx/design/R$attr": [
+          "state_collapsible",
+          "state_collapsed",
+          "bottomSheetDialogTheme"
+        ]
+      },
+      "android/support/graphics/drawable/PathInterpolatorCompat": {
+        "androidx/graphics/drawable/PathInterpolatorCompat": [
+          "EPSILON",
+          "PRECISION",
+          "MAX_NUM_POINTS"
+        ]
+      },
+      "android/support/animation/SpringAnimation": {
+        "androidx/animation/SpringAnimation": [
+          "UNSET"
+        ]
+      },
+      "android/support/v7/app/AppCompatDelegateImplV9$PanelFeatureState$SavedState": {
+        "androidx/app/AppCompatDelegateImplV9$PanelFeatureState$SavedState": [
+          "featureId",
+          "menuState",
+          "CREATOR",
+          "isOpen"
+        ]
+      },
+      "android/support/v7/view/menu/MenuBuilder": {
+        "androidx/view/menu/MenuBuilder": [
+          "EXPANDED_ACTION_VIEW_ID",
+          "TAG",
+          "PRESENTER_KEY",
+          "ACTION_VIEW_STATES_KEY",
+          "sCategoryToOrder"
+        ]
+      },
+      "android/support/v4/view/ViewGroupCompat": {
+        "androidx/view/ViewGroupCompat": [
+          "LAYOUT_MODE_OPTICAL_BOUNDS",
+          "LAYOUT_MODE_CLIP_BOUNDS",
+          "IMPL"
+        ]
+      },
+      "android/support/v14/preference/MultiSelectListPreferenceDialogFragment": {
+        "androidx/preference/MultiSelectListPreferenceDialogFragment": [
+          "SAVE_STATE_ENTRY_VALUES",
+          "SAVE_STATE_VALUES",
+          "SAVE_STATE_ENTRIES",
+          "SAVE_STATE_CHANGED"
+        ]
+      },
+      "android/support/exifinterface/BuildConfig": {
+        "androidx/exifinterface/BuildConfig": [
+          "VERSION_NAME",
+          "DEBUG",
+          "APPLICATION_ID",
+          "BUILD_TYPE",
+          "VERSION_CODE",
+          "FLAVOR"
+        ]
+      },
+      "android/support/v4/print/PrintHelper$PrintHelperApi19": {
+        "androidx/print/PrintHelper$PrintHelperApi19": [
+          "MAX_PRINT_SIZE",
+          "LOG_TAG"
+        ]
+      },
+      "android/support/v7/preference/R$layout": {
+        "androidx/preference/R$layout": [
+          "preference_list_fragment",
+          "preference_recyclerview",
+          "preference"
+        ]
+      },
+      "android/support/v7/widget/AdapterHelper$UpdateOp": {
+        "androidx/widget/AdapterHelper$UpdateOp": [
+          "positionStart",
+          "payload",
+          "MOVE",
+          "REMOVE",
+          "ADD",
+          "UPDATE",
+          "cmd",
+          "POOL_SIZE",
+          "itemCount"
+        ]
+      },
+      "android/support/v17/leanback/app/BrowseFragment$SetSelectionRunnable": {
+        "androidx/leanback/app/BrowseFragment$SetSelectionRunnable": [
+          "TYPE_INTERNAL_SYNC",
+          "TYPE_USER_REQUEST",
+          "TYPE_INVALID"
+        ]
+      },
+      "android/support/v17/leanback/app/BrowseSupportFragment$ExpandPreLayout": {
+        "androidx/leanback/app/BrowseSupportFragment$ExpandPreLayout": [
+          "STATE_SECOND_DRAW",
+          "STATE_INIT",
+          "STATE_FIRST_DRAW",
+          "mainFragmentAdapter"
+        ]
+      },
+      "android/support/v4/widget/PopupWindowCompat$PopupWindowCompatBaseImpl": {
+        "androidx/widget/PopupWindowCompat$PopupWindowCompatBaseImpl": [
+          "sGetWindowLayoutTypeMethodAttempted",
+          "sGetWindowLayoutTypeMethod",
+          "sSetWindowLayoutTypeMethod",
+          "sSetWindowLayoutTypeMethodAttempted"
+        ]
+      },
+      "android/support/v7/widget/SwitchCompat": {
+        "androidx/widget/SwitchCompat": [
+          "MONOSPACE",
+          "CHECKED_STATE_SET",
+          "TOUCH_MODE_DRAGGING",
+          "TOUCH_MODE_DOWN",
+          "THUMB_POS",
+          "TOUCH_MODE_IDLE",
+          "SERIF",
+          "THUMB_ANIMATION_DURATION",
+          "ACCESSIBILITY_EVENT_CLASS_NAME",
+          "SANS"
+        ]
+      },
+      "android/support/v7/widget/RecyclerView$LayoutManager$Properties": {
+        "androidx/widget/RecyclerView$LayoutManager$Properties": [
+          "reverseLayout",
+          "stackFromEnd",
+          "spanCount",
+          "orientation"
+        ]
+      },
+      "android/support/v4/app/NotificationManagerCompat$SideChannelManager": {
+        "androidx/app/NotificationManagerCompat$SideChannelManager": [
+          "MSG_QUEUE_TASK",
+          "MSG_RETRY_LISTENER_QUEUE",
+          "MSG_SERVICE_CONNECTED",
+          "MSG_SERVICE_DISCONNECTED"
+        ]
+      },
+      "android/support/v17/leanback/app/BrowseSupportFragment$MainFragmentAdapterRegistry": {
+        "androidx/leanback/app/BrowseSupportFragment$MainFragmentAdapterRegistry": [
+          "sDefaultFragmentFactory"
+        ]
+      },
+      "android/support/v7/preference/PreferenceFragmentCompat": {
+        "androidx/preference/PreferenceFragmentCompat": [
+          "MSG_BIND_PREFERENCES",
+          "PREFERENCES_TAG",
+          "ARG_PREFERENCE_ROOT",
+          "DIALOG_FRAGMENT_TAG"
+        ]
+      },
+      "android/support/v7/widget/SearchView": {
+        "androidx/widget/SearchView": [
+          "ENABLED_STATE_SET",
+          "EMPTY_STATE_SET",
+          "FOCUSED_STATE_SET",
+          "DBG",
+          "IME_OPTION_NO_MICROPHONE",
+          "LOG_TAG",
+          "HIDDEN_METHOD_INVOKER"
+        ]
+      },
+      "android/support/v4/view/AsyncLayoutInflater$BasicInflater": {
+        "androidx/view/AsyncLayoutInflater$BasicInflater": [
+          "sClassPrefixList"
+        ]
+      },
+      "android/support/v7/widget/DividerItemDecoration": {
+        "androidx/widget/DividerItemDecoration": [
+          "VERTICAL",
+          "HORIZONTAL",
+          "ATTRS",
+          "TAG"
+        ]
+      },
+      "android/support/wear/widget/SwipeDismissFrameLayout": {
+        "androidx/wear/widget/SwipeDismissFrameLayout": [
+          "DEFAULT_INTERPOLATION_FACTOR",
+          "TRANSLATION_MIN_ALPHA",
+          "TAG"
+        ]
+      },
+      "android/support/v17/leanback/app/HeadersFragment": {
+        "androidx/leanback/app/HeadersFragment": [
+          "sHeaderPresenter",
+          "sLayoutChangeListener"
+        ]
+      },
+      "android/support/customtabs/CustomTabsService": {
+        "androidx/browser/customtabs/CustomTabsService": [
+          "RESULT_FAILURE_MESSAGING_ERROR",
+          "RESULT_SUCCESS",
+          "RESULT_FAILURE_REMOTE_ERROR",
+          "ACTION_CUSTOM_TABS_CONNECTION",
+          "KEY_URL",
+          "RESULT_FAILURE_DISALLOWED",
+          "RELATION_HANDLE_ALL_URLS",
+          "RELATION_USE_AS_ORIGIN"
+        ]
+      },
+      "android/support/v17/leanback/app/GuidedStepFragment": {
+        "androidx/leanback/app/GuidedStepFragment": [
+          "IS_FRAMEWORK_FRAGMENT",
+          "entranceTransitionType",
+          "ENTRY_NAME_ENTRANCE",
+          "TAG_LEAN_BACK_ACTIONS_FRAGMENT",
+          "UI_STYLE_REPLACE",
+          "EXTRA_BUTTON_ACTION_PREFIX",
+          "UI_STYLE_DEFAULT",
+          "DEBUG",
+          "SLIDE_FROM_BOTTOM",
+          "UI_STYLE_ENTRANCE",
+          "SLIDE_FROM_SIDE",
+          "TAG",
+          "EXTRA_UI_STYLE",
+          "EXTRA_ACTION_PREFIX",
+          "ENTRY_NAME_REPLACE",
+          "UI_STYLE_ACTIVITY_ROOT"
+        ]
+      },
+      "android/support/v17/leanback/util/StateMachine": {
+        "androidx/leanback/util/StateMachine": [
+          "STATUS_ZERO",
+          "STATUS_INVOKED",
+          "DEBUG",
+          "TAG"
+        ]
+      },
+      "android/support/compat/R$drawable": {
+        "androidx/compat/R$drawable": [
+          "notification_template_icon_bg",
+          "notification_bg",
+          "notification_icon_background",
+          "notification_template_icon_low_bg",
+          "notification_bg_low"
+        ]
+      },
+      "android/support/v4/provider/DocumentsContractApi19": {
+        "androidx/provider/DocumentsContractApi19": [
+          "TAG",
+          "FLAG_VIRTUAL_DOCUMENT"
+        ]
+      },
+      "android/support/v4/app/JobIntentService": {
+        "androidx/app/JobIntentService": [
+          "TAG",
+          "sClassWorkEnqueuer",
+          "DEBUG",
+          "sLock"
+        ]
+      },
+      "android/support/v4/graphics/TypefaceCompatUtil": {
+        "androidx/graphics/TypefaceCompatUtil": [
+          "CACHE_FILE_PREFIX",
+          "TAG"
+        ]
+      },
+      "android/support/transition/Explode": {
+        "androidx/transition/Explode": [
+          "PROPNAME_SCREEN_BOUNDS",
+          "sDecelerate",
+          "sAccelerate"
+        ]
+      },
+      "android/support/v4/view/AccessibilityDelegateCompat": {
+        "androidx/view/AccessibilityDelegateCompat": [
+          "DEFAULT_DELEGATE",
+          "IMPL"
+        ]
+      },
+      "android/support/v4/view/MenuItemCompat": {
+        "androidx/view/MenuItemCompat": [
+          "IMPL",
+          "SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW",
+          "SHOW_AS_ACTION_WITH_TEXT",
+          "SHOW_AS_ACTION_NEVER",
+          "SHOW_AS_ACTION_ALWAYS",
+          "SHOW_AS_ACTION_IF_ROOM",
+          "TAG"
+        ]
+      },
+      "android/support/v4/widget/CircleImageView": {
+        "androidx/widget/CircleImageView": [
+          "SHADOW_RADIUS",
+          "Y_OFFSET",
+          "X_OFFSET",
+          "KEY_SHADOW_COLOR",
+          "SHADOW_ELEVATION",
+          "FILL_SHADOW_COLOR"
+        ]
+      },
+      "android/support/v7/widget/SuggestionsAdapter": {
+        "androidx/widget/SuggestionsAdapter": [
+          "REFINE_NONE",
+          "LOG_TAG",
+          "REFINE_ALL",
+          "INVALID_INDEX",
+          "DBG",
+          "QUERY_LIMIT",
+          "REFINE_BY_ENTRY"
+        ]
+      },
+      "android/support/wear/widget/drawer/WearableActionDrawerView$ActionItemViewHolder": {
+        "androidx/wear/widget/drawer/WearableActionDrawerView$ActionItemViewHolder": [
+          "view",
+          "iconView",
+          "textView"
+        ]
+      },
+      "android/support/v14/preference/PreferenceDialogFragment": {
+        "androidx/preference/PreferenceDialogFragment": [
+          "SAVE_STATE_POSITIVE_TEXT",
+          "SAVE_STATE_TITLE",
+          "SAVE_STATE_ICON",
+          "ARG_KEY",
+          "SAVE_STATE_MESSAGE",
+          "SAVE_STATE_NEGATIVE_TEXT",
+          "SAVE_STATE_LAYOUT"
+        ]
+      },
+      "android/support/v4/media/session/IMediaControllerCallback$Stub": {
+        "androidx/media/session/IMediaControllerCallback$Stub": [
+          "TRANSACTION_onSessionDestroyed",
+          "TRANSACTION_onMetadataChanged",
+          "TRANSACTION_onEvent",
+          "TRANSACTION_onQueueChanged",
+          "TRANSACTION_onSessionReady",
+          "TRANSACTION_onCaptioningEnabledChanged",
+          "TRANSACTION_onVolumeInfoChanged",
+          "TRANSACTION_onShuffleModeChangedRemoved",
+          "TRANSACTION_onRepeatModeChanged",
+          "TRANSACTION_onPlaybackStateChanged",
+          "TRANSACTION_onQueueTitleChanged",
+          "TRANSACTION_onShuffleModeChanged",
+          "DESCRIPTOR",
+          "TRANSACTION_onExtrasChanged"
+        ]
+      },
+      "android/support/v7/preference/EditTextPreference$SavedState": {
+        "androidx/preference/EditTextPreference$SavedState": [
+          "text",
+          "CREATOR"
+        ]
+      },
+      "android/support/v17/leanback/widget/PagingIndicator$Dot": {
+        "androidx/leanback/widget/PagingIndicator$Dot": [
+          "LEFT",
+          "LTR",
+          "RIGHT",
+          "RTL"
+        ]
+      },
+      "android/support/v7/widget/ScrollingTabContainerView$TabView": {
+        "androidx/widget/ScrollingTabContainerView$TabView": [
+          "BG_ATTRS"
+        ]
+      },
+      "android/support/wear/ambient/SharedLibraryVersion$PresenceHolder": {
+        "androidx/wear/ambient/SharedLibraryVersion$PresenceHolder": [
+          "PRESENT"
+        ]
+      },
+      "android/support/v4/graphics/BitmapCompat": {
+        "androidx/graphics/BitmapCompat": [
+          "IMPL"
+        ]
+      },
+      "android/support/wear/R$fraction": {
+        "androidx/wear/R$fraction": [
+          "ws_action_drawer_item_right_padding",
+          "ws_action_drawer_item_last_item_bottom_padding",
+          "ws_action_drawer_item_left_padding",
+          "ws_action_drawer_item_first_item_top_padding"
+        ]
+      },
+      "android/support/v7/media/MediaSessionStatus": {
+        "androidx/media/MediaSessionStatus": [
+          "KEY_TIMESTAMP",
+          "SESSION_STATE_INVALIDATED",
+          "KEY_EXTRAS",
+          "KEY_QUEUE_PAUSED",
+          "KEY_SESSION_STATE",
+          "SESSION_STATE_ENDED",
+          "SESSION_STATE_ACTIVE"
+        ]
+      },
+      "android/support/v4/app/NotificationManagerCompat$CancelTask": {
+        "androidx/app/NotificationManagerCompat$CancelTask": [
+          "tag",
+          "all",
+          "id",
+          "packageName"
+        ]
+      },
+      "android/support/v4/media/MediaBrowserCompat$MediaBrowserImplBase": {
+        "androidx/media/MediaBrowserCompat$MediaBrowserImplBase": [
+          "CONNECT_STATE_DISCONNECTED",
+          "CONNECT_STATE_CONNECTED",
+          "CONNECT_STATE_CONNECTING",
+          "CONNECT_STATE_SUSPENDED",
+          "CONNECT_STATE_DISCONNECTING"
+        ]
+      },
+      "android/support/wear/widget/WearableRecyclerView": {
+        "androidx/wear/widget/WearableRecyclerView": [
+          "TAG",
+          "NO_VALUE"
+        ]
+      },
+      "android/support/app/recommendation/RecommendationExtender": {
+        "androidx/app/recommendation/RecommendationExtender": [
+          "KEY_CONTENT_RUN_LENGTH",
+          "KEY_CONTENT_GENRES",
+          "KEY_CONTENT_TYPE",
+          "TAG",
+          "EXTRA_CONTENT_INFO_EXTENDER",
+          "KEY_CONTENT_MATURITY_RATING",
+          "KEY_CONTENT_STATUS",
+          "KEY_CONTENT_PRICING_VALUE",
+          "KEY_CONTENT_PRICING_TYPE"
+        ]
+      },
+      "android/support/v7/widget/ThemeUtils": {
+        "androidx/widget/ThemeUtils": [
+          "TEMP_ARRAY",
+          "DISABLED_STATE_SET",
+          "ACTIVATED_STATE_SET",
+          "NOT_PRESSED_OR_FOCUSED_STATE_SET",
+          "FOCUSED_STATE_SET",
+          "SELECTED_STATE_SET",
+          "CHECKED_STATE_SET",
+          "PRESSED_STATE_SET",
+          "EMPTY_STATE_SET",
+          "TL_TYPED_VALUE"
+        ]
+      },
+      "android/support/media/ExifInterface$ByteOrderedDataInputStream": {
+        "androidx/media/ExifInterface$ByteOrderedDataInputStream": [
+          "BIG_ENDIAN",
+          "LITTLE_ENDIAN"
+        ]
+      },
+      "android/support/v7/widget/helper/ItemTouchHelper$Callback": {
+        "androidx/widget/helper/ItemTouchHelper$Callback": [
+          "ABS_HORIZONTAL_DIR_FLAGS",
+          "sDragScrollInterpolator",
+          "DRAG_SCROLL_ACCELERATION_LIMIT_TIME_MS",
+          "DEFAULT_DRAG_ANIMATION_DURATION",
+          "RELATIVE_DIR_FLAGS",
+          "DEFAULT_SWIPE_ANIMATION_DURATION",
+          "sDragViewScrollCapInterpolator",
+          "sUICallback"
+        ]
+      },
+      "android/support/v4/media/MediaBrowserServiceCompat$BrowserRoot": {
+        "androidx/media/MediaBrowserServiceCompat$BrowserRoot": [
+          "EXTRA_SUGGESTION_KEYWORDS",
+          "EXTRA_SUGGESTED",
+          "EXTRA_OFFLINE",
+          "EXTRA_RECENT"
+        ]
+      },
+      "android/support/v17/leanback/widget/PlaybackControlsPresenter": {
+        "androidx/leanback/widget/PlaybackControlsPresenter": [
+          "sChildMarginBigger",
+          "sChildMarginBiggest"
+        ]
+      },
+      "android/support/v7/preference/PreferenceDialogFragmentCompat": {
+        "androidx/preference/PreferenceDialogFragmentCompat": [
+          "SAVE_STATE_LAYOUT",
+          "ARG_KEY",
+          "SAVE_STATE_ICON",
+          "SAVE_STATE_MESSAGE",
+          "SAVE_STATE_TITLE",
+          "SAVE_STATE_NEGATIVE_TEXT",
+          "SAVE_STATE_POSITIVE_TEXT"
+        ]
+      },
+      "android/support/v7/app/TwilightManager": {
+        "androidx/app/TwilightManager": [
+          "SUNRISE",
+          "SUNSET",
+          "TAG",
+          "sInstance"
+        ]
+      },
+      "android/support/v4/graphics/drawable/DrawableCompat": {
+        "androidx/graphics/drawable/DrawableCompat": [
+          "sSetLayoutDirectionMethod",
+          "sGetLayoutDirectionMethodFetched",
+          "sGetLayoutDirectionMethod",
+          "TAG",
+          "sSetLayoutDirectionMethodFetched"
+        ]
+      },
+      "android/support/coreutils/BuildConfig": {
+        "androidx/coreutils/BuildConfig": [
+          "FLAVOR",
+          "APPLICATION_ID",
+          "VERSION_NAME",
+          "BUILD_TYPE",
+          "VERSION_CODE",
+          "DEBUG"
+        ]
+      },
+      "android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory": {
+        "androidx/graphics/drawable/RoundedBitmapDrawableFactory": [
+          "TAG"
+        ]
+      },
+      "android/support/transition/GhostViewApi21": {
+        "androidx/transition/GhostViewApi21": [
+          "sAddGhostMethod",
+          "TAG",
+          "sRemoveGhostMethod",
+          "sAddGhostMethodFetched",
+          "sRemoveGhostMethodFetched",
+          "sGhostViewClassFetched",
+          "sGhostViewClass"
+        ]
+      },
+      "android/support/v7/app/ActionBar": {
+        "androidx/app/ActionBar": [
+          "NAVIGATION_MODE_TABS",
+          "NAVIGATION_MODE_LIST",
+          "DISPLAY_SHOW_TITLE",
+          "DISPLAY_SHOW_CUSTOM",
+          "DISPLAY_USE_LOGO",
+          "NAVIGATION_MODE_STANDARD",
+          "DISPLAY_HOME_AS_UP",
+          "DISPLAY_SHOW_HOME"
+        ]
+      },
+      "android/support/media/tv/TvContractCompat$WatchNextPrograms": {
+        "androidx/media/tv/TvContractCompat$WatchNextPrograms": [
+          "WATCH_NEXT_TYPE_WATCHLIST",
+          "WATCH_NEXT_TYPE_CONTINUE",
+          "WATCH_NEXT_TYPE_NEXT",
+          "COLUMN_LAST_ENGAGEMENT_TIME_UTC_MILLIS",
+          "CONTENT_URI",
+          "WATCH_NEXT_TYPE_NEW",
+          "CONTENT_TYPE",
+          "COLUMN_WATCH_NEXT_TYPE",
+          "CONTENT_ITEM_TYPE"
+        ]
+      },
+      "android/support/v4/view/accessibility/AccessibilityNodeInfoCompat$CollectionInfoCompat": {
+        "androidx/view/accessibility/AccessibilityNodeInfoCompat$CollectionInfoCompat": [
+          "SELECTION_MODE_MULTIPLE",
+          "SELECTION_MODE_SINGLE",
+          "SELECTION_MODE_NONE"
+        ]
+      },
+      "android/support/v17/preference/R$id": {
+        "androidx/leanback/preference/R$id": [
+          "button",
+          "settings_preference_fragment_container",
+          "main_frame",
+          "container",
+          "decor_title",
+          "settings_dialog_container"
+        ]
+      },
+      "android/support/wear/R$style": {
+        "androidx/wear/R$style": [
+          "Widget_Wear_WearableDrawerView",
+          "WsPageIndicatorViewStyle"
+        ]
+      },
+      "android/support/v17/leanback/widget/GuidedActionAdapterGroup": {
+        "androidx/leanback/widget/GuidedActionAdapterGroup": [
+          "TAG_EDIT",
+          "DEBUG_EDIT"
+        ]
+      },
+      "android/support/v7/widget/GridLayout$PackedMap": {
+        "androidx/widget/GridLayout$PackedMap": [
+          "values",
+          "index",
+          "keys"
+        ]
+      },
+      "android/support/v7/widget/ActionBarOverlayLayout": {
+        "androidx/widget/ActionBarOverlayLayout": [
+          "TAG",
+          "ATTRS",
+          "ACTION_BAR_ANIMATE_DELAY"
+        ]
+      },
+      "android/support/fragment/BuildConfig": {
+        "androidx/fragment/BuildConfig": [
+          "VERSION_NAME",
+          "BUILD_TYPE",
+          "VERSION_CODE",
+          "DEBUG",
+          "APPLICATION_ID",
+          "FLAVOR"
+        ]
+      },
+      "android/support/v4/app/NavUtils": {
+        "androidx/app/NavUtils": [
+          "TAG",
+          "PARENT_ACTIVITY"
+        ]
+      },
+      "android/support/v7/media/RemotePlaybackClient$ActionReceiver": {
+        "androidx/media/RemotePlaybackClient$ActionReceiver": [
+          "ACTION_ITEM_STATUS_CHANGED",
+          "ACTION_MESSAGE_RECEIVED",
+          "ACTION_SESSION_STATUS_CHANGED"
+        ]
+      },
+      "android/support/v4/app/JobIntentService$JobServiceEngineImpl": {
+        "androidx/app/JobIntentService$JobServiceEngineImpl": [
+          "TAG",
+          "DEBUG"
+        ]
+      },
+      "android/support/v4/app/FragmentActivity$NonConfigurationInstances": {
+        "androidx/app/FragmentActivity$NonConfigurationInstances": [
+          "custom",
+          "fragments",
+          "loaders"
+        ]
+      },
+      "android/support/text/emoji/flatbuffer/FlatBufferBuilder": {
+        "androidx/text/emoji/flatbuffer/FlatBufferBuilder": [
+          "encoder",
+          "vtable_in_use",
+          "minalign",
+          "vtables",
+          "num_vtables",
+          "force_defaults",
+          "space",
+          "bb",
+          "object_start",
+          "vtable",
+          "dst",
+          "vector_num_elems",
+          "nested",
+          "finished",
+          "utf8charset"
+        ]
+      },
+      "android/support/v7/mediarouter/R$interpolator": {
+        "androidx/mediarouter/R$interpolator": [
+          "mr_fast_out_slow_in",
+          "mr_linear_out_slow_in"
+        ]
+      },
+      "android/support/v7/recyclerview/R$dimen": {
+        "androidx/recyclerview/R$dimen": [
+          "item_touch_helper_max_drag_scroll_per_frame",
+          "fastscroll_minimum_range",
+          "item_touch_helper_swipe_escape_max_velocity",
+          "fastscroll_default_thickness",
+          "item_touch_helper_swipe_escape_velocity",
+          "fastscroll_margin"
+        ]
+      },
+      "android/support/v4/app/ShareCompat": {
+        "androidx/app/ShareCompat": [
+          "EXTRA_CALLING_PACKAGE",
+          "HISTORY_FILENAME_PREFIX",
+          "EXTRA_CALLING_ACTIVITY"
+        ]
+      },
+      "android/support/v4/media/AudioAttributesCompatApi21": {
+        "androidx/media/AudioAttributesCompatApi21": [
+          "TAG",
+          "sAudioAttributesToLegacyStreamType"
+        ]
+      },
+      "android/support/customtabs/CustomTabsCallback": {
+        "androidx/browser/customtabs/CustomTabsCallback": [
+          "NAVIGATION_FINISHED",
+          "NAVIGATION_ABORTED",
+          "NAVIGATION_FAILED",
+          "NAVIGATION_STARTED",
+          "TAB_HIDDEN",
+          "TAB_SHOWN"
+        ]
+      },
+      "android/support/wear/widget/CircularProgressLayout": {
+        "androidx/wear/widget/CircularProgressLayout": [
+          "DEFAULT_ROTATION",
+          "DEFAULT_UPDATE_INTERVAL"
+        ]
+      },
+      "android/support/v17/leanback/widget/ListRowPresenter": {
+        "androidx/leanback/widget/ListRowPresenter": [
+          "DEBUG",
+          "TAG",
+          "sExpandedSelectedRowTopPadding",
+          "DEFAULT_RECYCLED_POOL_SIZE",
+          "sExpandedRowNoHovercardBottomPadding",
+          "sSelectedRowTopPadding"
+        ]
+      },
+      "android/support/v7/preference/PreferenceGroupAdapter$PreferenceLayout": {
+        "androidx/preference/PreferenceGroupAdapter$PreferenceLayout": [
+          "widgetResId",
+          "resId",
+          "name"
+        ]
+      },
+      "android/support/transition/BuildConfig": {
+        "androidx/transition/BuildConfig": [
+          "FLAVOR",
+          "DEBUG",
+          "BUILD_TYPE",
+          "VERSION_CODE",
+          "APPLICATION_ID",
+          "VERSION_NAME"
+        ]
+      },
+      "android/support/transition/Styleable$Transition": {
+        "androidx/transition/Styleable$Transition": [
+          "MATCH_ORDER",
+          "START_DELAY",
+          "DURATION",
+          "INTERPOLATOR"
+        ]
+      },
+      "android/support/v4/view/accessibility/AccessibilityNodeInfoCompat$RangeInfoCompat": {
+        "androidx/view/accessibility/AccessibilityNodeInfoCompat$RangeInfoCompat": [
+          "RANGE_TYPE_PERCENT",
+          "RANGE_TYPE_INT",
+          "RANGE_TYPE_FLOAT"
+        ]
+      },
+      "android/support/transition/Styleable$Fade": {
+        "androidx/transition/Styleable$Fade": [
+          "FADING_MODE"
+        ]
+      },
+      "android/support/v4/app/SharedElementCallback": {
+        "androidx/app/SharedElementCallback": [
+          "BUNDLE_SNAPSHOT_BITMAP",
+          "BUNDLE_SNAPSHOT_IMAGE_MATRIX",
+          "BUNDLE_SNAPSHOT_IMAGE_SCALETYPE",
+          "MAX_IMAGE_SIZE"
+        ]
+      },
+      "android/support/design/widget/ThemeUtils": {
+        "androidx/design/widget/ThemeUtils": [
+          "APPCOMPAT_CHECK_ATTRS"
+        ]
+      },
+      "android/support/media/tv/TvContractCompat$Programs": {
+        "androidx/media/tv/TvContractCompat$Programs": [
+          "COLUMN_BROADCAST_GENRE",
+          "COLUMN_CHANNEL_ID",
+          "COLUMN_START_TIME_UTC_MILLIS",
+          "CONTENT_ITEM_TYPE",
+          "CONTENT_TYPE",
+          "COLUMN_EPISODE_NUMBER",
+          "COLUMN_END_TIME_UTC_MILLIS",
+          "COLUMN_RECORDING_PROHIBITED",
+          "CONTENT_URI",
+          "COLUMN_SEASON_NUMBER"
+        ]
+      },
+      "android/support/v17/leanback/R$integer": {
+        "androidx/leanback/R$integer": [
+          "lb_details_description_body_min_lines",
+          "lb_search_orb_pulse_duration_ms",
+          "lb_search_bar_speech_mode_background_alpha",
+          "lb_search_bar_text_mode_background_alpha",
+          "lb_details_description_body_max_lines",
+          "lb_card_activated_animation_duration",
+          "lb_card_selected_animation_duration",
+          "lb_search_orb_scale_duration_ms",
+          "lb_browse_rows_anim_duration",
+          "lb_playback_controls_show_time_ms",
+          "lb_card_selected_animation_delay"
+        ]
+      },
+      "android/support/mediacompat/R$id": {
+        "androidx/mediacompat/R$id": [
+          "action0",
+          "media_actions",
+          "cancel_action",
+          "status_bar_latest_event_content",
+          "end_padder"
+        ]
+      },
+      "android/support/v17/leanback/widget/Row": {
+        "androidx/leanback/widget/Row": [
+          "FLAG_ID_USE_HEADER",
+          "FLAG_ID_USE_ID",
+          "FLAG_ID_USE_MASK"
+        ]
+      },
+      "android/support/compat/R$layout": {
+        "androidx/compat/R$layout": [
+          "notification_action_tombstone",
+          "notification_template_custom_big",
+          "notification_action"
+        ]
+      },
+      "android/support/v4/app/FragmentManagerImpl$AnimationOrAnimator": {
+        "androidx/app/FragmentManagerImpl$AnimationOrAnimator": [
+          "animator",
+          "animation"
+        ]
+      },
+      "android/support/transition/VisibilityPropagation": {
+        "androidx/transition/VisibilityPropagation": [
+          "PROPNAME_VISIBILITY",
+          "PROPNAME_VIEW_CENTER",
+          "VISIBILITY_PROPAGATION_VALUES"
+        ]
+      },
+      "android/support/v13/BuildConfig": {
+        "androidx/BuildConfig": [
+          "VERSION_NAME",
+          "BUILD_TYPE",
+          "VERSION_CODE",
+          "DEBUG",
+          "APPLICATION_ID",
+          "FLAVOR"
+        ]
+      },
+      "android/support/v4/app/NotificationCompat$Action$WearableExtender": {
+        "androidx/app/NotificationCompat$Action$WearableExtender": [
+          "KEY_FLAGS",
+          "KEY_CONFIRM_LABEL",
+          "KEY_CANCEL_LABEL",
+          "FLAG_HINT_LAUNCHES_ACTIVITY",
+          "KEY_IN_PROGRESS_LABEL",
+          "EXTRA_WEARABLE_EXTENSIONS",
+          "FLAG_AVAILABLE_OFFLINE",
+          "FLAG_HINT_DISPLAY_INLINE",
+          "DEFAULT_FLAGS"
+        ]
+      },
+      "android/support/v4/view/ViewPager$ItemInfo": {
+        "androidx/view/ViewPager$ItemInfo": [
+          "offset",
+          "object",
+          "position",
+          "widthFactor",
+          "scrolling"
+        ]
+      },
+      "android/support/v17/leanback/transition/TransitionHelper": {
+        "androidx/leanback/transition/TransitionHelper": [
+          "SLIDE_RIGHT",
+          "sImpl",
+          "SLIDE_BOTTOM",
+          "FADE_OUT",
+          "SLIDE_TOP",
+          "FADE_IN",
+          "SLIDE_LEFT"
+        ]
+      },
+      "android/support/v4/widget/AutoSizeableTextView": {
+        "androidx/widget/AutoSizeableTextView": [
+          "PLATFORM_SUPPORTS_AUTOSIZE"
+        ]
+      },
+      "android/support/v7/widget/SimpleItemAnimator": {
+        "androidx/widget/SimpleItemAnimator": [
+          "DEBUG",
+          "TAG"
+        ]
+      },
+      "android/support/transition/TransitionManager": {
+        "androidx/transition/TransitionManager": [
+          "sPendingTransitions",
+          "sRunningTransitions",
+          "sDefaultTransition",
+          "LOG_TAG"
+        ]
+      },
+      "android/support/v17/leanback/widget/WindowAlignment$Axis": {
+        "androidx/leanback/widget/WindowAlignment$Axis": [
+          "PF_KEYLINE_OVER_HIGH_EDGE",
+          "PF_KEYLINE_OVER_LOW_EDGE"
+        ]
+      },
+      "android/support/v7/widget/AppCompatPopupWindow": {
+        "androidx/widget/AppCompatPopupWindow": [
+          "COMPAT_OVERLAP_ANCHOR"
+        ]
+      },
+      "android/support/constraint/solver/widgets/ConstraintAnchor$Type": {
+        "androidx/constraint/solver/widgets/ConstraintAnchor$Type": [
+          "RIGHT",
+          "TOP",
+          "BOTTOM",
+          "LEFT",
+          "BASELINE"
+        ]
+      },
+      "android/support/wear/widget/drawer/WearableDrawerLayout": {
+        "androidx/wear/widget/drawer/WearableDrawerLayout": [
+          "TAG",
+          "UP",
+          "OPENED_PERCENT_THRESHOLD",
+          "NESTED_SCROLL_SLOP_DP",
+          "GRAVITY_UNDEFINED",
+          "DOWN",
+          "PEEK_FADE_DURATION_MS",
+          "PEEK_AUTO_CLOSE_DELAY_MS"
+        ]
+      },
+      "android/support/v17/leanback/widget/ShadowOverlayHelper$Builder": {
+        "androidx/leanback/widget/ShadowOverlayHelper$Builder": [
+          "options",
+          "needsRoundedCorner",
+          "keepForegroundDrawable",
+          "preferZOrder",
+          "needsOverlay",
+          "needsShadow"
+        ]
+      },
+      "android/support/v7/media/MediaRouteProvider": {
+        "androidx/media/MediaRouteProvider": [
+          "MSG_DELIVER_DESCRIPTOR_CHANGED",
+          "MSG_DELIVER_DISCOVERY_REQUEST_CHANGED"
+        ]
+      },
+      "android/support/design/widget/AppBarLayout$LayoutParams": {
+        "androidx/design/widget/AppBarLayout$LayoutParams": [
+          "SCROLL_FLAG_ENTER_ALWAYS_COLLAPSED",
+          "bottomMargin",
+          "topMargin",
+          "SCROLL_FLAG_EXIT_UNTIL_COLLAPSED",
+          "SCROLL_FLAG_SCROLL",
+          "COLLAPSIBLE_FLAGS",
+          "FLAG_SNAP",
+          "SCROLL_FLAG_SNAP",
+          "FLAG_QUICK_RETURN",
+          "SCROLL_FLAG_ENTER_ALWAYS"
+        ]
+      },
+      "android/support/v7/media/MediaRouterJellybean": {
+        "androidx/media/MediaRouterJellybean": [
+          "TAG",
+          "DEVICE_OUT_BLUETOOTH",
+          "ROUTE_TYPE_LIVE_AUDIO",
+          "ROUTE_TYPE_USER",
+          "ROUTE_TYPE_LIVE_VIDEO",
+          "ALL_ROUTE_TYPES"
+        ]
+      },
+      "android/support/content/LoaderQueryRunner": {
+        "androidx/content/LoaderQueryRunner": [
+          "TAG",
+          "CONTENT_URI_KEY",
+          "DEBUG"
+        ]
+      },
+      "android/support/v17/preference/BuildConfig": {
+        "androidx/leanback/preference/BuildConfig": [
+          "BUILD_TYPE",
+          "VERSION_NAME",
+          "FLAVOR",
+          "DEBUG",
+          "VERSION_CODE",
+          "APPLICATION_ID"
+        ]
+      },
+      "android/support/v17/leanback/app/BackgroundManager$BackgroundContinuityService": {
+        "androidx/leanback/app/BackgroundManager$BackgroundContinuityService": [
+          "DEBUG",
+          "TAG",
+          "sService"
+        ]
+      },
+      "android/support/design/internal/NavigationMenuPresenter": {
+        "androidx/design/internal/NavigationMenuPresenter": [
+          "STATE_HEADER",
+          "STATE_ADAPTER",
+          "STATE_HIERARCHY"
+        ]
+      },
+      "android/support/v17/leanback/widget/RowPresenter$ViewHolder": {
+        "androidx/leanback/widget/RowPresenter$ViewHolder": [
+          "ACTIVATED_NOT_ASSIGNED",
+          "NOT_ACTIVATED",
+          "ACTIVATED",
+          "view"
+        ]
+      },
+      "android/support/v7/widget/AppCompatCheckedTextView": {
+        "androidx/widget/AppCompatCheckedTextView": [
+          "TINT_ATTRS"
+        ]
+      },
+      "android/support/transition/ChangeImageTransform": {
+        "androidx/transition/ChangeImageTransform": [
+          "PROPNAME_BOUNDS",
+          "NULL_MATRIX_EVALUATOR",
+          "ANIMATED_TRANSFORM_PROPERTY",
+          "sTransitionProperties",
+          "PROPNAME_MATRIX"
+        ]
+      },
+      "android/support/design/widget/AppBarLayout$Behavior$SavedState": {
+        "androidx/design/widget/AppBarLayout$Behavior$SavedState": [
+          "firstVisibleChildIndex",
+          "firstVisibleChildPercentageShown",
+          "firstVisibleChildAtMinimumHeight",
+          "CREATOR"
+        ]
+      },
+      "android/support/design/widget/Snackbar": {
+        "androidx/design/widget/Snackbar": [
+          "LENGTH_SHORT",
+          "LENGTH_INDEFINITE",
+          "LENGTH_LONG"
+        ]
+      },
+      "android/support/v7/app/ActionBarDrawerToggleHoneycomb$SetIndicatorInfo": {
+        "androidx/app/ActionBarDrawerToggleHoneycomb$SetIndicatorInfo": [
+          "setHomeActionContentDescription",
+          "setHomeAsUpIndicator",
+          "upIndicatorView"
+        ]
+      },
+      "android/support/v17/leanback/widget/Grid": {
+        "androidx/leanback/widget/Grid": [
+          "START_DEFAULT"
+        ]
+      },
+      "android/support/media/tv/WatchNextProgram": {
+        "androidx/media/tv/WatchNextProgram": [
+          "PROJECTION",
+          "INVALID_INT_VALUE",
+          "INVALID_LONG_VALUE"
+        ]
+      },
+      "android/support/v7/widget/TintContextWrapper": {
+        "androidx/widget/TintContextWrapper": [
+          "CACHE_LOCK",
+          "sCache"
+        ]
+      },
+      "android/support/text/emoji/flatbuffer/Constants": {
+        "androidx/text/emoji/flatbuffer/Constants": [
+          "SIZEOF_INT",
+          "SIZEOF_FLOAT",
+          "SIZEOF_LONG",
+          "FILE_IDENTIFIER_LENGTH",
+          "SIZEOF_SHORT",
+          "SIZEOF_BYTE",
+          "SIZEOF_DOUBLE"
+        ]
+      },
+      "android/support/wear/widget/CurvingLayoutCallback": {
+        "androidx/wear/widget/CurvingLayoutCallback": [
+          "EPSILON"
+        ]
+      },
+      "android/support/v4/app/AppOpsManagerCompat": {
+        "androidx/app/AppOpsManagerCompat": [
+          "MODE_IGNORED",
+          "MODE_DEFAULT",
+          "MODE_ALLOWED"
+        ]
+      },
+      "android/support/v4/widget/CursorAdapter": {
+        "androidx/widget/CursorAdapter": [
+          "FLAG_AUTO_REQUERY",
+          "FLAG_REGISTER_CONTENT_OBSERVER"
+        ]
+      },
+      "android/support/v17/leanback/app/DetailsBackgroundVideoHelper": {
+        "androidx/leanback/app/DetailsBackgroundVideoHelper": [
+          "NO_VIDEO",
+          "CROSSFADE_DELAY",
+          "INITIAL",
+          "BACKGROUND_CROSS_FADE_DURATION",
+          "PLAY_VIDEO"
+        ]
+      },
+      "android/support/transition/ChangeBounds": {
+        "androidx/transition/ChangeBounds": [
+          "PROPNAME_BOUNDS",
+          "POSITION_PROPERTY",
+          "sRectEvaluator",
+          "BOTTOM_RIGHT_PROPERTY",
+          "BOTTOM_RIGHT_ONLY_PROPERTY",
+          "DRAWABLE_ORIGIN_PROPERTY",
+          "sTransitionProperties",
+          "TOP_LEFT_ONLY_PROPERTY",
+          "PROPNAME_PARENT",
+          "TOP_LEFT_PROPERTY",
+          "PROPNAME_CLIP",
+          "PROPNAME_WINDOW_X",
+          "PROPNAME_WINDOW_Y"
+        ]
+      },
+      "android/support/v4/widget/SwipeProgressBar": {
+        "androidx/widget/SwipeProgressBar": [
+          "INTERPOLATOR",
+          "ANIMATION_DURATION_MS",
+          "FINISH_ANIMATION_DURATION_MS",
+          "COLOR2",
+          "COLOR1",
+          "COLOR4",
+          "COLOR3"
+        ]
+      },
+      "android/support/v17/leanback/media/PlaybackTransportControlGlue": {
+        "androidx/leanback/media/PlaybackTransportControlGlue": [
+          "UPDATE_PLAYBACK_STATE_DELAY_MS",
+          "sHandler",
+          "TAG",
+          "DEBUG",
+          "MSG_UPDATE_PLAYBACK_STATE"
+        ]
+      },
+      "android/support/media/tv/BasePreviewProgram": {
+        "androidx/media/tv/BasePreviewProgram": [
+          "IS_LIVE",
+          "IS_TRANSIENT",
+          "PROJECTION",
+          "INVALID_LONG_VALUE",
+          "INVALID_INT_VALUE",
+          "IS_BROWSABLE"
+        ]
+      },
+      "android/support/v4/widget/TextViewCompat$TextViewCompatBaseImpl": {
+        "androidx/widget/TextViewCompat$TextViewCompatBaseImpl": [
+          "sMaxModeFieldFetched",
+          "sMinimumFieldFetched",
+          "sMinModeField",
+          "sMaximumField",
+          "sMinModeFieldFetched",
+          "sMaxModeField",
+          "sMinimumField",
+          "LOG_TAG",
+          "sMaximumFieldFetched",
+          "LINES"
+        ]
+      },
+      "android/support/v7/media/SystemMediaRouteProvider$LegacyImpl$VolumeChangeReceiver": {
+        "androidx/media/SystemMediaRouteProvider$LegacyImpl$VolumeChangeReceiver": [
+          "EXTRA_VOLUME_STREAM_TYPE",
+          "VOLUME_CHANGED_ACTION",
+          "EXTRA_VOLUME_STREAM_VALUE"
+        ]
+      },
+      "android/support/transition/Styleable$ChangeBounds": {
+        "androidx/transition/Styleable$ChangeBounds": [
+          "RESIZE_CLIP"
+        ]
+      },
+      "android/support/v7/widget/ActivityChooserModel": {
+        "androidx/widget/ActivityChooserModel": [
+          "DEFAULT_ACTIVITY_INFLATION",
+          "ATTRIBUTE_TIME",
+          "TAG_HISTORICAL_RECORD",
+          "DEFAULT_HISTORY_FILE_NAME",
+          "HISTORY_FILE_EXTENSION",
+          "TAG_HISTORICAL_RECORDS",
+          "ATTRIBUTE_ACTIVITY",
+          "ATTRIBUTE_WEIGHT",
+          "DEBUG",
+          "INVALID_INDEX",
+          "DEFAULT_HISTORICAL_RECORD_WEIGHT",
+          "sDataModelRegistry",
+          "sRegistryLock",
+          "DEFAULT_HISTORY_MAX_LENGTH",
+          "LOG_TAG"
+        ]
+      },
+      "android/support/v7/widget/ViewUtils": {
+        "androidx/widget/ViewUtils": [
+          "TAG",
+          "sComputeFitSystemWindowsMethod"
+        ]
+      },
+      "android/support/v14/preference/ListPreferenceDialogFragment": {
+        "androidx/preference/ListPreferenceDialogFragment": [
+          "SAVE_STATE_ENTRIES",
+          "SAVE_STATE_INDEX",
+          "SAVE_STATE_ENTRY_VALUES"
+        ]
+      },
+      "android/support/v17/leanback/widget/RoundedRectHelper": {
+        "androidx/leanback/widget/RoundedRectHelper": [
+          "sInstance"
+        ]
+      },
+      "android/support/transition/Styleable$TransitionManager": {
+        "androidx/transition/Styleable$TransitionManager": [
+          "FROM_SCENE",
+          "TRANSITION",
+          "TO_SCENE"
+        ]
+      },
+      "android/support/v17/leanback/R$style": {
+        "androidx/leanback/R$style": [
+          "TextAppearance_Leanback_SearchTextEdit",
+          "Widget_Leanback_ImageCardView"
+        ]
+      },
+      "android/support/v7/widget/ShareActionProvider": {
+        "androidx/widget/ShareActionProvider": [
+          "DEFAULT_SHARE_HISTORY_FILE_NAME",
+          "DEFAULT_INITIAL_ACTIVITY_COUNT"
+        ]
+      },
+      "android/support/v4/app/ListFragment": {
+        "androidx/app/ListFragment": [
+          "INTERNAL_EMPTY_ID",
+          "INTERNAL_PROGRESS_CONTAINER_ID",
+          "INTERNAL_LIST_CONTAINER_ID"
+        ]
+      },
+      "android/support/design/widget/BottomSheetBehavior$SavedState": {
+        "androidx/design/widget/BottomSheetBehavior$SavedState": [
+          "state",
+          "CREATOR"
+        ]
+      },
+      "android/support/v7/appcompat/R$color": {
+        "androidx/appcompat/R$color": [
+          "abc_tint_switch_track",
+          "abc_tint_edittext",
+          "abc_tint_btn_checkable",
+          "abc_tint_default",
+          "abc_tint_spinner",
+          "abc_tint_seek_thumb",
+          "error_color_material",
+          "abc_input_method_navigation_guard"
+        ]
+      },
+      "android/support/v17/leanback/R$raw": {
+        "androidx/leanback/R$raw": [
+          "lb_voice_no_input",
+          "lb_voice_failure",
+          "lb_voice_success",
+          "lb_voice_open"
+        ]
+      },
+      "android/support/v7/util/DiffUtil$PostponedUpdate": {
+        "androidx/util/DiffUtil$PostponedUpdate": [
+          "posInOwnerList",
+          "removal",
+          "currentPos"
+        ]
+      },
+      "android/support/v7/graphics/drawable/DrawerArrowDrawable": {
+        "androidx/graphics/drawable/DrawerArrowDrawable": [
+          "ARROW_DIRECTION_LEFT",
+          "ARROW_HEAD_ANGLE",
+          "ARROW_DIRECTION_RIGHT",
+          "ARROW_DIRECTION_END",
+          "ARROW_DIRECTION_START"
+        ]
+      },
+      "android/support/v4/os/IResultReceiver$Stub": {
+        "androidx/os/IResultReceiver$Stub": [
+          "TRANSACTION_send",
+          "DESCRIPTOR"
+        ]
+      },
+      "android/support/v7/util/DiffUtil$Range": {
+        "androidx/util/DiffUtil$Range": [
+          "newListEnd",
+          "oldListEnd",
+          "newListStart",
+          "oldListStart"
+        ]
+      },
+      "android/support/v7/widget/ActionMenuPresenter$SavedState": {
+        "androidx/widget/ActionMenuPresenter$SavedState": [
+          "CREATOR",
+          "openSubMenuId"
+        ]
+      },
+      "android/support/v17/leanback/widget/GuidedActionAdapter": {
+        "androidx/leanback/widget/GuidedActionAdapter": [
+          "DEBUG",
+          "DEBUG_EDIT",
+          "TAG_EDIT",
+          "TAG"
+        ]
+      },
+      "android/support/v7/widget/AppCompatMultiAutoCompleteTextView": {
+        "androidx/widget/AppCompatMultiAutoCompleteTextView": [
+          "TINT_ATTRS"
+        ]
+      },
+      "android/support/compat/R$dimen": {
+        "androidx/compat/R$dimen": [
+          "notification_small_icon_background_padding",
+          "notification_top_pad_large_text",
+          "notification_large_icon_width",
+          "notification_small_icon_size_as_large",
+          "notification_big_circle_margin",
+          "notification_subtext_size",
+          "notification_top_pad",
+          "notification_right_icon_size"
+        ]
+      },
+      "android/support/customtabs/CustomTabsSessionToken": {
+        "androidx/browser/customtabs/CustomTabsSessionToken": [
+          "TAG"
+        ]
+      },
+      "android/support/v17/leanback/widget/StaggeredGrid$Location": {
+        "androidx/leanback/widget/StaggeredGrid$Location": [
+          "row",
+          "offset",
+          "size"
+        ]
+      },
+      "android/support/compat/R$color": {
+        "androidx/compat/R$color": [
+          "notification_action_color_filter"
+        ]
+      },
+      "android/support/v7/widget/DefaultItemAnimator$ChangeInfo": {
+        "androidx/widget/DefaultItemAnimator$ChangeInfo": [
+          "toY",
+          "toX",
+          "oldHolder",
+          "newHolder",
+          "fromY",
+          "fromX"
+        ]
+      },
+      "android/support/v17/leanback/widget/PlaybackControlsPresenter$ViewHolder": {
+        "androidx/leanback/widget/PlaybackControlsPresenter$ViewHolder": [
+          "view"
+        ]
+      },
+      "android/support/v4/app/DialogFragment": {
+        "androidx/app/DialogFragment": [
+          "SAVED_SHOWS_DIALOG",
+          "STYLE_NORMAL",
+          "STYLE_NO_INPUT",
+          "SAVED_CANCELABLE",
+          "SAVED_THEME",
+          "SAVED_DIALOG_STATE_TAG",
+          "STYLE_NO_TITLE",
+          "SAVED_BACK_STACK_ID",
+          "STYLE_NO_FRAME",
+          "SAVED_STYLE"
+        ]
+      },
+      "android/support/v7/palette/BuildConfig": {
+        "androidx/palette/BuildConfig": [
+          "VERSION_CODE",
+          "DEBUG",
+          "FLAVOR",
+          "BUILD_TYPE",
+          "APPLICATION_ID",
+          "VERSION_NAME"
+        ]
+      },
+      "android/support/v7/preference/R$attr": {
+        "androidx/preference/R$attr": [
+          "dialogPreferenceStyle",
+          "dropdownPreferenceStyle",
+          "preferenceScreenStyle",
+          "editTextPreferenceStyle",
+          "preferenceTheme",
+          "preferenceCategoryStyle",
+          "seekBarPreferenceStyle",
+          "switchPreferenceStyle",
+          "preferenceStyle",
+          "preferenceFragmentStyle",
+          "switchPreferenceCompatStyle",
+          "checkBoxPreferenceStyle",
+          "preferenceFragmentCompatStyle"
+        ]
+      },
+      "android/support/v17/leanback/widget/BaseCardView$LayoutParams": {
+        "androidx/leanback/widget/BaseCardView$LayoutParams": [
+          "VIEW_TYPE_INFO",
+          "VIEW_TYPE_MAIN",
+          "VIEW_TYPE_EXTRA",
+          "viewType"
+        ]
+      },
+      "android/support/wear/ambient/WearableControllerProvider": {
+        "androidx/wear/ambient/WearableControllerProvider": [
+          "TAG",
+          "sAmbientCallbacksVerifiedPresent"
+        ]
+      },
+      "android/support/design/R$anim": {
+        "androidx/design/R$anim": [
+          "design_snackbar_out",
+          "design_snackbar_in"
+        ]
+      },
+      "android/support/wear/ambient/AmbientMode": {
+        "androidx/wear/ambient/AmbientMode": [
+          "FRAGMENT_TAG",
+          "EXTRA_BURN_IN_PROTECTION",
+          "EXTRA_LOWBIT_AMBIENT"
+        ]
+      },
+      "android/support/text/emoji/flatbuffer/MetadataItem": {
+        "androidx/text/emoji/flatbuffer/MetadataItem": [
+          "bb_pos",
+          "bb"
+        ]
+      },
+      "android/support/v13/app/FragmentStatePagerAdapter": {
+        "androidx/app/FragmentStatePagerAdapter": [
+          "TAG",
+          "DEBUG"
+        ]
+      },
+      "android/support/v17/leanback/transition/LeanbackTransitionHelper": {
+        "androidx/leanback/transition/LeanbackTransitionHelper": [
+          "sImpl"
+        ]
+      },
+      "android/support/v7/view/menu/MenuItemWrapperICS": {
+        "androidx/view/menu/MenuItemWrapperICS": [
+          "LOG_TAG"
+        ]
+      },
+      "android/support/v4/content/IntentCompat": {
+        "androidx/content/IntentCompat": [
+          "EXTRA_HTML_TEXT",
+          "CATEGORY_LEANBACK_LAUNCHER",
+          "EXTRA_START_PLAYBACK"
+        ]
+      },
+      "android/support/graphics/drawable/AnimatedVectorDrawableCompat": {
+        "androidx/graphics/drawable/AnimatedVectorDrawableCompat": [
+          "LOGTAG",
+          "TARGET",
+          "ANIMATED_VECTOR",
+          "DBG_ANIMATION_VECTOR_DRAWABLE"
+        ]
+      },
+      "android/support/v7/appcompat/R$style": {
+        "androidx/appcompat/R$style": [
+          "Base_Widget_AppCompat_DrawerArrowToggle",
+          "Theme_AppCompat_Light",
+          "TextAppearance_AppCompat_Caption",
+          "Theme_AppCompat_CompactMenu",
+          "TextAppearance_AppCompat_Widget_ActionBar_Title",
+          "Animation_AppCompat_Tooltip"
+        ]
+      },
+      "android/support/media/instantvideo/widget/InstantVideoView": {
+        "androidx/media/instantvideo/widget/InstantVideoView": [
+          "TAG"
+        ]
+      },
+      "android/support/v17/leanback/transition/Scale": {
+        "androidx/leanback/transition/Scale": [
+          "PROPNAME_SCALE"
+        ]
+      },
+      "android/support/v4/content/WakefulBroadcastReceiver": {
+        "androidx/content/WakefulBroadcastReceiver": [
+          "EXTRA_WAKE_LOCK_ID",
+          "sActiveWakeLocks"
+        ]
+      },
+      "android/support/v17/leanback/widget/VerticalGridPresenter": {
+        "androidx/leanback/widget/VerticalGridPresenter": [
+          "DEBUG",
+          "TAG"
+        ]
+      },
+      "android/support/v17/leanback/widget/PersistentFocusWrapper": {
+        "androidx/leanback/widget/PersistentFocusWrapper": [
+          "DEBUG",
+          "TAG"
+        ]
+      },
+      "android/support/v4/provider/SelfDestructiveThread": {
+        "androidx/provider/SelfDestructiveThread": [
+          "MSG_DESTRUCTION",
+          "MSG_INVOKE_RUNNABLE"
+        ]
+      },
+      "android/support/customtabs/TrustedWebUtils": {
+        "androidx/browser/customtabs/TrustedWebUtils": [
+          "EXTRA_LAUNCH_AS_TRUSTED_WEB_ACTIVITY"
+        ]
+      },
+      "android/support/v17/leanback/app/RowsSupportFragment": {
+        "androidx/leanback/app/RowsSupportFragment": [
+          "TAG",
+          "ALIGN_TOP_NOT_SET",
+          "DEBUG"
+        ]
+      },
+      "android/support/v17/leanback/widget/ScaleFrameLayout": {
+        "androidx/leanback/widget/ScaleFrameLayout": [
+          "DEFAULT_CHILD_GRAVITY"
+        ]
+      },
+      "android/support/v4/media/MediaBrowserCompat$MediaItem": {
+        "androidx/media/MediaBrowserCompat$MediaItem": [
+          "CREATOR",
+          "FLAG_PLAYABLE",
+          "FLAG_BROWSABLE"
+        ]
+      },
+      "android/support/v4/provider/FontsContractCompat$FontRequestCallback": {
+        "androidx/provider/FontsContractCompat$FontRequestCallback": [
+          "FAIL_REASON_WRONG_CERTIFICATES",
+          "FAIL_REASON_FONT_UNAVAILABLE",
+          "FAIL_REASON_MALFORMED_QUERY",
+          "FAIL_REASON_SECURITY_VIOLATION",
+          "FAIL_REASON_FONT_LOAD_ERROR",
+          "RESULT_OK",
+          "FAIL_REASON_FONT_NOT_FOUND",
+          "FAIL_REASON_PROVIDER_NOT_FOUND"
+        ]
+      },
+      "android/support/dynamicanimation/BuildConfig": {
+        "androidx/dynamicanimation/BuildConfig": [
+          "BUILD_TYPE",
+          "VERSION_NAME",
+          "FLAVOR",
+          "APPLICATION_ID",
+          "VERSION_CODE",
+          "DEBUG"
+        ]
+      },
+      "android/support/v4/widget/NestedScrollView$SavedState": {
+        "androidx/widget/NestedScrollView$SavedState": [
+          "CREATOR",
+          "scrollPosition"
+        ]
+      },
+      "android/support/text/emoji/R$layout": {
+        "androidx/text/emoji/R$layout": [
+          "input_method_extract_view"
+        ]
+      },
+      "android/support/v4/app/FragmentManagerImpl$FragmentTag": {
+        "androidx/app/FragmentManagerImpl$FragmentTag": [
+          "Fragment",
+          "Fragment_tag",
+          "Fragment_name",
+          "Fragment_id"
+        ]
+      },
+      "android/support/v7/widget/GridLayout$Assoc": {
+        "androidx/widget/GridLayout$Assoc": [
+          "keyType",
+          "valueType"
+        ]
+      },
+      "android/support/text/emoji/bundled/BuildConfig": {
+        "androidx/text/emoji/bundled/BuildConfig": [
+          "BUILD_TYPE",
+          "VERSION_NAME",
+          "APPLICATION_ID",
+          "DEBUG",
+          "VERSION_CODE",
+          "FLAVOR"
+        ]
+      },
+      "android/support/v4/app/LoaderManagerImpl": {
+        "androidx/app/LoaderManagerImpl": [
+          "DEBUG",
+          "TAG"
+        ]
+      },
+      "android/support/v7/appcompat/R$string": {
+        "androidx/appcompat/R$string": [
+          "abc_action_bar_up_description",
+          "abc_searchview_description_search",
+          "abc_activity_chooser_view_see_all",
+          "abc_shareactionprovider_share_with",
+          "abc_shareactionprovider_share_with_application",
+          "abc_activitychooserview_choose_application"
+        ]
+      },
+      "android/support/v7/widget/AppCompatAutoCompleteTextView": {
+        "androidx/widget/AppCompatAutoCompleteTextView": [
+          "TINT_ATTRS"
+        ]
+      },
+      "android/support/graphics/drawable/animated/BuildConfig": {
+        "androidx/graphics/drawable/animated/BuildConfig": [
+          "BUILD_TYPE",
+          "FLAVOR",
+          "VERSION_NAME",
+          "APPLICATION_ID",
+          "VERSION_CODE",
+          "DEBUG"
+        ]
+      },
+      "android/support/v7/widget/ChildHelper": {
+        "androidx/widget/ChildHelper": [
+          "DEBUG",
+          "TAG"
+        ]
+      },
+      "android/support/wear/ambient/SharedLibraryVersion$VersionHolder": {
+        "androidx/wear/ambient/SharedLibraryVersion$VersionHolder": [
+          "VERSION"
+        ]
+      },
+      "android/support/media/tv/BaseProgram": {
+        "androidx/media/tv/BaseProgram": [
+          "INVALID_LONG_VALUE",
+          "PROJECTION",
+          "IS_SEARCHABLE",
+          "INVALID_INT_VALUE"
+        ]
+      },
+      "android/support/transition/TransitionUtils": {
+        "androidx/transition/TransitionUtils": [
+          "MAX_IMAGE_SIZE"
+        ]
+      },
+      "android/support/v17/leanback/widget/ItemAlignment": {
+        "androidx/leanback/widget/ItemAlignment": [
+          "horizontal",
+          "vertical"
+        ]
+      },
+      "android/support/design/widget/NavigationView$SavedState": {
+        "androidx/design/widget/NavigationView$SavedState": [
+          "CREATOR",
+          "menuState"
+        ]
+      },
+      "android/support/v17/leanback/widget/WindowAlignment": {
+        "androidx/leanback/widget/WindowAlignment": [
+          "horizontal",
+          "vertical"
+        ]
+      },
+      "android/support/design/internal/BottomNavigationMenuView": {
+        "androidx/design/internal/BottomNavigationMenuView": [
+          "ACTIVE_ANIMATION_DURATION_MS"
+        ]
+      },
+      "android/support/transition/Slide": {
+        "androidx/transition/Slide": [
+          "sDecelerate",
+          "PROPNAME_SCREEN_POSITION",
+          "sCalculateTop",
+          "sCalculateBottom",
+          "sCalculateRight",
+          "sAccelerate",
+          "sCalculateStart",
+          "sCalculateEnd",
+          "sCalculateLeft"
+        ]
+      },
+      "android/support/text/emoji/MetadataRepo": {
+        "androidx/text/emoji/MetadataRepo": [
+          "DEFAULT_ROOT_SIZE"
+        ]
+      },
+      "android/support/v7/app/AppCompatDelegateImplBase": {
+        "androidx/app/AppCompatDelegateImplBase": [
+          "EXCEPTION_HANDLER_MESSAGE_SUFFIX",
+          "sInstalledExceptionHandler",
+          "sWindowBackgroundStyleable",
+          "DEBUG",
+          "SHOULD_INSTALL_EXCEPTION_HANDLER"
+        ]
+      },
+      "android/support/v7/widget/StaggeredGridLayoutManager$LazySpanLookup$FullSpanItem": {
+        "androidx/widget/StaggeredGridLayoutManager$LazySpanLookup$FullSpanItem": [
+          "CREATOR"
+        ]
+      },
+      "android/support/v17/leanback/R$anim": {
+        "androidx/leanback/R$anim": [
+          "lb_decelerator_4"
+        ]
+      },
+      "android/support/v17/leanback/widget/PagingIndicator": {
+        "androidx/leanback/widget/PagingIndicator": [
+          "DURATION_DIAMETER",
+          "DURATION_TRANSLATION_X",
+          "DECELERATE_INTERPOLATOR",
+          "DOT_DIAMETER",
+          "DURATION_ALPHA",
+          "DOT_TRANSLATION_X",
+          "DOT_ALPHA"
+        ]
+      },
+      "android/support/v7/widget/ScrollingTabContainerView": {
+        "androidx/widget/ScrollingTabContainerView": [
+          "sAlphaInterpolator",
+          "TAG",
+          "FADE_DURATION"
+        ]
+      },
+      "android/support/v4/text/TextDirectionHeuristicsCompat$FirstStrong": {
+        "androidx/text/TextDirectionHeuristicsCompat$FirstStrong": [
+          "INSTANCE"
+        ]
+      },
+      "android/support/design/internal/NavigationMenuPresenter$NavigationMenuTextItem": {
+        "androidx/design/internal/NavigationMenuPresenter$NavigationMenuTextItem": [
+          "needsEmptyIcon"
+        ]
+      },
+      "android/support/v17/leanback/widget/Presenter$ViewHolder": {
+        "androidx/leanback/widget/Presenter$ViewHolder": [
+          "view"
+        ]
+      },
+      "android/support/customtabs/IPostMessageService$Stub": {
+        "androidx/browser/customtabs/IPostMessageService$Stub": [
+          "TRANSACTION_onPostMessage",
+          "DESCRIPTOR",
+          "TRANSACTION_onMessageChannelReady"
+        ]
+      },
+      "android/support/v4/view/ViewPropertyAnimatorCompat": {
+        "androidx/view/ViewPropertyAnimatorCompat": [
+          "LISTENER_TAG_ID",
+          "TAG"
+        ]
+      },
+      "android/support/v17/leanback/widget/AbstractMediaItemPresenter": {
+        "androidx/leanback/widget/AbstractMediaItemPresenter": [
+          "PLAY_STATE_PLAYING",
+          "PLAY_STATE_PAUSED",
+          "sTempRect",
+          "PLAY_STATE_INITIAL"
+        ]
+      },
+      "android/support/text/emoji/BuildConfig": {
+        "androidx/text/emoji/BuildConfig": [
+          "APPLICATION_ID",
+          "VERSION_NAME",
+          "BUILD_TYPE",
+          "VERSION_CODE",
+          "DEBUG",
+          "FLAVOR"
+        ]
+      },
+      "android/support/v4/media/session/MediaControllerCompat$TransportControls": {
+        "androidx/media/session/MediaControllerCompat$TransportControls": [
+          "EXTRA_LEGACY_STREAM_TYPE"
+        ]
+      },
+      "android/support/v4/view/WindowCompat": {
+        "androidx/view/WindowCompat": [
+          "FEATURE_ACTION_MODE_OVERLAY",
+          "FEATURE_ACTION_BAR",
+          "FEATURE_ACTION_BAR_OVERLAY"
+        ]
+      },
+      "android/support/v4/content/PermissionChecker": {
+        "androidx/content/PermissionChecker": [
+          "PERMISSION_GRANTED",
+          "PERMISSION_DENIED",
+          "PERMISSION_DENIED_APP_OP"
+        ]
+      },
+      "android/support/v4/text/TextUtilsCompat": {
+        "androidx/text/TextUtilsCompat": [
+          "ROOT",
+          "ARAB_SCRIPT_SUBTAG",
+          "HEBR_SCRIPT_SUBTAG"
+        ]
+      },
+      "android/support/v17/leanback/widget/DetailsOverviewRowPresenter": {
+        "androidx/leanback/widget/DetailsOverviewRowPresenter": [
+          "MORE_ACTIONS_FADE_MS",
+          "DEBUG",
+          "TAG",
+          "DEFAULT_TIMEOUT"
+        ]
+      },
+      "android/support/design/widget/TextInputLayout": {
+        "androidx/design/widget/TextInputLayout": [
+          "LOG_TAG",
+          "INVALID_MAX_LENGTH",
+          "ANIMATION_DURATION"
+        ]
+      },
+      "android/support/v7/media/RemotePlaybackClient": {
+        "androidx/media/RemotePlaybackClient": [
+          "DEBUG",
+          "TAG"
+        ]
+      },
+      "android/support/v17/leanback/widget/ListRowPresenter$ViewHolder": {
+        "androidx/leanback/widget/ListRowPresenter$ViewHolder": [
+          "view"
+        ]
+      },
+      "android/support/v7/content/res/AppCompatColorStateListInflater": {
+        "androidx/content/res/AppCompatColorStateListInflater": [
+          "DEFAULT_COLOR"
+        ]
+      },
+      "android/support/v7/view/menu/ExpandedMenuView": {
+        "androidx/view/menu/ExpandedMenuView": [
+          "TINT_ATTRS"
+        ]
+      },
+      "android/support/v7/widget/PositionMap$ContainerHelpers": {
+        "androidx/widget/PositionMap$ContainerHelpers": [
+          "EMPTY_BOOLEANS",
+          "EMPTY_INTS",
+          "EMPTY_OBJECTS",
+          "EMPTY_LONGS"
+        ]
+      },
+      "android/support/design/widget/TextInputLayout$SavedState": {
+        "androidx/design/widget/TextInputLayout$SavedState": [
+          "error",
+          "isPasswordToggledVisible",
+          "CREATOR"
+        ]
+      },
+      "android/support/v17/leanback/BuildConfig": {
+        "androidx/leanback/BuildConfig": [
+          "DEBUG",
+          "VERSION_CODE",
+          "VERSION_NAME",
+          "BUILD_TYPE",
+          "APPLICATION_ID",
+          "FLAVOR"
+        ]
+      },
+      "android/support/v7/widget/GridLayout$MutableInt": {
+        "androidx/widget/GridLayout$MutableInt": [
+          "value"
+        ]
+      },
+      "android/support/wear/R$dimen": {
+        "androidx/wear/R$dimen": [
+          "ws_action_drawer_item_icon_right_margin",
+          "ws_action_drawer_item_bottom_padding",
+          "ws_wearable_drawer_view_elevation",
+          "circular_progress_layout_stroke_width",
+          "ws_wrv_curve_default_x_offset",
+          "ws_action_drawer_item_top_padding"
+        ]
+      },
+      "android/support/wear/widget/drawer/WearableNavigationDrawerView": {
+        "androidx/wear/widget/drawer/WearableNavigationDrawerView": [
+          "MULTI_PAGE",
+          "SINGLE_PAGE",
+          "AUTO_CLOSE_DRAWER_DELAY_MS",
+          "TAG",
+          "DEFAULT_STYLE"
+        ]
+      },
+      "android/support/v7/graphics/Palette": {
+        "androidx/graphics/palette/Palette": [
+          "DEFAULT_CALCULATE_NUMBER_COLORS",
+          "MIN_CONTRAST_BODY_TEXT",
+          "LOG_TIMINGS",
+          "DEFAULT_FILTER",
+          "LOG_TAG",
+          "MIN_CONTRAST_TITLE_TEXT",
+          "DEFAULT_RESIZE_BITMAP_AREA"
+        ]
+      },
+      "android/support/v7/widget/RecyclerView$State": {
+        "androidx/widget/RecyclerView$State": [
+          "STEP_ANIMATIONS",
+          "STEP_LAYOUT",
+          "STEP_START"
+        ]
+      },
+      "android/support/design/widget/AppBarLayout": {
+        "androidx/design/widget/AppBarLayout": [
+          "PENDING_ACTION_FORCE",
+          "PENDING_ACTION_NONE",
+          "PENDING_ACTION_COLLAPSED",
+          "INVALID_SCROLL_RANGE",
+          "PENDING_ACTION_EXPANDED",
+          "PENDING_ACTION_ANIMATE_ENABLED"
+        ]
+      },
+      "android/support/design/widget/ViewUtilsLollipop": {
+        "androidx/design/widget/ViewUtilsLollipop": [
+          "STATE_LIST_ANIM_ATTRS"
+        ]
+      },
+      "android/support/v4/provider/FontsContractCompat": {
+        "androidx/provider/FontsContractCompat": [
+          "sByteArrayComparator",
+          "sBackgroundThread",
+          "BACKGROUND_THREAD_KEEP_ALIVE_DURATION_MS",
+          "sTypefaceCache",
+          "RESULT_CODE_WRONG_CERTIFICATES",
+          "RESULT_CODE_PROVIDER_NOT_FOUND",
+          "PARCEL_FONT_RESULTS",
+          "sPendingReplies",
+          "TAG",
+          "sLock"
+        ]
+      },
+      "android/support/media/tv/TvContractUtils": {
+        "androidx/media/tv/TvContractUtils": [
+          "DEBUG",
+          "EMPTY",
+          "TAG",
+          "DELIMITER"
+        ]
+      },
+      "android/support/v17/leanback/widget/RoundedRectHelperApi21": {
+        "androidx/leanback/widget/RoundedRectHelperApi21": [
+          "sRoundedRectProvider",
+          "MAX_CACHED_PROVIDER"
+        ]
+      },
+      "android/support/v17/leanback/app/BrowseFragment$MainFragmentAdapterRegistry": {
+        "androidx/leanback/app/BrowseFragment$MainFragmentAdapterRegistry": [
+          "sDefaultFragmentFactory"
+        ]
+      },
+      "android/support/v7/util/BatchingListUpdateCallback": {
+        "androidx/util/BatchingListUpdateCallback": [
+          "TYPE_CHANGE",
+          "TYPE_REMOVE",
+          "TYPE_NONE",
+          "TYPE_ADD"
+        ]
+      },
+      "android/support/v4/widget/DrawerLayout$SavedState": {
+        "androidx/widget/DrawerLayout$SavedState": [
+          "lockModeLeft",
+          "lockModeEnd",
+          "CREATOR",
+          "lockModeStart",
+          "openDrawerGravity",
+          "lockModeRight"
+        ]
+      },
+      "android/support/design/internal/NavigationMenuItemView": {
+        "androidx/design/internal/NavigationMenuItemView": [
+          "CHECKED_STATE_SET",
+          "EMPTY_STATE_SET"
+        ]
+      },
+      "android/support/customtabs/BuildConfig": {
+        "androidx/browser/customtabs/BuildConfig": [
+          "FLAVOR",
+          "VERSION_CODE",
+          "DEBUG",
+          "VERSION_NAME",
+          "BUILD_TYPE",
+          "APPLICATION_ID"
+        ]
+      },
+      "android/support/text/emoji/EmojiProcessor$ProcessorSm": {
+        "androidx/text/emoji/EmojiProcessor$ProcessorSm": [
+          "STATE_DEFAULT",
+          "STATE_WALKING"
+        ]
+      },
+      "android/support/v7/widget/ActivityChooserModel$DefaultSorter": {
+        "androidx/widget/ActivityChooserModel$DefaultSorter": [
+          "WEIGHT_DECAY_COEFFICIENT"
+        ]
+      },
+      "android/support/design/BuildConfig": {
+        "androidx/design/BuildConfig": [
+          "APPLICATION_ID",
+          "FLAVOR",
+          "VERSION_NAME",
+          "BUILD_TYPE",
+          "VERSION_CODE",
+          "DEBUG"
+        ]
+      },
+      "android/support/v4/content/pm/ShortcutManagerCompat": {
+        "androidx/content/pm/ShortcutManagerCompat": [
+          "ACTION_INSTALL_SHORTCUT",
+          "INSTALL_SHORTCUT_PERMISSION"
+        ]
+      },
+      "android/support/media/tv/TvContractCompat$Channels$Logo": {
+        "androidx/media/tv/TvContractCompat$Channels$Logo": [
+          "CONTENT_DIRECTORY"
+        ]
+      },
+      "android/support/v4/view/ViewCompat$ViewCompatBaseImpl": {
+        "androidx/view/ViewCompat$ViewCompatBaseImpl": [
+          "sChildrenDrawingOrderMethod",
+          "sMinHeightField",
+          "sMinHeightFieldFetched",
+          "sTransitionNameMap",
+          "sAccessibilityDelegateCheckFailed",
+          "sMinWidthField",
+          "sMinWidthFieldFetched",
+          "sAccessibilityDelegateField"
+        ]
+      },
+      "android/support/v4/content/res/TypedArrayUtils": {
+        "androidx/content/res/TypedArrayUtils": [
+          "NAMESPACE"
+        ]
+      },
+      "android/support/annotation/RestrictTo$Scope": {
+        "androidx/annotation/RestrictTo$Scope": [
+          "GROUP_ID",
+          "TESTS",
+          "LIBRARY",
+          "SUBCLASSES",
+          "LIBRARY_GROUP"
+        ]
+      },
+      "android/support/media/tv/ChannelLogoUtils": {
+        "androidx/media/tv/ChannelLogoUtils": [
+          "TAG",
+          "CONNECTION_TIMEOUT_MS_FOR_URLCONNECTION",
+          "READ_TIMEOUT_MS_FOR_URLCONNECTION"
+        ]
+      },
+      "android/support/text/emoji/EmojiProcessor": {
+        "androidx/text/emoji/EmojiProcessor": [
+          "ACTION_FLUSH",
+          "ACTION_ADVANCE_BOTH",
+          "ACTION_ADVANCE_END"
+        ]
+      },
+      "android/support/design/R$layout": {
+        "androidx/design/R$layout": [
+          "design_navigation_item",
+          "design_navigation_menu_item",
+          "design_navigation_item_separator",
+          "design_navigation_item_subheader",
+          "design_navigation_item_header",
+          "design_bottom_navigation_item",
+          "design_text_input_password_icon",
+          "design_layout_snackbar_include",
+          "design_layout_tab_text",
+          "design_layout_snackbar",
+          "design_bottom_sheet_dialog",
+          "design_layout_tab_icon",
+          "design_navigation_menu"
+        ]
+      },
+      "android/support/v4/app/NotificationCompatExtras": {
+        "androidx/app/NotificationCompatExtras": [
+          "EXTRA_ACTION_EXTRAS",
+          "EXTRA_GROUP_KEY",
+          "EXTRA_SORT_KEY",
+          "EXTRA_REMOTE_INPUTS",
+          "EXTRA_LOCAL_ONLY",
+          "EXTRA_GROUP_SUMMARY"
+        ]
+      },
+      "android/support/v17/leanback/widget/GridLayoutManager$SavedState": {
+        "androidx/leanback/widget/GridLayoutManager$SavedState": [
+          "CREATOR",
+          "index",
+          "childStates"
+        ]
+      },
+      "android/support/v7/widget/RecyclerView$ItemAnimator": {
+        "androidx/widget/RecyclerView$ItemAnimator": [
+          "FLAG_CHANGED",
+          "FLAG_REMOVED",
+          "FLAG_APPEARED_IN_PRE_LAYOUT",
+          "FLAG_INVALIDATED",
+          "FLAG_MOVED"
+        ]
+      },
+      "android/support/v17/leanback/widget/Grid$Location": {
+        "androidx/leanback/widget/Grid$Location": [
+          "row"
+        ]
+      },
+      "android/support/v17/leanback/media/MediaControllerGlue": {
+        "androidx/leanback/media/MediaControllerGlue": [
+          "DEBUG",
+          "TAG"
+        ]
+      },
+      "android/support/v17/preference/LeanbackListPreferenceDialogFragment": {
+        "androidx/leanback/preference/LeanbackListPreferenceDialogFragment": [
+          "SAVE_STATE_ENTRY_VALUES",
+          "SAVE_STATE_TITLE",
+          "SAVE_STATE_INITIAL_SELECTIONS",
+          "SAVE_STATE_IS_MULTI",
+          "SAVE_STATE_ENTRIES",
+          "SAVE_STATE_INITIAL_SELECTION",
+          "SAVE_STATE_MESSAGE"
+        ]
+      },
+      "android/support/v4/app/ServiceCompat": {
+        "androidx/app/ServiceCompat": [
+          "STOP_FOREGROUND_DETACH",
+          "START_STICKY",
+          "STOP_FOREGROUND_REMOVE"
+        ]
+      },
+      "android/support/wear/widget/SwipeDismissLayout": {
+        "androidx/wear/widget/SwipeDismissLayout": [
+          "TAG",
+          "DEFAULT_DISMISS_DRAG_WIDTH_RATIO",
+          "EDGE_SWIPE_THRESHOLD"
+        ]
+      },
+      "android/support/v4/os/LocaleListCompat": {
+        "androidx/os/LocaleListCompat": [
+          "sEmptyLocaleList",
+          "IMPL"
+        ]
+      },
+      "android/support/v7/preference/Preference$BaseSavedState": {
+        "androidx/preference/Preference$BaseSavedState": [
+          "CREATOR",
+          "EMPTY_STATE"
+        ]
+      },
+      "android/support/v7/media/MediaRouteProviderService": {
+        "androidx/media/MediaRouteProviderService": [
+          "TAG",
+          "SERVICE_INTERFACE",
+          "DEBUG",
+          "PRIVATE_MSG_CLIENT_DIED"
+        ]
+      },
+      "android/support/v7/view/menu/CascadingMenuPopup$CascadingMenuInfo": {
+        "androidx/view/menu/CascadingMenuPopup$CascadingMenuInfo": [
+          "position",
+          "menu",
+          "window"
+        ]
+      },
+      "android/support/v7/widget/ToolbarWidgetWrapper": {
+        "androidx/widget/ToolbarWidgetWrapper": [
+          "DEFAULT_FADE_DURATION_MS",
+          "AFFECTS_LOGO_MASK",
+          "TAG"
+        ]
+      },
+      "android/support/v17/leanback/widget/PlaybackTransportRowPresenter$ViewHolder": {
+        "androidx/leanback/widget/PlaybackTransportRowPresenter$ViewHolder": [
+          "view"
+        ]
+      },
+      "android/support/v7/widget/DefaultItemAnimator": {
+        "androidx/widget/DefaultItemAnimator": [
+          "DEBUG",
+          "sDefaultInterpolator"
+        ]
+      },
+      "android/support/v4/widget/PopupWindowCompat$PopupWindowCompatApi21Impl": {
+        "androidx/widget/PopupWindowCompat$PopupWindowCompatApi21Impl": [
+          "sOverlapAnchorField",
+          "TAG"
+        ]
+      },
+      "android/support/v17/preference/LeanbackSettingsFragment": {
+        "androidx/leanback/preference/LeanbackSettingsFragment": [
+          "PREFERENCE_FRAGMENT_TAG"
+        ]
+      },
+      "android/support/v17/leanback/widget/ParallaxTarget$PropertyValuesHolderTarget": {
+        "androidx/leanback/widget/ParallaxTarget$PropertyValuesHolderTarget": [
+          "PSEUDO_DURATION"
+        ]
+      },
+      "android/support/design/widget/SnackbarManager": {
+        "androidx/design/widget/SnackbarManager": [
+          "MSG_TIMEOUT",
+          "sSnackbarManager",
+          "LONG_DURATION_MS",
+          "SHORT_DURATION_MS"
+        ]
+      },
+      "android/support/v17/leanback/app/BaseRowSupportFragment": {
+        "androidx/leanback/app/BaseRowSupportFragment": [
+          "CURRENT_SELECTED_POSITION"
+        ]
+      },
+      "android/support/v7/app/MediaRouteVolumeSlider": {
+        "androidx/app/MediaRouteVolumeSlider": [
+          "TAG"
+        ]
+      },
+      "android/support/v7/util/AsyncListUtil$ViewCallback": {
+        "androidx/util/AsyncListUtil$ViewCallback": [
+          "HINT_SCROLL_DESC",
+          "HINT_SCROLL_NONE",
+          "HINT_SCROLL_ASC"
+        ]
+      },
+      "android/support/multidex/MultiDexExtractor$ExtractedDex": {
+        "androidx/multidex/MultiDexExtractor$ExtractedDex": [
+          "crc"
+        ]
+      },
+      "android/support/transition/AnimatorUtils": {
+        "androidx/transition/AnimatorUtils": [
+          "IMPL"
+        ]
+      },
+      "android/support/v7/app/AppCompatDelegateImplV14": {
+        "androidx/app/AppCompatDelegateImplV14": [
+          "KEY_LOCAL_NIGHT_MODE"
+        ]
+      },
+      "android/support/transition/Styleable$PatternPathMotion": {
+        "androidx/transition/Styleable$PatternPathMotion": [
+          "PATTERN_PATH_DATA"
+        ]
+      },
+      "android/support/v7/media/SystemMediaRouteProvider": {
+        "androidx/media/SystemMediaRouteProvider": [
+          "TAG",
+          "DEFAULT_ROUTE_ID",
+          "PACKAGE_NAME"
+        ]
+      },
+      "android/support/design/internal/BottomNavigationItemView": {
+        "androidx/design/internal/BottomNavigationItemView": [
+          "CHECKED_STATE_SET",
+          "INVALID_ITEM_POSITION"
+        ]
+      },
+      "android/support/wear/widget/ScrollManager": {
+        "androidx/wear/widget/ScrollManager": [
+          "ONE_SEC_IN_MS",
+          "FLING_EDGE_RATIO",
+          "VELOCITY_MULTIPLIER"
+        ]
+      },
+      "android/support/v7/widget/AppCompatTextHelper": {
+        "androidx/widget/AppCompatTextHelper": [
+          "MONOSPACE",
+          "SANS",
+          "SERIF"
+        ]
+      },
+      "android/support/v17/leanback/app/DetailsSupportFragment$WaitEnterTransitionTimeout": {
+        "androidx/leanback/app/DetailsSupportFragment$WaitEnterTransitionTimeout": [
+          "WAIT_ENTERTRANSITION_START"
+        ]
+      },
+      "android/support/v7/media/MediaRouteProviderDescriptor": {
+        "androidx/media/MediaRouteProviderDescriptor": [
+          "KEY_ROUTES"
+        ]
+      },
+      "android/support/v17/leanback/widget/TitleView": {
+        "androidx/leanback/widget/TitleView": [
+          "flags"
+        ]
+      },
+      "android/support/v4/media/session/MediaSessionCompat$QueueItem": {
+        "androidx/media/session/MediaSessionCompat$QueueItem": [
+          "CREATOR",
+          "UNKNOWN_ID"
+        ]
+      },
+      "android/support/v7/mediarouter/R$styleable": {
+        "androidx/mediarouter/R$styleable": [
+          "MediaRouteButton_android_minHeight",
+          "MediaRouteButton_mediaRouteButtonTint",
+          "MediaRouteButton_android_minWidth",
+          "MediaRouteButton_externalRouteEnabledDrawable",
+          "MediaRouteButton"
+        ]
+      },
+      "android/support/v7/view/menu/ActionMenuItemView": {
+        "androidx/view/menu/ActionMenuItemView": [
+          "TAG",
+          "MAX_ICON_SIZE"
+        ]
+      },
+      "android/support/v4/app/FragmentStatePagerAdapter": {
+        "androidx/app/FragmentStatePagerAdapter": [
+          "TAG",
+          "DEBUG"
+        ]
+      },
+      "android/support/wear/widget/drawer/ScrollViewFlingWatcher": {
+        "androidx/wear/widget/drawer/ScrollViewFlingWatcher": [
+          "MAX_WAIT_TIME_MS"
+        ]
+      },
+      "android/support/transition/MatrixUtils": {
+        "androidx/transition/MatrixUtils": [
+          "IDENTITY_MATRIX"
+        ]
+      },
+      "android/support/v4/app/NotificationCompat$Action": {
+        "androidx/app/NotificationCompat$Action": [
+          "icon",
+          "title",
+          "actionIntent"
+        ]
+      },
+      "android/support/v4/os/ResultReceiver": {
+        "androidx/os/ResultReceiver": [
+          "CREATOR"
+        ]
+      },
+      "android/support/v7/widget/AppCompatTextView": {
+        "androidx/widget/AppCompatTextView": [
+          "PLATFORM_SUPPORTS_AUTOSIZE"
+        ]
+      },
+      "android/support/v7/widget/StaggeredGridLayoutManager": {
+        "androidx/widget/StaggeredGridLayoutManager": [
+          "GAP_HANDLING_LAZY",
+          "GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS",
+          "TAG",
+          "VERTICAL",
+          "INVALID_OFFSET",
+          "MAX_SCROLL_FACTOR",
+          "GAP_HANDLING_NONE",
+          "DEBUG",
+          "HORIZONTAL"
+        ]
+      },
+      "android/support/design/widget/BottomNavigationView$SavedState": {
+        "androidx/design/widget/BottomNavigationView$SavedState": [
+          "CREATOR",
+          "menuPresenterState"
+        ]
+      },
+      "android/support/v7/widget/RoundRectDrawableWithShadow": {
+        "androidx/widget/RoundRectDrawableWithShadow": [
+          "sRoundRectHelper",
+          "COS_45",
+          "SHADOW_MULTIPLIER"
+        ]
+      },
+      "android/support/v7/preference/PreferenceCategory": {
+        "androidx/preference/PreferenceCategory": [
+          "TAG"
+        ]
+      },
+      "android/support/v7/widget/RecyclerView$ItemAnimator$ItemHolderInfo": {
+        "androidx/widget/RecyclerView$ItemAnimator$ItemHolderInfo": [
+          "left",
+          "changeFlags",
+          "top",
+          "right",
+          "bottom"
+        ]
+      },
+      "android/support/v7/preference/ListPreference$SavedState": {
+        "androidx/preference/ListPreference$SavedState": [
+          "CREATOR",
+          "value"
+        ]
+      },
+      "android/support/v7/widget/TooltipPopup": {
+        "androidx/widget/TooltipPopup": [
+          "TAG"
+        ]
+      },
+      "android/support/v4/net/ConnectivityManagerCompat": {
+        "androidx/net/ConnectivityManagerCompat": [
+          "RESTRICT_BACKGROUND_STATUS_ENABLED",
+          "RESTRICT_BACKGROUND_STATUS_WHITELISTED",
+          "RESTRICT_BACKGROUND_STATUS_DISABLED"
+        ]
+      },
+      "android/support/v7/widget/MenuPopupWindow": {
+        "androidx/widget/MenuPopupWindow": [
+          "sSetTouchModalMethod",
+          "TAG"
+        ]
+      },
+      "android/support/v17/leanback/app/RowsFragment": {
+        "androidx/leanback/app/RowsFragment": [
+          "TAG",
+          "ALIGN_TOP_NOT_SET",
+          "DEBUG"
+        ]
+      },
+      "android/support/v17/leanback/app/ProgressBarManager": {
+        "androidx/leanback/app/ProgressBarManager": [
+          "runnable",
+          "rootView",
+          "DEFAULT_PROGRESS_BAR_DELAY"
+        ]
+      },
+      "android/support/v7/media/RegisteredMediaRouteProvider": {
+        "androidx/media/RegisteredMediaRouteProvider": [
+          "DEBUG",
+          "TAG"
+        ]
+      },
+      "android/support/design/widget/CheckableImageButton": {
+        "androidx/design/widget/CheckableImageButton": [
+          "DRAWABLE_STATE_CHECKED"
+        ]
+      },
+      "android/support/transition/ImageViewUtils": {
+        "androidx/transition/ImageViewUtils": [
+          "IMPL"
+        ]
+      },
+      "android/support/v17/leanback/widget/PlaybackControlsRow$HighQualityAction": {
+        "androidx/leanback/widget/PlaybackControlsRow$HighQualityAction": [
+          "INDEX_OFF",
+          "INDEX_ON",
+          "OFF",
+          "ON"
+        ]
+      },
+      "android/support/wear/widget/drawer/PageIndicatorView": {
+        "androidx/wear/widget/drawer/PageIndicatorView": [
+          "TAG"
+        ]
+      },
+      "android/support/transition/TransitionValues": {
+        "androidx/transition/TransitionValues": [
+          "view",
+          "values"
+        ]
+      },
+      "android/support/v17/leanback/app/BaseRowFragment": {
+        "androidx/leanback/app/BaseRowFragment": [
+          "CURRENT_SELECTED_POSITION"
+        ]
+      },
+      "android/support/v7/mediarouter/R$drawable": {
+        "androidx/mediarouter/R$drawable": [
+          "mr_group_collapse",
+          "mr_group_expand"
+        ]
+      },
+      "android/support/transition/ViewUtils": {
+        "androidx/transition/ViewUtils": [
+          "sViewFlagsField",
+          "TRANSITION_ALPHA",
+          "IMPL",
+          "sViewFlagsFieldFetched",
+          "TAG",
+          "CLIP_BOUNDS",
+          "VISIBILITY_MASK"
+        ]
+      },
+      "android/support/v7/preference/AndroidResources": {
+        "androidx/preference/AndroidResources": [
+          "ANDROID_R_ICON_FRAME",
+          "ANDROID_R_SWITCH_WIDGET",
+          "ANDROID_R_PREFERENCE_FRAGMENT_STYLE",
+          "ANDROID_R_LIST_CONTAINER",
+          "ANDROID_R_EDITTEXT_PREFERENCE_STYLE"
+        ]
+      },
+      "android/support/v4/net/DatagramSocketWrapper$DatagramSocketImplWrapper": {
+        "androidx/net/DatagramSocketWrapper$DatagramSocketImplWrapper": [
+          "localport",
+          "fd"
+        ]
+      },
+      "android/support/v17/leanback/widget/PlaybackControlsRowPresenter$BoundData": {
+        "androidx/leanback/widget/PlaybackControlsRowPresenter$BoundData": [
+          "secondaryActionsAdapter",
+          "presenter",
+          "adapter"
+        ]
+      },
+      "android/support/v17/leanback/widget/PlaybackControlsRowPresenter$ViewHolder": {
+        "androidx/leanback/widget/PlaybackControlsRowPresenter$ViewHolder": [
+          "view"
+        ]
+      },
+      "android/support/v17/leanback/widget/Parallax$FloatProperty": {
+        "androidx/leanback/widget/Parallax$FloatProperty": [
+          "UNKNOWN_AFTER",
+          "UNKNOWN_BEFORE"
+        ]
+      },
+      "android/support/v17/leanback/widget/AbstractDetailsDescriptionPresenter$ViewHolder": {
+        "androidx/leanback/widget/AbstractDetailsDescriptionPresenter$ViewHolder": [
+          "view"
+        ]
+      },
+      "android/support/design/R$string": {
+        "androidx/design/R$string": [
+          "character_counter_pattern"
+        ]
+      },
+      "android/support/wear/ambient/AmbientDelegate": {
+        "androidx/wear/ambient/AmbientDelegate": [
+          "TAG",
+          "sInitAutoResumeEnabledMethod",
+          "sHasAutoResumeEnabledMethod"
+        ]
+      },
+      "android/support/v17/leanback/app/PlaybackFragment": {
+        "androidx/leanback/app/PlaybackFragment": [
+          "ANIMATION_MULTIPLIER",
+          "BG_DARK",
+          "BG_NONE",
+          "DEBUG",
+          "BUNDLE_CONTROL_VISIBLE_ON_CREATEVIEW",
+          "BG_LIGHT",
+          "TAG",
+          "ANIMATING",
+          "IDLE",
+          "START_FADE_OUT"
+        ]
+      },
+      "android/support/v7/widget/ActionBarOverlayLayout$LayoutParams": {
+        "androidx/widget/ActionBarOverlayLayout$LayoutParams": [
+          "topMargin",
+          "rightMargin",
+          "bottomMargin",
+          "leftMargin"
+        ]
+      },
+      "android/support/v4/os/EnvironmentCompat": {
+        "androidx/os/EnvironmentCompat": [
+          "MEDIA_UNKNOWN",
+          "TAG"
+        ]
+      },
+      "android/support/compat/R$string": {
+        "androidx/compat/R$string": [
+          "status_bar_notification_info_overflow"
+        ]
+      },
+      "android/support/v7/widget/PositionMap": {
+        "androidx/widget/PositionMap": [
+          "DELETED"
+        ]
+      },
+      "android/support/media/tv/Program": {
+        "androidx/media/tv/Program": [
+          "INVALID_LONG_VALUE",
+          "PROJECTION",
+          "IS_RECORDING_PROHIBITED"
+        ]
+      },
+      "android/support/v7/widget/ActionBarContextView": {
+        "androidx/widget/ActionBarContextView": [
+          "TAG"
+        ]
+      },
+      "android/support/v4/media/VolumeProviderCompat": {
+        "androidx/media/VolumeProviderCompat": [
+          "VOLUME_CONTROL_RELATIVE",
+          "VOLUME_CONTROL_FIXED",
+          "VOLUME_CONTROL_ABSOLUTE"
+        ]
+      },
+      "android/support/v7/view/menu/CascadingMenuPopup": {
+        "androidx/view/menu/CascadingMenuPopup": [
+          "SUBMENU_TIMEOUT_MS",
+          "HORIZ_POSITION_LEFT",
+          "HORIZ_POSITION_RIGHT"
+        ]
+      },
+      "android/support/v4/app/NotificationManagerCompat$ServiceConnectedEvent": {
+        "androidx/app/NotificationManagerCompat$ServiceConnectedEvent": [
+          "componentName",
+          "iBinder"
+        ]
+      },
+      "android/support/v7/app/AlertDialog": {
+        "androidx/app/AlertDialog": [
+          "LAYOUT_HINT_NONE",
+          "LAYOUT_HINT_SIDE"
+        ]
+      },
+      "android/support/v17/leanback/widget/picker/PickerUtility$DateConstant": {
+        "androidx/leanback/widget/picker/PickerUtility$DateConstant": [
+          "months",
+          "days",
+          "locale"
+        ]
+      },
+      "android/support/v7/app/MediaRouteChooserDialogFragment": {
+        "androidx/app/MediaRouteChooserDialogFragment": [
+          "ARGUMENT_SELECTOR"
+        ]
+      },
+      "android/support/design/widget/CoordinatorLayout$SavedState": {
+        "androidx/design/widget/CoordinatorLayout$SavedState": [
+          "behaviorStates",
+          "CREATOR"
+        ]
+      },
+      "android/support/v7/media/MediaRouteDiscoveryRequest": {
+        "androidx/media/MediaRouteDiscoveryRequest": [
+          "KEY_ACTIVE_SCAN",
+          "KEY_SELECTOR"
+        ]
+      },
+      "android/support/v17/leanback/widget/StaticShadowHelper": {
+        "androidx/leanback/widget/StaticShadowHelper": [
+          "sInstance"
+        ]
+      },
+      "android/support/design/widget/ShadowDrawableWrapper": {
+        "androidx/design/widget/ShadowDrawableWrapper": [
+          "SHADOW_MULTIPLIER",
+          "SHADOW_TOP_SCALE",
+          "COS_45",
+          "SHADOW_HORIZ_SCALE",
+          "SHADOW_BOTTOM_SCALE"
+        ]
+      },
+      "android/support/v13/app/FragmentCompat": {
+        "androidx/app/FragmentCompat": [
+          "sDelegate",
+          "IMPL"
+        ]
+      },
+      "android/support/v7/widget/GridLayout$Arc": {
+        "androidx/widget/GridLayout$Arc": [
+          "span",
+          "valid",
+          "value"
+        ]
+      },
+      "android/support/v4/content/AsyncTaskLoader": {
+        "androidx/content/AsyncTaskLoader": [
+          "DEBUG",
+          "TAG"
+        ]
+      },
+      "android/support/v7/gridlayout/BuildConfig": {
+        "androidx/gridlayout/BuildConfig": [
+          "DEBUG",
+          "VERSION_CODE",
+          "BUILD_TYPE",
+          "VERSION_NAME",
+          "FLAVOR",
+          "APPLICATION_ID"
+        ]
+      },
+      "android/support/text/emoji/flatbuffer/MetadataList": {
+        "androidx/text/emoji/flatbuffer/MetadataList": [
+          "bb_pos",
+          "bb"
+        ]
+      },
+      "android/support/text/emoji/widget/EditTextAttributeHelper": {
+        "androidx/text/emoji/widget/EditTextAttributeHelper": [
+          "MAX_EMOJI_COUNT"
+        ]
+      },
+      "android/support/v7/widget/ChildHelper$Bucket": {
+        "androidx/widget/ChildHelper$Bucket": [
+          "LAST_BIT",
+          "BITS_PER_WORD"
+        ]
+      },
+      "android/support/v4/content/LocalBroadcastManager": {
+        "androidx/content/LocalBroadcastManager": [
+          "DEBUG",
+          "MSG_EXEC_PENDING_BROADCASTS",
+          "TAG"
+        ]
+      },
+      "android/support/v7/widget/TooltipCompat": {
+        "androidx/widget/TooltipCompat": [
+          "IMPL"
+        ]
+      },
+      "android/support/media/tv/PreviewProgram": {
+        "androidx/media/tv/PreviewProgram": [
+          "INVALID_INT_VALUE",
+          "PROJECTION",
+          "INVALID_LONG_VALUE"
+        ]
+      },
+      "android/support/v7/preference/ListPreferenceDialogFragmentCompat": {
+        "androidx/preference/ListPreferenceDialogFragmentCompat": [
+          "SAVE_STATE_INDEX",
+          "SAVE_STATE_ENTRIES",
+          "SAVE_STATE_ENTRY_VALUES"
+        ]
+      },
+      "android/support/v7/widget/ViewInfoStore": {
+        "androidx/widget/ViewInfoStore": [
+          "DEBUG"
+        ]
+      },
+      "android/support/v7/widget/ActivityChooserModel$ActivityResolveInfo": {
+        "androidx/widget/ActivityChooserModel$ActivityResolveInfo": [
+          "weight",
+          "resolveInfo"
+        ]
+      },
+      "android/support/v7/preference/SeekBarPreference": {
+        "androidx/preference/SeekBarPreference": [
+          "TAG"
+        ]
+      },
+      "android/support/v7/widget/ActivityChooserView$InnerLayout": {
+        "androidx/widget/ActivityChooserView$InnerLayout": [
+          "TINT_ATTRS"
+        ]
+      },
+      "android/support/v7/app/AppCompatDelegateImplV9": {
+        "androidx/app/AppCompatDelegateImplV9": [
+          "IS_PRE_LOLLIPOP"
+        ]
+      },
+      "android/support/v7/media/SystemMediaRouteProvider$LegacyImpl": {
+        "androidx/media/SystemMediaRouteProvider$LegacyImpl": [
+          "PLAYBACK_STREAM",
+          "CONTROL_FILTERS"
+        ]
+      },
+      "android/support/transition/ViewUtilsApi22": {
+        "androidx/transition/ViewUtilsApi22": [
+          "sSetLeftTopRightBottomMethod",
+          "TAG",
+          "sSetLeftTopRightBottomMethodFetched"
+        ]
+      },
+      "android/support/v17/leanback/widget/ObjectAdapter": {
+        "androidx/leanback/widget/ObjectAdapter": [
+          "NO_ID"
+        ]
+      },
+      "android/support/content/Query": {
+        "androidx/content/Query": [
+          "DEBUG",
+          "TAG"
+        ]
+      },
+      "android/support/v17/leanback/media/MediaControllerAdapter": {
+        "androidx/leanback/media/MediaControllerAdapter": [
+          "TAG",
+          "DEBUG"
+        ]
+      },
+      "android/support/v7/recyclerview/R$id": {
+        "androidx/recyclerview/R$id": [
+          "item_touch_helper_previous_elevation"
+        ]
+      },
+      "android/support/v4/media/session/MediaSessionCompat$Callback$CallbackHandler": {
+        "androidx/media/session/MediaSessionCompat$Callback$CallbackHandler": [
+          "MSG_MEDIA_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT"
+        ]
+      },
+      "android/support/design/widget/FloatingActionButton": {
+        "androidx/design/widget/FloatingActionButton": [
+          "LOG_TAG",
+          "AUTO_MINI_LARGEST_SCREEN_WIDTH",
+          "SIZE_AUTO",
+          "SIZE_NORMAL",
+          "SIZE_MINI"
+        ]
+      },
+      "android/support/v17/leanback/widget/DetailsOverviewSharedElementHelper": {
+        "androidx/leanback/widget/DetailsOverviewSharedElementHelper": [
+          "DEBUG",
+          "TAG"
+        ]
+      },
+      "android/support/v7/app/AlertDialog$Builder": {
+        "androidx/app/AlertDialog$Builder": [
+          "P"
+        ]
+      },
+      "android/support/wear/internal/widget/drawer/SinglePagePresenter": {
+        "androidx/wear/internal/widget/drawer/SinglePagePresenter": [
+          "DRAWER_CLOSE_DELAY_MS"
+        ]
+      },
+      "android/support/v17/leanback/app/BrowseSupportFragment$SetSelectionRunnable": {
+        "androidx/leanback/app/BrowseSupportFragment$SetSelectionRunnable": [
+          "TYPE_INTERNAL_SYNC",
+          "TYPE_USER_REQUEST",
+          "TYPE_INVALID"
+        ]
+      },
+      "android/support/v7/preference/R$id": {
+        "androidx/preference/R$id": [
+          "switchWidget",
+          "seekbar",
+          "seekbar_value",
+          "icon_frame",
+          "spinner"
+        ]
+      },
+      "android/support/media/tv/BasePreviewProgram$Builder": {
+        "androidx/media/tv/BasePreviewProgram$Builder": [
+          "sFormat"
+        ]
+      },
+      "android/support/v4/widget/SlidingPaneLayout": {
+        "androidx/widget/SlidingPaneLayout": [
+          "DEFAULT_OVERHANG_SIZE",
+          "IMPL",
+          "DEFAULT_FADE_COLOR",
+          "TAG",
+          "MIN_FLING_VELOCITY"
+        ]
+      },
+      "android/support/v7/app/MediaRouteChooserDialog": {
+        "androidx/app/MediaRouteChooserDialog": [
+          "UPDATE_ROUTES_DELAY_MS",
+          "TAG",
+          "MSG_UPDATE_ROUTES"
+        ]
+      },
+      "android/support/v4/app/ShareCompat$IntentReader": {
+        "androidx/app/ShareCompat$IntentReader": [
+          "TAG"
+        ]
+      },
+      "android/support/v17/leanback/widget/ItemBridgeAdapter$ViewHolder": {
+        "androidx/leanback/widget/ItemBridgeAdapter$ViewHolder": [
+          "itemView"
+        ]
+      },
+      "android/support/v17/leanback/app/BrandedFragment": {
+        "androidx/leanback/app/BrandedFragment": [
+          "TITLE_SHOW"
+        ]
+      },
+      "android/support/v17/leanback/widget/SearchEditText": {
+        "androidx/leanback/widget/SearchEditText": [
+          "DEBUG",
+          "TAG"
+        ]
+      },
+      "android/support/v17/leanback/widget/PersistentFocusWrapper$SavedState": {
+        "androidx/leanback/widget/PersistentFocusWrapper$SavedState": [
+          "CREATOR"
+        ]
+      },
+      "android/support/percent/PercentFrameLayout$LayoutParams": {
+        "androidx/PercentFrameLayout$LayoutParams": [
+          "gravity"
+        ]
+      },
+      "android/support/text/emoji/widget/EmojiEditableFactory": {
+        "androidx/text/emoji/widget/EmojiEditableFactory": [
+          "sWatcherClass",
+          "sInstanceLock",
+          "sInstance"
+        ]
+      },
+      "android/support/v4/view/ViewPager$SavedState": {
+        "androidx/view/ViewPager$SavedState": [
+          "position",
+          "adapterState",
+          "CREATOR",
+          "loader"
+        ]
+      },
+      "android/support/v17/leanback/widget/RowPresenter": {
+        "androidx/leanback/widget/RowPresenter": [
+          "SYNC_ACTIVATED_TO_SELECTED",
+          "SYNC_ACTIVATED_TO_EXPANDED_AND_SELECTED",
+          "SYNC_ACTIVATED_CUSTOM",
+          "SYNC_ACTIVATED_TO_EXPANDED"
+        ]
+      },
+      "android/support/v7/preference/EditTextPreferenceDialogFragmentCompat": {
+        "androidx/preference/EditTextPreferenceDialogFragmentCompat": [
+          "SAVE_STATE_TEXT"
+        ]
+      },
+      "android/support/v17/leanback/graphics/ColorFilterCache": {
+        "androidx/leanback/graphics/ColorFilterCache": [
+          "sColorToFiltersMap"
+        ]
+      },
+      "android/support/v7/view/menu/ListMenuPresenter": {
+        "androidx/view/menu/ListMenuPresenter": [
+          "VIEWS_TAG",
+          "TAG"
+        ]
+      },
+      "android/support/v17/leanback/widget/VerticalGridPresenter$ViewHolder": {
+        "androidx/leanback/widget/VerticalGridPresenter$ViewHolder": [
+          "view"
+        ]
+      },
+      "android/support/v4/app/TaskStackBuilder": {
+        "androidx/app/TaskStackBuilder": [
+          "TAG",
+          "IMPL"
+        ]
+      },
+      "android/support/v7/content/res/AppCompatResources": {
+        "androidx/content/res/AppCompatResources": [
+          "LOG_TAG",
+          "sColorStateCaches",
+          "TL_TYPED_VALUE",
+          "sColorStateCacheLock"
+        ]
+      },
+      "android/support/v7/recyclerview/BuildConfig": {
+        "androidx/recyclerview/BuildConfig": [
+          "DEBUG",
+          "FLAVOR",
+          "VERSION_CODE",
+          "BUILD_TYPE",
+          "VERSION_NAME",
+          "APPLICATION_ID"
+        ]
+      },
+      "android/support/v4/hardware/display/DisplayManagerCompat": {
+        "androidx/hardware/display/DisplayManagerCompat": [
+          "DISPLAY_CATEGORY_PRESENTATION",
+          "sInstances"
+        ]
+      },
+      "android/support/v7/widget/ActionMenuPresenter": {
+        "androidx/widget/ActionMenuPresenter": [
+          "TAG"
+        ]
+      },
+      "android/support/v4/app/NotificationManagerCompat$NotifyTask": {
+        "androidx/app/NotificationManagerCompat$NotifyTask": [
+          "notif",
+          "id",
+          "tag",
+          "packageName"
+        ]
+      },
+      "android/support/text/emoji/FontRequestEmojiCompatConfig": {
+        "androidx/text/emoji/FontRequestEmojiCompatConfig": [
+          "DEFAULT_FONTS_CONTRACT"
+        ]
+      },
+      "android/support/v17/leanback/widget/ShadowHelperApi21": {
+        "androidx/leanback/widget/ShadowHelperApi21": [
+          "sOutlineProvider"
+        ]
+      },
+      "android/support/v17/leanback/widget/ItemBridgeAdapter": {
+        "androidx/leanback/widget/ItemBridgeAdapter": [
+          "TAG",
+          "DEBUG"
+        ]
+      },
+      "android/support/wear/utils/MetadataConstants": {
+        "androidx/wear/utils/MetadataConstants": [
+          "STANDALONE_METADATA_NAME",
+          "WATCH_FACE_PREVIEW_CIRCULAR_METADATA_NAME",
+          "NOTIFICATION_BRIDGE_MODE_NO_BRIDGING",
+          "NOTIFICATION_BRIDGE_MODE_BRIDGING",
+          "WATCH_FACE_PREVIEW_METADATA_NAME",
+          "NOTIFICATION_BRIDGE_MODE_METADATA_NAME"
+        ]
+      },
+      "android/support/transition/GhostViewUtils": {
+        "androidx/transition/GhostViewUtils": [
+          "CREATOR"
+        ]
+      },
+      "android/support/v17/leanback/widget/AbstractMediaListHeaderPresenter$ViewHolder": {
+        "androidx/leanback/widget/AbstractMediaListHeaderPresenter$ViewHolder": [
+          "view"
+        ]
+      },
+      "android/support/v17/leanback/widget/PlaybackControlsRowPresenter": {
+        "androidx/leanback/widget/PlaybackControlsRowPresenter": [
+          "sShadowZ"
+        ]
+      },
+      "android/support/v7/widget/CardView": {
+        "androidx/widget/CardView": [
+          "COLOR_BACKGROUND_ATTR",
+          "IMPL"
+        ]
+      },
+      "android/support/v7/app/ActionBar$Tab": {
+        "androidx/app/ActionBar$Tab": [
+          "INVALID_POSITION"
+        ]
+      },
+      "android/support/v7/app/MediaRouterThemeHelper": {
+        "androidx/app/MediaRouterThemeHelper": [
+          "MIN_CONTRAST",
+          "COLOR_WHITE_ON_DARK_BACKGROUND",
+          "COLOR_DARK_ON_LIGHT_BACKGROUND"
+        ]
+      },
+      "android/support/transition/ChangeClipBounds": {
+        "androidx/transition/ChangeClipBounds": [
+          "PROPNAME_CLIP",
+          "PROPNAME_BOUNDS",
+          "sTransitionProperties"
+        ]
+      },
+      "android/support/transition/Styleable$Slide": {
+        "androidx/transition/Styleable$Slide": [
+          "SLIDE_EDGE"
+        ]
+      },
+      "android/support/transition/TransitionSet": {
+        "androidx/transition/TransitionSet": [
+          "ORDERING_TOGETHER",
+          "ORDERING_SEQUENTIAL"
+        ]
+      },
+      "android/support/v7/widget/SearchView$SavedState": {
+        "androidx/widget/SearchView$SavedState": [
+          "CREATOR",
+          "isIconified"
+        ]
+      },
+      "android/support/v17/leanback/widget/PlaybackControlsPresenter$BoundData": {
+        "androidx/leanback/widget/PlaybackControlsPresenter$BoundData": [
+          "secondaryActionsAdapter"
+        ]
+      },
+      "android/support/v4/content/LocalBroadcastManager$BroadcastRecord": {
+        "androidx/content/LocalBroadcastManager$BroadcastRecord": [
+          "intent",
+          "receivers"
+        ]
+      },
+      "android/support/v7/preference/MultiSelectListPreferenceDialogFragmentCompat": {
+        "androidx/preference/MultiSelectListPreferenceDialogFragmentCompat": [
+          "SAVE_STATE_ENTRIES",
+          "SAVE_STATE_CHANGED",
+          "SAVE_STATE_ENTRY_VALUES",
+          "SAVE_STATE_VALUES"
+        ]
+      },
+      "android/support/transition/ViewGroupUtils": {
+        "androidx/transition/ViewGroupUtils": [
+          "IMPL"
+        ]
+      },
+      "android/support/v17/leanback/widget/ControlBarPresenter$ViewHolder": {
+        "androidx/leanback/widget/ControlBarPresenter$ViewHolder": [
+          "view"
+        ]
+      },
+      "android/support/v7/mediarouter/BuildConfig": {
+        "androidx/mediarouter/BuildConfig": [
+          "DEBUG",
+          "VERSION_CODE",
+          "FLAVOR",
+          "BUILD_TYPE",
+          "APPLICATION_ID",
+          "VERSION_NAME"
+        ]
+      },
+      "android/support/v17/leanback/R$transition": {
+        "androidx/leanback/R$transition": [
+          "lb_title_in",
+          "lb_browse_headers_out",
+          "lb_title_out",
+          "lb_browse_entrance_transition",
+          "lb_details_enter_transition",
+          "lb_vertical_grid_entrance_transition",
+          "lb_browse_headers_in"
+        ]
+      },
+      "android/support/design/R$drawable": {
+        "androidx/design/R$drawable": [
+          "navigation_empty_icon",
+          "design_bottom_navigation_item_background"
+        ]
+      },
+      "android/support/wear/widget/drawer/NestedScrollViewFlingWatcher": {
+        "androidx/wear/widget/drawer/NestedScrollViewFlingWatcher": [
+          "MAX_WAIT_TIME_MS"
+        ]
+      },
+      "android/support/v17/leanback/app/HeadersSupportFragment": {
+        "androidx/leanback/app/HeadersSupportFragment": [
+          "sHeaderPresenter",
+          "sLayoutChangeListener"
+        ]
+      },
+      "android/support/v17/leanback/widget/ShadowHelper": {
+        "androidx/leanback/widget/ShadowHelper": [
+          "sInstance"
+        ]
+      },
+      "android/support/percent/PercentLayoutHelper": {
+        "androidx/PercentLayoutHelper": [
+          "TAG",
+          "VERBOSE",
+          "DEBUG"
+        ]
+      },
+      "android/support/design/internal/BottomNavigationMenu": {
+        "androidx/design/internal/BottomNavigationMenu": [
+          "MAX_ITEM_COUNT"
+        ]
+      },
+      "android/support/design/internal/TextScale": {
+        "androidx/design/internal/TextScale": [
+          "PROPNAME_SCALE"
+        ]
+      },
+      "android/support/v17/leanback/widget/PlaybackTransportRowPresenter$BoundData": {
+        "androidx/leanback/widget/PlaybackTransportRowPresenter$BoundData": [
+          "adapter",
+          "presenter"
+        ]
+      },
+      "android/support/v13/view/inputmethod/EditorInfoCompat$EditorInfoCompatBaseImpl": {
+        "androidx/view/inputmethod/EditorInfoCompat$EditorInfoCompatBaseImpl": [
+          "CONTENT_MIME_TYPES_KEY"
+        ]
+      },
+      "android/support/v17/leanback/widget/ViewsStateBundle": {
+        "androidx/leanback/widget/ViewsStateBundle": [
+          "UNLIMITED",
+          "LIMIT_DEFAULT"
+        ]
+      },
+      "android/support/v4/content/ContextCompat": {
+        "androidx/content/ContextCompat": [
+          "sLock",
+          "TAG",
+          "sTempValue"
+        ]
+      },
+      "android/support/v4/util/SparseArrayCompat": {
+        "androidx/util/SparseArrayCompat": [
+          "DELETED"
+        ]
+      },
+      "android/support/transition/Styleable$VisibilityTransition": {
+        "androidx/transition/Styleable$VisibilityTransition": [
+          "TRANSITION_VISIBILITY_MODE"
+        ]
+      },
+      "android/support/v14/preference/MultiSelectListPreference$SavedState": {
+        "androidx/preference/MultiSelectListPreference$SavedState": [
+          "CREATOR",
+          "values"
+        ]
+      },
+      "android/support/v17/leanback/app/DetailsFragment$WaitEnterTransitionTimeout": {
+        "androidx/leanback/app/DetailsFragment$WaitEnterTransitionTimeout": [
+          "WAIT_ENTERTRANSITION_START"
+        ]
+      },
+      "android/support/text/emoji/MetadataListReader$OpenTypeReader": {
+        "androidx/text/emoji/MetadataListReader$OpenTypeReader": [
+          "UINT16_BYTE_COUNT",
+          "UINT32_BYTE_COUNT"
+        ]
+      },
+      "android/support/v7/preference/TwoStatePreference$SavedState": {
+        "androidx/preference/TwoStatePreference$SavedState": [
+          "checked",
+          "CREATOR"
+        ]
+      },
+      "android/support/v17/leanback/widget/Action": {
+        "androidx/leanback/widget/Action": [
+          "NO_ID"
+        ]
+      },
+      "android/support/v7/widget/Toolbar": {
+        "androidx/widget/Toolbar": [
+          "TAG"
+        ]
+      },
+      "android/support/v17/preference/LeanbackPreferenceDialogFragment": {
+        "androidx/leanback/preference/LeanbackPreferenceDialogFragment": [
+          "ARG_KEY"
+        ]
+      },
+      "android/support/v4/content/SharedPreferencesCompat$EditorCompat": {
+        "androidx/content/SharedPreferencesCompat$EditorCompat": [
+          "sInstance"
+        ]
+      },
+      "android/support/v17/leanback/widget/RowPresenter$ContainerViewHolder": {
+        "androidx/leanback/widget/RowPresenter$ContainerViewHolder": [
+          "view"
+        ]
+      },
+      "android/support/v7/app/MediaRouteChooserDialog$RouteComparator": {
+        "androidx/app/MediaRouteChooserDialog$RouteComparator": [
+          "sInstance"
+        ]
+      },
+      "android/support/v4/widget/PopupWindowCompat": {
+        "androidx/widget/PopupWindowCompat": [
+          "IMPL"
+        ]
+      },
+      "android/support/v4/widget/ImageViewCompat": {
+        "androidx/widget/ImageViewCompat": [
+          "IMPL"
+        ]
+      },
+      "android/support/v17/leanback/widget/GuidedActionsStylist$ViewHolder": {
+        "androidx/leanback/widget/GuidedActionsStylist$ViewHolder": [
+          "itemView"
+        ]
+      },
+      "android/support/v4/view/ActionProvider": {
+        "androidx/view/ActionProvider": [
+          "TAG"
+        ]
+      },
+      "android/support/v4/view/ViewConfigurationCompat": {
+        "androidx/view/ViewConfigurationCompat": [
+          "TAG",
+          "sGetScaledScrollFactorMethod"
+        ]
+      },
+      "android/support/customtabs/CustomTabsSession": {
+        "androidx/browser/customtabs/CustomTabsSession": [
+          "TAG"
+        ]
+      },
+      "android/support/v7/mediarouter/R$style": {
+        "androidx/mediarouter/R$style": [
+          "Theme_MediaRouter_LightControlPanel",
+          "Theme_MediaRouter_Light",
+          "Theme_MediaRouter_Light_DarkControlPanel",
+          "Theme_MediaRouter"
+        ]
+      },
+      "android/support/text/emoji/EmojiProcessor$CodepointIndexFinder": {
+        "androidx/text/emoji/EmojiProcessor$CodepointIndexFinder": [
+          "INVALID_INDEX"
+        ]
+      },
+      "android/support/v17/leanback/widget/NonOverlappingLinearLayoutWithForeground": {
+        "androidx/leanback/widget/NonOverlappingLinearLayoutWithForeground": [
+          "VERSION_M"
+        ]
+      },
+      "android/support/v4/view/ViewCompat$ViewCompatApi21Impl": {
+        "androidx/view/ViewCompat$ViewCompatApi21Impl": [
+          "sThreadLocalRect"
+        ]
+      },
+      "android/support/v4/app/NotificationCompat$DecoratedCustomViewStyle": {
+        "androidx/app/NotificationCompat$DecoratedCustomViewStyle": [
+          "MAX_ACTION_BUTTONS"
+        ]
+      },
+      "android/support/v7/widget/PagerSnapHelper": {
+        "androidx/widget/PagerSnapHelper": [
+          "MAX_SCROLL_ON_FLING_DURATION"
+        ]
+      },
+      "android/support/wear/widget/CircledImageView": {
+        "androidx/wear/widget/CircledImageView": [
+          "SQUARE_DIMEN_WIDTH",
+          "SQUARE_DIMEN_NONE",
+          "SQUARE_DIMEN_HEIGHT",
+          "ARGB_EVALUATOR"
+        ]
+      },
+      "android/support/v7/widget/RtlSpacingHelper": {
+        "androidx/widget/RtlSpacingHelper": [
+          "UNDEFINED"
+        ]
+      },
+      "android/support/v4/media/MediaBrowserServiceCompatApi26": {
+        "androidx/media/MediaBrowserServiceCompatApi26": [
+          "sResultFlags",
+          "TAG"
+        ]
+      },
+      "android/support/text/emoji/EmojiMetadata": {
+        "androidx/text/emoji/EmojiMetadata": [
+          "HAS_GLYPH_EXISTS",
+          "HAS_GLYPH_ABSENT",
+          "HAS_GLYPH_UNKNOWN",
+          "sMetadataItem"
+        ]
+      },
+      "android/support/mediacompat/R$integer": {
+        "androidx/mediacompat/R$integer": [
+          "cancel_button_image_alpha"
+        ]
+      },
+      "android/support/v7/content/res/AppCompatResources$ColorStateListCacheEntry": {
+        "androidx/content/res/AppCompatResources$ColorStateListCacheEntry": [
+          "value",
+          "configuration"
+        ]
+      },
+      "android/support/v17/leanback/widget/DetailsOverviewRowPresenter$ViewHolder": {
+        "androidx/leanback/widget/DetailsOverviewRowPresenter$ViewHolder": [
+          "view"
+        ]
+      },
+      "android/support/wear/R$color": {
+        "androidx/wear/R$color": [
+          "circular_progress_layout_background_color"
+        ]
+      },
+      "android/support/v17/leanback/widget/ItemAlignmentFacet": {
+        "androidx/leanback/widget/ItemAlignmentFacet": [
+          "ITEM_ALIGN_OFFSET_PERCENT_DISABLED"
+        ]
+      },
+      "android/support/wear/ambient/AmbientMode$AmbientController": {
+        "androidx/wear/ambient/AmbientMode$AmbientController": [
+          "TAG"
+        ]
+      },
+      "android/support/v13/view/inputmethod/InputConnectionCompat": {
+        "androidx/view/inputmethod/InputConnectionCompat": [
+          "INPUT_CONTENT_GRANT_READ_URI_PERMISSION",
+          "IMPL"
+        ]
+      },
+      "android/support/wear/widget/BezierSCurveInterpolator": {
+        "androidx/wear/widget/BezierSCurveInterpolator": [
+          "STEP_SIZE",
+          "VALUES",
+          "INSTANCE"
+        ]
+      },
+      "android/support/v4/graphics/PathParser": {
+        "androidx/graphics/PathParser": [
+          "LOGTAG"
+        ]
+      },
+      "android/support/v7/widget/RecyclerView$RecycledViewPool": {
+        "androidx/widget/RecyclerView$RecycledViewPool": [
+          "DEFAULT_MAX_SCRAP"
+        ]
+      },
+      "android/support/v7/media/MediaRouteSelector": {
+        "androidx/media/MediaRouteSelector": [
+          "KEY_CONTROL_CATEGORIES",
+          "EMPTY"
+        ]
+      },
+      "android/support/transition/PropertyValuesHolderUtils": {
+        "androidx/transition/PropertyValuesHolderUtils": [
+          "IMPL"
+        ]
+      },
+      "android/support/graphics/drawable/VectorDrawableCompat$VFullPath": {
+        "androidx/graphics/drawable/VectorDrawableCompat$VFullPath": [
+          "FILL_TYPE_WINDING"
+        ]
+      },
+      "android/support/mediacompat/R$color": {
+        "androidx/mediacompat/R$color": [
+          "notification_material_background_media_default_color"
+        ]
+      },
+      "android/support/v17/leanback/widget/RowHeaderPresenter$ViewHolder": {
+        "androidx/leanback/widget/RowHeaderPresenter$ViewHolder": [
+          "view"
+        ]
+      },
+      "android/support/design/widget/CollapsingToolbarLayout": {
+        "androidx/design/widget/CollapsingToolbarLayout": [
+          "DEFAULT_SCRIM_ANIMATION_DURATION"
+        ]
+      },
+      "android/support/v17/leanback/app/BrandedSupportFragment": {
+        "androidx/leanback/app/BrandedSupportFragment": [
+          "TITLE_SHOW"
+        ]
+      },
+      "android/support/design/internal/BottomNavigationPresenter$SavedState": {
+        "androidx/design/internal/BottomNavigationPresenter$SavedState": [
+          "CREATOR",
+          "selectedItemId"
+        ]
+      },
+      "android/support/v4/app/BackStackState": {
+        "androidx/app/BackStackState": [
+          "CREATOR"
+        ]
+      },
+      "android/support/v4/provider/FontsContractCompat$FontFamilyResult": {
+        "androidx/provider/FontsContractCompat$FontFamilyResult": [
+          "STATUS_WRONG_CERTIFICATES",
+          "STATUS_OK",
+          "STATUS_UNEXPECTED_DATA_PROVIDED"
+        ]
+      },
+      "android/support/v4/app/FragmentManagerState": {
+        "androidx/app/FragmentManagerState": [
+          "CREATOR"
+        ]
+      },
+      "android/support/media/ExifInterface$Rational": {
+        "androidx/media/ExifInterface$Rational": [
+          "denominator",
+          "numerator"
+        ]
+      },
+      "android/support/text/emoji/TypefaceEmojiSpan": {
+        "androidx/text/emoji/TypefaceEmojiSpan": [
+          "sDebugPaint"
+        ]
+      },
+      "android/support/design/widget/FloatingActionButton$Behavior": {
+        "androidx/design/widget/FloatingActionButton$Behavior": [
+          "AUTO_HIDE_DEFAULT"
+        ]
+      },
+      "android/support/graphics/drawable/ArgbEvaluator": {
+        "androidx/graphics/drawable/ArgbEvaluator": [
+          "sInstance"
+        ]
+      },
+      "android/support/v4/util/ContainerHelpers": {
+        "androidx/util/ContainerHelpers": [
+          "EMPTY_INTS",
+          "EMPTY_LONGS",
+          "EMPTY_OBJECTS"
+        ]
+      },
+      "android/support/v7/media/MediaRouterJellybeanMr1": {
+        "androidx/media/MediaRouterJellybeanMr1": [
+          "TAG"
+        ]
+      },
+      "android/support/v17/leanback/widget/ActionPresenterSelector$ActionViewHolder": {
+        "androidx/leanback/widget/ActionPresenterSelector$ActionViewHolder": [
+          "view"
+        ]
+      },
+      "android/support/v4/app/FragmentState": {
+        "androidx/app/FragmentState": [
+          "CREATOR"
+        ]
+      },
+      "android/support/v7/widget/LinearSnapHelper": {
+        "androidx/widget/LinearSnapHelper": [
+          "INVALID_DISTANCE"
+        ]
+      },
+      "android/support/v17/leanback/widget/FullWidthDetailsOverviewRowPresenter$ViewHolder": {
+        "androidx/leanback/widget/FullWidthDetailsOverviewRowPresenter$ViewHolder": [
+          "view"
+        ]
+      },
+      "android/support/constraint/solver/widgets/ConstraintAnchor$Strength": {
+        "androidx/constraint/solver/widgets/ConstraintAnchor$Strength": [
+          "STRONG"
+        ]
+      },
+      "android/support/design/widget/HeaderBehavior": {
+        "androidx/design/widget/HeaderBehavior": [
+          "INVALID_POINTER"
+        ]
+      },
+      "android/support/v7/widget/LinearLayoutManager$SavedState": {
+        "androidx/widget/LinearLayoutManager$SavedState": [
+          "CREATOR"
+        ]
+      },
+      "android/support/v7/widget/GridLayout$Interval": {
+        "androidx/widget/GridLayout$Interval": [
+          "min",
+          "max"
+        ]
+      },
+      "android/support/v4/widget/SlidingPaneLayout$SavedState": {
+        "androidx/widget/SlidingPaneLayout$SavedState": [
+          "CREATOR",
+          "isOpen"
+        ]
+      },
+      "android/support/v17/leanback/widget/DetailsOverviewLogoPresenter$ViewHolder": {
+        "androidx/leanback/widget/DetailsOverviewLogoPresenter$ViewHolder": [
+          "view"
+        ]
+      },
+      "android/support/v4/media/session/MediaSessionCompatApi24": {
+        "androidx/media/session/MediaSessionCompatApi24": [
+          "TAG"
+        ]
+      },
+      "android/support/v7/app/MediaRouteActionProvider": {
+        "androidx/app/MediaRouteActionProvider": [
+          "TAG"
+        ]
+      },
+      "android/support/v4/media/session/MediaButtonReceiver": {
+        "androidx/media/session/MediaButtonReceiver": [
+          "TAG"
+        ]
+      },
+      "android/support/v4/content/AsyncTaskLoader$LoadTask": {
+        "androidx/content/AsyncTaskLoader$LoadTask": [
+          "waiting"
+        ]
+      },
+      "android/support/v4/media/session/MediaSessionCompatApi21": {
+        "androidx/media/session/MediaSessionCompatApi21": [
+          "TAG"
+        ]
+      },
+      "android/support/design/internal/NavigationMenuPresenter$ViewHolder": {
+        "androidx/design/internal/NavigationMenuPresenter$ViewHolder": [
+          "itemView"
+        ]
+      },
+      "android/support/v7/widget/AppCompatButton": {
+        "androidx/widget/AppCompatButton": [
+          "PLATFORM_SUPPORTS_AUTOSIZE"
+        ]
+      },
+      "android/support/v17/leanback/widget/CheckableImageView": {
+        "androidx/leanback/widget/CheckableImageView": [
+          "CHECKED_STATE_SET"
+        ]
+      },
+      "android/support/v4/view/animation/FastOutLinearInInterpolator": {
+        "androidx/view/animation/FastOutLinearInInterpolator": [
+          "VALUES"
+        ]
+      },
+      "android/support/v7/app/MediaRouteDiscoveryFragment": {
+        "androidx/app/MediaRouteDiscoveryFragment": [
+          "ARGUMENT_SELECTOR"
+        ]
+      },
+      "android/support/v4/graphics/TypefaceCompatApi21Impl": {
+        "androidx/graphics/TypefaceCompatApi21Impl": [
+          "TAG"
+        ]
+      },
+      "android/support/v17/leanback/widget/FocusHighlightHelper$BrowseItemFocusHighlight": {
+        "androidx/leanback/widget/FocusHighlightHelper$BrowseItemFocusHighlight": [
+          "DURATION_MS"
+        ]
+      },
+      "android/support/transition/ImageViewUtilsApi21": {
+        "androidx/transition/ImageViewUtilsApi21": [
+          "TAG",
+          "sAnimateTransformMethodFetched",
+          "sAnimateTransformMethod"
+        ]
+      },
+      "android/support/v7/widget/RecyclerView$SmoothScroller$Action": {
+        "androidx/widget/RecyclerView$SmoothScroller$Action": [
+          "UNDEFINED_DURATION"
+        ]
+      },
+      "android/support/v4/media/session/PlaybackStateCompat$CustomAction": {
+        "androidx/media/session/PlaybackStateCompat$CustomAction": [
+          "CREATOR"
+        ]
+      },
+      "android/support/design/R$integer": {
+        "androidx/design/R$integer": [
+          "app_bar_elevation_anim_duration"
+        ]
+      },
+      "android/support/v4/provider/DocumentFile": {
+        "androidx/provider/DocumentFile": [
+          "TAG"
+        ]
+      },
+      "android/support/v7/widget/StaggeredGridLayoutManager$SavedState": {
+        "androidx/widget/StaggeredGridLayoutManager$SavedState": [
+          "CREATOR"
+        ]
+      },
+      "android/support/animation/FlingAnimation$DragForce": {
+        "androidx/animation/FlingAnimation$DragForce": [
+          "VELOCITY_THRESHOLD_MULTIPLIER",
+          "DEFAULT_FRICTION"
+        ]
+      },
+      "android/support/v17/leanback/transition/ParallaxTransition": {
+        "androidx/leanback/transition/ParallaxTransition": [
+          "sInterpolator"
+        ]
+      },
+      "android/support/v4/app/NotificationCompat$MessagingStyle": {
+        "androidx/app/NotificationCompat$MessagingStyle": [
+          "MAXIMUM_RETAINED_MESSAGES"
+        ]
+      },
+      "android/support/v7/widget/RecyclerView$SavedState": {
+        "androidx/widget/RecyclerView$SavedState": [
+          "CREATOR"
+        ]
+      },
+      "android/support/v17/leanback/widget/ResizingTextView": {
+        "androidx/leanback/widget/ResizingTextView": [
+          "TRIGGER_MAX_LINES"
+        ]
+      },
+      "android/support/v7/widget/StaggeredGridLayoutManager$LazySpanLookup": {
+        "androidx/widget/StaggeredGridLayoutManager$LazySpanLookup": [
+          "MIN_SIZE"
+        ]
+      },
+      "android/support/v4/media/session/MediaSessionCompat$MediaSessionImplBase": {
+        "androidx/media/session/MediaSessionCompat$MediaSessionImplBase": [
+          "RCC_PLAYSTATE_NONE"
+        ]
+      },
+      "android/support/v4/util/LongSparseArray": {
+        "androidx/util/LongSparseArray": [
+          "DELETED"
+        ]
+      },
+      "android/support/v4/view/accessibility/AccessibilityNodeProviderCompat": {
+        "androidx/view/accessibility/AccessibilityNodeProviderCompat": [
+          "HOST_VIEW_ID"
+        ]
+      },
+      "android/support/v4/view/AsyncLayoutInflater": {
+        "androidx/view/AsyncLayoutInflater": [
+          "TAG"
+        ]
+      },
+      "android/support/graphics/drawable/VectorDrawableCompat$VPathRenderer": {
+        "androidx/graphics/drawable/VectorDrawableCompat$VPathRenderer": [
+          "IDENTITY_MATRIX"
+        ]
+      },
+      "android/support/v13/view/DragAndDropPermissionsCompat": {
+        "androidx/view/DragAndDropPermissionsCompat": [
+          "IMPL"
+        ]
+      },
+      "android/support/v4/media/MediaBrowserCompatApi21": {
+        "androidx/media/MediaBrowserCompatApi21": [
+          "NULL_MEDIA_ITEM_ID"
+        ]
+      },
+      "android/support/v4/view/animation/FastOutSlowInInterpolator": {
+        "androidx/view/animation/FastOutSlowInInterpolator": [
+          "VALUES"
+        ]
+      },
+      "android/support/v7/widget/AppCompatProgressBarHelper": {
+        "androidx/widget/AppCompatProgressBarHelper": [
+          "TINT_ATTRS"
+        ]
+      },
+      "android/support/v4/media/ParceledListSliceAdapterApi21": {
+        "androidx/media/ParceledListSliceAdapterApi21": [
+          "sConstructor"
+        ]
+      },
+      "android/support/compat/R$integer": {
+        "androidx/compat/R$integer": [
+          "status_bar_notification_info_maxnum"
+        ]
+      },
+      "android/support/v14/preference/EditTextPreferenceDialogFragment": {
+        "androidx/preference/EditTextPreferenceDialogFragment": [
+          "SAVE_STATE_TEXT"
+        ]
+      },
+      "android/support/wear/R$array": {
+        "androidx/wear/R$array": [
+          "circular_progress_layout_color_scheme_colors"
+        ]
+      },
+      "android/support/v7/cardview/R$style": {
+        "androidx/cardview/R$style": [
+          "CardView"
+        ]
+      },
+      "android/support/design/internal/ParcelableSparseArray": {
+        "androidx/design/internal/ParcelableSparseArray": [
+          "CREATOR"
+        ]
+      },
+      "android/support/design/widget/CircularBorderDrawable": {
+        "androidx/design/widget/CircularBorderDrawable": [
+          "DRAW_STROKE_WIDTH_MULTIPLE"
+        ]
+      },
+      "android/support/transition/ObjectAnimatorUtils": {
+        "androidx/transition/ObjectAnimatorUtils": [
+          "IMPL"
+        ]
+      },
+      "android/support/v4/app/ActivityCompat": {
+        "androidx/app/ActivityCompat": [
+          "sDelegate"
+        ]
+      },
+      "android/support/wear/internal/widget/drawer/MultiPageUi": {
+        "androidx/wear/internal/widget/drawer/MultiPageUi": [
+          "TAG"
+        ]
+      },
+      "android/support/transition/ViewOverlayApi14$OverlayViewGroup": {
+        "androidx/transition/ViewOverlayApi14$OverlayViewGroup": [
+          "sInvalidateChildInParentFastMethod"
+        ]
+      },
+      "android/support/v4/text/TextDirectionHeuristicsCompat$TextDirectionHeuristicLocale": {
+        "androidx/text/TextDirectionHeuristicsCompat$TextDirectionHeuristicLocale": [
+          "INSTANCE"
+        ]
+      },
+      "android/support/v7/preference/Preference": {
+        "androidx/preference/Preference": [
+          "DEFAULT_ORDER"
+        ]
+      },
+      "android/support/v4/graphics/drawable/RoundedBitmapDrawable": {
+        "androidx/graphics/drawable/RoundedBitmapDrawable": [
+          "DEFAULT_PAINT_FLAGS"
+        ]
+      },
+      "android/support/design/widget/TabLayout$Tab": {
+        "androidx/design/widget/TabLayout$Tab": [
+          "INVALID_POSITION"
+        ]
+      },
+      "android/support/v7/widget/SnapHelper": {
+        "androidx/widget/SnapHelper": [
+          "MILLISECONDS_PER_INCH"
+        ]
+      },
+      "android/support/v7/widget/AbsActionBarView": {
+        "androidx/widget/AbsActionBarView": [
+          "FADE_DURATION"
+        ]
+      },
+      "android/support/v17/leanback/graphics/FitWidthBitmapDrawable": {
+        "androidx/leanback/graphics/FitWidthBitmapDrawable": [
+          "PROPERTY_VERTICAL_OFFSET"
+        ]
+      },
+      "android/support/v4/media/session/MediaSessionCompat$ResultReceiverWrapper": {
+        "androidx/media/session/MediaSessionCompat$ResultReceiverWrapper": [
+          "CREATOR"
+        ]
+      },
+      "android/support/v7/view/SupportMenuInflater$InflatedOnMenuItemClickListener": {
+        "androidx/view/SupportMenuInflater$InflatedOnMenuItemClickListener": [
+          "PARAM_TYPES"
+        ]
+      },
+      "android/support/v4/widget/EdgeEffectCompat": {
+        "androidx/widget/EdgeEffectCompat": [
+          "IMPL"
+        ]
+      },
+      "android/support/v7/app/MediaRouteDialogFactory": {
+        "androidx/app/MediaRouteDialogFactory": [
+          "sDefault"
+        ]
+      },
+      "android/support/v4/app/Fragment$SavedState": {
+        "androidx/app/Fragment$SavedState": [
+          "CREATOR"
+        ]
+      },
+      "android/support/transition/Styleable$TransitionSet": {
+        "androidx/transition/Styleable$TransitionSet": [
+          "TRANSITION_ORDERING"
+        ]
+      },
+      "android/support/v7/preference/PreferenceGroupAdapter": {
+        "androidx/preference/PreferenceGroupAdapter": [
+          "TAG"
+        ]
+      },
+      "android/support/v7/view/menu/MenuPopupHelper": {
+        "androidx/view/menu/MenuPopupHelper": [
+          "TOUCH_EPICENTER_SIZE_DP"
+        ]
+      },
+      "android/support/v4/app/NotificationCompat$Builder": {
+        "androidx/app/NotificationCompat$Builder": [
+          "MAX_CHARSEQUENCE_LENGTH"
+        ]
+      }
+    }
+  },
+  "proGuardMap": {
+    "rules": {}
+  }
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/config/ConfigParserTest.kt b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/config/ConfigParserTest.kt
new file mode 100644
index 0000000..4a03ef3
--- /dev/null
+++ b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/config/ConfigParserTest.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.core.config
+
+import com.google.common.truth.Truth
+import org.junit.Test
+
+class ConfigParserTest {
+
+    @Test fun parseConfig_validInput() {
+        val confStr =
+                "{\n" +
+                "    restrictToPackagePrefixes: [\"android/support/\"],\n" +
+                "    # Sample comment \n" +
+                "    rules: [\n" +
+                "        {\n" +
+                "            from: \"android/support/v14/preferences/(.*)\",\n" +
+                "            to: \"android/jetpack/prefs/main/{0}\"\n" +
+                "        },\n" +
+                "        {\n" +
+                "            from: \"android/support/v14/preferences/(.*)\",\n" +
+                "            to: \"android/jetpack/prefs/main/{0}\",\n" +
+                "            fieldSelectors: [\"dialog_(.*)\"]\n" +
+                "        }\n" +
+                "    ],\n" +
+                "    pomRules: [\n" +
+                "        {\n" +
+                "            from: {groupId: \"g\", artifactId: \"a\", version: \"1.0\"},\n" +
+                "            to: [\n" +
+                "                {groupId: \"g\", artifactId: \"a\", version: \"2.0\"} \n" +
+                "            ]\n" +
+                "        }\n" +
+                "    ]\n" +
+                "}"
+
+        val config = ConfigParser.parseFromString(confStr)
+
+        Truth.assertThat(config).isNotNull()
+        Truth.assertThat(config!!.restrictToPackagePrefixes[0]).isEqualTo("android/support/")
+        Truth.assertThat(config.rewriteRules.size).isEqualTo(2)
+    }
+}
+
diff --git a/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/map/MapGenerationTest.kt b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/map/MapGenerationTest.kt
new file mode 100644
index 0000000..e7f8570
--- /dev/null
+++ b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/map/MapGenerationTest.kt
@@ -0,0 +1,334 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.core.map
+
+import android.support.tools.jetifier.core.config.Config
+import android.support.tools.jetifier.core.rules.JavaField
+import android.support.tools.jetifier.core.rules.JavaType
+import android.support.tools.jetifier.core.rules.RewriteRule
+import android.support.tools.jetifier.core.transform.proguard.ProGuardTypesMap
+import com.google.common.truth.Truth
+import org.junit.Test
+
+
+class MapGenerationTest {
+
+    @Test fun fromOneType_toOneType() {
+        ScanTester
+            .testThatRules(
+                RewriteRule("android/support/v7/(.*)", "android/test/{0}")
+            )
+            .withAllowedPrefixes(
+                "android/support/"
+            )
+            .forGivenTypes(
+                JavaType("android/support/v7/pref/Preference")
+            )
+            .mapInto(
+                types = mapOf(
+                    "android/support/v7/pref/Preference" to "android/test/pref/Preference"
+                ),
+                fields = mapOf(
+                )
+            )
+            .andIsComplete()
+    }
+
+    @Test fun fromTwoTypes_toOneType_prefixRespected() {
+        ScanTester
+            .testThatRules(
+                RewriteRule("android/support/v7/(.*)", "android/test/{0}"),
+                RewriteRule("android/support/v14/(.*)", "android/test/{0}")
+            )
+            .withAllowedPrefixes(
+                "android/support/v7/"
+            )
+            .forGivenTypes(
+                JavaType("android/support/v7/pref/Preference"),
+                JavaType("android/support/v14/pref/PreferenceDialog")
+            )
+            .mapInto(
+                types = mapOf(
+                    "android/support/v7/pref/Preference" to "android/test/pref/Preference"
+                ),
+                fields = mapOf(
+                )
+            )
+            .andIsComplete()
+    }
+
+    @Test fun fromTwoTypes_toTwoTypes_distinctRules() {
+        ScanTester
+            .testThatRules(
+                RewriteRule("android/support/v7/(.*)", "android/test/{0}"),
+                RewriteRule("android/support/v14/(.*)", "android/test/{0}")
+            )
+            .withAllowedPrefixes(
+                "android/support/v7/",
+                "android/support/v14/"
+            )
+            .forGivenTypes(
+                JavaType("android/support/v7/pref/Preference"),
+                JavaType("android/support/v14/pref/PreferenceDialog")
+            )
+            .mapInto(
+                types = mapOf(
+                    "android/support/v7/pref/Preference" to "android/test/pref/Preference",
+                    "android/support/v14/pref/PreferenceDialog" to "android/test/pref/PreferenceDialog"
+                ),
+                fields = mapOf(
+                )
+            )
+            .andIsComplete()
+    }
+
+    @Test fun fromTwoTypes_toTwoTypes_respectsOrder() {
+        ScanTester
+            .testThatRules(
+                RewriteRule("android/support/v14/(.*)", "android/test/{0}"),
+                RewriteRule("android/support/(.*)", "android/fallback/{0}")
+            )
+            .withAllowedPrefixes(
+                "android/support/"
+            )
+            .forGivenTypes(
+                JavaType("android/support/v7/pref/Preference"),
+                JavaType("android/support/v14/pref/PreferenceDialog")
+            )
+            .mapInto(
+                types = mapOf(
+                    "android/support/v7/pref/Preference" to "android/fallback/v7/pref/Preference",
+                    "android/support/v14/pref/PreferenceDialog" to "android/test/pref/PreferenceDialog"
+                ),
+                fields = mapOf(
+                )
+            )
+            .andIsComplete()
+    }
+
+    @Test fun mapTwoFields_usingOneTypeRule() {
+        ScanTester
+            .testThatRules(
+                RewriteRule("android/support/v7/(.*)", "android/test/{0}")
+            )
+            .withAllowedPrefixes(
+                "android/support/"
+            )
+            .forGivenFields(
+                JavaField("android/support/v7/pref/Preference", "count"),
+                JavaField("android/support/v7/pref/Preference", "min")
+            )
+            .mapInto(
+                types = mapOf(
+                ),
+                fields = mapOf(
+                    "android/support/v7/pref/Preference" to mapOf(
+                        "android/test/pref/Preference" to listOf(
+                            "count",
+                            "min"
+                        )
+                    )
+                )
+            )
+            .andIsComplete()
+    }
+
+    @Test fun mapFieldInInnerClass_usingOneTypeRule() {
+        ScanTester
+            .testThatRules(
+                RewriteRule("android/support/v7/(.*)", "android/test/{0}")
+            )
+            .withAllowedPrefixes(
+                "android/support/"
+            )
+            .forGivenFields(
+                JavaField("android/support/v7/pref/R\$attr", "border")
+            )
+            .mapInto(
+                types = mapOf(
+                ),
+                fields = mapOf(
+                    "android/support/v7/pref/R\$attr" to mapOf(
+                        "android/test/pref/R\$attr" to listOf(
+                            "border"
+                        )
+                    )
+                )
+            )
+            .andIsComplete()
+    }
+
+    @Test fun mapPrivateFields_shouldIgnore() {
+        ScanTester
+            .testThatRules(
+                RewriteRule("android/support/v7/(.*)", "android/test/{0}")
+            )
+            .withAllowedPrefixes(
+                "android/support/"
+            )
+            .forGivenFields(
+                JavaField("android/support/v7/pref/Preference", "mCount"),
+                JavaField("android/support/v7/pref/Preference", "this$0")
+            )
+            .mapInto(
+                types = mapOf(
+                ),
+                fields = mapOf(
+                )
+            )
+            .andIsComplete()
+    }
+
+    @Test fun mapType_usingFieldSelector_shouldNotApply() {
+        ScanTester
+            .testThatRules(
+                RewriteRule("android/support/v7/(.*)", "android/test/{0}", listOf("count"))
+            )
+            .withAllowedPrefixes(
+                "android/support/"
+            )
+            .forGivenTypes(
+                JavaType("android/support/v7/pref/Preference")
+            )
+            .mapInto(
+                types = mapOf(
+                    "android/support/v7/pref/Preference" to "android/support/v7/pref/Preference"
+                ),
+                fields = mapOf(
+                )
+            )
+            .andIsNotComplete()
+    }
+
+    @Test fun mapField_noApplicableRule() {
+        ScanTester
+            .testThatRules(
+                RewriteRule("android/support/v7/(.*)", "android/test/{0}", listOf("count2"))
+            )
+            .withAllowedPrefixes(
+                "android/support/"
+            )
+            .forGivenFields(
+                JavaField("android/support/v7/pref/Preference", "count")
+            )
+            .mapInto(
+                types = mapOf(
+                ),
+                fields = mapOf(
+                    "android/support/v7/pref/Preference" to mapOf(
+                        "android/support/v7/pref/Preference" to listOf(
+                            "count"
+                        )
+                    )
+                )
+            )
+            .andIsNotComplete()
+    }
+
+    @Test fun mapTwoFields_usingTwoFieldsSelectors() {
+        ScanTester
+            .testThatRules(
+                RewriteRule("android/support/v7/(.*)", "android/test/{0}", listOf("count")),
+                RewriteRule("android/support/v7/(.*)", "android/test2/{0}", listOf("size"))
+            )
+            .withAllowedPrefixes(
+                "android/support/"
+            )
+            .forGivenFields(
+                JavaField("android/support/v7/pref/Preference", "count"),
+                JavaField("android/support/v7/pref/Preference", "size")
+            )
+            .mapInto(
+                types = mapOf(
+                ),
+                fields = mapOf(
+                    "android/support/v7/pref/Preference" to mapOf(
+                        "android/test/pref/Preference" to listOf(
+                            "count"
+                        ),
+                        "android/test2/pref/Preference" to listOf(
+                            "size"
+                        )
+                    )
+                )
+            )
+            .andIsComplete()
+    }
+
+
+    object ScanTester {
+
+        fun testThatRules(vararg rules: RewriteRule) = Step1(rules.toList())
+
+
+        class Step1(val rules: List<RewriteRule>) {
+
+            fun withAllowedPrefixes(vararg prefixes: String) = Step2(rules, prefixes.toList())
+
+
+            class Step2(val rules: List<RewriteRule>, val prefixes: List<String>) {
+
+                private val allTypes: MutableList<JavaType> = mutableListOf()
+                private val allFields: MutableList<JavaField> = mutableListOf()
+                private var wasMapIncomplete = false
+
+
+                fun forGivenTypes(vararg types: JavaType) : Step2 {
+                    allTypes.addAll(types)
+                    return this
+                }
+
+                fun forGivenFields(vararg fields: JavaField) : Step2 {
+                    allFields.addAll(fields)
+                    return this
+                }
+
+                fun mapInto(types: Map<String, String>,
+                            fields: Map<String, Map<String, List<String>>>) : Step2 {
+                    val config = Config(
+                        restrictToPackagePrefixes = prefixes,
+                        rewriteRules = rules,
+                        pomRewriteRules = emptyList(),
+                        typesMap = TypesMap.EMPTY,
+                        proGuardMap = ProGuardTypesMap.EMPTY)
+                    val scanner = MapGeneratorRemapper(config)
+
+                    allTypes.forEach { scanner.rewriteType(it) }
+                    allFields.forEach { scanner.rewriteField(it) }
+
+                    val typesMap = scanner.createTypesMap().toJson()
+                    wasMapIncomplete = scanner.isMapNotComplete
+
+                    Truth.assertThat(typesMap.types).containsExactlyEntriesIn(types)
+                    Truth.assertThat(typesMap.fields).containsExactlyEntriesIn(fields)
+                    return this
+                }
+
+                fun andIsNotComplete() {
+                    Truth.assertThat(wasMapIncomplete).isTrue()
+                }
+
+                fun andIsComplete() {
+                    Truth.assertThat(wasMapIncomplete).isFalse()
+                }
+            }
+
+        }
+
+    }
+}
+
diff --git a/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/RewriteRuleTest.kt b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/RewriteRuleTest.kt
new file mode 100644
index 0000000..ca6288d
--- /dev/null
+++ b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/RewriteRuleTest.kt
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.core.transform
+
+import android.support.tools.jetifier.core.rules.JavaField
+import android.support.tools.jetifier.core.rules.JavaType
+import android.support.tools.jetifier.core.rules.RewriteRule
+import com.google.common.truth.Truth
+import org.junit.Test
+
+
+class RewriteRuleTest {
+
+    @Test fun noRegEx_shouldRewrite() {
+        RuleTester
+            .testThatRule("A/B", "A/C")
+            .rewritesType("A/B")
+            .into("A/C")
+    }
+
+    @Test fun noRegEx_underscore_shouldRewrite() {
+        RuleTester
+            .testThatRule("A/B_B", "A/C")
+            .rewritesType("A/B_B")
+            .into("A/C")
+    }
+
+    @Test fun groupRegEx_shouldRewrite() {
+        RuleTester
+            .testThatRule("A/B/(.*)", "A/{0}")
+            .rewritesType("A/B/C/D")
+            .into("A/C/D")
+    }
+
+    @Test fun groupRegEx__innerClass_shouldRewrite() {
+        RuleTester
+            .testThatRule("A/B/(.*)", "A/{0}")
+            .rewritesType("A/B/C\$D")
+            .into("A/C\$D")
+    }
+
+    @Test fun fieldRule_noRegEx_shouldRewrite() {
+        RuleTester
+            .testThatRule("A/B", "A/C")
+            .withFieldSelector("MyField")
+            .rewritesField("A/B", "MyField")
+            .into("A/C", "MyField")
+    }
+
+    @Test fun fieldRule_innerClass_groupRegEx_shouldRewrite() {
+        RuleTester
+            .testThatRule("A/B$(.*)", "A/C\${0}")
+            .rewritesType("A/B\$D")
+            .into("A/C\$D")
+    }
+
+    @Test fun noFieldRule_shouldRewriteEvenWithField() {
+        RuleTester
+            .testThatRule("A/B", "A/C")
+            .rewritesField("A/B", "test")
+            .into("A/C", "test")
+    }
+
+
+    object RuleTester {
+
+        fun testThatRule(from: String, to: String) = RuleTesterStep1(from, to)
+
+        class RuleTesterStep1(val from: String, val to: String) {
+
+            val fieldSelectors: MutableList<String> = mutableListOf()
+
+            fun withFieldSelector(input: String) : RuleTesterStep1 {
+                fieldSelectors.add(input)
+                return this
+            }
+
+            fun rewritesField(inputType: String, inputField: String)
+                    = RuleTesterFinalFieldStep(from, to, inputType, inputField, fieldSelectors)
+
+            fun rewritesType(inputType: String)
+                    = RuleTesterFinalTypeStep(from, to, inputType, fieldSelectors)
+        }
+
+        class RuleTesterFinalFieldStep(val fromType: String,
+                                       val toType: String,
+                                       val inputType: String,
+                                       val inputField: String,
+                                       val fieldSelectors: List<String>) {
+
+            fun into(expectedTypeName: String, expectedFieldName: String) {
+                val fieldRule = RewriteRule(fromType, toType, fieldSelectors)
+                val result = fieldRule.apply(JavaField(inputType, inputField))
+                Truth.assertThat(result).isNotNull()
+
+                Truth.assertThat(result!!.owner.fullName).isEqualTo(expectedTypeName)
+                Truth.assertThat(result.name).isEqualTo(expectedFieldName)
+            }
+
+        }
+
+        class RuleTesterFinalTypeStep(val fromType: String,
+                                      val toType: String,
+                                      val inputType: String,
+                                      val fieldSelectors: List<String>) {
+
+            fun into(expectedResult: String) {
+                val fieldRule = RewriteRule(fromType, toType, fieldSelectors)
+                val result = fieldRule.apply(JavaType(inputType))
+                Truth.assertThat(result).isNotNull()
+
+                Truth.assertThat(result).isNotNull()
+                Truth.assertThat(result!!.fullName).isEqualTo(expectedResult)
+            }
+
+        }
+    }
+
+}
+
diff --git a/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/pom/PomDocumentTest.kt b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/pom/PomDocumentTest.kt
new file mode 100644
index 0000000..d55687f
--- /dev/null
+++ b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/pom/PomDocumentTest.kt
@@ -0,0 +1,426 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.core.transform.pom
+
+import android.support.tools.jetifier.core.archive.ArchiveFile
+import com.google.common.truth.Truth
+import org.junit.Test
+import java.nio.charset.StandardCharsets
+import java.nio.file.Paths
+
+class PomDocumentTest {
+
+    @Test fun pom_noRules_noChange() {
+        testRewriteToTheSame(
+            givenAndExpectedXml =
+            "  <dependencies>\n" +
+            "    <dependency>\n" +
+            "      <groupId>supportGroup</groupId>\n" +
+            "      <artifactId>supportArtifact</artifactId>\n" +
+            "      <version>4.0</version>\n" +
+            "      <type>jar</type>\n" +
+            "      <scope>test</scope>\n" +
+            "      <optional>true</optional>\n" +
+            "    </dependency>\n" +
+            "  </dependencies>",
+            rules = listOf()
+        )
+    }
+
+    @Test fun pom_oneRule_shouldApply() {
+        testRewrite(
+            givenXml =
+            "  <dependencies>\n" +
+            "    <dependency>\n" +
+            "      <groupId>supportGroup</groupId>\n" +
+            "      <artifactId>supportArtifact</artifactId>\n" +
+            "      <version>4.0</version>\n" +
+            "    </dependency>\n" +
+            "    <dependency>\n" +
+            "      <systemPath>test/test</systemPath>\n" +
+            "    </dependency>\n" +
+            "  </dependencies>",
+            expectedXml =
+            "  <dependencies>\n" +
+            "    <dependency>\n" +
+            "      <groupId>testGroup</groupId>\n" +
+            "      <artifactId>testArtifact</artifactId>\n" +
+            "      <version>1.0</version>\n" +
+            "    </dependency>\n" +
+            "    <dependency>\n" +
+            "      <systemPath>test/test</systemPath>\n" +
+            "    </dependency>\n" +
+            "  </dependencies>",
+            rules = listOf(
+                PomRewriteRule(
+                    PomDependency(
+                        groupId = "supportGroup", artifactId = "supportArtifact",
+                        version =  "4.0"),
+                    listOf(
+                        PomDependency(
+                            groupId = "testGroup", artifactId = "testArtifact",
+                            version = "1.0")
+                    )
+                )
+            )
+        )
+    }
+
+    @Test fun pom_oneRule_shouldSkipTestScopedRule() {
+        testRewriteToTheSame(
+            givenAndExpectedXml =
+            "  <dependencies>\n" +
+            "    <dependency>\n" +
+            "      <groupId>supportGroup</groupId>\n" +
+            "      <artifactId>supportArtifact</artifactId>\n" +
+            "      <version>4.0</version>\n" +
+            "      <scope>test</scope>\n" +
+            "    </dependency>\n" +
+            "  </dependencies>",
+            rules = listOf(
+                PomRewriteRule(
+                    PomDependency(
+                        groupId = "supportGroup", artifactId = "supportArtifact",
+                        version =  "4.0"),
+                    listOf(
+                        PomDependency(
+                            groupId = "testGroup", artifactId = "testArtifact",
+                            version = "1.0")
+                    )
+                )
+            )
+        )
+    }
+
+    @Test fun pom_oneRule_notApplicable() {
+        testRewriteToTheSame(
+            givenAndExpectedXml =
+            "  <dependencies>\n" +
+            "    <dependency>\n" +
+            "      <groupId>supportGroup</groupId>\n" +
+            "      <artifactId>supportArtifact</artifactId>\n" +
+            "      <version>4.0</version>\n" +
+            "    </dependency>\n" +
+            "  </dependencies>",
+            rules = listOf(
+                PomRewriteRule(
+                    PomDependency(
+                        groupId = "supportGroup", artifactId = "supportArtifact2",
+                        version =  "4.0"),
+                    listOf(
+                        PomDependency(
+                            groupId = "testGroup", artifactId = "testArtifact",
+                            version = "1.0")
+                    )
+                )
+            )
+        )
+    }
+
+    @Test fun pom_oneRule_appliedForEachType() {
+        testRewrite(
+            givenXml =
+            "  <dependencies>\n" +
+            "    <dependency>\n" +
+            "      <groupId>supportGroup</groupId>\n" +
+            "      <artifactId>supportArtifact</artifactId>\n" +
+            "      <version>4.0</version>\n" +
+            "      <type>test</type>\n" +
+            "    </dependency>\n" +
+            "    <dependency>\n" +
+            "      <groupId>supportGroup</groupId>\n" +
+            "      <artifactId>supportArtifact</artifactId>\n" +
+            "      <version>4.0</version>\n" +
+            "      <type>compile</type>\n" +
+            "    </dependency>\n" +
+            "  </dependencies>",
+            expectedXml =
+            "  <dependencies>\n" +
+            "    <dependency>\n" +
+            "      <groupId>testGroup</groupId>\n" +
+            "      <artifactId>testArtifact</artifactId>\n" +
+            "      <version>1.0</version>\n" +
+            "      <type>test</type>\n" +
+            "    </dependency>\n" +
+            "    <dependency>\n" +
+            "      <groupId>testGroup</groupId>\n" +
+            "      <artifactId>testArtifact</artifactId>\n" +
+            "      <version>1.0</version>\n" +
+            "      <type>compile</type>\n" +
+            "    </dependency>\n" +
+            "  </dependencies>",
+            rules = listOf(
+                PomRewriteRule(
+                    PomDependency(
+                        groupId = "supportGroup", artifactId = "supportArtifact",
+                        version =  "4.0"),
+                    listOf(
+                        PomDependency(
+                            groupId = "testGroup", artifactId = "testArtifact",
+                            version = "1.0")
+                    )
+                )
+            )
+        )
+    }
+
+    @Test fun pom_multipleTargets_shouldApplyAll() {
+        testRewrite(
+            givenXml =
+            "  <dependencies>\n" +
+            "    <dependency>\n" +
+            "      <groupId>supportGroup</groupId>\n" +
+            "      <artifactId>supportArtifact</artifactId>\n" +
+            "      <version>4.0</version>\n" +
+            "    </dependency>\n" +
+            "  </dependencies>",
+            expectedXml =
+            "  <dependencies>\n" +
+            "    <dependency>\n" +
+            "      <groupId>testGroup</groupId>\n" +
+            "      <artifactId>testArtifact</artifactId>\n" +
+            "      <version>1.0</version>\n" +
+            "    </dependency>\n" +
+            "    <dependency>\n" +
+            "      <groupId>testGroup2</groupId>\n" +
+            "      <artifactId>testArtifact2</artifactId>\n" +
+            "      <version>2.0</version>\n" +
+            "    </dependency>\n" +
+            "  </dependencies>",
+            rules = listOf(
+                PomRewriteRule(
+                    PomDependency(
+                        groupId = "supportGroup", artifactId = "supportArtifact",
+                        version =  "4.0"),
+                    listOf(
+                        PomDependency(
+                            groupId = "testGroup", artifactId = "testArtifact",
+                            version = "1.0"),
+                        PomDependency(
+                            groupId = "testGroup2", artifactId = "testArtifact2",
+                            version = "2.0"))
+                )
+            )
+        )
+    }
+
+    @Test fun pom_multipleRulesAndTargets_shouldApplyAll_distinct() {
+        testRewrite(
+            givenXml =
+            "  <dependencies>\n" +
+            "    <dependency>\n" +
+            "      <groupId>supportGroup</groupId>\n" +
+            "      <artifactId>supportArtifact</artifactId>\n" +
+            "      <version>4.0</version>\n" +
+            "    </dependency>\n" +
+            "    <dependency>\n" +
+            "      <groupId>supportGroup</groupId>\n" +
+            "      <artifactId>supportArtifact2</artifactId>\n" +
+            "      <version>4.0</version>\n" +
+            "    </dependency>\n" +
+            "  </dependencies>",
+            expectedXml =
+            "  <dependencies>\n" +
+            "    <dependency>\n" +
+            "      <groupId>testGroup</groupId>\n" +
+            "      <artifactId>testArtifact</artifactId>\n" +
+            "      <version>1.0</version>\n" +
+            "    </dependency>\n" +
+            "    <dependency>\n" +
+            "      <groupId>testGroup2</groupId>\n" +
+            "      <artifactId>testArtifact2</artifactId>\n" +
+            "      <version>2.0</version>\n" +
+            "    </dependency>\n" +
+            "  </dependencies>",
+            rules = listOf(
+                PomRewriteRule(
+                    PomDependency(
+                        groupId = "supportGroup", artifactId = "supportArtifact",
+                        version =  "4.0"),
+                    listOf(
+                        PomDependency(
+                            groupId = "testGroup", artifactId = "testArtifact",
+                            version = "1.0"),
+                        PomDependency(
+                            groupId = "testGroup2", artifactId = "testArtifact2",
+                            version = "2.0")
+                    )
+                ),
+                PomRewriteRule(
+                    PomDependency(
+                        groupId = "supportGroup", artifactId = "supportArtifact2",
+                        version =  "4.0"),
+                    listOf(
+                        PomDependency(
+                            groupId = "testGroup", artifactId = "testArtifact",
+                            version = "1.0"),
+                        PomDependency(
+                            groupId = "testGroup2", artifactId = "testArtifact2",
+                            version = "2.0"))
+                )
+            )
+        )
+    }
+
+    @Test fun pom_oneRule_hasToKeepExtraAttributesAndRewrite() {
+        testRewrite(
+            givenXml =
+            "  <dependencies>\n" +
+            "    <dependency>\n" +
+            "      <groupId>supportGroup</groupId>\n" +
+            "      <artifactId>supportArtifact</artifactId>\n" +
+            "      <version>4.0</version>\n" +
+            "      <classifier>hey</classifier>\n" +
+            "      <type>jar</type>\n" +
+            "      <scope>runtime</scope>\n" +
+            "      <systemPath>somePath</systemPath>\n" +
+            "      <optional>true</optional>\n" +
+            "    </dependency>\n" +
+            "  </dependencies>",
+            expectedXml =
+            "  <dependencies>\n" +
+            "    <dependency>\n" +
+            "      <groupId>testGroup</groupId>\n" +
+            "      <artifactId>testArtifact</artifactId>\n" +
+            "      <version>1.0</version>\n" +
+            "      <classifier>hey</classifier>\n" +
+            "      <type>jar</type>\n" +
+            "      <scope>runtime</scope>\n" +
+            "      <systemPath>somePath</systemPath>\n" +
+            "      <optional>true</optional>\n" +
+            "    </dependency>\n" +
+            "  </dependencies>",
+            rules = listOf(
+                PomRewriteRule(
+                    PomDependency(
+                        groupId = "supportGroup", artifactId = "supportArtifact",
+                        version =  "4.0"),
+                    listOf(
+                        PomDependency(
+                            groupId = "testGroup", artifactId = "testArtifact",
+                            version = "1.0")
+                    )
+                )
+            )
+        )
+    }
+
+    @Test fun pom_usingEmptyProperties_shouldNotCrash() {
+        val document = loadDocument(
+            "  <properties/>\n" +
+            "  <dependencies>\n" +
+            "    <dependency>\n" +
+            "      <groupId>supportGroup</groupId>\n" +
+            "      <artifactId>\${groupId.version.property}</artifactId>\n" +
+            "      <version>\${groupId.version.property}</version>\n" +
+            "    </dependency>\n" +
+            "  </dependencies>"
+        )
+
+        Truth.assertThat(document.dependencies).hasSize(1)
+    }
+
+    @Test fun pom_usingProperties_shouldResolve() {
+        val document = loadDocument(
+            "  <properties>\n" +
+            "    <groupId.version.property>1.0.0</groupId.version.property>\n" +
+            "    <groupId.artifactId.property>supportArtifact</groupId.artifactId.property>\n" +
+            "  </properties>\n" +
+            "  <dependencies>\n" +
+            "    <dependency>\n" +
+            "      <groupId>supportGroup</groupId>\n" +
+            "      <artifactId>\${groupId.artifactId.property}</artifactId>\n" +
+            "      <version>\${groupId.version.property}</version>\n" +
+            "    </dependency>\n" +
+            "  </dependencies>"
+        )
+
+        Truth.assertThat(document.dependencies).hasSize(1)
+
+        val dependency = document.dependencies.first()
+        Truth.assertThat(dependency.version).isEqualTo("1.0.0")
+        Truth.assertThat(dependency.artifactId).isEqualTo("supportArtifact")
+    }
+
+
+    private fun testRewriteToTheSame(givenAndExpectedXml: String, rules: List<PomRewriteRule>) {
+        testRewrite(givenAndExpectedXml, givenAndExpectedXml, rules)
+    }
+
+    private fun testRewrite(givenXml: String, expectedXml : String, rules: List<PomRewriteRule>) {
+        val given =
+            "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+            "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" " +
+                "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " +
+                "xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n" +
+            "  <!-- Some comment -->\n" +
+            "  <groupId>test.group</groupId>\n" +
+            "  <artifactId>test.artifact.id</artifactId>\n" +
+            "  <version>1.0</version>\n" +
+            "  $givenXml\n" +
+            "</project>\n"
+
+        var expected =
+            "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+            "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" " +
+                "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " +
+                "xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n" +
+            "  <!-- Some comment -->\n" +
+            "  <groupId>test.group</groupId>\n" +
+            "  <artifactId>test.artifact.id</artifactId>\n" +
+            "  <version>1.0</version>\n" +
+            "  $expectedXml\n" +
+            "</project>\n"
+
+        val file = ArchiveFile(Paths.get("pom.xml"), given.toByteArray())
+        val pomDocument = PomDocument.loadFrom(file)
+        pomDocument.applyRules(rules)
+        pomDocument.saveBackToFileIfNeeded()
+        var strResult = file.data.toString(StandardCharsets.UTF_8)
+
+        // Remove spaces in front of '<' and the back of '>'
+        expected = expected.replace(">[ ]+".toRegex(), ">")
+        expected = expected.replace("[ ]+<".toRegex(), "<")
+
+        strResult = strResult.replace(">[ ]+".toRegex(), ">")
+        strResult = strResult.replace("[ ]+<".toRegex(), "<")
+
+        // Replace newline characters to match the ones we are using in the expected string
+        strResult = strResult.replace("\\r\\n".toRegex(), "\n")
+
+        Truth.assertThat(strResult).isEqualTo(expected)
+    }
+
+    private fun loadDocument(givenXml : String) : PomDocument {
+        val given =
+            "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+            "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" " +
+            "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " +
+            "xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n" +
+            "  <!-- Some comment -->\n" +
+            "  <groupId>test.group</groupId>\n" +
+            "  <artifactId>test.artifact.id</artifactId>\n" +
+            "  <version>1.0</version>\n" +
+            "  $givenXml\n" +
+            "</project>\n"
+
+        val file = ArchiveFile(Paths.get("pom.xml"), given.toByteArray())
+        val pomDocument = PomDocument.loadFrom(file)
+        return pomDocument
+    }
+}
+
diff --git a/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/pom/PomRewriteRuleTest.kt b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/pom/PomRewriteRuleTest.kt
new file mode 100644
index 0000000..34ebd04
--- /dev/null
+++ b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/pom/PomRewriteRuleTest.kt
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.core.transform.pom
+
+import com.google.common.truth.Truth
+import org.junit.Test
+
+class PomRewriteRuleTest {
+
+    @Test fun versions_nullInRule_match() {
+        testVersionsMatch(
+            ruleVersion = null,
+            pomVersion = "27.0.0"
+        )
+    }
+
+    @Test fun versions_nullInPom_match() {
+        testVersionsMatch(
+            ruleVersion = "27.0.0",
+            pomVersion = null
+        )
+    }
+
+    @Test fun versions_nullBoth_match() {
+        testVersionsMatch(
+            ruleVersion = null,
+            pomVersion = null
+        )
+    }
+
+    @Test fun versions_same_match() {
+        testVersionsMatch(
+            ruleVersion = "27.0.0",
+            pomVersion = "27.0.0"
+        )
+    }
+
+    @Test fun versions_same_strict_match() {
+        testVersionsMatch(
+            ruleVersion = "27.0.0",
+            pomVersion = "[27.0.0]"
+        )
+    }
+
+    @Test fun versions_different_noMatch() {
+        testVersionsDoNotMatch(
+            ruleVersion = "27.0.0",
+            pomVersion = "26.0.0"
+        )
+    }
+
+    @Test fun versions_release_match() {
+        testVersionsMatch(
+            ruleVersion = "27.0.0",
+            pomVersion = "release"
+        )
+    }
+
+    @Test fun versions_latest_match() {
+        testVersionsMatch(
+            ruleVersion = "27.0.0",
+            pomVersion = "latest"
+        )
+    }
+
+    @Test fun versions_range_rightOpen_match() {
+        testVersionsMatch(
+            ruleVersion = "27.0.0",
+            pomVersion = "(26.0.0,]"
+        )
+    }
+
+    @Test fun versions_range_rightOpen2_match() {
+        testVersionsMatch(
+            ruleVersion = "27.0.0",
+            pomVersion = "(26.0.0,)"
+        )
+    }
+
+    @Test fun versions_range_inclusive_match() {
+        testVersionsMatch(
+            ruleVersion = "27.0.0",
+            pomVersion = "[21.0.0,27.0.0]"
+        )
+    }
+
+    @Test fun versions_range_inclusive_noMatch() {
+        testVersionsDoNotMatch(
+            ruleVersion = "27.0.0",
+            pomVersion = "[21.0.0,26.0.0]"
+        )
+    }
+
+    @Test fun versions_range_exclusive_noMatch() {
+        testVersionsDoNotMatch(
+            ruleVersion = "27.0.0",
+            pomVersion = "[21.0.0,27.0.0)"
+        )
+    }
+
+    @Test fun versions_exclusionRange_match() {
+        testVersionsMatch(
+            ruleVersion = "27.0.0",
+            pomVersion = "(,26.0.0),(26.0.0,)"
+        )
+    }
+
+    private fun testVersionsMatch(ruleVersion: String?, pomVersion: String?) {
+        val from = PomDependency(version = ruleVersion)
+        val pom = PomDependency(version = pomVersion)
+
+        val rule = PomRewriteRule(from, listOf(from))
+
+        Truth.assertThat(rule.validateVersion(pom)).isTrue()
+    }
+
+    private fun testVersionsDoNotMatch(ruleVersion: String?, pomVersion: String?) {
+        val from = PomDependency(version = ruleVersion)
+        val pom = PomDependency(version = pomVersion)
+
+        val rule = PomRewriteRule(from, listOf(from))
+
+        Truth.assertThat(rule.validateVersion(pom)).isFalse()
+    }
+
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassFilterTest.kt b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassFilterTest.kt
new file mode 100644
index 0000000..2c7d7e2
--- /dev/null
+++ b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassFilterTest.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.core.transform.proguard
+
+import org.junit.Test
+
+class ClassFilterTest {
+
+    @Test fun proGuard_classFilter() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment"
+            )
+            .testThatGivenProGuard(
+                "-adaptclassstrings support.Activity, support.Fragment, keep.Me"
+            )
+            .rewritesTo(
+                "-adaptclassstrings test.Activity, test.Fragment, keep.Me"
+            )
+    }
+
+    @Test fun proGuard_classFilter_newLineIgnored() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment"
+            )
+            .testThatGivenProGuard(
+                "-adaptclassstrings support.Activity, support.Fragment, keep.Me \n" +
+                " support.Activity"
+            )
+            .rewritesTo(
+                "-adaptclassstrings test.Activity, test.Fragment, keep.Me \n" +
+                " support.Activity"
+            )
+    }
+
+    @Test fun proGuard_classFilter_spacesRespected() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment"
+            )
+            .testThatGivenProGuard(
+                "  -adaptclassstrings  support.Activity ,  support.Fragment,keep.Me  "
+            )
+            .rewritesTo(
+                "  -adaptclassstrings  test.Activity, test.Fragment, keep.Me"
+            )
+    }
+
+    @Test fun proGuard_classFilter_negation() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment"
+            )
+            .testThatGivenProGuard(
+                "  -adaptclassstrings !support.Activity, !support.Fragment, !keep.Me  "
+            )
+            .rewritesTo(
+                "  -adaptclassstrings !test.Activity, !test.Fragment, !keep.Me"
+            )
+    }
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassSpecTest.kt b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassSpecTest.kt
new file mode 100644
index 0000000..e64590f
--- /dev/null
+++ b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassSpecTest.kt
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.core.transform.proguard
+
+import org.junit.Test
+
+class ClassSpecTest {
+
+    @Test fun proGuard_classSpec_simple() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment",
+                "support/Annotation" to "test/Annotation"
+            )
+            .testThatGivenProGuard(
+                "-keep class support.Activity"
+            )
+            .rewritesTo(
+                "-keep class test.Activity"
+            )
+    }
+
+    @Test fun proGuard_classSpec_allExistingRules() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment",
+                "support/Annotation" to "test/Annotation"
+            )
+            .testThatGivenProGuard(
+                "-keep class support.Activity \n" +
+                "-keepclassmembers class support.Activity \n" +
+                "-keepclasseswithmembers class support.Activity \n" +
+                "-keepnames class support.Activity \n" +
+                "-keepclassmembernames class support.Activity \n" +
+                "-keepclasseswithmembernames class support.Activity \n" +
+                "-whyareyoukeeping class support.Activity \n" +
+                "-assumenosideeffects class support.Activity"
+            )
+            .rewritesTo(
+                "-keep class test.Activity \n" +
+                "-keepclassmembers class test.Activity \n" +
+                "-keepclasseswithmembers class test.Activity \n" +
+                "-keepnames class test.Activity \n" +
+                "-keepclassmembernames class test.Activity \n" +
+                "-keepclasseswithmembernames class test.Activity \n" +
+                "-whyareyoukeeping class test.Activity \n" +
+                "-assumenosideeffects class test.Activity"
+            )
+    }
+
+    @Test fun proGuard_classSpec_rulesModifiers() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment",
+                "support/Annotation" to "test/Annotation"
+            )
+            .testThatGivenProGuard(
+                "-keep includedescriptorclasses class support.Activity \n" +
+                "-keep allowshrinking class support.Activity \n" +
+                "-keep allowoptimization class support.Activity \n" +
+                "-keep allowobfuscation class support.Activity \n" +
+                "-keep allowshrinking allowoptimization allowobfuscation class support.Activity \n" +
+                "-keep allowshrinking   allowoptimization   allowobfuscation  class support.Activity"
+            )
+            .rewritesTo(
+                "-keep includedescriptorclasses class test.Activity \n" +
+                "-keep allowshrinking class test.Activity \n" +
+                "-keep allowoptimization class test.Activity \n" +
+                "-keep allowobfuscation class test.Activity \n" +
+                "-keep allowshrinking allowoptimization allowobfuscation class test.Activity \n" +
+                "-keep allowshrinking   allowoptimization   allowobfuscation  class test.Activity"
+            )
+    }
+
+    @Test fun proGuard_classSpec_extends() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment",
+                "support/Annotation" to "test/Annotation"
+            )
+            .testThatGivenProGuard(
+                "-keep class * extends support.Activity \n" +
+                "-keep class support.Fragment extends support.Activity"
+            )
+            .rewritesTo(
+                "-keep class * extends test.Activity \n" +
+                "-keep class test.Fragment extends test.Activity"
+            )
+    }
+
+    @Test fun proGuard_classSpec_modifiers_extends() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity"
+            )
+            .testThatGivenProGuard(
+                "-keep !public enum * extends support.Activity \n" +
+                "-keep public !final enum * extends support.Activity"
+            )
+            .rewritesTo(
+                "-keep !public enum * extends test.Activity \n" +
+                "-keep public !final enum * extends test.Activity"
+            )
+    }
+
+    @Test fun proGuard_classSpec_annotation() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment",
+                "support/Annotation" to "test/Annotation"
+            )
+            .testThatGivenProGuard(
+                "-keep @support.Annotation public class support.Activity \n" +
+                "-keep @some.Annotation public class support.Activity"
+            )
+            .rewritesTo(
+                "-keep @test.Annotation public class test.Activity \n" +
+                "-keep @some.Annotation public class test.Activity"
+            )
+    }
+
+    @Test fun proGuard_classSpec_annotation_extends() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment",
+                "support/Annotation" to "test/Annotation"
+            )
+            .testThatGivenProGuard(
+                "-keep @support.Annotation public class * extends support.Activity\n" +
+                "-keep @some.Annotation !public class * extends support.Activity"
+            )
+            .rewritesTo(
+                "-keep @test.Annotation public class * extends test.Activity\n" +
+                "-keep @some.Annotation !public class * extends test.Activity"
+            )
+    }
+
+    @Test fun proGuard_classSpec_annotation_extends_spaces() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment",
+                "support/Annotation" to "test/Annotation"
+            )
+            .testThatGivenProGuard(
+                "-keep \t @support.Annotation \t public  class  *  extends support.Activity"
+            )
+            .rewritesTo(
+                "-keep \t @test.Annotation \t public  class  *  extends test.Activity"
+            )
+    }
+
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassSpecTest_FieldTypeSelector.kt b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassSpecTest_FieldTypeSelector.kt
new file mode 100644
index 0000000..2832385
--- /dev/null
+++ b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassSpecTest_FieldTypeSelector.kt
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.core.transform.proguard
+
+import org.junit.Test
+
+class ClassSpecTest_FieldTypeSelector {
+
+    @Test fun proGuard_fieldTypeSelector() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment"
+            )
+            .testThatGivenProGuard(
+                "-keep public class * { \n" +
+                "  support.Activity height; \n" +
+                "  support.Fragment *; \n" +
+                "  keep.Me width; \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * { \n" +
+                "  test.Activity height; \n" +
+                "  test.Fragment *; \n" +
+                "  keep.Me width; \n" +
+                "}"
+            )
+    }
+
+    @Test fun proGuard_fieldTypeSelector_modifiers() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment"
+            )
+            .testThatGivenProGuard(
+                "-keep public class * { \n" +
+                "  public support.Fragment height; \n" +
+                "  !public !static support.Fragment height; \n" +
+                "  !protected support.Fragment height; \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * { \n" +
+                "  public test.Fragment height; \n" +
+                "  !public !static test.Fragment height; \n" +
+                "  !protected test.Fragment height; \n" +
+                "}"
+            )
+    }
+
+    @Test fun proGuard_fieldTypeSelector_annotation() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment",
+                "support/Annotation" to "test/Annotation"
+            )
+            .testThatGivenProGuard(
+                "-keep public class * { \n" +
+                "  @support.Annotation support.Fragment height; \n" +
+                "  @some.Annotation support.Fragment height; \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * { \n" +
+                "  @test.Annotation test.Fragment height; \n" +
+                "  @some.Annotation test.Fragment height; \n" +
+                "}"
+            )
+    }
+
+    @Test fun proGuard_fieldTypeSelector_modifiers_annotation() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment",
+                "support/Annotation" to "test/Annotation"
+            )
+            .testThatGivenProGuard(
+                "-keep public class * { \n" +
+                "  @support.Annotation public support.Fragment height; \n" +
+                "  @support.Annotation !public !static support.Fragment height; \n" +
+                "  @support.Annotation !protected volatile support.Fragment height; \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * { \n" +
+                "  @test.Annotation public test.Fragment height; \n" +
+                "  @test.Annotation !public !static test.Fragment height; \n" +
+                "  @test.Annotation !protected volatile test.Fragment height; \n" +
+                "}"
+            )
+    }
+
+    @Test fun proGuard_fieldTypeSelector_modifiers_annotation_spaces() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment",
+                "support/Annotation" to "test/Annotation"
+            )
+            .testThatGivenProGuard(
+                "-keep public class * { \n" +
+                "  @support.Annotation  public  static \t support.Fragment  height ; \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * { \n" +
+                "  @test.Annotation  public  static \t test.Fragment  height ; \n" +
+                "}"
+            )
+    }
+
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassSpecTest_FieldsSelector.kt b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassSpecTest_FieldsSelector.kt
new file mode 100644
index 0000000..6f6a1f9
--- /dev/null
+++ b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassSpecTest_FieldsSelector.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.core.transform.proguard
+
+import org.junit.Test
+
+class ClassSpecTest_FieldsSelector {
+
+    @Test fun proGuard_fieldsSelector_minimal() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity"
+            )
+            .testThatGivenProGuard(
+                "-keep public class * extends support.Activity { \n" +
+                "  <fields>; \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * extends test.Activity { \n" +
+                "  <fields>; \n" +
+                "}"
+            )
+    }
+
+    @Test fun proGuard_fieldsSelector_modifiers() {
+        ProGuardTester
+            .forGivenPrefixes(
+            )
+            .forGivenTypesMap(
+            )
+            .testThatGivenProGuard(
+                "-keep public class * { \n" +
+                "  public <fields>; \n" +
+                "  public static <fields>; \n" +
+                "  !private !protected <fields>; \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * { \n" +
+                "  public <fields>; \n" +
+                "  public static <fields>; \n" +
+                "  !private !protected <fields>; \n" +
+                "}"
+            )
+    }
+
+    @Test fun proGuard_fieldsSelector_modifiers_annotation() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Annotation" to "test/Annotation"
+            )
+            .testThatGivenProGuard(
+                "-keep public class * { \n" +
+                "  @support.Annotation public <fields>; \n" +
+                "  @support.Annotation public static <fields>; \n" +
+                "  @support.Annotation !private !protected <fields>; \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * { \n" +
+                "  @test.Annotation public <fields>; \n" +
+                "  @test.Annotation public static <fields>; \n" +
+                "  @test.Annotation !private !protected <fields>; \n" +
+                "}"
+            )
+    }
+
+    @Test fun proGuard_fieldsSelector_modifiers_annotation_spaces() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Annotation" to "test/Annotation"
+            )
+            .testThatGivenProGuard(
+                "-keep public class * { \n" +
+                "  @support.Annotation  public \t  <fields> ; \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * { \n" +
+                "  @test.Annotation  public \t  <fields> ; \n" +
+                "}"
+            )
+    }
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassSpecTest_MethodInitSelector.kt b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassSpecTest_MethodInitSelector.kt
new file mode 100644
index 0000000..9a792cf
--- /dev/null
+++ b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassSpecTest_MethodInitSelector.kt
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.core.transform.proguard
+
+import org.junit.Test
+
+class ClassSpecTest_MethodInitSelector {
+
+    @Test fun proGuard_methodsInitSelector() {
+        ProGuardTester
+            .forGivenPrefixes(
+            )
+            .forGivenTypesMap(
+            )
+            .testThatGivenProGuard(
+                "-keep public class * { \n" +
+                "  <methods>; \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * { \n" +
+                "  <methods>; \n" +
+                "}"
+            )
+    }
+
+    @Test fun proGuard_methodsInitSelector_modifiers() {
+        ProGuardTester
+            .forGivenPrefixes(
+            )
+            .forGivenTypesMap(
+            )
+            .testThatGivenProGuard(
+                "-keep public class * { \n" +
+                "  public <methods>; \n" +
+                "  public static <methods>; \n" +
+                "  public !static <methods>; \n" +
+                "  !private static <methods>; \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * { \n" +
+                "  public <methods>; \n" +
+                "  public static <methods>; \n" +
+                "  public !static <methods>; \n" +
+                "  !private static <methods>; \n" +
+                "}"
+            )
+    }
+
+    @Test fun proGuard_methodsInitSelector_modifiers_annotation() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Annotation" to "test/Annotation"
+            )
+            .testThatGivenProGuard(
+                "-keep public class * { \n" +
+                "  @support.Annotation public <methods>; \n" +
+                "  @support.Annotation public static <methods>; \n" +
+                "  @support.Annotation public !static <methods>; \n" +
+                "  @support.Annotation !private static <methods>; \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * { \n" +
+                "  @test.Annotation public <methods>; \n" +
+                "  @test.Annotation public static <methods>; \n" +
+                "  @test.Annotation public !static <methods>; \n" +
+                "  @test.Annotation !private static <methods>; \n" +
+                "}"
+            )
+    }
+
+    @Test fun proGuard_methodInitSelector() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment"
+            )
+            .testThatGivenProGuard(
+                "-keep public class * { \n" +
+                "  <init>(); \n" +
+                "  <init>(*); \n" +
+                "  <init>(...); \n" +
+                "  <init>(support.Activity); \n" +
+                "  <init>(support.Activity, support.Fragment, keep.Please); \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * { \n" +
+                "  <init>(); \n" +
+                "  <init>(*); \n" +
+                "  <init>(...); \n" +
+                "  <init>(test.Activity); \n" +
+                "  <init>(test.Activity, test.Fragment, keep.Please); \n" +
+                "}"
+            )
+    }
+
+    @Test fun proGuard_methodInitSelector_modifiers() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment"
+            )
+            .testThatGivenProGuard(
+                "-keep public class * { \n" +
+                "  public <init>(); \n" +
+                "  public static <init>(*); \n" +
+                "  !public !static <init>(...); \n" +
+                "  !private static <init>(support.Activity); \n" +
+                "  public !abstract <init>(support.Activity, support.Fragment, keep.Please); \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * { \n" +
+                "  public <init>(); \n" +
+                "  public static <init>(*); \n" +
+                "  !public !static <init>(...); \n" +
+                "  !private static <init>(test.Activity); \n" +
+                "  public !abstract <init>(test.Activity, test.Fragment, keep.Please); \n" +
+                "}"
+            )
+    }
+
+    @Test fun proGuard_methodInitSelector_annotation() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment",
+                "support/Annotation" to "test/Annotation"
+            )
+            .testThatGivenProGuard(
+                "-keep public class * { \n" +
+                "  @support.Annotation <init>(); \n" +
+                "  @support.Annotation <init>(*); \n" +
+                "  @support.Annotation <init>(...); \n" +
+                "  @keep.Me <init>(support.Activity); \n" +
+                "  @support.Annotation <init>(support.Activity, support.Fragment, keep.Please); \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * { \n" +
+                "  @test.Annotation <init>(); \n" +
+                "  @test.Annotation <init>(*); \n" +
+                "  @test.Annotation <init>(...); \n" +
+                "  @keep.Me <init>(test.Activity); \n" +
+                "  @test.Annotation <init>(test.Activity, test.Fragment, keep.Please); \n" +
+                "}"
+            )
+    }
+
+    @Test fun proGuard_methodInitSelector_modifiers_annotation() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment",
+                "support/Annotation" to "test/Annotation"
+            )
+            .testThatGivenProGuard(
+                "-keep public class * { \n" +
+                "  @support.Annotation public <init>(); \n" +
+                "  @support.Annotation public static <init>(*); \n" +
+                "  @support.Annotation !public !static <init>(...); \n" +
+                "  @support.Annotation !private static <init>(support.Activity); \n" +
+                "  @support.Annotation public !abstract <init>(support.Activity, support.Fragment, keep.Please); \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * { \n" +
+                "  @test.Annotation public <init>(); \n" +
+                "  @test.Annotation public static <init>(*); \n" +
+                "  @test.Annotation !public !static <init>(...); \n" +
+                "  @test.Annotation !private static <init>(test.Activity); \n" +
+                "  @test.Annotation public !abstract <init>(test.Activity, test.Fragment, keep.Please); \n" +
+                "}"
+            )
+    }
+
+    @Test fun proGuard_methodInitSelector_modifiers_annotation_test() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment",
+                "support/Annotation" to "test/Annotation"
+            )
+            .testThatGivenProGuard(
+                "-keep public class * { \n" +
+                "  @support.Annotation  public  !abstract \t <init> ( support.Activity , support.Fragment, keep.Please); \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * { \n" +
+                "  @test.Annotation  public  !abstract \t <init> (test.Activity, test.Fragment, keep.Please); \n" +
+                "}"
+            )
+    }
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassSpecTest_MethodSelectorWithReturnType.kt b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassSpecTest_MethodSelectorWithReturnType.kt
new file mode 100644
index 0000000..d9960b4
--- /dev/null
+++ b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassSpecTest_MethodSelectorWithReturnType.kt
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.core.transform.proguard
+
+import org.junit.Test
+
+class ClassSpecTest_MethodSelectorWithReturnType {
+
+    @Test fun proGuard_methodReturnTypeSelector() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment"
+            )
+            .testThatGivenProGuard(
+                "-keep public class * { \n" +
+                "  void get*(); \n" +
+                "  void get*(...); \n" +
+                "  void get*(*); \n" +
+                "  void get*(support.Activity); \n" +
+                "  void get?(support.Activity); \n" +
+                "  void get(support.Activity); \n" +
+                "  void *(support.Activity, support.Fragment, keep.Please); \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * { \n" +
+                "  void get*(); \n" +
+                "  void get*(...); \n" +
+                "  void get*(*); \n" +
+                "  void get*(test.Activity); \n" +
+                "  void get?(test.Activity); \n" +
+                "  void get(test.Activity); \n" +
+                "  void *(test.Activity, test.Fragment, keep.Please); \n" +
+                "}"
+            )
+    }
+
+    @Test fun proGuard_methodReturnTypeSelector_voidResult() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment"
+            )
+            .testThatGivenProGuard(
+                "-keep public class * { \n" +
+                "  void get(); \n" +
+                "  void get(...); \n" +
+                "  void get(*); \n" +
+                "  void get(support.Activity); \n" +
+                "  void get(support.Activity, support.Fragment, keep.Please); \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * { \n" +
+                "  void get(); \n" +
+                "  void get(...); \n" +
+                "  void get(*); \n" +
+                "  void get(test.Activity); \n" +
+                "  void get(test.Activity, test.Fragment, keep.Please); \n" +
+                "}"
+            )
+    }
+
+    @Test fun proGuard_methodReturnTypeSelector_starResult() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment"
+            )
+            .testThatGivenProGuard(
+                "-keep public class * { \n" +
+                "  * get(); \n" +
+                "  * get(...); \n" +
+                "  * get(*); \n" +
+                "  * get(support.Activity); \n" +
+                "  * get(support.Activity, support.Fragment, keep.Please); \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * { \n" +
+                "  * get(); \n" +
+                "  * get(...); \n" +
+                "  * get(*); \n" +
+                "  * get(test.Activity); \n" +
+                "  * get(test.Activity, test.Fragment, keep.Please); \n" +
+                "}"
+            )
+    }
+
+    @Test fun proGuard_methodReturnTypeSelector_typeResult() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment"
+            )
+            .testThatGivenProGuard(
+                "-keep public class * { \n" +
+                "  support.Fragment get(); \n" +
+                "  support.Fragment get(...); \n" +
+                "  support.Fragment get(*); \n" +
+                "  support.Fragment get(support.Activity); \n" +
+                "  support.Fragment get(support.Activity, support.Fragment, keep.Please); \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * { \n" +
+                "  test.Fragment get(); \n" +
+                "  test.Fragment get(...); \n" +
+                "  test.Fragment get(*); \n" +
+                "  test.Fragment get(test.Activity); \n" +
+                "  test.Fragment get(test.Activity, test.Fragment, keep.Please); \n" +
+                "}"
+            )
+    }
+
+    @Test fun proGuard_methodReturnTypeSelector_typeResult_wildcards() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment"
+            )
+            .testThatGivenProGuard(
+                "-keep public class * { \n" +
+                "  support.Fragment get*(); \n" +
+                "  support.Fragment get?(...); \n" +
+                "  support.Fragment *(*); \n" +
+                "  support.Fragment *(support.Activity); \n" +
+                "  support.Fragment *(support.Activity, support.Fragment, keep.Please); \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * { \n" +
+                "  test.Fragment get*(); \n" +
+                "  test.Fragment get?(...); \n" +
+                "  test.Fragment *(*); \n" +
+                "  test.Fragment *(test.Activity); \n" +
+                "  test.Fragment *(test.Activity, test.Fragment, keep.Please); \n" +
+                "}"
+            )
+    }
+
+    @Test fun proGuard_methodReturnTypeSelector_typeResult_modifiers() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment"
+            )
+            .testThatGivenProGuard(
+                "-keep public class * { \n" +
+                "  public support.Fragment get(); \n" +
+                "  public static support.Fragment get(...); \n" +
+                "  !public !static support.Fragment get(*); \n" +
+                "  private support.Fragment get(support.Activity); \n" +
+                "  public abstract support.Fragment get(support.Activity, support.Fragment, keep.Please); \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * { \n" +
+                "  public test.Fragment get(); \n" +
+                "  public static test.Fragment get(...); \n" +
+                "  !public !static test.Fragment get(*); \n" +
+                "  private test.Fragment get(test.Activity); \n" +
+                "  public abstract test.Fragment get(test.Activity, test.Fragment, keep.Please); \n" +
+                "}"
+            )
+    }
+
+    @Test fun proGuard_methodReturnTypeSelector_typeResult_annotation() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment",
+                "support/Annotation" to "test/Annotation"
+            )
+            .testThatGivenProGuard(
+                "-keep public class * { \n" +
+                "  @support.Annotation support.Fragment get(); \n" +
+                "  @support.Annotation support.Fragment get(...); \n" +
+                "  @support.Annotation support.Fragment get(*); \n" +
+                "  @keep.Me support.Fragment get(support.Activity); \n" +
+                "  @support.Annotation support.Fragment get(support.Activity, support.Fragment, keep.Please); \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * { \n" +
+                "  @test.Annotation test.Fragment get(); \n" +
+                "  @test.Annotation test.Fragment get(...); \n" +
+                "  @test.Annotation test.Fragment get(*); \n" +
+                "  @keep.Me test.Fragment get(test.Activity); \n" +
+                "  @test.Annotation test.Fragment get(test.Activity, test.Fragment, keep.Please); \n" +
+                "}"
+            )
+    }
+
+    @Test fun proGuard_methodReturnTypeSelector_typeResult_modifiers_annotation() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment",
+                "support/Annotation" to "test/Annotation"
+            )
+            .testThatGivenProGuard(
+                "-keep public class * { \n" +
+                "  @support.Annotation public support.Fragment get(); \n" +
+                "  @support.Annotation public static support.Fragment get(...); \n" +
+                "  @support.Annotation !public !static support.Fragment get(*); \n" +
+                "  @support.Annotation private support.Fragment get(support.Activity); \n" +
+                "  @support.Annotation public abstract support.Fragment get(support.Activity, support.Fragment,  keep.Please); \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * { \n" +
+                "  @test.Annotation public test.Fragment get(); \n" +
+                "  @test.Annotation public static test.Fragment get(...); \n" +
+                "  @test.Annotation !public !static test.Fragment get(*); \n" +
+                "  @test.Annotation private test.Fragment get(test.Activity); \n" +
+                "  @test.Annotation public abstract test.Fragment get(test.Activity, test.Fragment, keep.Please); \n" +
+                "}"
+            )
+    }
+
+    @Test fun proGuard_methodReturnTypeSelector_typeResult_modifiers_annotation_spaces() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment",
+                "support/Annotation" to "test/Annotation"
+            )
+            .testThatGivenProGuard(
+                "-keep public class * { \n" +
+                "  @support.Annotation  support.Fragment \t get(support.Activity ,  support.Fragment ,  keep.Please) ; \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * { \n" +
+                "  @test.Annotation  test.Fragment \t get(test.Activity, test.Fragment, keep.Please) ; \n" +
+                "}"
+            )
+    }
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassSpecTest_NamedCtorSelector.kt b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassSpecTest_NamedCtorSelector.kt
new file mode 100644
index 0000000..21b8b8c
--- /dev/null
+++ b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ClassSpecTest_NamedCtorSelector.kt
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.core.transform.proguard
+
+import org.junit.Test
+
+class ClassSpecTest_NamedCtorSelector {
+
+    @Test fun proGuard_ctorSelector() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment"
+            )
+            .testThatGivenProGuard(
+                "-keep public class * { \n" +
+                "  support.Activity(); \n" +
+                "  support.Activity(...); \n" +
+                "  support.Activity(*); \n" +
+                "  support.Activity(support.Activity); \n" +
+                "  support.Activity(support.Activity, support.Fragment, keep.Please); \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * { \n" +
+                "  test.Activity(); \n" +
+                "  test.Activity(...); \n" +
+                "  test.Activity(*); \n" +
+                "  test.Activity(test.Activity); \n" +
+                "  test.Activity(test.Activity, test.Fragment, keep.Please); \n" +
+                "}"
+            )
+    }
+
+    @Test fun proGuard_ctorSelector_modifiers() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment"
+            )
+            .testThatGivenProGuard(
+                "-keep public class * { \n" +
+                "  public support.Activity(); \n" +
+                "  public static support.Activity(...); \n" +
+                "  !private support.Activity(*); \n" +
+                "  !public !static support.Activity(support.Activity); \n" +
+                "  !protected support.Activity(support.Activity, support.Fragment, keep.Please); \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * { \n" +
+                "  public test.Activity(); \n" +
+                "  public static test.Activity(...); \n" +
+                "  !private test.Activity(*); \n" +
+                "  !public !static test.Activity(test.Activity); \n" +
+                "  !protected test.Activity(test.Activity, test.Fragment, keep.Please); \n" +
+                "}"
+            )
+    }
+
+    @Test fun proGuard_ctorSelector_annotation() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment",
+                "support/Annotation" to "test/Annotation"
+            )
+            .testThatGivenProGuard(
+                "-keep public class * { \n" +
+                "  @support.Annotation support.Activity(); \n" +
+                "  @support.Annotation support.Activity(...); \n" +
+                "  @support.Annotation support.Activity(*); \n" +
+                "  @support.Annotation support.Activity(support.Activity); \n" +
+                "  @support.Annotation support.Activity(support.Activity, support.Fragment, keep.Please); \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * { \n" +
+                "  @test.Annotation test.Activity(); \n" +
+                "  @test.Annotation test.Activity(...); \n" +
+                "  @test.Annotation test.Activity(*); \n" +
+                "  @test.Annotation test.Activity(test.Activity); \n" +
+                "  @test.Annotation test.Activity(test.Activity, test.Fragment, keep.Please); \n" +
+                "}"
+            )
+    }
+
+    @Test fun proGuard_ctorSelector_modifiers_annotation() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment",
+                "support/Annotation" to "test/Annotation"
+            )
+            .testThatGivenProGuard(
+                "-keep public class * { \n" +
+                "  @support.Annotation public support.Activity(); \n" +
+                "  @support.Annotation public static support.Activity(...); \n" +
+                "  @support.Annotation !private support.Activity(*); \n" +
+                "  @support.Annotation !public !static support.Activity(support.Activity); \n" +
+                "  @support.Annotation !protected support.Activity(support.Activity, support.Fragment, keep.Please); \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * { \n" +
+                "  @test.Annotation public test.Activity(); \n" +
+                "  @test.Annotation public static test.Activity(...); \n" +
+                "  @test.Annotation !private test.Activity(*); \n" +
+                "  @test.Annotation !public !static test.Activity(test.Activity); \n" +
+                "  @test.Annotation !protected test.Activity(test.Activity, test.Fragment, keep.Please); \n" +
+                "}"
+            )
+    }
+
+    @Test fun proGuard_ctorSelector_modifiers_annotation_spaces() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment",
+                "support/Annotation" to "test/Annotation"
+            )
+            .testThatGivenProGuard(
+                "-keep public class * { \n" +
+                "  @support.Annotation  !protected \t support.Activity( support.Activity ); \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * { \n" +
+                "  @test.Annotation  !protected \t test.Activity(test.Activity); \n" +
+                "}"
+            )
+    }
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardTester.kt b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardTester.kt
new file mode 100644
index 0000000..cae21d0
--- /dev/null
+++ b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardTester.kt
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.core.transform.proguard
+
+import android.support.tools.jetifier.core.archive.ArchiveFile
+import android.support.tools.jetifier.core.config.Config
+import android.support.tools.jetifier.core.map.TypesMap
+import android.support.tools.jetifier.core.rules.JavaType
+import android.support.tools.jetifier.core.transform.TransformationContext
+import com.google.common.truth.Truth
+import java.nio.charset.StandardCharsets
+import java.nio.file.Paths
+
+
+/**
+ * Helper to test ProGuard rewriting logic using lightweight syntax.
+ */
+object ProGuardTester {
+
+    private var javaTypes = emptyList<Pair<String, String>>()
+    private var proGuardTypes = emptyList<Pair<ProGuardType, ProGuardType>>()
+    private var prefixes = emptyList<String>()
+
+    fun forGivenPrefixes(vararg prefixes: String) : ProGuardTester {
+        this.prefixes = prefixes.toList()
+        return this
+    }
+
+    fun forGivenTypesMap(vararg rules: Pair<String, String>) : ProGuardTester {
+        this.javaTypes = rules.toList()
+        return this
+    }
+
+    fun forGivenProGuardMap(vararg rules: Pair<String, String>) : ProGuardTester {
+        this.proGuardTypes = rules.map {
+            ProGuardType.fromDotNotation(it.first) to ProGuardType.fromDotNotation(it.second) }
+            .toList()
+        return this
+    }
+
+    fun testThatGivenType(givenType: String) : ProGuardTesterForType {
+        return ProGuardTesterForType(createConfig(), givenType)
+    }
+
+    fun testThatGivenArguments(givenArgs: String) : ProGuardTesterForArgs {
+        return ProGuardTesterForArgs(createConfig(), givenArgs)
+    }
+
+    fun testThatGivenProGuard(given: String) : ProGuardTesterForFile {
+        return ProGuardTesterForFile(createConfig(), given)
+    }
+
+    private fun createConfig() : Config {
+        return Config(
+            restrictToPackagePrefixes = prefixes,
+            rewriteRules = emptyList(),
+            pomRewriteRules =  emptyList(),
+            typesMap = TypesMap(
+                types = javaTypes.map { JavaType(it.first) to JavaType(it.second) }.toMap(),
+                fields = emptyMap()),
+            proGuardMap = ProGuardTypesMap(proGuardTypes.toMap()))
+    }
+
+
+    class ProGuardTesterForFile(private val config: Config, private val given: String) {
+
+        fun rewritesTo(expected: String) {
+            val context = TransformationContext(config)
+            val transformer = ProGuardTransformer(context)
+            val file = ArchiveFile(Paths.get("proguard.txt"), given.toByteArray())
+            transformer.runTransform(file)
+
+            val result = file.data.toString(StandardCharsets.UTF_8)
+
+            Truth.assertThat(result).isEqualTo(expected)
+        }
+
+    }
+
+    class ProGuardTesterForType(private val config: Config, private val given: String) {
+
+        fun getsRewrittenTo(expectedType: String) {
+            val context = TransformationContext(config)
+            val mapper = ProGuardTypesMapper(context)
+            val result = mapper.replaceType(given)
+
+            Truth.assertThat(result).isEqualTo(expectedType)
+        }
+
+    }
+
+    class ProGuardTesterForArgs(private val config: Config, private val given: String) {
+
+        fun getRewrittenTo(expectedArguments: String) {
+            val context = TransformationContext(config)
+            val mapper = ProGuardTypesMapper(context)
+            val result = mapper.replaceMethodArgs(given)
+
+            Truth.assertThat(result).isEqualTo(expectedArguments)
+        }
+    }
+
+}
+
diff --git a/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardTypesMapperTest.kt b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardTypesMapperTest.kt
new file mode 100644
index 0000000..5e12aff
--- /dev/null
+++ b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ProGuardTypesMapperTest.kt
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.core.transform.proguard
+
+import org.junit.Test
+
+class ProGuardTypesMapperTest {
+
+    @Test fun proGuard_typeMapper_wildcard_simple() {
+        ProGuardTester
+            .testThatGivenType("*")
+            .getsRewrittenTo("*")
+    }
+
+    @Test fun proGuard_typeMapper_wildcard_double() {
+        ProGuardTester
+            .testThatGivenType("**")
+            .getsRewrittenTo("**")
+    }
+
+    @Test fun proGuard_typeMapper_wildcard_composed() {
+        ProGuardTester
+            .testThatGivenType("**/*")
+            .getsRewrittenTo("**/*")
+    }
+
+    @Test fun proGuard_typeMapper_wildcard_viaMap() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenProGuardMap(
+                "support/v7/*" to "test/v7/*"
+            )
+            .testThatGivenType("support.v7.*")
+            .getsRewrittenTo("test.v7.*")
+    }
+
+    @Test fun proGuard_typeMapper_wildcard_viaMap2() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenProGuardMap(
+                "support/v7/**" to "test/v7/**"
+            )
+            .testThatGivenType("support.v7.**")
+            .getsRewrittenTo("test.v7.**")
+    }
+
+    @Test fun proGuard_typeMapper_wildcard_viaTypesMap() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/v7/Activity" to "test/v7/Activity"
+            )
+            .testThatGivenType("support.v7.Activity")
+            .getsRewrittenTo("test.v7.Activity")
+    }
+
+    @Test fun proGuard_typeMapper_wildcard_notFoundInMap() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenProGuardMap(
+                "support/**" to "test/**"
+            )
+            .testThatGivenType("keep.me.**")
+            .getsRewrittenTo("keep.me.**")
+    }
+
+    @Test fun proGuard_typeMapper_differentPrefix_notRewritten() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "hello/Activity" to "test/Activity"
+            )
+            .testThatGivenType("hello.Activity")
+            .getsRewrittenTo("hello.Activity")
+    }
+
+    @Test fun proGuard_typeMapper_differentPrefix_wildcard_getsRewritten() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenProGuardMap(
+                "hello/**" to "test/**"
+            )
+            .testThatGivenType("hello.**")
+            .getsRewrittenTo("test.**")
+    }
+
+    @Test fun proGuard_typeMapper_innerClass() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity\$InnerClass" to "test/Activity\$InnerClass"
+            )
+            .testThatGivenType("support.Activity\$InnerClass")
+            .getsRewrittenTo("test.Activity\$InnerClass")
+    }
+
+    @Test fun proGuard_typeMapper_innerClass_wildcard() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenProGuardMap(
+                "**R\$Attrs" to "**R2\$Attrs"
+            )
+            .testThatGivenType("**R\$Attrs")
+            .getsRewrittenTo("**R2\$Attrs")
+    }
+
+    @Test fun proGuard_argsMapper_tripleDots() {
+        ProGuardTester
+            .testThatGivenArguments("...")
+            .getRewrittenTo("...")
+    }
+
+    @Test fun proGuard_argsMapper_wildcard() {
+        ProGuardTester
+            .testThatGivenArguments("*")
+            .getRewrittenTo("*")
+    }
+
+    @Test fun proGuard_argsMapper_wildcards() {
+        ProGuardTester
+            .testThatGivenArguments("**, **")
+            .getRewrittenTo("**, **")
+    }
+
+    @Test fun proGuard_argsMapper_viaMaps() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity"
+            )
+            .forGivenProGuardMap(
+                "support/v7/**" to "test/v7/**"
+            )
+            .testThatGivenArguments("support.Activity, support.v7.**, keep.Me")
+            .getRewrittenTo("test.Activity, test.v7.**, keep.Me")
+    }
+
+    @Test fun proGuard_argsMapper_viaMaps_spaces() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity"
+            )
+            .forGivenProGuardMap(
+                "support/v7/**" to "test/v7/**"
+            )
+            .testThatGivenArguments(" support.Activity , \t support.v7.**,  keep.Me ")
+            .getRewrittenTo("test.Activity, test.v7.**, keep.Me")
+    }
+
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ProguardSamplesTest.kt b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ProguardSamplesTest.kt
new file mode 100644
index 0000000..0542e7d
--- /dev/null
+++ b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/proguard/ProguardSamplesTest.kt
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.core.transform.proguard
+
+import org.junit.Test
+
+class ProguardSamplesTest {
+
+    @Test fun proGuard_sample() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "android/app/",
+                "android/view/",
+                "android/content/",
+                "android/os/",
+                "android/webkit/"
+            )
+            .forGivenTypesMap(
+                "android/app/Activity" to "test/app/Activity",
+                "android/app/Application" to "test/app/Application",
+                "android/view/View" to "test/view/View",
+                "android/view/MenuItem" to "test/view/MenuItem",
+                "android/content/Context" to "test/content/Context",
+                "android/os/Parcelable" to "test/os/Parcelable",
+                "android/webkit/JavascriptInterface" to "test/webkit/JavascriptInterface"
+            )
+            .testThatGivenProGuard(
+               "-injars      bin/classes \n" +
+               "-injars      libs \n" +
+               "-outjars     bin/classes-processed.jar \n" +
+               "-libraryjars /usr/local/java/android-sdk/platforms/android-9/android.jar \n" +
+               "\n" +
+               "-dontpreverify \n" +
+               "-repackageclasses '' \n" +
+               "-allowaccessmodification \n" +
+               "-optimizations !code/simplification/arithmetic \n" +
+               "-keepattributes *Annotation* \n" +
+               "\n" +
+               "-keep public class * extends android.app.Activity \n" +
+               "-keep public class * extends android.app.Application \n" +
+               " \n" +
+               "-keep public class * extends android.view.View { \n" +
+               "      public <init>(android.content.Context); \n" +
+               "      public <init>(android.content.Context, android.util.AttributeSet); \n" +
+               "      public <init>(android.content.Context, android.util.AttributeSet, int); \n" +
+               "      public void set*(...); \n" +
+               "} \n" +
+               "\n" +
+               "-keepclasseswithmembers class * { \n" +
+               "    public <init>(android.content.Context, android.util.AttributeSet); \n" +
+               "} \n" +
+               "\n" +
+               "-keepclasseswithmembers class * { \n" +
+               "    public <init>(android.content.Context, android.util.AttributeSet, int); \n" +
+               "} \n" +
+               "\n" +
+               "-keepclassmembers class * extends android.content.Context { \n" +
+               "    public void *(android.view.View); \n" +
+               "    public void *(android.view.MenuItem); \n" +
+               "} \n" +
+               "\n" +
+               "-keepclassmembers class * implements android.os.Parcelable { \n" +
+               "    static ** CREATOR; \n" +
+               "} \n" +
+               "\n" +
+               "-keepclassmembers class **.R\$* { \n" +
+               "    public static <fields>; \n" +
+               "} \n" +
+               "\n" +
+               "-keepclassmembers class * { \n" +
+               "    @android.webkit.JavascriptInterface <methods>; \n" +
+               "} "
+            )
+            .rewritesTo(
+                "-injars      bin/classes \n" +
+                "-injars      libs \n" +
+                "-outjars     bin/classes-processed.jar \n" +
+                "-libraryjars /usr/local/java/android-sdk/platforms/android-9/android.jar \n" +
+                "\n" +
+                "-dontpreverify \n" +
+                "-repackageclasses '' \n" +
+                "-allowaccessmodification \n" +
+                "-optimizations !code/simplification/arithmetic \n" +
+                "-keepattributes *Annotation* \n" +
+                "\n" +
+                "-keep public class * extends test.app.Activity \n" +
+                "-keep public class * extends test.app.Application \n" +
+                " \n" +
+                "-keep public class * extends test.view.View { \n" +
+                "      public <init>(test.content.Context); \n" +
+                "      public <init>(test.content.Context, android.util.AttributeSet); \n" +
+                "      public <init>(test.content.Context, android.util.AttributeSet, int); \n" +
+                "      public void set*(...); \n" +
+                "} \n" +
+                "\n" +
+                "-keepclasseswithmembers class * { \n" +
+                "    public <init>(test.content.Context, android.util.AttributeSet); \n" +
+                "} \n" +
+                "\n" +
+                "-keepclasseswithmembers class * { \n" +
+                "    public <init>(test.content.Context, android.util.AttributeSet, int); \n" +
+                "} \n" +
+                "\n" +
+                "-keepclassmembers class * extends test.content.Context { \n" +
+                "    public void *(test.view.View); \n" +
+                "    public void *(test.view.MenuItem); \n" +
+                "} \n" +
+                "\n" +
+                "-keepclassmembers class * implements test.os.Parcelable { \n" +
+                "    static ** CREATOR; \n" +
+                "} \n" +
+                "\n" +
+                "-keepclassmembers class **.R\$* { \n" +
+                "    public static <fields>; \n" +
+                "} \n" +
+                "\n" +
+                "-keepclassmembers class * { \n" +
+                "    @test.webkit.JavascriptInterface <methods>; \n" +
+                "} "
+            )
+    }
+
+    @Test fun proGuard_sample2() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "android/support/v7/"
+            )
+            .forGivenTypesMap(
+                "android/support/v7/preference/Preference" to "test/Preference"
+            )
+            .testThatGivenProGuard(
+                "-keep public class android.support.v7.preference.Preference {\n" +
+                "  public <init>(android.content.Context, android.util.AttributeSet);\n" +
+                "}\n" +
+                "-keep public class * extends android.support.v7.preference.Preference {\n" +
+                "  public <init>(android.content.Context, android.util.AttributeSet);\n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class test.Preference {\n" +
+                "  public <init>(android.content.Context, android.util.AttributeSet);\n" +
+                "}\n" +
+                "-keep public class * extends test.Preference {\n" +
+                "  public <init>(android.content.Context, android.util.AttributeSet);\n" +
+                "}"
+            )
+    }
+
+    @Test fun proGuard_sample3() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "android/support/design/",
+                "android/support/v7/"
+            )
+            .forGivenTypesMap(
+                "support/Fragment" to "test/Fragment",
+                "android/support/v7/widget/RoundRectDrawable" to "test/RoundRectDrawable"
+            )
+            .forGivenProGuardMap(
+                "android/support/design.**" to "test/design.**",
+                "android/support/design/R\$*" to "test/design/R\$*"
+            )
+            .testThatGivenProGuard(
+                "-dontwarn android.support.design.**\n" +
+                "-keep class android.support.design.** { *; }\n" +
+                "-keep interface android.support.design.** { *; }\n" +
+                "-keep public class android.support.design.R\$* { *; }\n" +
+                "-keep class android.support.v7.widget.RoundRectDrawable { *; }"
+            )
+            .rewritesTo(
+                "-dontwarn test.design.**\n" +
+                "-keep class test.design.** { *; }\n" +
+                "-keep interface test.design.** { *; }\n" +
+                "-keep public class test.design.R\$* { *; }\n" +
+                "-keep class test.RoundRectDrawable { *; }"
+            )
+    }
+
+    @Test fun proGuard_sample4() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "android/support/design/",
+                "android/support/v7/",
+                "android/support/v4/"
+            )
+            .forGivenTypesMap(
+                "android/support/v7/widget/LinearLayoutManager" to "test/LinearLayoutManager",
+                "android/support/v4/view/ActionProvider" to "test/ActionProvider"
+            )
+            .forGivenProGuardMap(
+                "android/support/v7/**" to "test/v7/**",
+                "android/support/v7/widget/**" to "test/v7/widget/**",
+                "android/support/v7/internal/widget/**" to "test/v7/internal/widget/**",
+                "android/support/v7/internal/**" to "test/v7/internal/**"
+            )
+            .testThatGivenProGuard(
+                "-dontwarn android.support.v7.**\n" +
+                "-keep public class android.support.v7.widget.** { *; }\n" +
+                "-keep public class android.support.v7.internal.widget.** { *; }\n" +
+                "-keep class android.support.v7.widget.LinearLayoutManager { *; }\n" +
+                "-keep class android.support.v7.internal.** { *; }\n" +
+                "-keep interface android.support.v7.internal.** { *; }\n" +
+                "\n" +
+                "-keep class android.support.v7.** { *; }\n" +
+                "-keep interface android.support.v7.** { *; }\n" +
+                "\n" +
+                "-keep public class * extends android.support.v4.view.ActionProvider {\n" +
+                "    public <init>(android.content.Context);"
+            )
+            .rewritesTo(
+                "-dontwarn test.v7.**\n" +
+                "-keep public class test.v7.widget.** { *; }\n" +
+                "-keep public class test.v7.internal.widget.** { *; }\n" +
+                "-keep class test.LinearLayoutManager { *; }\n" +
+                "-keep class test.v7.internal.** { *; }\n" +
+                "-keep interface test.v7.internal.** { *; }\n" +
+                "\n" +
+                "-keep class test.v7.** { *; }\n" +
+                "-keep interface test.v7.** { *; }\n" +
+                "\n" +
+                "-keep public class * extends test.ActionProvider {\n" +
+                "    public <init>(android.content.Context);"
+            )
+    }
+
+    @Test fun proGuard_sample5() {
+        ProGuardTester
+            .forGivenPrefixes(
+                "support/"
+            )
+            .forGivenTypesMap(
+                "support/Activity" to "test/Activity",
+                "support/Fragment" to "test/Fragment",
+                "support/Annotation" to "test/Annotation"
+            )
+            .testThatGivenProGuard(
+                "-keep public class * extends support.Activity { \n" +
+                "  public static <fields>; \n" +
+                "  public !static <methods>; \n" +
+                "  public support.Fragment height; \n" +
+                "  public static <fields>; \n" +
+                "  public not.related.Type width; public support.Fragment width; \n" +
+                "  ignoreMe; \n" +
+                "  @support.Annotation public support.Fragment get(); \n" +
+                "}"
+            )
+            .rewritesTo(
+                "-keep public class * extends test.Activity { \n" +
+                "  public static <fields>; \n" +
+                "  public !static <methods>; \n" +
+                "  public test.Fragment height; \n" +
+                "  public static <fields>; \n" +
+                "  public not.related.Type width; public test.Fragment width; \n" +
+                "  ignoreMe; \n" +
+                "  @test.Annotation public test.Fragment get(); \n" +
+                "}"
+            )
+    }
+
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/resource/XmlResourcesTransformerTest.kt b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/resource/XmlResourcesTransformerTest.kt
new file mode 100644
index 0000000..5788b40
--- /dev/null
+++ b/jetifier/jetifier/core/src/test/kotlin/android/support/tools/jetifier/core/transform/resource/XmlResourcesTransformerTest.kt
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.core.transform.resource
+
+import android.support.tools.jetifier.core.config.Config
+import android.support.tools.jetifier.core.rules.JavaType
+import android.support.tools.jetifier.core.map.TypesMap
+import android.support.tools.jetifier.core.transform.TransformationContext
+import android.support.tools.jetifier.core.transform.proguard.ProGuardTypesMap
+import com.google.common.truth.Truth
+import org.junit.Test
+import java.nio.charset.Charset
+
+class XmlResourcesTransformerTest {
+
+    @Test fun layout_noPrefix_noChange() {
+        testRewriteToTheSame(
+            givenAndExpectedXml =
+                "<android.support.v7.preference.Preference>\n" +
+                "</android.support.v7.preference.Preference>",
+            prefixes = listOf(),
+            map = mapOf(
+                "android/support/v7/preference/Preference" to "android/test/pref/Preference"
+            )
+        )
+    }
+
+    @Test fun layout_noRule_noChange() {
+        testRewriteToTheSame(
+            givenAndExpectedXml =
+                "<android.support.v7.preference.Preference>\n" +
+                "</android.support.v7.preference.Preference>",
+            prefixes = listOf("android/support/v7/"),
+            map = mapOf()
+        )
+    }
+
+    @Test fun layout_notApplicablePrefix_noChange() {
+        testRewriteToTheSame(
+            givenAndExpectedXml =
+                "<android.support.v7.preference.Preference>\n" +
+                "</android.support.v7.preference.Preference>",
+            prefixes = listOf("android/support/v14/"),
+            map = mapOf(
+                "android/support/v7/preference/Preference" to "android/test/pref/Preference"
+            )
+        )
+    }
+
+    @Test fun layout_notApplicablePrefix2_noChange() {
+        testRewriteToTheSame(
+            givenAndExpectedXml =
+                "<my.android.support.v7.preference.Preference>\n" +
+                "</my.android.support.v7.preference.Preference>",
+            prefixes = listOf("android/support/v7/"),
+            map = mapOf(
+                "android/support/v7/preference/Preference" to "android/test/pref/Preference"
+            )
+        )
+    }
+
+    @Test fun layout_notApplicableRule_noChange() {
+        testRewriteToTheSame(
+            givenAndExpectedXml =
+                "<android.support.v7.preference.Preference>\n" +
+                "</android.support.v7.preference.Preference>",
+            prefixes = listOf("android/support/"),
+            map = mapOf(
+                "android/support2/v7/preference/Preference" to "android/test/pref/Preference"
+            )
+        )
+    }
+
+    @Test fun layout_onePrefix_oneRule_oneRewrite() {
+        testRewrite(
+            givenXml =
+                "<android.support.v7.preference.Preference/>",
+            expectedXml =
+                "<android.test.pref.Preference/>",
+            prefixes = listOf("android/support/"),
+            map = mapOf(
+                "android/support/v7/preference/Preference" to "android/test/pref/Preference"
+            )
+        )
+    }
+
+    @Test fun layout_onePrefix_oneRule_attribute_oneRewrite() {
+        testRewrite(
+            givenXml =
+                "<android.support.v7.preference.Preference \n" +
+                "    someAttribute=\"android.support.v7.preference.Preference\"/>",
+            expectedXml =
+                "<android.test.pref.Preference \n" +
+                "    someAttribute=\"android.support.v7.preference.Preference\"/>",
+            prefixes = listOf("android/support/"),
+            map =  mapOf(
+                "android/support/v7/preference/Preference" to "android/test/pref/Preference"
+            )
+        )
+    }
+
+    @Test fun layout_onePrefix_oneRule_twoRewrites() {
+        testRewrite(
+            givenXml =
+                "<android.support.v7.preference.Preference>\n" +
+                "</android.support.v7.preference.Preference>",
+            expectedXml =
+                "<android.test.pref.Preference>\n" +
+                "</android.test.pref.Preference>",
+            prefixes = listOf("android/support/"),
+            map = mapOf(
+                "android/support/v7/preference/Preference" to "android/test/pref/Preference"
+            )
+        )
+    }
+
+    @Test fun layout_onePrefix_oneRule_viewTag_simple() {
+        testRewrite(
+            givenXml =
+                "<view class=\"android.support.v7.preference.Preference\">",
+            expectedXml =
+                "<view class=\"android.test.pref.Preference\">",
+            prefixes = listOf("android/support/"),
+            map = mapOf(
+                "android/support/v7/preference/Preference" to "android/test/pref/Preference"
+            )
+        )
+    }
+
+    @Test fun layout_onePrefix_oneRule_viewTag_stuffAround() {
+        testRewrite(
+            givenXml =
+                "<view notRelated=\"true\" " +
+                "      class=\"android.support.v7.preference.Preference\"" +
+                "      ignoreMe=\"android.support.v7.preference.Preference\">",
+            expectedXml =
+                "<view notRelated=\"true\" " +
+                "      class=\"android.test.pref.Preference\"" +
+                "      ignoreMe=\"android.support.v7.preference.Preference\">",
+            prefixes = listOf("android/support/"),
+            map = mapOf(
+                "android/support/v7/preference/Preference" to "android/test/pref/Preference"
+            )
+        )
+    }
+
+    @Test fun layout_onePrefix_oneRule_viewInText_notMatched() {
+        testRewriteToTheSame(
+            givenAndExpectedXml =
+                "<test attribute=\"view\" class=\"android.support.v7.preference.Preference\">",
+            prefixes = listOf("android/support/"),
+            map = mapOf(
+                "android/support/v7/preference/Preference" to "android/test/pref/Preference"
+            )
+        )
+    }
+
+    @Test fun layout_onePrefix_oneRule_identity() {
+        testRewrite(
+            givenXml =
+                "<android.support.v7.preference.Preference>\n" +
+                "</android.support.v7.preference.Preference>",
+            expectedXml =
+                "<android.support.v7.preference.Preference>\n" +
+                "</android.support.v7.preference.Preference>",
+            prefixes = listOf("android/support/"),
+            map = mapOf(
+                "android/support/v7/preference/Preference" to "android/support/v7/preference/Preference"
+            )
+        )
+    }
+
+    @Test fun layout_twoPrefixes_threeRules_multipleRewrites() {
+        testRewrite(
+            givenXml =
+                "<android.support.v7.preference.Preference>\n" +
+                "  <android.support.v14.preference.DialogPreference" +
+                "      someAttribute=\"someValue\"/>\n" +
+                "  <android.support.v14.preference.DialogPreference" +
+                "      someAttribute=\"someValue2\"/>\n" +
+                "  <!-- This one should be ignored --> \n" +
+                "  <android.support.v21.preference.DialogPreference" +
+                "      someAttribute=\"someValue2\"/>\n" +
+                "</android.support.v7.preference.Preference>\n" +
+                "\n" +
+                "<android.support.v7.preference.ListPreference/>",
+            expectedXml =
+                "<android.test.pref.Preference>\n" +
+                "  <android.test14.pref.DialogPreference" +
+                "      someAttribute=\"someValue\"/>\n" +
+                "  <android.test14.pref.DialogPreference" +
+                "      someAttribute=\"someValue2\"/>\n" +
+                "  <!-- This one should be ignored --> \n" +
+                "  <android.support.v21.preference.DialogPreference" +
+                "      someAttribute=\"someValue2\"/>\n" +
+                "</android.test.pref.Preference>\n" +
+                "\n" +
+                "<android.test.pref.ListPref/>",
+            prefixes = listOf(
+                "android/support/v7/",
+                "android/support/v14/"
+            ),
+            map = mapOf(
+                "android/support/v7/preference/ListPreference" to "android/test/pref/ListPref",
+                "android/support/v7/preference/Preference" to "android/test/pref/Preference",
+                "android/support/v14/preference/DialogPreference" to "android/test14/pref/DialogPreference",
+                "android/support/v21/preference/DialogPreference" to "android/test21/pref/DialogPreference"
+            )
+        )
+    }
+
+    private fun testRewriteToTheSame(givenAndExpectedXml: String,
+                                     prefixes: List<String>,
+                                     map: Map<String, String>) {
+        testRewrite(givenAndExpectedXml, givenAndExpectedXml, prefixes, map)
+    }
+
+    private fun testRewrite(givenXml : String,
+                            expectedXml : String,
+                            prefixes: List<String>,
+                            map: Map<String, String>) {
+        val given =
+            "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
+            "$givenXml\n"
+
+        val expected =
+            "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
+            "$expectedXml\n"
+
+        val typesMap = TypesMap(map.map{ JavaType(it.key) to JavaType(it.value) }.toMap(),
+            emptyMap())
+        val config = Config(prefixes, emptyList(), emptyList(), typesMap, ProGuardTypesMap.EMPTY)
+        val context = TransformationContext(config)
+        val processor = XmlResourcesTransformer(context)
+        val result = processor.transform(given.toByteArray())
+        val strResult = result.toString(Charset.defaultCharset())
+
+        Truth.assertThat(strResult).isEqualTo(expected)
+    }
+}
+
diff --git a/jetifier/jetifier/gradle-plugin/build.gradle b/jetifier/jetifier/gradle-plugin/build.gradle
new file mode 100644
index 0000000..ca24b72
--- /dev/null
+++ b/jetifier/jetifier/gradle-plugin/build.gradle
@@ -0,0 +1,40 @@
+/*
+ * 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
+ */
+
+version '0.1'
+
+apply plugin: 'maven-publish'
+
+dependencies {
+    compile project(':core')
+    compileOnly gradleApi()
+}
+
+// Task to create a jar with all the required dependencies bundled inside
+task fatJar(type: Jar) {
+    baseName = project.name + '-all'
+    destinationDir = rootProject.ext.distDir
+    from { configurations.runtime.collect { it.isDirectory() ? it : zipTree(it) } }
+    with jar
+}
+
+publishing {
+    publications {
+        mavenJava(MavenPublication) {
+            from components.java
+        }
+    }
+}
diff --git a/jetifier/jetifier/gradle-plugin/src/main/kotlin/android/support/tools/jetifier/plugin/gradle/JetifierExtension.kt b/jetifier/jetifier/gradle-plugin/src/main/kotlin/android/support/tools/jetifier/plugin/gradle/JetifierExtension.kt
new file mode 100644
index 0000000..b215d27
--- /dev/null
+++ b/jetifier/jetifier/gradle-plugin/src/main/kotlin/android/support/tools/jetifier/plugin/gradle/JetifierExtension.kt
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.plugin.gradle
+
+import org.gradle.api.Project
+import org.gradle.api.artifacts.Configuration
+import org.gradle.api.artifacts.Dependency
+import org.gradle.api.file.FileCollection
+import java.nio.file.Paths
+
+/**
+ * Defines methods that can be used in gradle on the "jetifier" object and triggers [JetifyLibsTask]
+ * or [JetifyGlobalTask] based on its usage.
+ */
+open class JetifierExtension(val project : Project) {
+
+    /**
+     * Adds dependency defined via string notation to be processed by jetifyLibs task.
+     *
+     *
+     * Example usage in Gradle:
+     * dependencies {
+     *   compile jetifier.process('groupId:artifactId:1.0')
+     * }
+     */
+    fun process(dependencyNotation: String): FileCollection {
+        return process(project.dependencies.create(dependencyNotation))
+    }
+
+    /**
+     * Adds dependency to be processed by jetifyLibs task.
+     */
+    fun process(dependency: Dependency): FileCollection {
+        val configuration = project.configurations.detachedConfiguration()
+        configuration.dependencies.add(dependency)
+        return process(configuration)
+    }
+
+    /**
+     * Adds dependencies defined via file collection to be processed by jetifyLibs task.
+     *
+     * Example usage in Gradle for a single file:
+     * dependencies {
+     *   compile jetifier.process(files('../myFile1.jar'))
+     *   compile jetifier.process(files('../myFile2.jar'))
+     * }
+     *
+     * Example usage in Gradle for a configuration:
+     * configurations.create('depToRefactor')
+     *
+     * dependencies {
+     *    depToRefactor 'test:myDependency:1.0'
+     *    depToRefactor 'test:myDependency2:1.0'
+     * }
+     *
+     * dependencies {
+     *   compile jetifier.process(configurations.depToRefactor)
+     * }
+     */
+    fun process(files: FileCollection): FileCollection {
+        return JetifyLibsTask.resolveTask(project).addFilesToProcess(files)
+    }
+
+    /**
+     * Adds a whole configuration to be processed by jetifyGlobal task. This is the recommended way
+     * if processing a set of dependencies where it is unknown which exactly need to be rewritten.
+     *
+     * This will create a new detached configuration and resolve all the dependencies = obtaining
+     * all the files. Jetifier is then run with all the files and only the files that were rewritten
+     * are added to the given configuration and the original dependencies that didn't have to be
+     * changed are kept.
+     *
+     * Advantage is that all the dependencies that didn't have to be changed are kept intact so
+     * their artifactsIds and groupIds are kept (instead of replacing them with files) which allows
+     * other steps in the build process to use the artifacts information to generate pom files
+     * and other stuff.
+     *
+     * This will NOT resolve the given configuration as the dependencies are resolved in a detached
+     * configuration. If you give it a configuration that was already resolved the process will
+     * end up with exception saying that resolved configuration cannot be changed. This is expected
+     * as Jetifier cannot add new files to an already resolved configuration.
+     *
+     *
+     * Example usage in Gradle:
+     * jetifier.addConfigurationToProcess(configurations.implementation)
+     * afterEvaluate {
+     *   tasks.preBuild.dependsOn tasks.jetifyGlobal
+     * }
+     *
+     *
+     */
+    fun addConfigurationToProcess(config: Configuration) {
+        JetifyGlobalTask.resolveTask(project).addConfigurationToProcess(config)
+    }
+
+    /**
+     * Sets a custom configuration file to be used by Jetifier.
+     */
+    fun setConfigFile(configFilePath: String) {
+        TasksCommon.configFilePath = Paths.get(configFilePath)
+    }
+
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/gradle-plugin/src/main/kotlin/android/support/tools/jetifier/plugin/gradle/JetifierLoggerAdapter.kt b/jetifier/jetifier/gradle-plugin/src/main/kotlin/android/support/tools/jetifier/plugin/gradle/JetifierLoggerAdapter.kt
new file mode 100644
index 0000000..9d85851
--- /dev/null
+++ b/jetifier/jetifier/gradle-plugin/src/main/kotlin/android/support/tools/jetifier/plugin/gradle/JetifierLoggerAdapter.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.plugin.gradle
+
+import android.support.tools.jetifier.core.utils.LogConsumer
+import org.gradle.api.logging.Logger
+
+/**
+ * Logging adapter to hook jetfier logging into gradle.
+ */
+class JetifierLoggerAdapter(val gradleLogger: Logger) : LogConsumer {
+
+    override fun error(message: String) {
+        gradleLogger.error(message)
+    }
+
+    override fun info(message: String) {
+        gradleLogger.info(message)
+    }
+
+    override fun verbose(message: String) {
+        gradleLogger.info(message)
+    }
+
+    override fun debug(message: String) {
+        gradleLogger.debug(message)
+    }
+
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/gradle-plugin/src/main/kotlin/android/support/tools/jetifier/plugin/gradle/JetifierPlugin.kt b/jetifier/jetifier/gradle-plugin/src/main/kotlin/android/support/tools/jetifier/plugin/gradle/JetifierPlugin.kt
new file mode 100644
index 0000000..109e4c4
--- /dev/null
+++ b/jetifier/jetifier/gradle-plugin/src/main/kotlin/android/support/tools/jetifier/plugin/gradle/JetifierPlugin.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.plugin.gradle
+
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+
+/**
+ * This serves as the main entry point of this plugin and registers the extension object.
+ */
+open class JetifierPlugin : Plugin<Project>  {
+
+    companion object {
+        const val GROOVY_OBJECT_NAME : String = "jetifier"
+    }
+
+    override fun apply(project: Project) {
+        project.extensions.create(GROOVY_OBJECT_NAME, JetifierExtension::class.java, project)
+    }
+
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/gradle-plugin/src/main/kotlin/android/support/tools/jetifier/plugin/gradle/JetifyGlobalTask.kt b/jetifier/jetifier/gradle-plugin/src/main/kotlin/android/support/tools/jetifier/plugin/gradle/JetifyGlobalTask.kt
new file mode 100644
index 0000000..4d35625
--- /dev/null
+++ b/jetifier/jetifier/gradle-plugin/src/main/kotlin/android/support/tools/jetifier/plugin/gradle/JetifyGlobalTask.kt
@@ -0,0 +1,136 @@
+package android.support.tools.jetifier.plugin.gradle
+
+import android.support.tools.jetifier.core.config.ConfigParser
+import org.gradle.api.DefaultTask
+import org.gradle.api.Project
+import org.gradle.api.artifacts.Configuration
+import org.gradle.api.artifacts.Dependency
+import org.gradle.api.artifacts.FileCollectionDependency
+import org.gradle.api.artifacts.ProjectDependency
+import org.gradle.api.logging.LogLevel
+import org.gradle.api.tasks.TaskAction
+import java.io.File
+
+/**
+ * Task that processes whole configurations. This is the recommended way if processing a set of
+ * dependencies where it is unknown which exactly need to be rewritten.
+ *
+ * This will create a new detached configuration and resolve all the dependencies = obtaining
+ * all the files. Jetifier is then run with all the files and only the files that were rewritten
+ * are added to the given configuration and the original dependencies that didn't have to be
+ * changed are kept.
+ *
+ * Advantage is that all the dependencies that didn't have to be changed are kept intact so
+ * their artifactsIds and groupIds are kept (instead of replacing them with files) which allows
+ * other steps in the build process to use the artifacts information to generate pom files
+ * and other stuff.
+ *
+ * This will NOT resolve the given configurations as the dependencies are resolved in a detached
+ * configuration. If you give it a configuration that was already resolved the process will
+ * end up with exception saying that resolved configuration cannot be changed. This is expected
+ * as Jetifier cannot add new files to an already resolved configuration.
+ *
+ * Example usage in Gradle:
+ * jetifier.addConfigurationToProcess(configurations.implementation)
+ * afterEvaluate {
+ *   tasks.preBuild.dependsOn tasks.jetifyGlobal
+ * }
+ *
+ * TODO: Add caching for this task
+ */
+open class JetifyGlobalTask : DefaultTask() {
+
+    companion object {
+        const val TASK_NAME = "jetifyGlobal"
+        const val GROUP_ID = "Pre-build"
+        // TODO: Get back to this once the name of the library is decided.
+        const val DESCRIPTION = "Rewrites input libraries to run with jetpack"
+
+        const val OUTPUT_DIR_APPENDIX = "jetifier"
+
+        fun resolveTask(project: Project) : JetifyGlobalTask {
+            val task = project.tasks.findByName(TASK_NAME) as? JetifyGlobalTask
+            if (task != null) {
+                return task
+            }
+            return project.tasks.create(TASK_NAME, JetifyGlobalTask::class.java)
+        }
+    }
+
+    private var configurationsToProcess = mutableListOf<Configuration>()
+
+    private val outputDir = File(project.buildDir, OUTPUT_DIR_APPENDIX)
+
+
+    override fun getGroup() = GROUP_ID
+
+    override fun getDescription() = DESCRIPTION
+
+    /**
+     * Add a whole configuration to be processed by Jetifier.
+     *
+     * See [JetifierExtension] for details on how to use this.
+     */
+    fun addConfigurationToProcess(config: Configuration) {
+        configurationsToProcess.add(config)
+    }
+
+    @TaskAction
+    @Throws(Exception::class)
+    fun run() {
+        val config = ConfigParser.loadConfigOrFail(TasksCommon.configFilePath)
+
+        val dependenciesMap = mutableMapOf<File, MutableSet<Dependency>>()
+        // Build a map where for each file we have a set of dependencies that pulled that file in.
+        configurationsToProcess.forEach { conf ->
+            for (dep in conf.dependencies) {
+                if (dep is ProjectDependency) {
+                    project.logger.log(LogLevel.DEBUG, "Ignoring project dependency {}", dep.name)
+                    continue
+                }
+
+                val fileDep = dep as? FileCollectionDependency
+                if (fileDep != null) {
+                    fileDep.files.forEach {
+                        dependenciesMap
+                            .getOrPut(it, { mutableSetOf<Dependency>() } )
+                            .add(fileDep)
+                    }
+                } else {
+                    if (TasksCommon.shouldSkipArtifact(dep.name, dep.group, config)) {
+                        project.logger.log(
+                            LogLevel.DEBUG, "Skipping rewriting of support library {}:{}:{}",
+                            dep.group, dep.name, dep.version)
+                        continue
+                    }
+
+                    val detached = project.configurations.detachedConfiguration()
+                    detached.dependencies.add(dep)
+                    detached.resolvedConfiguration.resolvedArtifacts.forEach {
+                        dependenciesMap
+                            .getOrPut(it.file, { mutableSetOf<Dependency>() } )
+                            .add(dep)
+                    }
+                }
+            }
+        }
+
+        // Process the files using Jetifier
+        val result = TasksCommon.processFiles(config, dependenciesMap.keys, project.logger, outputDir)
+
+        // Apply changes
+        configurationsToProcess.forEach { conf ->
+            // Apply that on our set
+            result.filesToRemove.forEach { file ->
+                dependenciesMap[file]!!.forEach {
+                    conf.dependencies.remove(it)
+                }
+            }
+
+            result.filesToAdd.forEach {
+                project.dependencies.add(conf.name, project.files(it))
+            }
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/gradle-plugin/src/main/kotlin/android/support/tools/jetifier/plugin/gradle/JetifyLibsTask.kt b/jetifier/jetifier/gradle-plugin/src/main/kotlin/android/support/tools/jetifier/plugin/gradle/JetifyLibsTask.kt
new file mode 100644
index 0000000..873c8ec
--- /dev/null
+++ b/jetifier/jetifier/gradle-plugin/src/main/kotlin/android/support/tools/jetifier/plugin/gradle/JetifyLibsTask.kt
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.plugin.gradle
+
+import android.support.tools.jetifier.core.config.ConfigParser
+import org.gradle.api.DefaultTask
+import org.gradle.api.Project
+import org.gradle.api.file.FileCollection
+import org.gradle.api.tasks.InputFiles
+import org.gradle.api.tasks.OutputDirectory
+import org.gradle.api.tasks.TaskAction
+import java.io.File
+
+/**
+ * Task that processes given file collections using Jetifier.
+ *
+ * This task also utilizes Gradle caching so it's run only when needed.
+ *
+ * Example usage in Gradle:
+ * dependencies {
+ *   compile jetifier.process('groupId:artifactId:1.0')
+ * }
+ */
+open class JetifyLibsTask : DefaultTask() {
+
+    companion object {
+        const val TASK_NAME = "jetifyLibs"
+        const val GROUP_ID = "Pre-build"
+        // TODO: Get back to this once the name of the library is decided.
+        const val DESCRIPTION = "Rewrites input libraries to run with jetpack"
+
+        const val OUTPUT_DIR_APPENDIX = "jetifier"
+
+
+        fun resolveTask(project: Project) : JetifyLibsTask {
+            val task = project.tasks.findByName(TASK_NAME) as? JetifyLibsTask
+            if (task != null) {
+                return task
+            }
+            return project.tasks.create(TASK_NAME, JetifyLibsTask::class.java)
+        }
+
+    }
+
+    private var filesToProcess : FileCollection = project.files()
+
+    private val outputDir = File(project.buildDir, OUTPUT_DIR_APPENDIX)
+
+
+    override fun getGroup() = GROUP_ID
+
+    override fun getDescription() = DESCRIPTION
+
+    /**
+     * Adds individual files collection to be processed by Jetifier.
+     *
+     * See [JetifierExtension] for details on how to use this.
+     */
+    fun addFilesToProcess(files: FileCollection) : FileCollection {
+        filesToProcess = filesToProcess.plus(files)
+        return project.files(files.map { File(outputDir, it.name) }.toList())
+    }
+
+    /**
+     * Used by Gradle to figure out whether this task should be re-run. If the result of this method
+     * is different then the task is re-run.
+     */
+    @InputFiles
+    fun getInputFiles() : FileCollection {
+        return filesToProcess
+    }
+
+    /**
+     * Used by Gradle to figure out whether this task should be re-run and if other tasks that are
+     * relying on files from this directory should be re-run. Actually not having this and only
+     * having [InputFiles] annotation would disable the whole incremental mechanism for this task
+     * and lead to constant re-runs.
+     */
+    @OutputDirectory
+    fun getOutputDir() : File {
+        return outputDir
+    }
+
+    @TaskAction
+    @Throws(Exception::class)
+    fun run() {
+        val config = ConfigParser.loadConfigOrFail(TasksCommon.configFilePath)
+
+        // Process the files using Jetifier
+        TasksCommon.processFiles(config, filesToProcess.toSet(), project.logger, outputDir)
+    }
+
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/gradle-plugin/src/main/kotlin/android/support/tools/jetifier/plugin/gradle/TasksCommon.kt b/jetifier/jetifier/gradle-plugin/src/main/kotlin/android/support/tools/jetifier/plugin/gradle/TasksCommon.kt
new file mode 100644
index 0000000..dcc6375
--- /dev/null
+++ b/jetifier/jetifier/gradle-plugin/src/main/kotlin/android/support/tools/jetifier/plugin/gradle/TasksCommon.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.plugin.gradle
+
+import android.support.tools.jetifier.core.Processor
+import android.support.tools.jetifier.core.TransformationResult
+import android.support.tools.jetifier.core.config.Config
+import android.support.tools.jetifier.core.utils.Log
+import org.gradle.api.logging.LogLevel
+import org.gradle.api.logging.Logger
+import java.io.File
+import java.nio.file.Path
+
+class TasksCommon {
+
+    companion object {
+
+        var configFilePath: Path? = null
+
+
+        fun processFiles(config: Config, filesToProcess: Set<File>, logger: Logger, outputDir: File) : TransformationResult {
+            outputDir.mkdirs()
+
+            logger.log(LogLevel.DEBUG, "Jetifier will now process the following files:")
+            filesToProcess.forEach {
+                logger.log(LogLevel.DEBUG, it.absolutePath)
+            }
+
+            // Hook to the gradle logger
+            Log.logConsumer = JetifierLoggerAdapter(logger)
+
+            val processor = Processor(config)
+            return processor.transform(filesToProcess, outputDir.toPath())
+        }
+
+        fun shouldSkipArtifact(artifactId: String, groupId: String?, config: Config) : Boolean {
+            return config.pomRewriteRules.any {
+                it.from.artifactId == artifactId && it.from.groupId == groupId
+            }
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/gradle-plugin/src/main/resources/META-INF/gradle-plugins/androidx.tools.jetifier.properties b/jetifier/jetifier/gradle-plugin/src/main/resources/META-INF/gradle-plugins/androidx.tools.jetifier.properties
new file mode 100644
index 0000000..7ee7e73
--- /dev/null
+++ b/jetifier/jetifier/gradle-plugin/src/main/resources/META-INF/gradle-plugins/androidx.tools.jetifier.properties
@@ -0,0 +1,17 @@
+#
+# 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
+#
+
+implementation-class=android.support.tools.jetifier.plugin.gradle.JetifierPlugin
\ No newline at end of file
diff --git a/jetifier/jetifier/gradle/wrapper/gradle-wrapper.jar b/jetifier/jetifier/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..2411732
--- /dev/null
+++ b/jetifier/jetifier/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/jetifier/jetifier/gradle/wrapper/gradle-wrapper.properties b/jetifier/jetifier/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..452b52a
--- /dev/null
+++ b/jetifier/jetifier/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=../../../../../../tools/external/gradle/gradle-4.3-bin.zip
diff --git a/jetifier/jetifier/gradlew b/jetifier/jetifier/gradlew
new file mode 100755
index 0000000..04fca86
--- /dev/null
+++ b/jetifier/jetifier/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+    echo "$*"
+}
+
+die ( ) {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+  NONSTOP* )
+    nonstop=true
+    ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Escape application args
+save ( ) {
+    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+    echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when runTransform from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+  cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/jetifier/jetifier/gradlew.bat b/jetifier/jetifier/gradlew.bat
new file mode 100644
index 0000000..e95643d
--- /dev/null
+++ b/jetifier/jetifier/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off

+@rem ##########################################################################

+@rem

+@rem  Gradle startup script for Windows

+@rem

+@rem ##########################################################################

+

+@rem Set local scope for the variables with windows NT shell

+if "%OS%"=="Windows_NT" setlocal

+

+set DIRNAME=%~dp0

+if "%DIRNAME%" == "" set DIRNAME=.

+set APP_BASE_NAME=%~n0

+set APP_HOME=%DIRNAME%

+

+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.

+set DEFAULT_JVM_OPTS=

+

+@rem Find java.exe

+if defined JAVA_HOME goto findJavaFromJavaHome

+

+set JAVA_EXE=java.exe

+%JAVA_EXE% -version >NUL 2>&1

+if "%ERRORLEVEL%" == "0" goto init

+

+echo.

+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

+echo.

+echo Please set the JAVA_HOME variable in your environment to match the

+echo location of your Java installation.

+

+goto fail

+

+:findJavaFromJavaHome

+set JAVA_HOME=%JAVA_HOME:"=%

+set JAVA_EXE=%JAVA_HOME%/bin/java.exe

+

+if exist "%JAVA_EXE%" goto init

+

+echo.

+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%

+echo.

+echo Please set the JAVA_HOME variable in your environment to match the

+echo location of your Java installation.

+

+goto fail

+

+:init

+@rem Get command-line arguments, handling Windows variants

+

+if not "%OS%" == "Windows_NT" goto win9xME_args

+

+:win9xME_args

+@rem Slurp the command line arguments.

+set CMD_LINE_ARGS=

+set _SKIP=2

+

+:win9xME_args_slurp

+if "x%~1" == "x" goto execute

+

+set CMD_LINE_ARGS=%*

+

+:execute

+@rem Setup the command line

+

+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar

+

+@rem Execute Gradle

+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%

+

+:end

+@rem End local scope for the variables with windows NT shell

+if "%ERRORLEVEL%"=="0" goto mainEnd

+

+:fail

+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of

+rem the _cmd.exe /c_ return code!

+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1

+exit /b 1

+

+:mainEnd

+if "%OS%"=="Windows_NT" endlocal

+

+:omega

diff --git a/jetifier/jetifier/preprocessor/build.gradle b/jetifier/jetifier/preprocessor/build.gradle
new file mode 100644
index 0000000..b688a9d
--- /dev/null
+++ b/jetifier/jetifier/preprocessor/build.gradle
@@ -0,0 +1,26 @@
+/*
+ * 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
+ */
+
+version '1.0'
+
+apply plugin: "application"
+
+mainClassName = "android.support.tools.jetifier.preprocessor.MainKt"
+
+dependencies {
+    compile project(':core')
+    compile group: 'commons-cli', name: 'commons-cli', version: '1.3.1'
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/preprocessor/scripts/processDefaultConfig.sh b/jetifier/jetifier/preprocessor/scripts/processDefaultConfig.sh
new file mode 100644
index 0000000..d451c76
--- /dev/null
+++ b/jetifier/jetifier/preprocessor/scripts/processDefaultConfig.sh
@@ -0,0 +1,77 @@
+#!/bin/sh
+
+# 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
+
+# WHAT IT DOES
+# Grabs all the support libraries and runs them through a preprocessor
+# using the default Jetifier config to generate the final mappings.
+
+ROOT_DIR=$(dirname $(readlink -f $0))
+OUT_DIR="$ROOT_DIR/out"
+TEMP_LOG="$OUT_DIR/tempLog"
+
+JETIFIER_DIR="$ROOT_DIR/../.."
+DEFAULT_CONFIG="$JETIFIER_DIR/core/src/main/resources/default.config"
+GENERATED_CONFIG="$JETIFIER_DIR/core/src/main/resources/default.generated.config"
+PREPROCESSOR_DISTRO_PATH="$JETIFIER_DIR/preprocessor/build/distributions/preprocessor-1.0.zip"
+PREPROCESSOR_BIN_PATH="$OUT_DIR/preprocessor-1.0/bin/preprocessor"
+SUPPORT_LIBS_DOWNLOADED="$OUT_DIR/supportLibs"
+
+GREEN='\033[0;32m'
+RED='\033[0;31m'
+NC='\033[0m' # No Color
+
+function exitAndFail() {
+	cat $TEMP_LOG
+	echo -e "${RED}FAILED${NC}"
+	exit 1
+}
+
+function printSectionStart() {
+	echo ""
+	echo "======================================================"
+	echo "$1"
+	echo "======================================================"
+}
+
+function printSuccess() {
+	echo -e "${GREEN}SUCCESS${NC}"
+}
+
+function buildProjectUsingGradle() {
+	cd $1
+	sh gradlew clean build $2 > $TEMP_LOG --stacktrace || exitAndFail 2>&1
+}
+
+
+rm -r $OUT_DIR
+mkdir $OUT_DIR
+echo "OUT dir is at '$OUT_DIR'"
+
+printSectionStart "Downloading all affected support libraries"
+wget -nd -i $ROOT_DIR/repo-links -P $SUPPORT_LIBS_DOWNLOADED
+
+printSectionStart "Preparing Jetifier"
+buildProjectUsingGradle $JETIFIER_DIR
+echo "[OK] Clean build done"
+
+unzip $PREPROCESSOR_DISTRO_PATH -d $OUT_DIR > /dev/null
+echo "[OK] Copied & unziped jetifier preprocessor"
+
+printSectionStart "Preprocessing mappings on support libraries"
+sh $PREPROCESSOR_BIN_PATH -i "$SUPPORT_LIBS_DOWNLOADED" -o "$GENERATED_CONFIG" -c "$DEFAULT_CONFIG" -l verbose || exitAndFail
+echo "[OK] Done, config generated into $GENERATED_CONFIG"
+
+printSuccess
diff --git a/jetifier/jetifier/preprocessor/scripts/repo-links b/jetifier/jetifier/preprocessor/scripts/repo-links
new file mode 100644
index 0000000..961c149
--- /dev/null
+++ b/jetifier/jetifier/preprocessor/scripts/repo-links
@@ -0,0 +1,52 @@
+# 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
+
+https://maven.google.com/com/android/support/animated-vector-drawable/27.0.1/animated-vector-drawable-27.0.1.aar
+https://maven.google.com/com/android/support/appcompat-v7/27.0.1/appcompat-v7-27.0.1.aar
+https://maven.google.com/com/android/support/cardview-v7/27.0.1/cardview-v7-27.0.1.aar
+https://maven.google.com/com/android/support/customtabs/27.0.1/customtabs-27.0.1.aar
+https://maven.google.com/com/android/support/design/27.0.1/design-27.0.1.aar
+https://maven.google.com/com/android/support/exifinterface/27.0.1/exifinterface-27.0.1.aar
+https://maven.google.com/com/android/support/gridlayout-v7/27.0.1/gridlayout-v7-27.0.1.aar
+https://maven.google.com/com/android/support/instantvideo/26.0.0-alpha1/instantvideo-26.0.0-alpha1.aar
+https://maven.google.com/com/android/support/leanback-v17/27.0.1/leanback-v17-27.0.1.aar
+https://maven.google.com/com/android/support/mediarouter-v7/27.0.1/mediarouter-v7-27.0.1.aar
+https://maven.google.com/com/android/support/multidex/1.0.2/multidex-1.0.2.aar
+https://maven.google.com/com/android/support/multidex-instrumentation/1.0.2/multidex-instrumentation-1.0.2.aar
+https://maven.google.com/com/android/support/palette-v7/27.0.1/palette-v7-27.0.1.aar
+https://maven.google.com/com/android/support/percent/27.0.1/percent-27.0.1.aar
+https://maven.google.com/com/android/support/preference-leanback-v17/27.0.1/preference-leanback-v17-27.0.1.aar
+https://maven.google.com/com/android/support/preference-v14/27.0.1/preference-v14-27.0.1.aar
+https://maven.google.com/com/android/support/preference-v7/27.0.1/preference-v7-27.0.1.aar
+https://maven.google.com/com/android/support/recommendation/27.0.1/recommendation-27.0.1.aar
+https://maven.google.com/com/android/support/recyclerview-v7/27.0.1/recyclerview-v7-27.0.1.aar
+https://maven.google.com/com/android/support/support-annotations/27.0.1/support-annotations-27.0.1.jar
+https://maven.google.com/com/android/support/support-compat/27.0.1/support-compat-27.0.1.aar
+https://maven.google.com/com/android/support/support-content/27.0.1/support-content-27.0.1.aar
+https://maven.google.com/com/android/support/support-core-ui/27.0.1/support-core-ui-27.0.1.aar
+https://maven.google.com/com/android/support/support-core-utils/27.0.1/support-core-utils-27.0.1.aar
+https://maven.google.com/com/android/support/support-dynamic-animation/27.0.1/support-dynamic-animation-27.0.1.aar
+https://maven.google.com/com/android/support/support-emoji/27.0.1/support-emoji-27.0.1.aar
+https://maven.google.com/com/android/support/support-emoji-appcompat/27.0.1/support-emoji-appcompat-27.0.1.aar
+https://maven.google.com/com/android/support/support-emoji-bundled/27.0.1/support-emoji-bundled-27.0.1.aar
+https://maven.google.com/com/android/support/support-fragment/27.0.1/support-fragment-27.0.1.aar
+https://maven.google.com/com/android/support/support-media-compat/27.0.1/support-media-compat-27.0.1.aar
+https://maven.google.com/com/android/support/support-tv-provider/27.0.1/support-tv-provider-27.0.1.aar
+https://maven.google.com/com/android/support/support-v13/27.0.1/support-v13-27.0.1.aar
+https://maven.google.com/com/android/support/support-v4/27.0.1/support-v4-27.0.1.aar
+https://maven.google.com/com/android/support/support-vector-drawable/27.0.1/support-vector-drawable-27.0.1.aar
+https://maven.google.com/com/android/support/transition/27.0.1/transition-27.0.1.aar
+https://maven.google.com/com/android/support/wear/27.0.1/wear-27.0.1.aar
+https://maven.google.com/com/android/support/wearable/27.0.1/wearable-27.0.1.aar
+https://maven.google.com/com/android/support/constraint/constraint-layout/1.0.2/constraint-layout-1.0.2.aar
diff --git a/jetifier/jetifier/preprocessor/src/main/kotlin/android/support/tools/jetifier/preprocessor/ConfigGenerator.kt b/jetifier/jetifier/preprocessor/src/main/kotlin/android/support/tools/jetifier/preprocessor/ConfigGenerator.kt
new file mode 100644
index 0000000..ec12ac8
--- /dev/null
+++ b/jetifier/jetifier/preprocessor/src/main/kotlin/android/support/tools/jetifier/preprocessor/ConfigGenerator.kt
@@ -0,0 +1,72 @@
+package android.support.tools.jetifier.preprocessor
+
+import android.support.tools.jetifier.core.archive.Archive
+import android.support.tools.jetifier.core.config.Config
+import android.support.tools.jetifier.core.config.ConfigParser
+import android.support.tools.jetifier.core.map.LibraryMapGenerator
+import java.io.File
+import java.nio.file.Path
+
+class ConfigGenerator {
+
+    companion object {
+        private const val LEGAL_NOTICE =
+            "# Copyright (C) 2017 The Android Open Source Project\n" +
+            "#\n" +
+            "# Licensed under the Apache License, Version 2.0 (the \"License\");\n" +
+            "# you may not use this file except in compliance with the License.\n" +
+            "# You may obtain a copy of the License at\n" +
+            "#\n" +
+            "#      http://www.apache.org/licenses/LICENSE-2.0\n" +
+            "#\n" +
+            "# Unless required by applicable law or agreed to in writing, software\n" +
+            "# distributed under the License is distributed on an \"AS IS\" BASIS,\n" +
+            "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n" +
+            "# See the License for the specific language governing permissions and\n" +
+            "# limitations under the License\n"
+
+        private const val GEN_NOTICE =
+            "# DO NOT EDIT MANUALLY! This file was auto-generated using Jetifier preprocessor.\n" +
+            "# To make some changes in the configuration edit \"default.config\" and run\n" +
+            "# preprocessor/scripts/processDefaultConfig.sh script to update this file.\n"
+    }
+
+    fun generateMapping(
+        config: Config,
+        inputLibraries: List<File>,
+        outputConfigPath: Path) {
+
+        val mapper = LibraryMapGenerator(config)
+        inputLibraries.forEach {
+            if (it.isDirectory) {
+                it.listFiles().forEach { fileInDir ->
+                    val library = Archive.Builder.extract(fileInDir)
+                    mapper.scanLibrary(library)
+                }
+            } else {
+                val library = Archive.Builder.extract(it)
+                mapper.scanLibrary(library)
+            }
+        }
+
+        val map = mapper.generateMap()
+        val newConfig = config.setNewMap(map)
+
+        saveConfigToFile(newConfig, outputConfigPath.toFile())
+    }
+
+    private fun saveConfigToFile(configToSave: Config, outputFile : File) {
+        val sb = StringBuilder()
+        sb.append(LEGAL_NOTICE)
+        sb.append("\n")
+        sb.append(GEN_NOTICE)
+        sb.append("\n")
+        sb.append(ConfigParser.writeToString(configToSave))
+
+        if (outputFile.exists()) {
+            outputFile.delete()
+        }
+        outputFile.createNewFile()
+        outputFile.writeText(sb.toString())
+    }
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/preprocessor/src/main/kotlin/android/support/tools/jetifier/preprocessor/Main.kt b/jetifier/jetifier/preprocessor/src/main/kotlin/android/support/tools/jetifier/preprocessor/Main.kt
new file mode 100644
index 0000000..58cb20f
--- /dev/null
+++ b/jetifier/jetifier/preprocessor/src/main/kotlin/android/support/tools/jetifier/preprocessor/Main.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.preprocessor
+
+import android.support.tools.jetifier.core.config.ConfigParser
+import android.support.tools.jetifier.core.utils.Log
+import org.apache.commons.cli.CommandLine
+import org.apache.commons.cli.DefaultParser
+import org.apache.commons.cli.HelpFormatter
+import org.apache.commons.cli.Option
+import org.apache.commons.cli.Options
+import org.apache.commons.cli.ParseException
+import java.io.File
+import java.nio.file.Paths
+
+class Main {
+
+    companion object {
+        const val TAG = "Main"
+        const val TOOL_NAME = "preprocessor"
+
+        val OPTIONS = Options()
+        val OPTION_INPUT_LIBS = createOption("i", "Input libraries paths", multiple = true)
+        val OPTION_INPUT_CONFIG = createOption("c", "Input config path")
+        val OPTION_OUTPUT_CONFIG = createOption("o", "Output config path")
+        val OPTION_LOG_LEVEL = createOption("l", "Logging level. debug, verbose, default",
+            isRequired = false)
+
+        private fun createOption(argName: String,
+                                 desc: String,
+                                 isRequired: Boolean = true,
+                                 multiple: Boolean = false) : Option {
+            val op = Option(argName, true, desc)
+            op.isRequired = isRequired
+            if (multiple) {
+                op.args = Option.UNLIMITED_VALUES
+            }
+            OPTIONS.addOption(op)
+            return op
+        }
+    }
+
+    fun run(args : Array<String>) {
+        val cmd = parseCmdLine(args)
+        if (cmd == null) {
+            System.exit(1)
+            return
+        }
+
+        Log.setLevel(cmd.getOptionValue(OPTION_LOG_LEVEL.opt))
+
+        val inputLibraries = cmd.getOptionValues(OPTION_INPUT_LIBS.opt).map { File(it) }
+        val inputConfigPath = Paths.get(cmd.getOptionValue(OPTION_INPUT_CONFIG.opt))
+        val outputConfigPath = Paths.get(cmd.getOptionValue(OPTION_OUTPUT_CONFIG.opt))
+
+        val config = ConfigParser.loadFromFile(inputConfigPath)
+        if (config == null) {
+            System.exit(1)
+            return
+        }
+
+        val generator = ConfigGenerator()
+        generator.generateMapping(config, inputLibraries, outputConfigPath)
+    }
+
+    private fun parseCmdLine(args : Array<String>) : CommandLine? {
+        try {
+            return DefaultParser().parse(OPTIONS, args)
+        } catch (e: ParseException) {
+            Log.e(TAG, e.message.orEmpty())
+            HelpFormatter().printHelp(TOOL_NAME, OPTIONS)
+        }
+        return null
+    }
+
+}
+
+
+fun main(args : Array<String>) {
+    Main().run(args)
+}
\ No newline at end of file
diff --git a/jetifier/jetifier/settings.gradle b/jetifier/jetifier/settings.gradle
new file mode 100644
index 0000000..7d13b73
--- /dev/null
+++ b/jetifier/jetifier/settings.gradle
@@ -0,0 +1,22 @@
+/*
+ * 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
+ */
+
+rootProject.name = 'jetifier'
+include 'core'
+include 'gradle-plugin'
+include 'standalone'
+include 'preprocessor'
+
diff --git a/jetifier/jetifier/standalone/build.gradle b/jetifier/jetifier/standalone/build.gradle
new file mode 100644
index 0000000..8814d50
--- /dev/null
+++ b/jetifier/jetifier/standalone/build.gradle
@@ -0,0 +1,27 @@
+/*
+ * 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
+ */
+
+version '1.0'
+
+apply plugin: "application"
+
+mainClassName = "android.support.tools.jetifier.standalone.MainKt"
+
+dependencies {
+    compile project(':core')
+    compile group: 'commons-cli', name: 'commons-cli', version: '1.3.1'
+}
+
diff --git a/jetifier/jetifier/standalone/src/main/kotlin/android/support/tools/jetifier/standalone/Main.kt b/jetifier/jetifier/standalone/src/main/kotlin/android/support/tools/jetifier/standalone/Main.kt
new file mode 100644
index 0000000..de310e4
--- /dev/null
+++ b/jetifier/jetifier/standalone/src/main/kotlin/android/support/tools/jetifier/standalone/Main.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.tools.jetifier.standalone
+
+import android.support.tools.jetifier.core.Processor
+import android.support.tools.jetifier.core.config.Config
+import android.support.tools.jetifier.core.config.ConfigParser
+import android.support.tools.jetifier.core.utils.Log
+import org.apache.commons.cli.CommandLine
+import org.apache.commons.cli.DefaultParser
+import org.apache.commons.cli.HelpFormatter
+import org.apache.commons.cli.Option
+import org.apache.commons.cli.Options
+import org.apache.commons.cli.ParseException
+import java.io.File
+import java.nio.file.Paths
+
+class Main {
+
+    companion object {
+        const val TAG = "Main"
+        const val TOOL_NAME = "standalone"
+
+        val OPTIONS = Options()
+        val OPTION_INPUT = createOption("i", "Input libraries paths", multiple = true)
+        val OPTION_OUTPUT = createOption("o", "Output config path")
+        val OPTION_CONFIG = createOption("c", "Input config path", isRequired = false)
+        val OPTION_LOG_LEVEL = createOption("l", "Logging level. debug, verbose, default",
+            isRequired = false)
+
+        private fun createOption(argName: String,
+                                 desc: String,
+                                 isRequired: Boolean = true,
+                                 multiple: Boolean = false) : Option {
+            val op = Option(argName, true, desc)
+            op.isRequired = isRequired
+            if (multiple) {
+                op.args = Option.UNLIMITED_VALUES
+            }
+            OPTIONS.addOption(op)
+            return op
+        }
+    }
+
+    fun run(args : Array<String>) {
+        val cmd = parseCmdLine(args)
+        if (cmd == null) {
+            System.exit(1)
+            return
+        }
+
+        Log.setLevel(cmd.getOptionValue(OPTION_LOG_LEVEL.opt))
+
+        val inputLibraries = cmd.getOptionValues(OPTION_INPUT.opt).map { File(it) }.toSet()
+        val outputPath = Paths.get(cmd.getOptionValue(OPTION_OUTPUT.opt))
+
+        val config : Config?
+        if (cmd.hasOption(OPTION_CONFIG.opt)) {
+            val configPath = Paths.get(cmd.getOptionValue(OPTION_CONFIG.opt))
+            config = ConfigParser.loadFromFile(configPath)
+        } else {
+            config = ConfigParser.loadDefaultConfig()
+        }
+
+        if (config == null) {
+            Log.e(TAG, "Failed to load the config file")
+            System.exit(1)
+            return
+        }
+
+        val processor = Processor(config)
+        processor.transform(inputLibraries, outputPath)
+    }
+
+    private fun parseCmdLine(args : Array<String>) : CommandLine? {
+        try {
+            return DefaultParser().parse(OPTIONS, args)
+        } catch (e: ParseException) {
+            Log.e(TAG, e.message.orEmpty())
+            HelpFormatter().printHelp(TOOL_NAME, OPTIONS)
+        }
+        return null
+    }
+
+}
+
+
+fun main(args : Array<String>) {
+    Main().run(args)
+}
\ No newline at end of file
diff --git a/v17/leanback/Android.mk b/leanback/Android.mk
similarity index 100%
rename from v17/leanback/Android.mk
rename to leanback/Android.mk
diff --git a/v17/leanback/AndroidManifest.xml b/leanback/AndroidManifest.xml
similarity index 100%
rename from v17/leanback/AndroidManifest.xml
rename to leanback/AndroidManifest.xml
diff --git a/v17/leanback/OWNERS b/leanback/OWNERS
similarity index 100%
rename from v17/leanback/OWNERS
rename to leanback/OWNERS
diff --git a/v17/leanback/api/26.0.0.ignore b/leanback/api/26.0.0.ignore
similarity index 100%
rename from v17/leanback/api/26.0.0.ignore
rename to leanback/api/26.0.0.ignore
diff --git a/v17/leanback/api/26.0.0.txt b/leanback/api/26.0.0.txt
similarity index 100%
rename from v17/leanback/api/26.0.0.txt
rename to leanback/api/26.0.0.txt
diff --git a/v17/leanback/api/26.1.0.ignore b/leanback/api/26.1.0.ignore
similarity index 100%
rename from v17/leanback/api/26.1.0.ignore
rename to leanback/api/26.1.0.ignore
diff --git a/v17/leanback/api/26.1.0.txt b/leanback/api/26.1.0.txt
similarity index 100%
rename from v17/leanback/api/26.1.0.txt
rename to leanback/api/26.1.0.txt
diff --git a/leanback/api/27.0.0.ignore b/leanback/api/27.0.0.ignore
new file mode 100644
index 0000000..35526be
--- /dev/null
+++ b/leanback/api/27.0.0.ignore
@@ -0,0 +1,9 @@
+5e0df8d
+9cc8101
+8be589f
+9acc564
+77de988
+3b4fb8e
+8ec3ebc
+f7a9631
+afc0ff4
diff --git a/v17/leanback/api/27.0.0.txt b/leanback/api/27.0.0.txt
similarity index 100%
rename from v17/leanback/api/27.0.0.txt
rename to leanback/api/27.0.0.txt
diff --git a/leanback/api/current.txt b/leanback/api/current.txt
new file mode 100644
index 0000000..b045d8f
--- /dev/null
+++ b/leanback/api/current.txt
@@ -0,0 +1,3104 @@
+package android.support.v17.leanback.app {
+
+  public final class BackgroundManager {
+    method public void attach(android.view.Window);
+    method public void attachToView(android.view.View);
+    method public void clearDrawable();
+    method public final int getColor();
+    method public deprecated android.graphics.drawable.Drawable getDefaultDimLayer();
+    method public deprecated android.graphics.drawable.Drawable getDimLayer();
+    method public android.graphics.drawable.Drawable getDrawable();
+    method public static android.support.v17.leanback.app.BackgroundManager getInstance(android.app.Activity);
+    method public boolean isAttached();
+    method public boolean isAutoReleaseOnStop();
+    method public void release();
+    method public void setAutoReleaseOnStop(boolean);
+    method public void setBitmap(android.graphics.Bitmap);
+    method public void setColor(int);
+    method public deprecated void setDimLayer(android.graphics.drawable.Drawable);
+    method public void setDrawable(android.graphics.drawable.Drawable);
+    method public void setThemeDrawableResourceId(int);
+  }
+
+  public deprecated class BaseFragment extends android.support.v17.leanback.app.BrandedFragment {
+    method protected java.lang.Object createEntranceTransition();
+    method public final android.support.v17.leanback.app.ProgressBarManager getProgressBarManager();
+    method protected void onEntranceTransitionEnd();
+    method protected void onEntranceTransitionPrepare();
+    method protected void onEntranceTransitionStart();
+    method public void prepareEntranceTransition();
+    method protected void runEntranceTransition(java.lang.Object);
+    method public void startEntranceTransition();
+  }
+
+  public class BaseSupportFragment extends android.support.v17.leanback.app.BrandedSupportFragment {
+    method protected java.lang.Object createEntranceTransition();
+    method public final android.support.v17.leanback.app.ProgressBarManager getProgressBarManager();
+    method protected void onEntranceTransitionEnd();
+    method protected void onEntranceTransitionPrepare();
+    method protected void onEntranceTransitionStart();
+    method public void prepareEntranceTransition();
+    method protected void runEntranceTransition(java.lang.Object);
+    method public void startEntranceTransition();
+  }
+
+  public deprecated class BrandedFragment extends android.app.Fragment {
+    ctor public BrandedFragment();
+    method public android.graphics.drawable.Drawable getBadgeDrawable();
+    method public int getSearchAffordanceColor();
+    method public android.support.v17.leanback.widget.SearchOrbView.Colors getSearchAffordanceColors();
+    method public java.lang.CharSequence getTitle();
+    method public android.view.View getTitleView();
+    method public android.support.v17.leanback.widget.TitleViewAdapter getTitleViewAdapter();
+    method public void installTitleView(android.view.LayoutInflater, android.view.ViewGroup, android.os.Bundle);
+    method public final boolean isShowingTitle();
+    method public android.view.View onInflateTitleView(android.view.LayoutInflater, android.view.ViewGroup, android.os.Bundle);
+    method public void setBadgeDrawable(android.graphics.drawable.Drawable);
+    method public void setOnSearchClickedListener(android.view.View.OnClickListener);
+    method public void setSearchAffordanceColor(int);
+    method public void setSearchAffordanceColors(android.support.v17.leanback.widget.SearchOrbView.Colors);
+    method public void setTitle(java.lang.CharSequence);
+    method public void setTitleView(android.view.View);
+    method public void showTitle(boolean);
+    method public void showTitle(int);
+  }
+
+  public class BrandedSupportFragment extends android.support.v4.app.Fragment {
+    ctor public BrandedSupportFragment();
+    method public android.graphics.drawable.Drawable getBadgeDrawable();
+    method public int getSearchAffordanceColor();
+    method public android.support.v17.leanback.widget.SearchOrbView.Colors getSearchAffordanceColors();
+    method public java.lang.CharSequence getTitle();
+    method public android.view.View getTitleView();
+    method public android.support.v17.leanback.widget.TitleViewAdapter getTitleViewAdapter();
+    method public void installTitleView(android.view.LayoutInflater, android.view.ViewGroup, android.os.Bundle);
+    method public final boolean isShowingTitle();
+    method public android.view.View onInflateTitleView(android.view.LayoutInflater, android.view.ViewGroup, android.os.Bundle);
+    method public void setBadgeDrawable(android.graphics.drawable.Drawable);
+    method public void setOnSearchClickedListener(android.view.View.OnClickListener);
+    method public void setSearchAffordanceColor(int);
+    method public void setSearchAffordanceColors(android.support.v17.leanback.widget.SearchOrbView.Colors);
+    method public void setTitle(java.lang.CharSequence);
+    method public void setTitleView(android.view.View);
+    method public void showTitle(boolean);
+    method public void showTitle(int);
+  }
+
+  public deprecated class BrowseFragment extends android.support.v17.leanback.app.BaseFragment {
+    ctor public BrowseFragment();
+    method public static android.os.Bundle createArgs(android.os.Bundle, java.lang.String, int);
+    method public void enableMainFragmentScaling(boolean);
+    method public deprecated void enableRowScaling(boolean);
+    method public android.support.v17.leanback.widget.ObjectAdapter getAdapter();
+    method public int getBrandColor();
+    method public android.support.v17.leanback.app.HeadersFragment getHeadersFragment();
+    method public int getHeadersState();
+    method public android.app.Fragment getMainFragment();
+    method public final android.support.v17.leanback.app.BrowseFragment.MainFragmentAdapterRegistry getMainFragmentRegistry();
+    method public android.support.v17.leanback.widget.OnItemViewClickedListener getOnItemViewClickedListener();
+    method public android.support.v17.leanback.widget.OnItemViewSelectedListener getOnItemViewSelectedListener();
+    method public android.support.v17.leanback.app.RowsFragment getRowsFragment();
+    method public int getSelectedPosition();
+    method public android.support.v17.leanback.widget.RowPresenter.ViewHolder getSelectedRowViewHolder();
+    method public final boolean isHeadersTransitionOnBackEnabled();
+    method public boolean isInHeadersTransition();
+    method public boolean isShowingHeaders();
+    method public android.support.v17.leanback.app.HeadersFragment onCreateHeadersFragment();
+    method public void setAdapter(android.support.v17.leanback.widget.ObjectAdapter);
+    method public void setBrandColor(int);
+    method public void setBrowseTransitionListener(android.support.v17.leanback.app.BrowseFragment.BrowseTransitionListener);
+    method public void setHeaderPresenterSelector(android.support.v17.leanback.widget.PresenterSelector);
+    method public void setHeadersState(int);
+    method public final void setHeadersTransitionOnBackEnabled(boolean);
+    method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.OnItemViewClickedListener);
+    method public void setOnItemViewSelectedListener(android.support.v17.leanback.widget.OnItemViewSelectedListener);
+    method public void setSelectedPosition(int);
+    method public void setSelectedPosition(int, boolean);
+    method public void setSelectedPosition(int, boolean, android.support.v17.leanback.widget.Presenter.ViewHolderTask);
+    method public void startHeadersTransition(boolean);
+    field public static final int HEADERS_DISABLED = 3; // 0x3
+    field public static final int HEADERS_ENABLED = 1; // 0x1
+    field public static final int HEADERS_HIDDEN = 2; // 0x2
+  }
+
+  public static deprecated class BrowseFragment.BrowseTransitionListener {
+    ctor public BrowseFragment.BrowseTransitionListener();
+    method public void onHeadersTransitionStart(boolean);
+    method public void onHeadersTransitionStop(boolean);
+  }
+
+  public static abstract deprecated class BrowseFragment.FragmentFactory<T extends android.app.Fragment> {
+    ctor public BrowseFragment.FragmentFactory();
+    method public abstract T createFragment(java.lang.Object);
+  }
+
+  public static abstract deprecated interface BrowseFragment.FragmentHost {
+    method public abstract void notifyDataReady(android.support.v17.leanback.app.BrowseFragment.MainFragmentAdapter);
+    method public abstract void notifyViewCreated(android.support.v17.leanback.app.BrowseFragment.MainFragmentAdapter);
+    method public abstract void showTitleView(boolean);
+  }
+
+  public static deprecated class BrowseFragment.ListRowFragmentFactory extends android.support.v17.leanback.app.BrowseFragment.FragmentFactory {
+    ctor public BrowseFragment.ListRowFragmentFactory();
+    method public android.support.v17.leanback.app.RowsFragment createFragment(java.lang.Object);
+  }
+
+  public static deprecated class BrowseFragment.MainFragmentAdapter<T extends android.app.Fragment> {
+    ctor public BrowseFragment.MainFragmentAdapter(T);
+    method public final T getFragment();
+    method public final android.support.v17.leanback.app.BrowseFragment.FragmentHost getFragmentHost();
+    method public boolean isScalingEnabled();
+    method public boolean isScrolling();
+    method public void onTransitionEnd();
+    method public boolean onTransitionPrepare();
+    method public void onTransitionStart();
+    method public void setAlignment(int);
+    method public void setEntranceTransitionState(boolean);
+    method public void setExpand(boolean);
+    method public void setScalingEnabled(boolean);
+  }
+
+  public static abstract deprecated interface BrowseFragment.MainFragmentAdapterProvider {
+    method public abstract android.support.v17.leanback.app.BrowseFragment.MainFragmentAdapter getMainFragmentAdapter();
+  }
+
+  public static final deprecated class BrowseFragment.MainFragmentAdapterRegistry {
+    ctor public BrowseFragment.MainFragmentAdapterRegistry();
+    method public android.app.Fragment createFragment(java.lang.Object);
+    method public void registerFragment(java.lang.Class, android.support.v17.leanback.app.BrowseFragment.FragmentFactory);
+  }
+
+  public static deprecated class BrowseFragment.MainFragmentRowsAdapter<T extends android.app.Fragment> {
+    ctor public BrowseFragment.MainFragmentRowsAdapter(T);
+    method public android.support.v17.leanback.widget.RowPresenter.ViewHolder findRowViewHolderByPosition(int);
+    method public final T getFragment();
+    method public int getSelectedPosition();
+    method public void setAdapter(android.support.v17.leanback.widget.ObjectAdapter);
+    method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.OnItemViewClickedListener);
+    method public void setOnItemViewSelectedListener(android.support.v17.leanback.widget.OnItemViewSelectedListener);
+    method public void setSelectedPosition(int, boolean, android.support.v17.leanback.widget.Presenter.ViewHolderTask);
+    method public void setSelectedPosition(int, boolean);
+  }
+
+  public static abstract deprecated interface BrowseFragment.MainFragmentRowsAdapterProvider {
+    method public abstract android.support.v17.leanback.app.BrowseFragment.MainFragmentRowsAdapter getMainFragmentRowsAdapter();
+  }
+
+  public class BrowseSupportFragment extends android.support.v17.leanback.app.BaseSupportFragment {
+    ctor public BrowseSupportFragment();
+    method public static android.os.Bundle createArgs(android.os.Bundle, java.lang.String, int);
+    method public void enableMainFragmentScaling(boolean);
+    method public deprecated void enableRowScaling(boolean);
+    method public android.support.v17.leanback.widget.ObjectAdapter getAdapter();
+    method public int getBrandColor();
+    method public int getHeadersState();
+    method public android.support.v17.leanback.app.HeadersSupportFragment getHeadersSupportFragment();
+    method public android.support.v4.app.Fragment getMainFragment();
+    method public final android.support.v17.leanback.app.BrowseSupportFragment.MainFragmentAdapterRegistry getMainFragmentRegistry();
+    method public android.support.v17.leanback.widget.OnItemViewClickedListener getOnItemViewClickedListener();
+    method public android.support.v17.leanback.widget.OnItemViewSelectedListener getOnItemViewSelectedListener();
+    method public android.support.v17.leanback.app.RowsSupportFragment getRowsSupportFragment();
+    method public int getSelectedPosition();
+    method public android.support.v17.leanback.widget.RowPresenter.ViewHolder getSelectedRowViewHolder();
+    method public final boolean isHeadersTransitionOnBackEnabled();
+    method public boolean isInHeadersTransition();
+    method public boolean isShowingHeaders();
+    method public android.support.v17.leanback.app.HeadersSupportFragment onCreateHeadersSupportFragment();
+    method public void setAdapter(android.support.v17.leanback.widget.ObjectAdapter);
+    method public void setBrandColor(int);
+    method public void setBrowseTransitionListener(android.support.v17.leanback.app.BrowseSupportFragment.BrowseTransitionListener);
+    method public void setHeaderPresenterSelector(android.support.v17.leanback.widget.PresenterSelector);
+    method public void setHeadersState(int);
+    method public final void setHeadersTransitionOnBackEnabled(boolean);
+    method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.OnItemViewClickedListener);
+    method public void setOnItemViewSelectedListener(android.support.v17.leanback.widget.OnItemViewSelectedListener);
+    method public void setSelectedPosition(int);
+    method public void setSelectedPosition(int, boolean);
+    method public void setSelectedPosition(int, boolean, android.support.v17.leanback.widget.Presenter.ViewHolderTask);
+    method public void startHeadersTransition(boolean);
+    field public static final int HEADERS_DISABLED = 3; // 0x3
+    field public static final int HEADERS_ENABLED = 1; // 0x1
+    field public static final int HEADERS_HIDDEN = 2; // 0x2
+  }
+
+  public static class BrowseSupportFragment.BrowseTransitionListener {
+    ctor public BrowseSupportFragment.BrowseTransitionListener();
+    method public void onHeadersTransitionStart(boolean);
+    method public void onHeadersTransitionStop(boolean);
+  }
+
+  public static abstract class BrowseSupportFragment.FragmentFactory<T extends android.support.v4.app.Fragment> {
+    ctor public BrowseSupportFragment.FragmentFactory();
+    method public abstract T createFragment(java.lang.Object);
+  }
+
+  public static abstract interface BrowseSupportFragment.FragmentHost {
+    method public abstract void notifyDataReady(android.support.v17.leanback.app.BrowseSupportFragment.MainFragmentAdapter);
+    method public abstract void notifyViewCreated(android.support.v17.leanback.app.BrowseSupportFragment.MainFragmentAdapter);
+    method public abstract void showTitleView(boolean);
+  }
+
+  public static class BrowseSupportFragment.ListRowFragmentFactory extends android.support.v17.leanback.app.BrowseSupportFragment.FragmentFactory {
+    ctor public BrowseSupportFragment.ListRowFragmentFactory();
+    method public android.support.v17.leanback.app.RowsSupportFragment createFragment(java.lang.Object);
+  }
+
+  public static class BrowseSupportFragment.MainFragmentAdapter<T extends android.support.v4.app.Fragment> {
+    ctor public BrowseSupportFragment.MainFragmentAdapter(T);
+    method public final T getFragment();
+    method public final android.support.v17.leanback.app.BrowseSupportFragment.FragmentHost getFragmentHost();
+    method public boolean isScalingEnabled();
+    method public boolean isScrolling();
+    method public void onTransitionEnd();
+    method public boolean onTransitionPrepare();
+    method public void onTransitionStart();
+    method public void setAlignment(int);
+    method public void setEntranceTransitionState(boolean);
+    method public void setExpand(boolean);
+    method public void setScalingEnabled(boolean);
+  }
+
+  public static abstract interface BrowseSupportFragment.MainFragmentAdapterProvider {
+    method public abstract android.support.v17.leanback.app.BrowseSupportFragment.MainFragmentAdapter getMainFragmentAdapter();
+  }
+
+  public static final class BrowseSupportFragment.MainFragmentAdapterRegistry {
+    ctor public BrowseSupportFragment.MainFragmentAdapterRegistry();
+    method public android.support.v4.app.Fragment createFragment(java.lang.Object);
+    method public void registerFragment(java.lang.Class, android.support.v17.leanback.app.BrowseSupportFragment.FragmentFactory);
+  }
+
+  public static class BrowseSupportFragment.MainFragmentRowsAdapter<T extends android.support.v4.app.Fragment> {
+    ctor public BrowseSupportFragment.MainFragmentRowsAdapter(T);
+    method public android.support.v17.leanback.widget.RowPresenter.ViewHolder findRowViewHolderByPosition(int);
+    method public final T getFragment();
+    method public int getSelectedPosition();
+    method public void setAdapter(android.support.v17.leanback.widget.ObjectAdapter);
+    method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.OnItemViewClickedListener);
+    method public void setOnItemViewSelectedListener(android.support.v17.leanback.widget.OnItemViewSelectedListener);
+    method public void setSelectedPosition(int, boolean, android.support.v17.leanback.widget.Presenter.ViewHolderTask);
+    method public void setSelectedPosition(int, boolean);
+  }
+
+  public static abstract interface BrowseSupportFragment.MainFragmentRowsAdapterProvider {
+    method public abstract android.support.v17.leanback.app.BrowseSupportFragment.MainFragmentRowsAdapter getMainFragmentRowsAdapter();
+  }
+
+  public deprecated class DetailsFragment extends android.support.v17.leanback.app.BaseFragment {
+    ctor public DetailsFragment();
+    method public android.support.v17.leanback.widget.ObjectAdapter getAdapter();
+    method public android.support.v17.leanback.widget.BaseOnItemViewClickedListener getOnItemViewClickedListener();
+    method public android.support.v17.leanback.widget.DetailsParallax getParallax();
+    method public android.support.v17.leanback.app.RowsFragment getRowsFragment();
+    method protected deprecated android.view.View inflateTitle(android.view.LayoutInflater, android.view.ViewGroup, android.os.Bundle);
+    method protected void onSetDetailsOverviewRowStatus(android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter, android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter.ViewHolder, int, int, int);
+    method protected void onSetRowStatus(android.support.v17.leanback.widget.RowPresenter, android.support.v17.leanback.widget.RowPresenter.ViewHolder, int, int, int);
+    method public void setAdapter(android.support.v17.leanback.widget.ObjectAdapter);
+    method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.BaseOnItemViewClickedListener);
+    method public void setOnItemViewSelectedListener(android.support.v17.leanback.widget.BaseOnItemViewSelectedListener);
+    method public void setSelectedPosition(int);
+    method public void setSelectedPosition(int, boolean);
+    method protected void setupDetailsOverviewRowPresenter(android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter);
+    method protected void setupPresenter(android.support.v17.leanback.widget.Presenter);
+  }
+
+  public deprecated class DetailsFragmentBackgroundController {
+    ctor public DetailsFragmentBackgroundController(android.support.v17.leanback.app.DetailsFragment);
+    method public boolean canNavigateToVideoFragment();
+    method public void enableParallax();
+    method public void enableParallax(android.graphics.drawable.Drawable, android.graphics.drawable.Drawable, android.support.v17.leanback.widget.ParallaxTarget.PropertyValuesHolderTarget);
+    method public final android.app.Fragment findOrCreateVideoFragment();
+    method public final android.graphics.drawable.Drawable getBottomDrawable();
+    method public final android.graphics.Bitmap getCoverBitmap();
+    method public final android.graphics.drawable.Drawable getCoverDrawable();
+    method public final int getParallaxDrawableMaxOffset();
+    method public final android.support.v17.leanback.media.PlaybackGlue getPlaybackGlue();
+    method public final int getSolidColor();
+    method public android.support.v17.leanback.media.PlaybackGlueHost onCreateGlueHost();
+    method public android.app.Fragment onCreateVideoFragment();
+    method public final void setCoverBitmap(android.graphics.Bitmap);
+    method public final void setParallaxDrawableMaxOffset(int);
+    method public final void setSolidColor(int);
+    method public void setupVideoPlayback(android.support.v17.leanback.media.PlaybackGlue);
+    method public final void switchToRows();
+    method public final void switchToVideo();
+  }
+
+  public class DetailsSupportFragment extends android.support.v17.leanback.app.BaseSupportFragment {
+    ctor public DetailsSupportFragment();
+    method public android.support.v17.leanback.widget.ObjectAdapter getAdapter();
+    method public android.support.v17.leanback.widget.BaseOnItemViewClickedListener getOnItemViewClickedListener();
+    method public android.support.v17.leanback.widget.DetailsParallax getParallax();
+    method public android.support.v17.leanback.app.RowsSupportFragment getRowsSupportFragment();
+    method protected deprecated android.view.View inflateTitle(android.view.LayoutInflater, android.view.ViewGroup, android.os.Bundle);
+    method protected void onSetDetailsOverviewRowStatus(android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter, android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter.ViewHolder, int, int, int);
+    method protected void onSetRowStatus(android.support.v17.leanback.widget.RowPresenter, android.support.v17.leanback.widget.RowPresenter.ViewHolder, int, int, int);
+    method public void setAdapter(android.support.v17.leanback.widget.ObjectAdapter);
+    method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.BaseOnItemViewClickedListener);
+    method public void setOnItemViewSelectedListener(android.support.v17.leanback.widget.BaseOnItemViewSelectedListener);
+    method public void setSelectedPosition(int);
+    method public void setSelectedPosition(int, boolean);
+    method protected void setupDetailsOverviewRowPresenter(android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter);
+    method protected void setupPresenter(android.support.v17.leanback.widget.Presenter);
+  }
+
+  public class DetailsSupportFragmentBackgroundController {
+    ctor public DetailsSupportFragmentBackgroundController(android.support.v17.leanback.app.DetailsSupportFragment);
+    method public boolean canNavigateToVideoSupportFragment();
+    method public void enableParallax();
+    method public void enableParallax(android.graphics.drawable.Drawable, android.graphics.drawable.Drawable, android.support.v17.leanback.widget.ParallaxTarget.PropertyValuesHolderTarget);
+    method public final android.support.v4.app.Fragment findOrCreateVideoSupportFragment();
+    method public final android.graphics.drawable.Drawable getBottomDrawable();
+    method public final android.graphics.Bitmap getCoverBitmap();
+    method public final android.graphics.drawable.Drawable getCoverDrawable();
+    method public final int getParallaxDrawableMaxOffset();
+    method public final android.support.v17.leanback.media.PlaybackGlue getPlaybackGlue();
+    method public final int getSolidColor();
+    method public android.support.v17.leanback.media.PlaybackGlueHost onCreateGlueHost();
+    method public android.support.v4.app.Fragment onCreateVideoSupportFragment();
+    method public final void setCoverBitmap(android.graphics.Bitmap);
+    method public final void setParallaxDrawableMaxOffset(int);
+    method public final void setSolidColor(int);
+    method public void setupVideoPlayback(android.support.v17.leanback.media.PlaybackGlue);
+    method public final void switchToRows();
+    method public final void switchToVideo();
+  }
+
+  public deprecated class ErrorFragment extends android.support.v17.leanback.app.BrandedFragment {
+    ctor public ErrorFragment();
+    method public android.graphics.drawable.Drawable getBackgroundDrawable();
+    method public android.view.View.OnClickListener getButtonClickListener();
+    method public java.lang.String getButtonText();
+    method public android.graphics.drawable.Drawable getImageDrawable();
+    method public java.lang.CharSequence getMessage();
+    method public boolean isBackgroundTranslucent();
+    method public void setBackgroundDrawable(android.graphics.drawable.Drawable);
+    method public void setButtonClickListener(android.view.View.OnClickListener);
+    method public void setButtonText(java.lang.String);
+    method public void setDefaultBackground(boolean);
+    method public void setImageDrawable(android.graphics.drawable.Drawable);
+    method public void setMessage(java.lang.CharSequence);
+  }
+
+  public class ErrorSupportFragment extends android.support.v17.leanback.app.BrandedSupportFragment {
+    ctor public ErrorSupportFragment();
+    method public android.graphics.drawable.Drawable getBackgroundDrawable();
+    method public android.view.View.OnClickListener getButtonClickListener();
+    method public java.lang.String getButtonText();
+    method public android.graphics.drawable.Drawable getImageDrawable();
+    method public java.lang.CharSequence getMessage();
+    method public boolean isBackgroundTranslucent();
+    method public void setBackgroundDrawable(android.graphics.drawable.Drawable);
+    method public void setButtonClickListener(android.view.View.OnClickListener);
+    method public void setButtonText(java.lang.String);
+    method public void setDefaultBackground(boolean);
+    method public void setImageDrawable(android.graphics.drawable.Drawable);
+    method public void setMessage(java.lang.CharSequence);
+  }
+
+  public deprecated class GuidedStepFragment extends android.app.Fragment {
+    ctor public GuidedStepFragment();
+    method public static int add(android.app.FragmentManager, android.support.v17.leanback.app.GuidedStepFragment);
+    method public static int add(android.app.FragmentManager, android.support.v17.leanback.app.GuidedStepFragment, int);
+    method public static int addAsRoot(android.app.Activity, android.support.v17.leanback.app.GuidedStepFragment, int);
+    method public void collapseAction(boolean);
+    method public void collapseSubActions();
+    method public void expandAction(android.support.v17.leanback.widget.GuidedAction, boolean);
+    method public void expandSubActions(android.support.v17.leanback.widget.GuidedAction);
+    method public android.support.v17.leanback.widget.GuidedAction findActionById(long);
+    method public int findActionPositionById(long);
+    method public android.support.v17.leanback.widget.GuidedAction findButtonActionById(long);
+    method public int findButtonActionPositionById(long);
+    method public void finishGuidedStepFragments();
+    method public android.view.View getActionItemView(int);
+    method public java.util.List<android.support.v17.leanback.widget.GuidedAction> getActions();
+    method public android.view.View getButtonActionItemView(int);
+    method public java.util.List<android.support.v17.leanback.widget.GuidedAction> getButtonActions();
+    method public static android.support.v17.leanback.app.GuidedStepFragment getCurrentGuidedStepFragment(android.app.FragmentManager);
+    method public android.support.v17.leanback.widget.GuidanceStylist getGuidanceStylist();
+    method public android.support.v17.leanback.widget.GuidedActionsStylist getGuidedActionsStylist();
+    method public android.support.v17.leanback.widget.GuidedActionsStylist getGuidedButtonActionsStylist();
+    method public int getSelectedActionPosition();
+    method public int getSelectedButtonActionPosition();
+    method public int getUiStyle();
+    method public boolean isExpanded();
+    method public boolean isFocusOutEndAllowed();
+    method public boolean isFocusOutStartAllowed();
+    method public boolean isSubActionsExpanded();
+    method public void notifyActionChanged(int);
+    method public void notifyButtonActionChanged(int);
+    method protected void onAddSharedElementTransition(android.app.FragmentTransaction, android.support.v17.leanback.app.GuidedStepFragment);
+    method public void onCreateActions(java.util.List<android.support.v17.leanback.widget.GuidedAction>, android.os.Bundle);
+    method public android.support.v17.leanback.widget.GuidedActionsStylist onCreateActionsStylist();
+    method public android.view.View onCreateBackgroundView(android.view.LayoutInflater, android.view.ViewGroup, android.os.Bundle);
+    method public void onCreateButtonActions(java.util.List<android.support.v17.leanback.widget.GuidedAction>, android.os.Bundle);
+    method public android.support.v17.leanback.widget.GuidedActionsStylist onCreateButtonActionsStylist();
+    method public android.support.v17.leanback.widget.GuidanceStylist.Guidance onCreateGuidance(android.os.Bundle);
+    method public android.support.v17.leanback.widget.GuidanceStylist onCreateGuidanceStylist();
+    method public void onGuidedActionClicked(android.support.v17.leanback.widget.GuidedAction);
+    method public void onGuidedActionEditCanceled(android.support.v17.leanback.widget.GuidedAction);
+    method public deprecated void onGuidedActionEdited(android.support.v17.leanback.widget.GuidedAction);
+    method public long onGuidedActionEditedAndProceed(android.support.v17.leanback.widget.GuidedAction);
+    method public void onGuidedActionFocused(android.support.v17.leanback.widget.GuidedAction);
+    method protected void onProvideFragmentTransitions();
+    method public int onProvideTheme();
+    method public boolean onSubGuidedActionClicked(android.support.v17.leanback.widget.GuidedAction);
+    method public void openInEditMode(android.support.v17.leanback.widget.GuidedAction);
+    method public void popBackStackToGuidedStepFragment(java.lang.Class, int);
+    method public void setActions(java.util.List<android.support.v17.leanback.widget.GuidedAction>);
+    method public void setActionsDiffCallback(android.support.v17.leanback.widget.DiffCallback<android.support.v17.leanback.widget.GuidedAction>);
+    method public void setButtonActions(java.util.List<android.support.v17.leanback.widget.GuidedAction>);
+    method public void setSelectedActionPosition(int);
+    method public void setSelectedButtonActionPosition(int);
+    method public void setUiStyle(int);
+    field public static final java.lang.String EXTRA_UI_STYLE = "uiStyle";
+    field public static final int UI_STYLE_ACTIVITY_ROOT = 2; // 0x2
+    field public static final deprecated int UI_STYLE_DEFAULT = 0; // 0x0
+    field public static final int UI_STYLE_ENTRANCE = 1; // 0x1
+    field public static final int UI_STYLE_REPLACE = 0; // 0x0
+  }
+
+  public class GuidedStepSupportFragment extends android.support.v4.app.Fragment {
+    ctor public GuidedStepSupportFragment();
+    method public static int add(android.support.v4.app.FragmentManager, android.support.v17.leanback.app.GuidedStepSupportFragment);
+    method public static int add(android.support.v4.app.FragmentManager, android.support.v17.leanback.app.GuidedStepSupportFragment, int);
+    method public static int addAsRoot(android.support.v4.app.FragmentActivity, android.support.v17.leanback.app.GuidedStepSupportFragment, int);
+    method public void collapseAction(boolean);
+    method public void collapseSubActions();
+    method public void expandAction(android.support.v17.leanback.widget.GuidedAction, boolean);
+    method public void expandSubActions(android.support.v17.leanback.widget.GuidedAction);
+    method public android.support.v17.leanback.widget.GuidedAction findActionById(long);
+    method public int findActionPositionById(long);
+    method public android.support.v17.leanback.widget.GuidedAction findButtonActionById(long);
+    method public int findButtonActionPositionById(long);
+    method public void finishGuidedStepSupportFragments();
+    method public android.view.View getActionItemView(int);
+    method public java.util.List<android.support.v17.leanback.widget.GuidedAction> getActions();
+    method public android.view.View getButtonActionItemView(int);
+    method public java.util.List<android.support.v17.leanback.widget.GuidedAction> getButtonActions();
+    method public static android.support.v17.leanback.app.GuidedStepSupportFragment getCurrentGuidedStepSupportFragment(android.support.v4.app.FragmentManager);
+    method public android.support.v17.leanback.widget.GuidanceStylist getGuidanceStylist();
+    method public android.support.v17.leanback.widget.GuidedActionsStylist getGuidedActionsStylist();
+    method public android.support.v17.leanback.widget.GuidedActionsStylist getGuidedButtonActionsStylist();
+    method public int getSelectedActionPosition();
+    method public int getSelectedButtonActionPosition();
+    method public int getUiStyle();
+    method public boolean isExpanded();
+    method public boolean isFocusOutEndAllowed();
+    method public boolean isFocusOutStartAllowed();
+    method public boolean isSubActionsExpanded();
+    method public void notifyActionChanged(int);
+    method public void notifyButtonActionChanged(int);
+    method protected void onAddSharedElementTransition(android.support.v4.app.FragmentTransaction, android.support.v17.leanback.app.GuidedStepSupportFragment);
+    method public void onCreateActions(java.util.List<android.support.v17.leanback.widget.GuidedAction>, android.os.Bundle);
+    method public android.support.v17.leanback.widget.GuidedActionsStylist onCreateActionsStylist();
+    method public android.view.View onCreateBackgroundView(android.view.LayoutInflater, android.view.ViewGroup, android.os.Bundle);
+    method public void onCreateButtonActions(java.util.List<android.support.v17.leanback.widget.GuidedAction>, android.os.Bundle);
+    method public android.support.v17.leanback.widget.GuidedActionsStylist onCreateButtonActionsStylist();
+    method public android.support.v17.leanback.widget.GuidanceStylist.Guidance onCreateGuidance(android.os.Bundle);
+    method public android.support.v17.leanback.widget.GuidanceStylist onCreateGuidanceStylist();
+    method public void onGuidedActionClicked(android.support.v17.leanback.widget.GuidedAction);
+    method public void onGuidedActionEditCanceled(android.support.v17.leanback.widget.GuidedAction);
+    method public deprecated void onGuidedActionEdited(android.support.v17.leanback.widget.GuidedAction);
+    method public long onGuidedActionEditedAndProceed(android.support.v17.leanback.widget.GuidedAction);
+    method public void onGuidedActionFocused(android.support.v17.leanback.widget.GuidedAction);
+    method protected void onProvideFragmentTransitions();
+    method public int onProvideTheme();
+    method public boolean onSubGuidedActionClicked(android.support.v17.leanback.widget.GuidedAction);
+    method public void openInEditMode(android.support.v17.leanback.widget.GuidedAction);
+    method public void popBackStackToGuidedStepSupportFragment(java.lang.Class, int);
+    method public void setActions(java.util.List<android.support.v17.leanback.widget.GuidedAction>);
+    method public void setActionsDiffCallback(android.support.v17.leanback.widget.DiffCallback<android.support.v17.leanback.widget.GuidedAction>);
+    method public void setButtonActions(java.util.List<android.support.v17.leanback.widget.GuidedAction>);
+    method public void setSelectedActionPosition(int);
+    method public void setSelectedButtonActionPosition(int);
+    method public void setUiStyle(int);
+    field public static final java.lang.String EXTRA_UI_STYLE = "uiStyle";
+    field public static final int UI_STYLE_ACTIVITY_ROOT = 2; // 0x2
+    field public static final deprecated int UI_STYLE_DEFAULT = 0; // 0x0
+    field public static final int UI_STYLE_ENTRANCE = 1; // 0x1
+    field public static final int UI_STYLE_REPLACE = 0; // 0x0
+  }
+
+  public deprecated class HeadersFragment extends android.app.Fragment {
+    ctor public HeadersFragment();
+    method public boolean isScrolling();
+    method public void onTransitionEnd();
+    method public void onTransitionStart();
+    method public void setOnHeaderClickedListener(android.support.v17.leanback.app.HeadersFragment.OnHeaderClickedListener);
+    method public void setOnHeaderViewSelectedListener(android.support.v17.leanback.app.HeadersFragment.OnHeaderViewSelectedListener);
+  }
+
+  public static abstract deprecated interface HeadersFragment.OnHeaderClickedListener {
+    method public abstract void onHeaderClicked(android.support.v17.leanback.widget.RowHeaderPresenter.ViewHolder, android.support.v17.leanback.widget.Row);
+  }
+
+  public static abstract deprecated interface HeadersFragment.OnHeaderViewSelectedListener {
+    method public abstract void onHeaderSelected(android.support.v17.leanback.widget.RowHeaderPresenter.ViewHolder, android.support.v17.leanback.widget.Row);
+  }
+
+  public class HeadersSupportFragment extends android.support.v4.app.Fragment {
+    ctor public HeadersSupportFragment();
+    method public boolean isScrolling();
+    method public void onTransitionEnd();
+    method public void onTransitionStart();
+    method public void setOnHeaderClickedListener(android.support.v17.leanback.app.HeadersSupportFragment.OnHeaderClickedListener);
+    method public void setOnHeaderViewSelectedListener(android.support.v17.leanback.app.HeadersSupportFragment.OnHeaderViewSelectedListener);
+  }
+
+  public static abstract interface HeadersSupportFragment.OnHeaderClickedListener {
+    method public abstract void onHeaderClicked(android.support.v17.leanback.widget.RowHeaderPresenter.ViewHolder, android.support.v17.leanback.widget.Row);
+  }
+
+  public static abstract interface HeadersSupportFragment.OnHeaderViewSelectedListener {
+    method public abstract void onHeaderSelected(android.support.v17.leanback.widget.RowHeaderPresenter.ViewHolder, android.support.v17.leanback.widget.Row);
+  }
+
+  public abstract deprecated class OnboardingFragment extends android.app.Fragment {
+    ctor public OnboardingFragment();
+    method public final int getArrowBackgroundColor();
+    method public final int getArrowColor();
+    method protected final int getCurrentPageIndex();
+    method public final int getDescriptionViewTextColor();
+    method public final int getDotBackgroundColor();
+    method public final int getIconResourceId();
+    method public final int getLogoResourceId();
+    method protected abstract int getPageCount();
+    method protected abstract java.lang.CharSequence getPageDescription(int);
+    method protected abstract java.lang.CharSequence getPageTitle(int);
+    method public final java.lang.CharSequence getStartButtonText();
+    method public final int getTitleViewTextColor();
+    method protected final boolean isLogoAnimationFinished();
+    method protected void moveToNextPage();
+    method protected void moveToPreviousPage();
+    method protected abstract android.view.View onCreateBackgroundView(android.view.LayoutInflater, android.view.ViewGroup);
+    method protected abstract android.view.View onCreateContentView(android.view.LayoutInflater, android.view.ViewGroup);
+    method protected android.animation.Animator onCreateDescriptionAnimator();
+    method protected android.animation.Animator onCreateEnterAnimation();
+    method protected abstract android.view.View onCreateForegroundView(android.view.LayoutInflater, android.view.ViewGroup);
+    method protected android.animation.Animator onCreateLogoAnimation();
+    method protected android.animation.Animator onCreateTitleAnimator();
+    method protected void onFinishFragment();
+    method protected void onLogoAnimationFinished();
+    method protected void onPageChanged(int, int);
+    method public int onProvideTheme();
+    method public void setArrowBackgroundColor(int);
+    method public void setArrowColor(int);
+    method public void setDescriptionViewTextColor(int);
+    method public void setDotBackgroundColor(int);
+    method public final void setIconResouceId(int);
+    method public final void setLogoResourceId(int);
+    method public void setStartButtonText(java.lang.CharSequence);
+    method public void setTitleViewTextColor(int);
+    method protected final void startEnterAnimation(boolean);
+  }
+
+  public abstract class OnboardingSupportFragment extends android.support.v4.app.Fragment {
+    ctor public OnboardingSupportFragment();
+    method public final int getArrowBackgroundColor();
+    method public final int getArrowColor();
+    method protected final int getCurrentPageIndex();
+    method public final int getDescriptionViewTextColor();
+    method public final int getDotBackgroundColor();
+    method public final int getIconResourceId();
+    method public final int getLogoResourceId();
+    method protected abstract int getPageCount();
+    method protected abstract java.lang.CharSequence getPageDescription(int);
+    method protected abstract java.lang.CharSequence getPageTitle(int);
+    method public final java.lang.CharSequence getStartButtonText();
+    method public final int getTitleViewTextColor();
+    method protected final boolean isLogoAnimationFinished();
+    method protected void moveToNextPage();
+    method protected void moveToPreviousPage();
+    method protected abstract android.view.View onCreateBackgroundView(android.view.LayoutInflater, android.view.ViewGroup);
+    method protected abstract android.view.View onCreateContentView(android.view.LayoutInflater, android.view.ViewGroup);
+    method protected android.animation.Animator onCreateDescriptionAnimator();
+    method protected android.animation.Animator onCreateEnterAnimation();
+    method protected abstract android.view.View onCreateForegroundView(android.view.LayoutInflater, android.view.ViewGroup);
+    method protected android.animation.Animator onCreateLogoAnimation();
+    method protected android.animation.Animator onCreateTitleAnimator();
+    method protected void onFinishFragment();
+    method protected void onLogoAnimationFinished();
+    method protected void onPageChanged(int, int);
+    method public int onProvideTheme();
+    method public void setArrowBackgroundColor(int);
+    method public void setArrowColor(int);
+    method public void setDescriptionViewTextColor(int);
+    method public void setDotBackgroundColor(int);
+    method public final void setIconResouceId(int);
+    method public final void setLogoResourceId(int);
+    method public void setStartButtonText(java.lang.CharSequence);
+    method public void setTitleViewTextColor(int);
+    method protected final void startEnterAnimation(boolean);
+  }
+
+  public deprecated class PlaybackFragment extends android.app.Fragment {
+    ctor public PlaybackFragment();
+    method public deprecated void fadeOut();
+    method public android.support.v17.leanback.widget.ObjectAdapter getAdapter();
+    method public int getBackgroundType();
+    method public android.support.v17.leanback.app.ProgressBarManager getProgressBarManager();
+    method public void hideControlsOverlay(boolean);
+    method public boolean isControlsOverlayAutoHideEnabled();
+    method public boolean isControlsOverlayVisible();
+    method public deprecated boolean isFadingEnabled();
+    method public void notifyPlaybackRowChanged();
+    method protected void onBufferingStateChanged(boolean);
+    method protected void onError(int, java.lang.CharSequence);
+    method protected void onVideoSizeChanged(int, int);
+    method public void setAdapter(android.support.v17.leanback.widget.ObjectAdapter);
+    method public void setBackgroundType(int);
+    method public void setControlsOverlayAutoHideEnabled(boolean);
+    method public deprecated void setFadingEnabled(boolean);
+    method public void setHostCallback(android.support.v17.leanback.media.PlaybackGlueHost.HostCallback);
+    method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.BaseOnItemViewClickedListener);
+    method public void setOnItemViewSelectedListener(android.support.v17.leanback.widget.BaseOnItemViewSelectedListener);
+    method public final void setOnKeyInterceptListener(android.view.View.OnKeyListener);
+    method public void setOnPlaybackItemViewClickedListener(android.support.v17.leanback.widget.BaseOnItemViewClickedListener);
+    method public void setPlaybackRow(android.support.v17.leanback.widget.Row);
+    method public void setPlaybackRowPresenter(android.support.v17.leanback.widget.PlaybackRowPresenter);
+    method public void setPlaybackSeekUiClient(android.support.v17.leanback.widget.PlaybackSeekUi.Client);
+    method public void setSelectedPosition(int);
+    method public void setSelectedPosition(int, boolean);
+    method public void showControlsOverlay(boolean);
+    method public void tickle();
+    field public static final int BG_DARK = 1; // 0x1
+    field public static final int BG_LIGHT = 2; // 0x2
+    field public static final int BG_NONE = 0; // 0x0
+  }
+
+  public deprecated class PlaybackFragmentGlueHost extends android.support.v17.leanback.media.PlaybackGlueHost implements android.support.v17.leanback.widget.PlaybackSeekUi {
+    ctor public PlaybackFragmentGlueHost(android.support.v17.leanback.app.PlaybackFragment);
+    method public void fadeOut();
+    method public void setPlaybackSeekUiClient(android.support.v17.leanback.widget.PlaybackSeekUi.Client);
+  }
+
+  public class PlaybackSupportFragment extends android.support.v4.app.Fragment {
+    ctor public PlaybackSupportFragment();
+    method public deprecated void fadeOut();
+    method public android.support.v17.leanback.widget.ObjectAdapter getAdapter();
+    method public int getBackgroundType();
+    method public android.support.v17.leanback.app.ProgressBarManager getProgressBarManager();
+    method public void hideControlsOverlay(boolean);
+    method public boolean isControlsOverlayAutoHideEnabled();
+    method public boolean isControlsOverlayVisible();
+    method public deprecated boolean isFadingEnabled();
+    method public void notifyPlaybackRowChanged();
+    method protected void onBufferingStateChanged(boolean);
+    method protected void onError(int, java.lang.CharSequence);
+    method protected void onVideoSizeChanged(int, int);
+    method public void setAdapter(android.support.v17.leanback.widget.ObjectAdapter);
+    method public void setBackgroundType(int);
+    method public void setControlsOverlayAutoHideEnabled(boolean);
+    method public deprecated void setFadingEnabled(boolean);
+    method public void setHostCallback(android.support.v17.leanback.media.PlaybackGlueHost.HostCallback);
+    method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.BaseOnItemViewClickedListener);
+    method public void setOnItemViewSelectedListener(android.support.v17.leanback.widget.BaseOnItemViewSelectedListener);
+    method public final void setOnKeyInterceptListener(android.view.View.OnKeyListener);
+    method public void setOnPlaybackItemViewClickedListener(android.support.v17.leanback.widget.BaseOnItemViewClickedListener);
+    method public void setPlaybackRow(android.support.v17.leanback.widget.Row);
+    method public void setPlaybackRowPresenter(android.support.v17.leanback.widget.PlaybackRowPresenter);
+    method public void setPlaybackSeekUiClient(android.support.v17.leanback.widget.PlaybackSeekUi.Client);
+    method public void setSelectedPosition(int);
+    method public void setSelectedPosition(int, boolean);
+    method public void showControlsOverlay(boolean);
+    method public void tickle();
+    field public static final int BG_DARK = 1; // 0x1
+    field public static final int BG_LIGHT = 2; // 0x2
+    field public static final int BG_NONE = 0; // 0x0
+  }
+
+  public class PlaybackSupportFragmentGlueHost extends android.support.v17.leanback.media.PlaybackGlueHost implements android.support.v17.leanback.widget.PlaybackSeekUi {
+    ctor public PlaybackSupportFragmentGlueHost(android.support.v17.leanback.app.PlaybackSupportFragment);
+    method public void fadeOut();
+    method public void setPlaybackSeekUiClient(android.support.v17.leanback.widget.PlaybackSeekUi.Client);
+  }
+
+  public final class ProgressBarManager {
+    ctor public ProgressBarManager();
+    method public void disableProgressBar();
+    method public void enableProgressBar();
+    method public long getInitialDelay();
+    method public void hide();
+    method public void setInitialDelay(long);
+    method public void setProgressBarView(android.view.View);
+    method public void setRootView(android.view.ViewGroup);
+    method public void show();
+  }
+
+  public deprecated class RowsFragment extends android.app.Fragment implements android.support.v17.leanback.app.BrowseFragment.MainFragmentAdapterProvider android.support.v17.leanback.app.BrowseFragment.MainFragmentRowsAdapterProvider {
+    ctor public RowsFragment();
+    method public deprecated void enableRowScaling(boolean);
+    method protected android.support.v17.leanback.widget.VerticalGridView findGridViewFromRoot(android.view.View);
+    method public android.support.v17.leanback.widget.RowPresenter.ViewHolder findRowViewHolderByPosition(int);
+    method public android.support.v17.leanback.app.BrowseFragment.MainFragmentAdapter getMainFragmentAdapter();
+    method public android.support.v17.leanback.app.BrowseFragment.MainFragmentRowsAdapter getMainFragmentRowsAdapter();
+    method public android.support.v17.leanback.widget.BaseOnItemViewClickedListener getOnItemViewClickedListener();
+    method public android.support.v17.leanback.widget.BaseOnItemViewSelectedListener getOnItemViewSelectedListener();
+    method public android.support.v17.leanback.widget.RowPresenter.ViewHolder getRowViewHolder(int);
+    method public boolean isScrolling();
+    method public void onTransitionEnd();
+    method public boolean onTransitionPrepare();
+    method public void setAlignment(int);
+    method public void setEntranceTransitionState(boolean);
+    method public void setExpand(boolean);
+    method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.BaseOnItemViewClickedListener);
+    method public void setOnItemViewSelectedListener(android.support.v17.leanback.widget.BaseOnItemViewSelectedListener);
+    method public void setSelectedPosition(int, boolean, android.support.v17.leanback.widget.Presenter.ViewHolderTask);
+  }
+
+  public static class RowsFragment.MainFragmentAdapter extends android.support.v17.leanback.app.BrowseFragment.MainFragmentAdapter {
+    ctor public RowsFragment.MainFragmentAdapter(android.support.v17.leanback.app.RowsFragment);
+  }
+
+  public static deprecated class RowsFragment.MainFragmentRowsAdapter extends android.support.v17.leanback.app.BrowseFragment.MainFragmentRowsAdapter {
+    ctor public RowsFragment.MainFragmentRowsAdapter(android.support.v17.leanback.app.RowsFragment);
+  }
+
+  public class RowsSupportFragment extends android.support.v4.app.Fragment implements android.support.v17.leanback.app.BrowseSupportFragment.MainFragmentAdapterProvider android.support.v17.leanback.app.BrowseSupportFragment.MainFragmentRowsAdapterProvider {
+    ctor public RowsSupportFragment();
+    method public deprecated void enableRowScaling(boolean);
+    method protected android.support.v17.leanback.widget.VerticalGridView findGridViewFromRoot(android.view.View);
+    method public android.support.v17.leanback.widget.RowPresenter.ViewHolder findRowViewHolderByPosition(int);
+    method public android.support.v17.leanback.app.BrowseSupportFragment.MainFragmentAdapter getMainFragmentAdapter();
+    method public android.support.v17.leanback.app.BrowseSupportFragment.MainFragmentRowsAdapter getMainFragmentRowsAdapter();
+    method public android.support.v17.leanback.widget.BaseOnItemViewClickedListener getOnItemViewClickedListener();
+    method public android.support.v17.leanback.widget.BaseOnItemViewSelectedListener getOnItemViewSelectedListener();
+    method public android.support.v17.leanback.widget.RowPresenter.ViewHolder getRowViewHolder(int);
+    method public boolean isScrolling();
+    method public void onTransitionEnd();
+    method public boolean onTransitionPrepare();
+    method public void setAlignment(int);
+    method public void setEntranceTransitionState(boolean);
+    method public void setExpand(boolean);
+    method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.BaseOnItemViewClickedListener);
+    method public void setOnItemViewSelectedListener(android.support.v17.leanback.widget.BaseOnItemViewSelectedListener);
+    method public void setSelectedPosition(int, boolean, android.support.v17.leanback.widget.Presenter.ViewHolderTask);
+  }
+
+  public static class RowsSupportFragment.MainFragmentAdapter extends android.support.v17.leanback.app.BrowseSupportFragment.MainFragmentAdapter {
+    ctor public RowsSupportFragment.MainFragmentAdapter(android.support.v17.leanback.app.RowsSupportFragment);
+  }
+
+  public static class RowsSupportFragment.MainFragmentRowsAdapter extends android.support.v17.leanback.app.BrowseSupportFragment.MainFragmentRowsAdapter {
+    ctor public RowsSupportFragment.MainFragmentRowsAdapter(android.support.v17.leanback.app.RowsSupportFragment);
+  }
+
+  public deprecated class SearchFragment extends android.app.Fragment {
+    ctor public SearchFragment();
+    method public static android.os.Bundle createArgs(android.os.Bundle, java.lang.String);
+    method public static android.os.Bundle createArgs(android.os.Bundle, java.lang.String, java.lang.String);
+    method public void displayCompletions(java.util.List<java.lang.String>);
+    method public void displayCompletions(android.view.inputmethod.CompletionInfo[]);
+    method public android.graphics.drawable.Drawable getBadgeDrawable();
+    method public android.content.Intent getRecognizerIntent();
+    method public android.support.v17.leanback.app.RowsFragment getRowsFragment();
+    method public java.lang.String getTitle();
+    method public static android.support.v17.leanback.app.SearchFragment newInstance(java.lang.String);
+    method public void setBadgeDrawable(android.graphics.drawable.Drawable);
+    method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.OnItemViewClickedListener);
+    method public void setOnItemViewSelectedListener(android.support.v17.leanback.widget.OnItemViewSelectedListener);
+    method public void setSearchAffordanceColors(android.support.v17.leanback.widget.SearchOrbView.Colors);
+    method public void setSearchAffordanceColorsInListening(android.support.v17.leanback.widget.SearchOrbView.Colors);
+    method public void setSearchQuery(java.lang.String, boolean);
+    method public void setSearchQuery(android.content.Intent, boolean);
+    method public void setSearchResultProvider(android.support.v17.leanback.app.SearchFragment.SearchResultProvider);
+    method public deprecated void setSpeechRecognitionCallback(android.support.v17.leanback.widget.SpeechRecognitionCallback);
+    method public void setTitle(java.lang.String);
+    method public void startRecognition();
+  }
+
+  public static abstract interface SearchFragment.SearchResultProvider {
+    method public abstract android.support.v17.leanback.widget.ObjectAdapter getResultsAdapter();
+    method public abstract boolean onQueryTextChange(java.lang.String);
+    method public abstract boolean onQueryTextSubmit(java.lang.String);
+  }
+
+  public class SearchSupportFragment extends android.support.v4.app.Fragment {
+    ctor public SearchSupportFragment();
+    method public static android.os.Bundle createArgs(android.os.Bundle, java.lang.String);
+    method public static android.os.Bundle createArgs(android.os.Bundle, java.lang.String, java.lang.String);
+    method public void displayCompletions(java.util.List<java.lang.String>);
+    method public void displayCompletions(android.view.inputmethod.CompletionInfo[]);
+    method public android.graphics.drawable.Drawable getBadgeDrawable();
+    method public android.content.Intent getRecognizerIntent();
+    method public android.support.v17.leanback.app.RowsSupportFragment getRowsSupportFragment();
+    method public java.lang.String getTitle();
+    method public static android.support.v17.leanback.app.SearchSupportFragment newInstance(java.lang.String);
+    method public void setBadgeDrawable(android.graphics.drawable.Drawable);
+    method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.OnItemViewClickedListener);
+    method public void setOnItemViewSelectedListener(android.support.v17.leanback.widget.OnItemViewSelectedListener);
+    method public void setSearchAffordanceColors(android.support.v17.leanback.widget.SearchOrbView.Colors);
+    method public void setSearchAffordanceColorsInListening(android.support.v17.leanback.widget.SearchOrbView.Colors);
+    method public void setSearchQuery(java.lang.String, boolean);
+    method public void setSearchQuery(android.content.Intent, boolean);
+    method public void setSearchResultProvider(android.support.v17.leanback.app.SearchSupportFragment.SearchResultProvider);
+    method public deprecated void setSpeechRecognitionCallback(android.support.v17.leanback.widget.SpeechRecognitionCallback);
+    method public void setTitle(java.lang.String);
+    method public void startRecognition();
+  }
+
+  public static abstract interface SearchSupportFragment.SearchResultProvider {
+    method public abstract android.support.v17.leanback.widget.ObjectAdapter getResultsAdapter();
+    method public abstract boolean onQueryTextChange(java.lang.String);
+    method public abstract boolean onQueryTextSubmit(java.lang.String);
+  }
+
+  public deprecated class VerticalGridFragment extends android.support.v17.leanback.app.BaseFragment {
+    ctor public VerticalGridFragment();
+    method public android.support.v17.leanback.widget.ObjectAdapter getAdapter();
+    method public android.support.v17.leanback.widget.VerticalGridPresenter getGridPresenter();
+    method public android.support.v17.leanback.widget.OnItemViewClickedListener getOnItemViewClickedListener();
+    method public void setAdapter(android.support.v17.leanback.widget.ObjectAdapter);
+    method public void setGridPresenter(android.support.v17.leanback.widget.VerticalGridPresenter);
+    method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.OnItemViewClickedListener);
+    method public void setOnItemViewSelectedListener(android.support.v17.leanback.widget.OnItemViewSelectedListener);
+    method public void setSelectedPosition(int);
+  }
+
+  public class VerticalGridSupportFragment extends android.support.v17.leanback.app.BaseSupportFragment {
+    ctor public VerticalGridSupportFragment();
+    method public android.support.v17.leanback.widget.ObjectAdapter getAdapter();
+    method public android.support.v17.leanback.widget.VerticalGridPresenter getGridPresenter();
+    method public android.support.v17.leanback.widget.OnItemViewClickedListener getOnItemViewClickedListener();
+    method public void setAdapter(android.support.v17.leanback.widget.ObjectAdapter);
+    method public void setGridPresenter(android.support.v17.leanback.widget.VerticalGridPresenter);
+    method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.OnItemViewClickedListener);
+    method public void setOnItemViewSelectedListener(android.support.v17.leanback.widget.OnItemViewSelectedListener);
+    method public void setSelectedPosition(int);
+  }
+
+  public deprecated class VideoFragment extends android.support.v17.leanback.app.PlaybackFragment {
+    ctor public VideoFragment();
+    method public android.view.SurfaceView getSurfaceView();
+    method public void setSurfaceHolderCallback(android.view.SurfaceHolder.Callback);
+  }
+
+  public deprecated class VideoFragmentGlueHost extends android.support.v17.leanback.app.PlaybackFragmentGlueHost implements android.support.v17.leanback.media.SurfaceHolderGlueHost {
+    ctor public VideoFragmentGlueHost(android.support.v17.leanback.app.VideoFragment);
+    method public void setSurfaceHolderCallback(android.view.SurfaceHolder.Callback);
+  }
+
+  public class VideoSupportFragment extends android.support.v17.leanback.app.PlaybackSupportFragment {
+    ctor public VideoSupportFragment();
+    method public android.view.SurfaceView getSurfaceView();
+    method public void setSurfaceHolderCallback(android.view.SurfaceHolder.Callback);
+  }
+
+  public class VideoSupportFragmentGlueHost extends android.support.v17.leanback.app.PlaybackSupportFragmentGlueHost implements android.support.v17.leanback.media.SurfaceHolderGlueHost {
+    ctor public VideoSupportFragmentGlueHost(android.support.v17.leanback.app.VideoSupportFragment);
+    method public void setSurfaceHolderCallback(android.view.SurfaceHolder.Callback);
+  }
+
+}
+
+package android.support.v17.leanback.database {
+
+  public abstract class CursorMapper {
+    ctor public CursorMapper();
+    method protected abstract java.lang.Object bind(android.database.Cursor);
+    method protected abstract void bindColumns(android.database.Cursor);
+    method public java.lang.Object convert(android.database.Cursor);
+  }
+
+}
+
+package android.support.v17.leanback.graphics {
+
+  public class BoundsRule {
+    ctor public BoundsRule();
+    ctor public BoundsRule(android.support.v17.leanback.graphics.BoundsRule);
+    method public void calculateBounds(android.graphics.Rect, android.graphics.Rect);
+    field public android.support.v17.leanback.graphics.BoundsRule.ValueRule bottom;
+    field public android.support.v17.leanback.graphics.BoundsRule.ValueRule left;
+    field public android.support.v17.leanback.graphics.BoundsRule.ValueRule right;
+    field public android.support.v17.leanback.graphics.BoundsRule.ValueRule top;
+  }
+
+  public static final class BoundsRule.ValueRule {
+    method public static android.support.v17.leanback.graphics.BoundsRule.ValueRule absoluteValue(int);
+    method public int getAbsoluteValue();
+    method public float getFraction();
+    method public static android.support.v17.leanback.graphics.BoundsRule.ValueRule inheritFromParent(float);
+    method public static android.support.v17.leanback.graphics.BoundsRule.ValueRule inheritFromParentWithOffset(float, int);
+    method public void setAbsoluteValue(int);
+    method public void setFraction(float);
+  }
+
+  public final class ColorFilterCache {
+    method public static android.support.v17.leanback.graphics.ColorFilterCache getColorFilterCache(int);
+    method public android.graphics.ColorFilter getFilterForLevel(float);
+  }
+
+  public final class ColorFilterDimmer {
+    method public void applyFilterToView(android.view.View);
+    method public static android.support.v17.leanback.graphics.ColorFilterDimmer create(android.support.v17.leanback.graphics.ColorFilterCache, float, float);
+    method public static android.support.v17.leanback.graphics.ColorFilterDimmer createDefault(android.content.Context);
+    method public android.graphics.ColorFilter getColorFilter();
+    method public android.graphics.Paint getPaint();
+    method public void setActiveLevel(float);
+  }
+
+  public final class ColorOverlayDimmer {
+    method public int applyToColor(int);
+    method public static android.support.v17.leanback.graphics.ColorOverlayDimmer createColorOverlayDimmer(int, float, float);
+    method public static android.support.v17.leanback.graphics.ColorOverlayDimmer createDefault(android.content.Context);
+    method public void drawColorOverlay(android.graphics.Canvas, android.view.View, boolean);
+    method public int getAlpha();
+    method public float getAlphaFloat();
+    method public android.graphics.Paint getPaint();
+    method public boolean needsDraw();
+    method public void setActiveLevel(float);
+  }
+
+  public class CompositeDrawable extends android.graphics.drawable.Drawable implements android.graphics.drawable.Drawable.Callback {
+    ctor public CompositeDrawable();
+    method public void addChildDrawable(android.graphics.drawable.Drawable);
+    method public void draw(android.graphics.Canvas);
+    method public android.support.v17.leanback.graphics.CompositeDrawable.ChildDrawable getChildAt(int);
+    method public int getChildCount();
+    method public android.graphics.drawable.Drawable getDrawable(int);
+    method public int getOpacity();
+    method public void invalidateDrawable(android.graphics.drawable.Drawable);
+    method public void removeChild(int);
+    method public void removeDrawable(android.graphics.drawable.Drawable);
+    method public void scheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable, long);
+    method public void setAlpha(int);
+    method public void setChildDrawableAt(int, android.graphics.drawable.Drawable);
+    method public void setColorFilter(android.graphics.ColorFilter);
+    method public void unscheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable);
+  }
+
+  public static final class CompositeDrawable.ChildDrawable {
+    ctor public CompositeDrawable.ChildDrawable(android.graphics.drawable.Drawable, android.support.v17.leanback.graphics.CompositeDrawable);
+    method public android.support.v17.leanback.graphics.BoundsRule getBoundsRule();
+    method public android.graphics.drawable.Drawable getDrawable();
+    method public void recomputeBounds();
+    field public static final android.util.Property<android.support.v17.leanback.graphics.CompositeDrawable.ChildDrawable, java.lang.Integer> BOTTOM_ABSOLUTE;
+    field public static final android.util.Property<android.support.v17.leanback.graphics.CompositeDrawable.ChildDrawable, java.lang.Float> BOTTOM_FRACTION;
+    field public static final android.util.Property<android.support.v17.leanback.graphics.CompositeDrawable.ChildDrawable, java.lang.Integer> LEFT_ABSOLUTE;
+    field public static final android.util.Property<android.support.v17.leanback.graphics.CompositeDrawable.ChildDrawable, java.lang.Float> LEFT_FRACTION;
+    field public static final android.util.Property<android.support.v17.leanback.graphics.CompositeDrawable.ChildDrawable, java.lang.Integer> RIGHT_ABSOLUTE;
+    field public static final android.util.Property<android.support.v17.leanback.graphics.CompositeDrawable.ChildDrawable, java.lang.Float> RIGHT_FRACTION;
+    field public static final android.util.Property<android.support.v17.leanback.graphics.CompositeDrawable.ChildDrawable, java.lang.Integer> TOP_ABSOLUTE;
+    field public static final android.util.Property<android.support.v17.leanback.graphics.CompositeDrawable.ChildDrawable, java.lang.Float> TOP_FRACTION;
+  }
+
+  public class FitWidthBitmapDrawable extends android.graphics.drawable.Drawable {
+    ctor public FitWidthBitmapDrawable();
+    method public void draw(android.graphics.Canvas);
+    method public android.graphics.Bitmap getBitmap();
+    method public int getOpacity();
+    method public android.graphics.Rect getSource();
+    method public int getVerticalOffset();
+    method public void setAlpha(int);
+    method public void setBitmap(android.graphics.Bitmap);
+    method public void setColorFilter(android.graphics.ColorFilter);
+    method public void setSource(android.graphics.Rect);
+    method public void setVerticalOffset(int);
+    field public static final android.util.Property<android.support.v17.leanback.graphics.FitWidthBitmapDrawable, java.lang.Integer> PROPERTY_VERTICAL_OFFSET;
+  }
+
+}
+
+package android.support.v17.leanback.media {
+
+  public class MediaControllerAdapter extends android.support.v17.leanback.media.PlayerAdapter {
+    ctor public MediaControllerAdapter(android.support.v4.media.session.MediaControllerCompat);
+    method public android.graphics.drawable.Drawable getMediaArt(android.content.Context);
+    method public android.support.v4.media.session.MediaControllerCompat getMediaController();
+    method public java.lang.CharSequence getMediaSubtitle();
+    method public java.lang.CharSequence getMediaTitle();
+    method public void pause();
+    method public void play();
+  }
+
+  public abstract deprecated class MediaControllerGlue extends android.support.v17.leanback.media.PlaybackControlGlue {
+    ctor public MediaControllerGlue(android.content.Context, int[], int[]);
+    method public void attachToMediaController(android.support.v4.media.session.MediaControllerCompat);
+    method public void detach();
+    method public int getCurrentPosition();
+    method public int getCurrentSpeedId();
+    method public android.graphics.drawable.Drawable getMediaArt();
+    method public final android.support.v4.media.session.MediaControllerCompat getMediaController();
+    method public int getMediaDuration();
+    method public java.lang.CharSequence getMediaSubtitle();
+    method public java.lang.CharSequence getMediaTitle();
+    method public long getSupportedActions();
+    method public boolean hasValidMedia();
+    method public boolean isMediaPlaying();
+  }
+
+  public class MediaPlayerAdapter extends android.support.v17.leanback.media.PlayerAdapter {
+    ctor public MediaPlayerAdapter(android.content.Context);
+    method protected boolean onError(int, int);
+    method protected boolean onInfo(int, int);
+    method protected void onSeekComplete();
+    method public void pause();
+    method public void play();
+    method public void release();
+    method public void reset();
+    method public boolean setDataSource(android.net.Uri);
+  }
+
+  public class PlaybackBannerControlGlue<T extends android.support.v17.leanback.media.PlayerAdapter> extends android.support.v17.leanback.media.PlaybackBaseControlGlue {
+    ctor public PlaybackBannerControlGlue(android.content.Context, int[], T);
+    ctor public PlaybackBannerControlGlue(android.content.Context, int[], int[], T);
+    method public int[] getFastForwardSpeeds();
+    method public int[] getRewindSpeeds();
+    method public void onActionClicked(android.support.v17.leanback.widget.Action);
+    method protected android.support.v17.leanback.widget.PlaybackRowPresenter onCreateRowPresenter();
+    method public boolean onKey(android.view.View, int, android.view.KeyEvent);
+    field public static final int ACTION_CUSTOM_LEFT_FIRST = 1; // 0x1
+    field public static final int ACTION_CUSTOM_RIGHT_FIRST = 4096; // 0x1000
+    field public static final int ACTION_FAST_FORWARD = 128; // 0x80
+    field public static final int ACTION_PLAY_PAUSE = 64; // 0x40
+    field public static final int ACTION_REWIND = 32; // 0x20
+    field public static final int ACTION_SKIP_TO_NEXT = 256; // 0x100
+    field public static final int ACTION_SKIP_TO_PREVIOUS = 16; // 0x10
+    field public static final int PLAYBACK_SPEED_FAST_L0 = 10; // 0xa
+    field public static final int PLAYBACK_SPEED_FAST_L1 = 11; // 0xb
+    field public static final int PLAYBACK_SPEED_FAST_L2 = 12; // 0xc
+    field public static final int PLAYBACK_SPEED_FAST_L3 = 13; // 0xd
+    field public static final int PLAYBACK_SPEED_FAST_L4 = 14; // 0xe
+    field public static final int PLAYBACK_SPEED_INVALID = -1; // 0xffffffff
+    field public static final int PLAYBACK_SPEED_NORMAL = 1; // 0x1
+    field public static final int PLAYBACK_SPEED_PAUSED = 0; // 0x0
+  }
+
+  public abstract class PlaybackBaseControlGlue<T extends android.support.v17.leanback.media.PlayerAdapter> extends android.support.v17.leanback.media.PlaybackGlue implements android.support.v17.leanback.widget.OnActionClickedListener android.view.View.OnKeyListener {
+    ctor public PlaybackBaseControlGlue(android.content.Context, T);
+    method public android.graphics.drawable.Drawable getArt();
+    method public final long getBufferedPosition();
+    method public android.support.v17.leanback.widget.PlaybackControlsRow getControlsRow();
+    method public long getCurrentPosition();
+    method public final long getDuration();
+    method public android.support.v17.leanback.widget.PlaybackRowPresenter getPlaybackRowPresenter();
+    method public final T getPlayerAdapter();
+    method public java.lang.CharSequence getSubtitle();
+    method public long getSupportedActions();
+    method public java.lang.CharSequence getTitle();
+    method public boolean isControlsOverlayAutoHideEnabled();
+    method public final boolean isPlaying();
+    method public final boolean isPrepared();
+    method protected static void notifyItemChanged(android.support.v17.leanback.widget.ArrayObjectAdapter, java.lang.Object);
+    method protected void onCreatePrimaryActions(android.support.v17.leanback.widget.ArrayObjectAdapter);
+    method protected abstract android.support.v17.leanback.widget.PlaybackRowPresenter onCreateRowPresenter();
+    method protected void onCreateSecondaryActions(android.support.v17.leanback.widget.ArrayObjectAdapter);
+    method protected void onMetadataChanged();
+    method protected void onPlayCompleted();
+    method protected void onPlayStateChanged();
+    method protected void onPreparedStateChanged();
+    method protected void onUpdateBufferedProgress();
+    method protected void onUpdateDuration();
+    method protected void onUpdateProgress();
+    method public final void seekTo(long);
+    method public void setArt(android.graphics.drawable.Drawable);
+    method public void setControlsOverlayAutoHideEnabled(boolean);
+    method public void setControlsRow(android.support.v17.leanback.widget.PlaybackControlsRow);
+    method public void setPlaybackRowPresenter(android.support.v17.leanback.widget.PlaybackRowPresenter);
+    method public void setSubtitle(java.lang.CharSequence);
+    method public void setTitle(java.lang.CharSequence);
+    field public static final int ACTION_CUSTOM_LEFT_FIRST = 1; // 0x1
+    field public static final int ACTION_CUSTOM_RIGHT_FIRST = 4096; // 0x1000
+    field public static final int ACTION_FAST_FORWARD = 128; // 0x80
+    field public static final int ACTION_PLAY_PAUSE = 64; // 0x40
+    field public static final int ACTION_REPEAT = 512; // 0x200
+    field public static final int ACTION_REWIND = 32; // 0x20
+    field public static final int ACTION_SHUFFLE = 1024; // 0x400
+    field public static final int ACTION_SKIP_TO_NEXT = 256; // 0x100
+    field public static final int ACTION_SKIP_TO_PREVIOUS = 16; // 0x10
+  }
+
+  public abstract class PlaybackControlGlue extends android.support.v17.leanback.media.PlaybackGlue implements android.support.v17.leanback.widget.OnActionClickedListener android.view.View.OnKeyListener {
+    ctor public PlaybackControlGlue(android.content.Context, int[]);
+    ctor public PlaybackControlGlue(android.content.Context, int[], int[]);
+    method public void enableProgressUpdating(boolean);
+    method public android.support.v17.leanback.widget.PlaybackControlsRow getControlsRow();
+    method public deprecated android.support.v17.leanback.widget.PlaybackControlsRowPresenter getControlsRowPresenter();
+    method public abstract int getCurrentPosition();
+    method public abstract int getCurrentSpeedId();
+    method public int[] getFastForwardSpeeds();
+    method public abstract android.graphics.drawable.Drawable getMediaArt();
+    method public abstract int getMediaDuration();
+    method public abstract java.lang.CharSequence getMediaSubtitle();
+    method public abstract java.lang.CharSequence getMediaTitle();
+    method public android.support.v17.leanback.widget.PlaybackRowPresenter getPlaybackRowPresenter();
+    method public int[] getRewindSpeeds();
+    method public abstract long getSupportedActions();
+    method public int getUpdatePeriod();
+    method public abstract boolean hasValidMedia();
+    method public boolean isFadingEnabled();
+    method public abstract boolean isMediaPlaying();
+    method public void onActionClicked(android.support.v17.leanback.widget.Action);
+    method protected void onCreateControlsRowAndPresenter();
+    method protected void onCreatePrimaryActions(android.support.v17.leanback.widget.SparseArrayObjectAdapter);
+    method protected void onCreateSecondaryActions(android.support.v17.leanback.widget.ArrayObjectAdapter);
+    method public boolean onKey(android.view.View, int, android.view.KeyEvent);
+    method protected void onMetadataChanged();
+    method protected void onStateChanged();
+    method public void play(int);
+    method public final void play();
+    method public void setControlsRow(android.support.v17.leanback.widget.PlaybackControlsRow);
+    method public deprecated void setControlsRowPresenter(android.support.v17.leanback.widget.PlaybackControlsRowPresenter);
+    method public void setFadingEnabled(boolean);
+    method public void setPlaybackRowPresenter(android.support.v17.leanback.widget.PlaybackRowPresenter);
+    method public void updateProgress();
+    field public static final int ACTION_CUSTOM_LEFT_FIRST = 1; // 0x1
+    field public static final int ACTION_CUSTOM_RIGHT_FIRST = 4096; // 0x1000
+    field public static final int ACTION_FAST_FORWARD = 128; // 0x80
+    field public static final int ACTION_PLAY_PAUSE = 64; // 0x40
+    field public static final int ACTION_REWIND = 32; // 0x20
+    field public static final int ACTION_SKIP_TO_NEXT = 256; // 0x100
+    field public static final int ACTION_SKIP_TO_PREVIOUS = 16; // 0x10
+    field public static final int PLAYBACK_SPEED_FAST_L0 = 10; // 0xa
+    field public static final int PLAYBACK_SPEED_FAST_L1 = 11; // 0xb
+    field public static final int PLAYBACK_SPEED_FAST_L2 = 12; // 0xc
+    field public static final int PLAYBACK_SPEED_FAST_L3 = 13; // 0xd
+    field public static final int PLAYBACK_SPEED_FAST_L4 = 14; // 0xe
+    field public static final int PLAYBACK_SPEED_INVALID = -1; // 0xffffffff
+    field public static final int PLAYBACK_SPEED_NORMAL = 1; // 0x1
+    field public static final int PLAYBACK_SPEED_PAUSED = 0; // 0x0
+  }
+
+  public abstract class PlaybackGlue {
+    ctor public PlaybackGlue(android.content.Context);
+    method public void addPlayerCallback(android.support.v17.leanback.media.PlaybackGlue.PlayerCallback);
+    method public android.content.Context getContext();
+    method public android.support.v17.leanback.media.PlaybackGlueHost getHost();
+    method protected java.util.List<android.support.v17.leanback.media.PlaybackGlue.PlayerCallback> getPlayerCallbacks();
+    method public boolean isPlaying();
+    method public boolean isPrepared();
+    method public void next();
+    method protected void onAttachedToHost(android.support.v17.leanback.media.PlaybackGlueHost);
+    method protected void onDetachedFromHost();
+    method protected void onHostPause();
+    method protected void onHostResume();
+    method protected void onHostStart();
+    method protected void onHostStop();
+    method public void pause();
+    method public void play();
+    method public void playWhenPrepared();
+    method public void previous();
+    method public void removePlayerCallback(android.support.v17.leanback.media.PlaybackGlue.PlayerCallback);
+    method public final void setHost(android.support.v17.leanback.media.PlaybackGlueHost);
+  }
+
+  public static abstract class PlaybackGlue.PlayerCallback {
+    ctor public PlaybackGlue.PlayerCallback();
+    method public void onPlayCompleted(android.support.v17.leanback.media.PlaybackGlue);
+    method public void onPlayStateChanged(android.support.v17.leanback.media.PlaybackGlue);
+    method public void onPreparedStateChanged(android.support.v17.leanback.media.PlaybackGlue);
+  }
+
+  public abstract class PlaybackGlueHost {
+    ctor public PlaybackGlueHost();
+    method public deprecated void fadeOut();
+    method public android.support.v17.leanback.media.PlaybackGlueHost.PlayerCallback getPlayerCallback();
+    method public void hideControlsOverlay(boolean);
+    method public boolean isControlsOverlayAutoHideEnabled();
+    method public boolean isControlsOverlayVisible();
+    method public void notifyPlaybackRowChanged();
+    method public void setControlsOverlayAutoHideEnabled(boolean);
+    method public deprecated void setFadingEnabled(boolean);
+    method public void setHostCallback(android.support.v17.leanback.media.PlaybackGlueHost.HostCallback);
+    method public void setOnActionClickedListener(android.support.v17.leanback.widget.OnActionClickedListener);
+    method public void setOnKeyInterceptListener(android.view.View.OnKeyListener);
+    method public void setPlaybackRow(android.support.v17.leanback.widget.Row);
+    method public void setPlaybackRowPresenter(android.support.v17.leanback.widget.PlaybackRowPresenter);
+    method public void showControlsOverlay(boolean);
+  }
+
+  public static abstract class PlaybackGlueHost.HostCallback {
+    ctor public PlaybackGlueHost.HostCallback();
+    method public void onHostDestroy();
+    method public void onHostPause();
+    method public void onHostResume();
+    method public void onHostStart();
+    method public void onHostStop();
+  }
+
+  public static class PlaybackGlueHost.PlayerCallback {
+    ctor public PlaybackGlueHost.PlayerCallback();
+    method public void onBufferingStateChanged(boolean);
+    method public void onError(int, java.lang.CharSequence);
+    method public void onVideoSizeChanged(int, int);
+  }
+
+  public class PlaybackTransportControlGlue<T extends android.support.v17.leanback.media.PlayerAdapter> extends android.support.v17.leanback.media.PlaybackBaseControlGlue {
+    ctor public PlaybackTransportControlGlue(android.content.Context, T);
+    method public final android.support.v17.leanback.widget.PlaybackSeekDataProvider getSeekProvider();
+    method public final boolean isSeekEnabled();
+    method public void onActionClicked(android.support.v17.leanback.widget.Action);
+    method protected android.support.v17.leanback.widget.PlaybackRowPresenter onCreateRowPresenter();
+    method public boolean onKey(android.view.View, int, android.view.KeyEvent);
+    method public final void setSeekEnabled(boolean);
+    method public final void setSeekProvider(android.support.v17.leanback.widget.PlaybackSeekDataProvider);
+  }
+
+  public abstract class PlayerAdapter {
+    ctor public PlayerAdapter();
+    method public void fastForward();
+    method public long getBufferedPosition();
+    method public final android.support.v17.leanback.media.PlayerAdapter.Callback getCallback();
+    method public long getCurrentPosition();
+    method public long getDuration();
+    method public long getSupportedActions();
+    method public boolean isPlaying();
+    method public boolean isPrepared();
+    method public void next();
+    method public void onAttachedToHost(android.support.v17.leanback.media.PlaybackGlueHost);
+    method public void onDetachedFromHost();
+    method public abstract void pause();
+    method public abstract void play();
+    method public void previous();
+    method public void rewind();
+    method public void seekTo(long);
+    method public final void setCallback(android.support.v17.leanback.media.PlayerAdapter.Callback);
+    method public void setProgressUpdatingEnabled(boolean);
+    method public void setRepeatAction(int);
+    method public void setShuffleAction(int);
+  }
+
+  public static class PlayerAdapter.Callback {
+    ctor public PlayerAdapter.Callback();
+    method public void onBufferedPositionChanged(android.support.v17.leanback.media.PlayerAdapter);
+    method public void onBufferingStateChanged(android.support.v17.leanback.media.PlayerAdapter, boolean);
+    method public void onCurrentPositionChanged(android.support.v17.leanback.media.PlayerAdapter);
+    method public void onDurationChanged(android.support.v17.leanback.media.PlayerAdapter);
+    method public void onError(android.support.v17.leanback.media.PlayerAdapter, int, java.lang.String);
+    method public void onMetadataChanged(android.support.v17.leanback.media.PlayerAdapter);
+    method public void onPlayCompleted(android.support.v17.leanback.media.PlayerAdapter);
+    method public void onPlayStateChanged(android.support.v17.leanback.media.PlayerAdapter);
+    method public void onPreparedStateChanged(android.support.v17.leanback.media.PlayerAdapter);
+    method public void onVideoSizeChanged(android.support.v17.leanback.media.PlayerAdapter, int, int);
+  }
+
+  public abstract interface SurfaceHolderGlueHost {
+    method public abstract void setSurfaceHolderCallback(android.view.SurfaceHolder.Callback);
+  }
+
+}
+
+package android.support.v17.leanback.system {
+
+  public class Settings {
+    method public boolean getBoolean(java.lang.String);
+    method public static android.support.v17.leanback.system.Settings getInstance(android.content.Context);
+    method public void setBoolean(java.lang.String, boolean);
+    field public static final java.lang.String OUTLINE_CLIPPING_DISABLED = "OUTLINE_CLIPPING_DISABLED";
+    field public static final java.lang.String PREFER_STATIC_SHADOWS = "PREFER_STATIC_SHADOWS";
+  }
+
+}
+
+package android.support.v17.leanback.widget {
+
+  public abstract class AbstractDetailsDescriptionPresenter extends android.support.v17.leanback.widget.Presenter {
+    ctor public AbstractDetailsDescriptionPresenter();
+    method protected abstract void onBindDescription(android.support.v17.leanback.widget.AbstractDetailsDescriptionPresenter.ViewHolder, java.lang.Object);
+    method public final void onBindViewHolder(android.support.v17.leanback.widget.Presenter.ViewHolder, java.lang.Object);
+    method public final android.support.v17.leanback.widget.AbstractDetailsDescriptionPresenter.ViewHolder onCreateViewHolder(android.view.ViewGroup);
+    method public void onUnbindViewHolder(android.support.v17.leanback.widget.Presenter.ViewHolder);
+  }
+
+  public static class AbstractDetailsDescriptionPresenter.ViewHolder extends android.support.v17.leanback.widget.Presenter.ViewHolder {
+    ctor public AbstractDetailsDescriptionPresenter.ViewHolder(android.view.View);
+    method public android.widget.TextView getBody();
+    method public android.widget.TextView getSubtitle();
+    method public android.widget.TextView getTitle();
+  }
+
+  public abstract class AbstractMediaItemPresenter extends android.support.v17.leanback.widget.RowPresenter {
+    ctor public AbstractMediaItemPresenter();
+    ctor public AbstractMediaItemPresenter(int);
+    method protected android.support.v17.leanback.widget.RowPresenter.ViewHolder createRowViewHolder(android.view.ViewGroup);
+    method public android.support.v17.leanback.widget.Presenter getActionPresenter();
+    method protected int getMediaPlayState(java.lang.Object);
+    method public int getThemeId();
+    method public boolean hasMediaRowSeparator();
+    method protected abstract void onBindMediaDetails(android.support.v17.leanback.widget.AbstractMediaItemPresenter.ViewHolder, java.lang.Object);
+    method public void onBindMediaPlayState(android.support.v17.leanback.widget.AbstractMediaItemPresenter.ViewHolder);
+    method protected void onBindRowActions(android.support.v17.leanback.widget.AbstractMediaItemPresenter.ViewHolder);
+    method protected void onUnbindMediaDetails(android.support.v17.leanback.widget.AbstractMediaItemPresenter.ViewHolder);
+    method public void onUnbindMediaPlayState(android.support.v17.leanback.widget.AbstractMediaItemPresenter.ViewHolder);
+    method public void setActionPresenter(android.support.v17.leanback.widget.Presenter);
+    method public void setBackgroundColor(int);
+    method public void setHasMediaRowSeparator(boolean);
+    method public void setThemeId(int);
+    field public static final int PLAY_STATE_INITIAL = 0; // 0x0
+    field public static final int PLAY_STATE_PAUSED = 1; // 0x1
+    field public static final int PLAY_STATE_PLAYING = 2; // 0x2
+  }
+
+  public static class AbstractMediaItemPresenter.ViewHolder extends android.support.v17.leanback.widget.RowPresenter.ViewHolder {
+    ctor public AbstractMediaItemPresenter.ViewHolder(android.view.View);
+    method public android.view.ViewGroup getMediaItemActionsContainer();
+    method public android.view.View getMediaItemDetailsView();
+    method public android.widget.TextView getMediaItemDurationView();
+    method public android.widget.TextView getMediaItemNameView();
+    method public android.widget.TextView getMediaItemNumberView();
+    method public android.widget.ViewFlipper getMediaItemNumberViewFlipper();
+    method public android.view.View getMediaItemPausedView();
+    method public android.view.View getMediaItemPlayingView();
+    method public android.support.v17.leanback.widget.MultiActionsProvider.MultiAction[] getMediaItemRowActions();
+    method public android.view.View getMediaItemRowSeparator();
+    method public android.view.View getSelectorView();
+    method public void notifyActionChanged(android.support.v17.leanback.widget.MultiActionsProvider.MultiAction);
+    method public void notifyDetailsChanged();
+    method public void notifyPlayStateChanged();
+    method public void onBindRowActions();
+    method public void setSelectedMediaItemNumberView(int);
+  }
+
+  public abstract class AbstractMediaListHeaderPresenter extends android.support.v17.leanback.widget.RowPresenter {
+    ctor public AbstractMediaListHeaderPresenter(android.content.Context, int);
+    ctor public AbstractMediaListHeaderPresenter();
+    method protected android.support.v17.leanback.widget.RowPresenter.ViewHolder createRowViewHolder(android.view.ViewGroup);
+    method protected abstract void onBindMediaListHeaderViewHolder(android.support.v17.leanback.widget.AbstractMediaListHeaderPresenter.ViewHolder, java.lang.Object);
+    method public void setBackgroundColor(int);
+  }
+
+  public static class AbstractMediaListHeaderPresenter.ViewHolder extends android.support.v17.leanback.widget.RowPresenter.ViewHolder {
+    ctor public AbstractMediaListHeaderPresenter.ViewHolder(android.view.View);
+    method public android.widget.TextView getHeaderView();
+  }
+
+  public class Action {
+    ctor public Action(long);
+    ctor public Action(long, java.lang.CharSequence);
+    ctor public Action(long, java.lang.CharSequence, java.lang.CharSequence);
+    ctor public Action(long, java.lang.CharSequence, java.lang.CharSequence, android.graphics.drawable.Drawable);
+    method public final void addKeyCode(int);
+    method public final android.graphics.drawable.Drawable getIcon();
+    method public final long getId();
+    method public final java.lang.CharSequence getLabel1();
+    method public final java.lang.CharSequence getLabel2();
+    method public final void removeKeyCode(int);
+    method public final boolean respondsToKeyCode(int);
+    method public final void setIcon(android.graphics.drawable.Drawable);
+    method public final void setId(long);
+    method public final void setLabel1(java.lang.CharSequence);
+    method public final void setLabel2(java.lang.CharSequence);
+    field public static final long NO_ID = -1L; // 0xffffffffffffffffL
+  }
+
+  public class ArrayObjectAdapter extends android.support.v17.leanback.widget.ObjectAdapter {
+    ctor public ArrayObjectAdapter(android.support.v17.leanback.widget.PresenterSelector);
+    ctor public ArrayObjectAdapter(android.support.v17.leanback.widget.Presenter);
+    ctor public ArrayObjectAdapter();
+    method public void add(java.lang.Object);
+    method public void add(int, java.lang.Object);
+    method public void addAll(int, java.util.Collection);
+    method public void clear();
+    method public java.lang.Object get(int);
+    method public int indexOf(java.lang.Object);
+    method public void move(int, int);
+    method public void notifyArrayItemRangeChanged(int, int);
+    method public boolean remove(java.lang.Object);
+    method public int removeItems(int, int);
+    method public void replace(int, java.lang.Object);
+    method public void setItems(java.util.List, android.support.v17.leanback.widget.DiffCallback);
+    method public int size();
+    method public <E> java.util.List<E> unmodifiableList();
+  }
+
+  public class BaseCardView extends android.widget.FrameLayout {
+    ctor public BaseCardView(android.content.Context);
+    ctor public BaseCardView(android.content.Context, android.util.AttributeSet);
+    ctor public BaseCardView(android.content.Context, android.util.AttributeSet, int);
+    method protected android.support.v17.leanback.widget.BaseCardView.LayoutParams generateDefaultLayoutParams();
+    method public android.support.v17.leanback.widget.BaseCardView.LayoutParams generateLayoutParams(android.util.AttributeSet);
+    method protected android.support.v17.leanback.widget.BaseCardView.LayoutParams generateLayoutParams(android.view.ViewGroup.LayoutParams);
+    method public int getCardType();
+    method public deprecated int getExtraVisibility();
+    method public int getInfoVisibility();
+    method public boolean isSelectedAnimationDelayed();
+    method public void setCardType(int);
+    method public deprecated void setExtraVisibility(int);
+    method public void setInfoVisibility(int);
+    method public void setSelectedAnimationDelayed(boolean);
+    field public static final int CARD_REGION_VISIBLE_ACTIVATED = 1; // 0x1
+    field public static final int CARD_REGION_VISIBLE_ALWAYS = 0; // 0x0
+    field public static final int CARD_REGION_VISIBLE_SELECTED = 2; // 0x2
+    field public static final int CARD_TYPE_INFO_OVER = 1; // 0x1
+    field public static final int CARD_TYPE_INFO_UNDER = 2; // 0x2
+    field public static final int CARD_TYPE_INFO_UNDER_WITH_EXTRA = 3; // 0x3
+    field public static final int CARD_TYPE_MAIN_ONLY = 0; // 0x0
+  }
+
+  public static class BaseCardView.LayoutParams extends android.widget.FrameLayout.LayoutParams {
+    ctor public BaseCardView.LayoutParams(android.content.Context, android.util.AttributeSet);
+    ctor public BaseCardView.LayoutParams(int, int);
+    ctor public BaseCardView.LayoutParams(android.view.ViewGroup.LayoutParams);
+    ctor public BaseCardView.LayoutParams(android.support.v17.leanback.widget.BaseCardView.LayoutParams);
+    field public static final int VIEW_TYPE_EXTRA = 2; // 0x2
+    field public static final int VIEW_TYPE_INFO = 1; // 0x1
+    field public static final int VIEW_TYPE_MAIN = 0; // 0x0
+    field public int viewType;
+  }
+
+  public abstract class BaseGridView extends android.support.v7.widget.RecyclerView {
+    method public void addOnChildViewHolderSelectedListener(android.support.v17.leanback.widget.OnChildViewHolderSelectedListener);
+    method public void animateIn();
+    method public void animateOut();
+    method public int getChildDrawingOrder(int, int);
+    method public deprecated int getHorizontalMargin();
+    method public int getHorizontalSpacing();
+    method public int getInitialPrefetchItemCount();
+    method public int getItemAlignmentOffset();
+    method public float getItemAlignmentOffsetPercent();
+    method public int getItemAlignmentViewId();
+    method public android.support.v17.leanback.widget.BaseGridView.OnUnhandledKeyListener getOnUnhandledKeyListener();
+    method public final int getSaveChildrenLimitNumber();
+    method public final int getSaveChildrenPolicy();
+    method public int getSelectedPosition();
+    method public deprecated int getVerticalMargin();
+    method public int getVerticalSpacing();
+    method public void getViewSelectedOffsets(android.view.View, int[]);
+    method public int getWindowAlignment();
+    method public int getWindowAlignmentOffset();
+    method public float getWindowAlignmentOffsetPercent();
+    method public boolean hasPreviousViewInSameRow(int);
+    method public boolean isChildLayoutAnimated();
+    method public boolean isFocusDrawingOrderEnabled();
+    method public final boolean isFocusSearchDisabled();
+    method public boolean isItemAlignmentOffsetWithPadding();
+    method public boolean isScrollEnabled();
+    method public boolean isWindowAlignmentPreferKeyLineOverHighEdge();
+    method public boolean isWindowAlignmentPreferKeyLineOverLowEdge();
+    method public boolean onRequestFocusInDescendants(int, android.graphics.Rect);
+    method public void removeOnChildViewHolderSelectedListener(android.support.v17.leanback.widget.OnChildViewHolderSelectedListener);
+    method public void setAnimateChildLayout(boolean);
+    method public void setChildrenVisibility(int);
+    method public void setFocusDrawingOrderEnabled(boolean);
+    method public final void setFocusSearchDisabled(boolean);
+    method public void setGravity(int);
+    method public void setHasOverlappingRendering(boolean);
+    method public deprecated void setHorizontalMargin(int);
+    method public void setHorizontalSpacing(int);
+    method public void setInitialPrefetchItemCount(int);
+    method public void setItemAlignmentOffset(int);
+    method public void setItemAlignmentOffsetPercent(float);
+    method public void setItemAlignmentOffsetWithPadding(boolean);
+    method public void setItemAlignmentViewId(int);
+    method public deprecated void setItemMargin(int);
+    method public void setItemSpacing(int);
+    method public void setLayoutEnabled(boolean);
+    method public void setOnChildLaidOutListener(android.support.v17.leanback.widget.OnChildLaidOutListener);
+    method public void setOnChildSelectedListener(android.support.v17.leanback.widget.OnChildSelectedListener);
+    method public void setOnChildViewHolderSelectedListener(android.support.v17.leanback.widget.OnChildViewHolderSelectedListener);
+    method public void setOnKeyInterceptListener(android.support.v17.leanback.widget.BaseGridView.OnKeyInterceptListener);
+    method public void setOnMotionInterceptListener(android.support.v17.leanback.widget.BaseGridView.OnMotionInterceptListener);
+    method public void setOnTouchInterceptListener(android.support.v17.leanback.widget.BaseGridView.OnTouchInterceptListener);
+    method public void setOnUnhandledKeyListener(android.support.v17.leanback.widget.BaseGridView.OnUnhandledKeyListener);
+    method public void setPruneChild(boolean);
+    method public final void setSaveChildrenLimitNumber(int);
+    method public final void setSaveChildrenPolicy(int);
+    method public void setScrollEnabled(boolean);
+    method public void setSelectedPosition(int);
+    method public void setSelectedPosition(int, int);
+    method public void setSelectedPosition(int, android.support.v17.leanback.widget.ViewHolderTask);
+    method public void setSelectedPositionSmooth(int);
+    method public void setSelectedPositionSmooth(int, android.support.v17.leanback.widget.ViewHolderTask);
+    method public deprecated void setVerticalMargin(int);
+    method public void setVerticalSpacing(int);
+    method public void setWindowAlignment(int);
+    method public void setWindowAlignmentOffset(int);
+    method public void setWindowAlignmentOffsetPercent(float);
+    method public void setWindowAlignmentPreferKeyLineOverHighEdge(boolean);
+    method public void setWindowAlignmentPreferKeyLineOverLowEdge(boolean);
+    field public static final float ITEM_ALIGN_OFFSET_PERCENT_DISABLED = -1.0f;
+    field public static final int SAVE_ALL_CHILD = 3; // 0x3
+    field public static final int SAVE_LIMITED_CHILD = 2; // 0x2
+    field public static final int SAVE_NO_CHILD = 0; // 0x0
+    field public static final int SAVE_ON_SCREEN_CHILD = 1; // 0x1
+    field public static final int WINDOW_ALIGN_BOTH_EDGE = 3; // 0x3
+    field public static final int WINDOW_ALIGN_HIGH_EDGE = 2; // 0x2
+    field public static final int WINDOW_ALIGN_LOW_EDGE = 1; // 0x1
+    field public static final int WINDOW_ALIGN_NO_EDGE = 0; // 0x0
+    field public static final float WINDOW_ALIGN_OFFSET_PERCENT_DISABLED = -1.0f;
+  }
+
+  public static abstract interface BaseGridView.OnKeyInterceptListener {
+    method public abstract boolean onInterceptKeyEvent(android.view.KeyEvent);
+  }
+
+  public static abstract interface BaseGridView.OnMotionInterceptListener {
+    method public abstract boolean onInterceptMotionEvent(android.view.MotionEvent);
+  }
+
+  public static abstract interface BaseGridView.OnTouchInterceptListener {
+    method public abstract boolean onInterceptTouchEvent(android.view.MotionEvent);
+  }
+
+  public static abstract interface BaseGridView.OnUnhandledKeyListener {
+    method public abstract boolean onUnhandledKey(android.view.KeyEvent);
+  }
+
+  public abstract interface BaseOnItemViewClickedListener<T> {
+    method public abstract void onItemClicked(android.support.v17.leanback.widget.Presenter.ViewHolder, java.lang.Object, android.support.v17.leanback.widget.RowPresenter.ViewHolder, T);
+  }
+
+  public abstract interface BaseOnItemViewSelectedListener<T> {
+    method public abstract void onItemSelected(android.support.v17.leanback.widget.Presenter.ViewHolder, java.lang.Object, android.support.v17.leanback.widget.RowPresenter.ViewHolder, T);
+  }
+
+  public class BrowseFrameLayout extends android.widget.FrameLayout {
+    ctor public BrowseFrameLayout(android.content.Context);
+    ctor public BrowseFrameLayout(android.content.Context, android.util.AttributeSet);
+    ctor public BrowseFrameLayout(android.content.Context, android.util.AttributeSet, int);
+    method public android.support.v17.leanback.widget.BrowseFrameLayout.OnChildFocusListener getOnChildFocusListener();
+    method public android.support.v17.leanback.widget.BrowseFrameLayout.OnFocusSearchListener getOnFocusSearchListener();
+    method public void setOnChildFocusListener(android.support.v17.leanback.widget.BrowseFrameLayout.OnChildFocusListener);
+    method public void setOnDispatchKeyListener(android.view.View.OnKeyListener);
+    method public void setOnFocusSearchListener(android.support.v17.leanback.widget.BrowseFrameLayout.OnFocusSearchListener);
+  }
+
+  public static abstract interface BrowseFrameLayout.OnChildFocusListener {
+    method public abstract void onRequestChildFocus(android.view.View, android.view.View);
+    method public abstract boolean onRequestFocusInDescendants(int, android.graphics.Rect);
+  }
+
+  public static abstract interface BrowseFrameLayout.OnFocusSearchListener {
+    method public abstract android.view.View onFocusSearch(android.view.View, int);
+  }
+
+  public final class ClassPresenterSelector extends android.support.v17.leanback.widget.PresenterSelector {
+    ctor public ClassPresenterSelector();
+    method public android.support.v17.leanback.widget.ClassPresenterSelector addClassPresenter(java.lang.Class<?>, android.support.v17.leanback.widget.Presenter);
+    method public android.support.v17.leanback.widget.ClassPresenterSelector addClassPresenterSelector(java.lang.Class<?>, android.support.v17.leanback.widget.PresenterSelector);
+    method public android.support.v17.leanback.widget.Presenter getPresenter(java.lang.Object);
+  }
+
+  public class ControlButtonPresenterSelector extends android.support.v17.leanback.widget.PresenterSelector {
+    ctor public ControlButtonPresenterSelector();
+    method public android.support.v17.leanback.widget.Presenter getPresenter(java.lang.Object);
+    method public android.support.v17.leanback.widget.Presenter getPrimaryPresenter();
+    method public android.support.v17.leanback.widget.Presenter getSecondaryPresenter();
+  }
+
+  public class CursorObjectAdapter extends android.support.v17.leanback.widget.ObjectAdapter {
+    ctor public CursorObjectAdapter(android.support.v17.leanback.widget.PresenterSelector);
+    ctor public CursorObjectAdapter(android.support.v17.leanback.widget.Presenter);
+    ctor public CursorObjectAdapter();
+    method public void changeCursor(android.database.Cursor);
+    method public void close();
+    method public java.lang.Object get(int);
+    method public final android.database.Cursor getCursor();
+    method public final android.support.v17.leanback.database.CursorMapper getMapper();
+    method protected final void invalidateCache(int);
+    method protected final void invalidateCache(int, int);
+    method public boolean isClosed();
+    method protected void onCursorChanged();
+    method protected void onMapperChanged();
+    method public final void setMapper(android.support.v17.leanback.database.CursorMapper);
+    method public int size();
+    method public android.database.Cursor swapCursor(android.database.Cursor);
+  }
+
+  public class DetailsOverviewLogoPresenter extends android.support.v17.leanback.widget.Presenter {
+    ctor public DetailsOverviewLogoPresenter();
+    method public boolean isBoundToImage(android.support.v17.leanback.widget.DetailsOverviewLogoPresenter.ViewHolder, android.support.v17.leanback.widget.DetailsOverviewRow);
+    method public void onBindViewHolder(android.support.v17.leanback.widget.Presenter.ViewHolder, java.lang.Object);
+    method public android.view.View onCreateView(android.view.ViewGroup);
+    method public android.support.v17.leanback.widget.Presenter.ViewHolder onCreateViewHolder(android.view.ViewGroup);
+    method public void onUnbindViewHolder(android.support.v17.leanback.widget.Presenter.ViewHolder);
+    method public void setContext(android.support.v17.leanback.widget.DetailsOverviewLogoPresenter.ViewHolder, android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter.ViewHolder, android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter);
+  }
+
+  public static class DetailsOverviewLogoPresenter.ViewHolder extends android.support.v17.leanback.widget.Presenter.ViewHolder {
+    ctor public DetailsOverviewLogoPresenter.ViewHolder(android.view.View);
+    method public android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter getParentPresenter();
+    method public android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter.ViewHolder getParentViewHolder();
+    method public boolean isSizeFromDrawableIntrinsic();
+    method public void setSizeFromDrawableIntrinsic(boolean);
+    field protected android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter mParentPresenter;
+    field protected android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter.ViewHolder mParentViewHolder;
+  }
+
+  public class DetailsOverviewRow extends android.support.v17.leanback.widget.Row {
+    ctor public DetailsOverviewRow(java.lang.Object);
+    method public final deprecated void addAction(android.support.v17.leanback.widget.Action);
+    method public final deprecated void addAction(int, android.support.v17.leanback.widget.Action);
+    method public android.support.v17.leanback.widget.Action getActionForKeyCode(int);
+    method public final deprecated java.util.List<android.support.v17.leanback.widget.Action> getActions();
+    method public final android.support.v17.leanback.widget.ObjectAdapter getActionsAdapter();
+    method public final android.graphics.drawable.Drawable getImageDrawable();
+    method public final java.lang.Object getItem();
+    method public boolean isImageScaleUpAllowed();
+    method public final deprecated boolean removeAction(android.support.v17.leanback.widget.Action);
+    method public final void setActionsAdapter(android.support.v17.leanback.widget.ObjectAdapter);
+    method public final void setImageBitmap(android.content.Context, android.graphics.Bitmap);
+    method public final void setImageDrawable(android.graphics.drawable.Drawable);
+    method public void setImageScaleUpAllowed(boolean);
+    method public final void setItem(java.lang.Object);
+  }
+
+  public static class DetailsOverviewRow.Listener {
+    ctor public DetailsOverviewRow.Listener();
+    method public void onActionsAdapterChanged(android.support.v17.leanback.widget.DetailsOverviewRow);
+    method public void onImageDrawableChanged(android.support.v17.leanback.widget.DetailsOverviewRow);
+    method public void onItemChanged(android.support.v17.leanback.widget.DetailsOverviewRow);
+  }
+
+  public deprecated class DetailsOverviewRowPresenter extends android.support.v17.leanback.widget.RowPresenter {
+    ctor public DetailsOverviewRowPresenter(android.support.v17.leanback.widget.Presenter);
+    method protected android.support.v17.leanback.widget.RowPresenter.ViewHolder createRowViewHolder(android.view.ViewGroup);
+    method public int getBackgroundColor();
+    method public android.support.v17.leanback.widget.OnActionClickedListener getOnActionClickedListener();
+    method public boolean isStyleLarge();
+    method public final boolean isUsingDefaultSelectEffect();
+    method public void setBackgroundColor(int);
+    method public void setOnActionClickedListener(android.support.v17.leanback.widget.OnActionClickedListener);
+    method public final void setSharedElementEnterTransition(android.app.Activity, java.lang.String, long);
+    method public final void setSharedElementEnterTransition(android.app.Activity, java.lang.String);
+    method public void setStyleLarge(boolean);
+  }
+
+  public final class DetailsOverviewRowPresenter.ViewHolder extends android.support.v17.leanback.widget.RowPresenter.ViewHolder {
+    ctor public DetailsOverviewRowPresenter.ViewHolder(android.view.View, android.support.v17.leanback.widget.Presenter);
+    field public final android.support.v17.leanback.widget.Presenter.ViewHolder mDetailsDescriptionViewHolder;
+  }
+
+  public class DetailsParallax extends android.support.v17.leanback.widget.RecyclerViewParallax {
+    ctor public DetailsParallax();
+    method public android.support.v17.leanback.widget.Parallax.IntProperty getOverviewRowBottom();
+    method public android.support.v17.leanback.widget.Parallax.IntProperty getOverviewRowTop();
+  }
+
+  public abstract class DiffCallback<Value> {
+    ctor public DiffCallback();
+    method public abstract boolean areContentsTheSame(Value, Value);
+    method public abstract boolean areItemsTheSame(Value, Value);
+    method public java.lang.Object getChangePayload(Value, Value);
+  }
+
+  public class DividerPresenter extends android.support.v17.leanback.widget.Presenter {
+    ctor public DividerPresenter();
+    method public void onBindViewHolder(android.support.v17.leanback.widget.Presenter.ViewHolder, java.lang.Object);
+    method public android.support.v17.leanback.widget.Presenter.ViewHolder onCreateViewHolder(android.view.ViewGroup);
+    method public void onUnbindViewHolder(android.support.v17.leanback.widget.Presenter.ViewHolder);
+  }
+
+  public class DividerRow extends android.support.v17.leanback.widget.Row {
+    ctor public DividerRow();
+    method public final boolean isRenderedAsRowView();
+  }
+
+  public abstract interface FacetProvider {
+    method public abstract java.lang.Object getFacet(java.lang.Class<?>);
+  }
+
+  public abstract interface FacetProviderAdapter {
+    method public abstract android.support.v17.leanback.widget.FacetProvider getFacetProvider(int);
+  }
+
+  public abstract interface FocusHighlight {
+    field public static final int ZOOM_FACTOR_LARGE = 3; // 0x3
+    field public static final int ZOOM_FACTOR_MEDIUM = 2; // 0x2
+    field public static final int ZOOM_FACTOR_NONE = 0; // 0x0
+    field public static final int ZOOM_FACTOR_SMALL = 1; // 0x1
+    field public static final int ZOOM_FACTOR_XSMALL = 4; // 0x4
+  }
+
+  public class FocusHighlightHelper {
+    ctor public FocusHighlightHelper();
+    method public static void setupBrowseItemFocusHighlight(android.support.v17.leanback.widget.ItemBridgeAdapter, int, boolean);
+    method public static deprecated void setupHeaderItemFocusHighlight(android.support.v17.leanback.widget.VerticalGridView);
+    method public static deprecated void setupHeaderItemFocusHighlight(android.support.v17.leanback.widget.VerticalGridView, boolean);
+    method public static void setupHeaderItemFocusHighlight(android.support.v17.leanback.widget.ItemBridgeAdapter);
+    method public static void setupHeaderItemFocusHighlight(android.support.v17.leanback.widget.ItemBridgeAdapter, boolean);
+  }
+
+  public abstract interface FragmentAnimationProvider {
+    method public abstract void onImeAppearing(java.util.List<android.animation.Animator>);
+    method public abstract void onImeDisappearing(java.util.List<android.animation.Animator>);
+  }
+
+  public class FullWidthDetailsOverviewRowPresenter extends android.support.v17.leanback.widget.RowPresenter {
+    ctor public FullWidthDetailsOverviewRowPresenter(android.support.v17.leanback.widget.Presenter);
+    ctor public FullWidthDetailsOverviewRowPresenter(android.support.v17.leanback.widget.Presenter, android.support.v17.leanback.widget.DetailsOverviewLogoPresenter);
+    method protected android.support.v17.leanback.widget.RowPresenter.ViewHolder createRowViewHolder(android.view.ViewGroup);
+    method public final int getActionsBackgroundColor();
+    method public final int getAlignmentMode();
+    method public final int getBackgroundColor();
+    method public final int getInitialState();
+    method protected int getLayoutResourceId();
+    method public android.support.v17.leanback.widget.OnActionClickedListener getOnActionClickedListener();
+    method public final boolean isParticipatingEntranceTransition();
+    method public final boolean isUsingDefaultSelectEffect();
+    method public final void notifyOnBindLogo(android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter.ViewHolder);
+    method protected void onLayoutLogo(android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter.ViewHolder, int, boolean);
+    method protected void onLayoutOverviewFrame(android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter.ViewHolder, int, boolean);
+    method protected void onStateChanged(android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter.ViewHolder, int);
+    method public final void setActionsBackgroundColor(int);
+    method public final void setAlignmentMode(int);
+    method public final void setBackgroundColor(int);
+    method public final void setInitialState(int);
+    method public final void setListener(android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter.Listener);
+    method public void setOnActionClickedListener(android.support.v17.leanback.widget.OnActionClickedListener);
+    method public final void setParticipatingEntranceTransition(boolean);
+    method public final void setState(android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter.ViewHolder, int);
+    field public static final int ALIGN_MODE_MIDDLE = 1; // 0x1
+    field public static final int ALIGN_MODE_START = 0; // 0x0
+    field public static final int STATE_FULL = 1; // 0x1
+    field public static final int STATE_HALF = 0; // 0x0
+    field public static final int STATE_SMALL = 2; // 0x2
+    field protected int mInitialState;
+  }
+
+  public static abstract class FullWidthDetailsOverviewRowPresenter.Listener {
+    ctor public FullWidthDetailsOverviewRowPresenter.Listener();
+    method public void onBindLogo(android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter.ViewHolder);
+  }
+
+  public class FullWidthDetailsOverviewRowPresenter.ViewHolder extends android.support.v17.leanback.widget.RowPresenter.ViewHolder {
+    ctor public FullWidthDetailsOverviewRowPresenter.ViewHolder(android.view.View, android.support.v17.leanback.widget.Presenter, android.support.v17.leanback.widget.DetailsOverviewLogoPresenter);
+    method protected android.support.v17.leanback.widget.DetailsOverviewRow.Listener createRowListener();
+    method public final android.view.ViewGroup getActionsRow();
+    method public final android.view.ViewGroup getDetailsDescriptionFrame();
+    method public final android.support.v17.leanback.widget.Presenter.ViewHolder getDetailsDescriptionViewHolder();
+    method public final android.support.v17.leanback.widget.DetailsOverviewLogoPresenter.ViewHolder getLogoViewHolder();
+    method public final android.view.ViewGroup getOverviewView();
+    method public final int getState();
+    field protected final android.support.v17.leanback.widget.DetailsOverviewRow.Listener mRowListener;
+  }
+
+  public class FullWidthDetailsOverviewRowPresenter.ViewHolder.DetailsOverviewRowListener extends android.support.v17.leanback.widget.DetailsOverviewRow.Listener {
+    ctor public FullWidthDetailsOverviewRowPresenter.ViewHolder.DetailsOverviewRowListener();
+  }
+
+  public class FullWidthDetailsOverviewSharedElementHelper extends android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter.Listener {
+    ctor public FullWidthDetailsOverviewSharedElementHelper();
+    method public boolean getAutoStartSharedElementTransition();
+    method public void setAutoStartSharedElementTransition(boolean);
+    method public void setSharedElementEnterTransition(android.app.Activity, java.lang.String);
+    method public void setSharedElementEnterTransition(android.app.Activity, java.lang.String, long);
+    method public void startPostponedEnterTransition();
+  }
+
+  public class GuidanceStylist implements android.support.v17.leanback.widget.FragmentAnimationProvider {
+    ctor public GuidanceStylist();
+    method public android.widget.TextView getBreadcrumbView();
+    method public android.widget.TextView getDescriptionView();
+    method public android.widget.ImageView getIconView();
+    method public android.widget.TextView getTitleView();
+    method public android.view.View onCreateView(android.view.LayoutInflater, android.view.ViewGroup, android.support.v17.leanback.widget.GuidanceStylist.Guidance);
+    method public void onDestroyView();
+    method public void onImeAppearing(java.util.List<android.animation.Animator>);
+    method public void onImeDisappearing(java.util.List<android.animation.Animator>);
+    method public int onProvideLayoutId();
+  }
+
+  public static class GuidanceStylist.Guidance {
+    ctor public GuidanceStylist.Guidance(java.lang.String, java.lang.String, java.lang.String, android.graphics.drawable.Drawable);
+    method public java.lang.String getBreadcrumb();
+    method public java.lang.String getDescription();
+    method public android.graphics.drawable.Drawable getIconDrawable();
+    method public java.lang.String getTitle();
+  }
+
+  public class GuidedAction extends android.support.v17.leanback.widget.Action {
+    ctor protected GuidedAction();
+    method public int getCheckSetId();
+    method public java.lang.CharSequence getDescription();
+    method public int getDescriptionEditInputType();
+    method public int getDescriptionInputType();
+    method public java.lang.CharSequence getEditDescription();
+    method public int getEditInputType();
+    method public java.lang.CharSequence getEditTitle();
+    method public int getInputType();
+    method public android.content.Intent getIntent();
+    method public java.util.List<android.support.v17.leanback.widget.GuidedAction> getSubActions();
+    method public java.lang.CharSequence getTitle();
+    method public boolean hasEditableActivatorView();
+    method public boolean hasMultilineDescription();
+    method public boolean hasNext();
+    method public boolean hasSubActions();
+    method public boolean hasTextEditable();
+    method public boolean infoOnly();
+    method public final boolean isAutoSaveRestoreEnabled();
+    method public boolean isChecked();
+    method public boolean isDescriptionEditable();
+    method public boolean isEditTitleUsed();
+    method public boolean isEditable();
+    method public boolean isEnabled();
+    method public boolean isFocusable();
+    method public void onRestoreInstanceState(android.os.Bundle, java.lang.String);
+    method public void onSaveInstanceState(android.os.Bundle, java.lang.String);
+    method public void setChecked(boolean);
+    method public void setDescription(java.lang.CharSequence);
+    method public void setEditDescription(java.lang.CharSequence);
+    method public void setEditTitle(java.lang.CharSequence);
+    method public void setEnabled(boolean);
+    method public void setFocusable(boolean);
+    method public void setIntent(android.content.Intent);
+    method public void setSubActions(java.util.List<android.support.v17.leanback.widget.GuidedAction>);
+    method public void setTitle(java.lang.CharSequence);
+    field public static final long ACTION_ID_CANCEL = -5L; // 0xfffffffffffffffbL
+    field public static final long ACTION_ID_CONTINUE = -7L; // 0xfffffffffffffff9L
+    field public static final long ACTION_ID_CURRENT = -3L; // 0xfffffffffffffffdL
+    field public static final long ACTION_ID_FINISH = -6L; // 0xfffffffffffffffaL
+    field public static final long ACTION_ID_NEXT = -2L; // 0xfffffffffffffffeL
+    field public static final long ACTION_ID_NO = -9L; // 0xfffffffffffffff7L
+    field public static final long ACTION_ID_OK = -4L; // 0xfffffffffffffffcL
+    field public static final long ACTION_ID_YES = -8L; // 0xfffffffffffffff8L
+    field public static final int CHECKBOX_CHECK_SET_ID = -1; // 0xffffffff
+    field public static final int DEFAULT_CHECK_SET_ID = 1; // 0x1
+    field public static final int NO_CHECK_SET = 0; // 0x0
+  }
+
+  public static class GuidedAction.Builder extends android.support.v17.leanback.widget.GuidedAction.BuilderBase {
+    ctor public deprecated GuidedAction.Builder();
+    ctor public GuidedAction.Builder(android.content.Context);
+    method public android.support.v17.leanback.widget.GuidedAction build();
+  }
+
+  public static abstract class GuidedAction.BuilderBase<B extends android.support.v17.leanback.widget.GuidedAction.BuilderBase> {
+    ctor public GuidedAction.BuilderBase(android.content.Context);
+    method protected final void applyValues(android.support.v17.leanback.widget.GuidedAction);
+    method public B autoSaveRestoreEnabled(boolean);
+    method public B checkSetId(int);
+    method public B checked(boolean);
+    method public B clickAction(long);
+    method public B description(java.lang.CharSequence);
+    method public B description(int);
+    method public B descriptionEditInputType(int);
+    method public B descriptionEditable(boolean);
+    method public B descriptionInputType(int);
+    method public B editDescription(java.lang.CharSequence);
+    method public B editDescription(int);
+    method public B editInputType(int);
+    method public B editTitle(java.lang.CharSequence);
+    method public B editTitle(int);
+    method public B editable(boolean);
+    method public B enabled(boolean);
+    method public B focusable(boolean);
+    method public android.content.Context getContext();
+    method public B hasEditableActivatorView(boolean);
+    method public B hasNext(boolean);
+    method public B icon(android.graphics.drawable.Drawable);
+    method public B icon(int);
+    method public deprecated B iconResourceId(int, android.content.Context);
+    method public B id(long);
+    method public B infoOnly(boolean);
+    method public B inputType(int);
+    method public B intent(android.content.Intent);
+    method public B multilineDescription(boolean);
+    method public B subActions(java.util.List<android.support.v17.leanback.widget.GuidedAction>);
+    method public B title(java.lang.CharSequence);
+    method public B title(int);
+  }
+
+  public class GuidedActionDiffCallback extends android.support.v17.leanback.widget.DiffCallback {
+    ctor public GuidedActionDiffCallback();
+    method public boolean areContentsTheSame(android.support.v17.leanback.widget.GuidedAction, android.support.v17.leanback.widget.GuidedAction);
+    method public boolean areItemsTheSame(android.support.v17.leanback.widget.GuidedAction, android.support.v17.leanback.widget.GuidedAction);
+    method public static final android.support.v17.leanback.widget.GuidedActionDiffCallback getInstance();
+  }
+
+  public class GuidedActionEditText extends android.widget.EditText implements android.support.v17.leanback.widget.ImeKeyMonitor {
+    ctor public GuidedActionEditText(android.content.Context);
+    ctor public GuidedActionEditText(android.content.Context, android.util.AttributeSet);
+    ctor public GuidedActionEditText(android.content.Context, android.util.AttributeSet, int);
+    method public void setImeKeyListener(android.support.v17.leanback.widget.ImeKeyMonitor.ImeKeyListener);
+  }
+
+  public class GuidedActionsStylist implements android.support.v17.leanback.widget.FragmentAnimationProvider {
+    ctor public GuidedActionsStylist();
+    method public void collapseAction(boolean);
+    method public void expandAction(android.support.v17.leanback.widget.GuidedAction, boolean);
+    method public android.support.v17.leanback.widget.VerticalGridView getActionsGridView();
+    method public android.support.v17.leanback.widget.GuidedAction getExpandedAction();
+    method public int getItemViewType(android.support.v17.leanback.widget.GuidedAction);
+    method public android.support.v17.leanback.widget.VerticalGridView getSubActionsGridView();
+    method public final boolean isBackKeyToCollapseActivatorView();
+    method public final boolean isBackKeyToCollapseSubActions();
+    method public boolean isButtonActions();
+    method public boolean isExpandTransitionSupported();
+    method public boolean isExpanded();
+    method public boolean isInExpandTransition();
+    method public boolean isSubActionsExpanded();
+    method public void onAnimateItemChecked(android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder, boolean);
+    method public void onAnimateItemFocused(android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder, boolean);
+    method public void onAnimateItemPressed(android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder, boolean);
+    method public void onAnimateItemPressedCancelled(android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder);
+    method public void onBindActivatorView(android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder, android.support.v17.leanback.widget.GuidedAction);
+    method public void onBindCheckMarkView(android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder, android.support.v17.leanback.widget.GuidedAction);
+    method public void onBindChevronView(android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder, android.support.v17.leanback.widget.GuidedAction);
+    method public void onBindViewHolder(android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder, android.support.v17.leanback.widget.GuidedAction);
+    method public android.view.View onCreateView(android.view.LayoutInflater, android.view.ViewGroup);
+    method public android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder onCreateViewHolder(android.view.ViewGroup);
+    method public android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder onCreateViewHolder(android.view.ViewGroup, int);
+    method public void onDestroyView();
+    method protected deprecated void onEditingModeChange(android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder, android.support.v17.leanback.widget.GuidedAction, boolean);
+    method protected void onEditingModeChange(android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder, boolean, boolean);
+    method public void onImeAppearing(java.util.List<android.animation.Animator>);
+    method public void onImeDisappearing(java.util.List<android.animation.Animator>);
+    method public int onProvideItemLayoutId();
+    method public int onProvideItemLayoutId(int);
+    method public int onProvideLayoutId();
+    method public boolean onUpdateActivatorView(android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder, android.support.v17.leanback.widget.GuidedAction);
+    method public void onUpdateExpandedViewHolder(android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder);
+    method public void openInEditMode(android.support.v17.leanback.widget.GuidedAction);
+    method public void setAsButtonActions();
+    method public final void setBackKeyToCollapseActivatorView(boolean);
+    method public final void setBackKeyToCollapseSubActions(boolean);
+    method public deprecated void setEditingMode(android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder, android.support.v17.leanback.widget.GuidedAction, boolean);
+    method public deprecated void setExpandedViewHolder(android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder);
+    method protected void setupImeOptions(android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder, android.support.v17.leanback.widget.GuidedAction);
+    method public deprecated void startExpandedTransition(android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder);
+    field public static final int VIEW_TYPE_DATE_PICKER = 1; // 0x1
+    field public static final int VIEW_TYPE_DEFAULT = 0; // 0x0
+  }
+
+  public static class GuidedActionsStylist.ViewHolder extends android.support.v7.widget.RecyclerView.ViewHolder implements android.support.v17.leanback.widget.FacetProvider {
+    ctor public GuidedActionsStylist.ViewHolder(android.view.View);
+    ctor public GuidedActionsStylist.ViewHolder(android.view.View, boolean);
+    method public android.support.v17.leanback.widget.GuidedAction getAction();
+    method public android.widget.ImageView getCheckmarkView();
+    method public android.widget.ImageView getChevronView();
+    method public android.view.View getContentView();
+    method public android.widget.TextView getDescriptionView();
+    method public android.widget.EditText getEditableDescriptionView();
+    method public android.widget.EditText getEditableTitleView();
+    method public android.view.View getEditingView();
+    method public java.lang.Object getFacet(java.lang.Class<?>);
+    method public android.widget.ImageView getIconView();
+    method public android.widget.TextView getTitleView();
+    method public boolean isInEditing();
+    method public boolean isInEditingActivatorView();
+    method public boolean isInEditingDescription();
+    method public boolean isInEditingText();
+    method public boolean isInEditingTitle();
+    method public boolean isSubAction();
+  }
+
+  public class GuidedDatePickerAction extends android.support.v17.leanback.widget.GuidedAction {
+    ctor public GuidedDatePickerAction();
+    method public long getDate();
+    method public java.lang.String getDatePickerFormat();
+    method public long getMaxDate();
+    method public long getMinDate();
+    method public void setDate(long);
+  }
+
+  public static final class GuidedDatePickerAction.Builder extends android.support.v17.leanback.widget.GuidedDatePickerAction.BuilderBase {
+    ctor public GuidedDatePickerAction.Builder(android.content.Context);
+    method public android.support.v17.leanback.widget.GuidedDatePickerAction build();
+  }
+
+  public static abstract class GuidedDatePickerAction.BuilderBase<B extends android.support.v17.leanback.widget.GuidedDatePickerAction.BuilderBase> extends android.support.v17.leanback.widget.GuidedAction.BuilderBase {
+    ctor public GuidedDatePickerAction.BuilderBase(android.content.Context);
+    method protected final void applyDatePickerValues(android.support.v17.leanback.widget.GuidedDatePickerAction);
+    method public B date(long);
+    method public B datePickerFormat(java.lang.String);
+    method public B maxDate(long);
+    method public B minDate(long);
+  }
+
+  public class HeaderItem {
+    ctor public HeaderItem(long, java.lang.String);
+    ctor public HeaderItem(java.lang.String);
+    method public java.lang.CharSequence getContentDescription();
+    method public java.lang.CharSequence getDescription();
+    method public final long getId();
+    method public final java.lang.String getName();
+    method public void setContentDescription(java.lang.CharSequence);
+    method public void setDescription(java.lang.CharSequence);
+  }
+
+  public class HorizontalGridView extends android.support.v17.leanback.widget.BaseGridView {
+    ctor public HorizontalGridView(android.content.Context);
+    ctor public HorizontalGridView(android.content.Context, android.util.AttributeSet);
+    ctor public HorizontalGridView(android.content.Context, android.util.AttributeSet, int);
+    method public final boolean getFadingLeftEdge();
+    method public final int getFadingLeftEdgeLength();
+    method public final int getFadingLeftEdgeOffset();
+    method public final boolean getFadingRightEdge();
+    method public final int getFadingRightEdgeLength();
+    method public final int getFadingRightEdgeOffset();
+    method protected void initAttributes(android.content.Context, android.util.AttributeSet);
+    method public final void setFadingLeftEdge(boolean);
+    method public final void setFadingLeftEdgeLength(int);
+    method public final void setFadingLeftEdgeOffset(int);
+    method public final void setFadingRightEdge(boolean);
+    method public final void setFadingRightEdgeLength(int);
+    method public final void setFadingRightEdgeOffset(int);
+    method public void setNumRows(int);
+    method public void setRowHeight(int);
+  }
+
+  public final class HorizontalHoverCardSwitcher extends android.support.v17.leanback.widget.PresenterSwitcher {
+    ctor public HorizontalHoverCardSwitcher();
+    method protected void insertView(android.view.View);
+    method public void select(android.support.v17.leanback.widget.HorizontalGridView, android.view.View, java.lang.Object);
+  }
+
+  public class ImageCardView extends android.support.v17.leanback.widget.BaseCardView {
+    ctor public deprecated ImageCardView(android.content.Context, int);
+    ctor public ImageCardView(android.content.Context, android.util.AttributeSet, int);
+    ctor public ImageCardView(android.content.Context);
+    ctor public ImageCardView(android.content.Context, android.util.AttributeSet);
+    method public android.graphics.drawable.Drawable getBadgeImage();
+    method public java.lang.CharSequence getContentText();
+    method public android.graphics.drawable.Drawable getInfoAreaBackground();
+    method public android.graphics.drawable.Drawable getMainImage();
+    method public final android.widget.ImageView getMainImageView();
+    method public java.lang.CharSequence getTitleText();
+    method public void setBadgeImage(android.graphics.drawable.Drawable);
+    method public void setContentText(java.lang.CharSequence);
+    method public void setInfoAreaBackground(android.graphics.drawable.Drawable);
+    method public void setInfoAreaBackgroundColor(int);
+    method public void setMainImage(android.graphics.drawable.Drawable);
+    method public void setMainImage(android.graphics.drawable.Drawable, boolean);
+    method public void setMainImageAdjustViewBounds(boolean);
+    method public void setMainImageDimensions(int, int);
+    method public void setMainImageScaleType(android.widget.ImageView.ScaleType);
+    method public void setTitleText(java.lang.CharSequence);
+    field public static final int CARD_TYPE_FLAG_CONTENT = 2; // 0x2
+    field public static final int CARD_TYPE_FLAG_ICON_LEFT = 8; // 0x8
+    field public static final int CARD_TYPE_FLAG_ICON_RIGHT = 4; // 0x4
+    field public static final int CARD_TYPE_FLAG_IMAGE_ONLY = 0; // 0x0
+    field public static final int CARD_TYPE_FLAG_TITLE = 1; // 0x1
+  }
+
+  public abstract interface ImeKeyMonitor {
+    method public abstract void setImeKeyListener(android.support.v17.leanback.widget.ImeKeyMonitor.ImeKeyListener);
+  }
+
+  public static abstract interface ImeKeyMonitor.ImeKeyListener {
+    method public abstract boolean onKeyPreIme(android.widget.EditText, int, android.view.KeyEvent);
+  }
+
+  public final class ItemAlignmentFacet {
+    ctor public ItemAlignmentFacet();
+    method public android.support.v17.leanback.widget.ItemAlignmentFacet.ItemAlignmentDef[] getAlignmentDefs();
+    method public boolean isMultiAlignment();
+    method public void setAlignmentDefs(android.support.v17.leanback.widget.ItemAlignmentFacet.ItemAlignmentDef[]);
+    field public static final float ITEM_ALIGN_OFFSET_PERCENT_DISABLED = -1.0f;
+  }
+
+  public static class ItemAlignmentFacet.ItemAlignmentDef {
+    ctor public ItemAlignmentFacet.ItemAlignmentDef();
+    method public final int getItemAlignmentFocusViewId();
+    method public final int getItemAlignmentOffset();
+    method public final float getItemAlignmentOffsetPercent();
+    method public final int getItemAlignmentViewId();
+    method public boolean isAlignedToTextViewBaseLine();
+    method public final boolean isItemAlignmentOffsetWithPadding();
+    method public final void setAlignedToTextViewBaseline(boolean);
+    method public final void setItemAlignmentFocusViewId(int);
+    method public final void setItemAlignmentOffset(int);
+    method public final void setItemAlignmentOffsetPercent(float);
+    method public final void setItemAlignmentOffsetWithPadding(boolean);
+    method public final void setItemAlignmentViewId(int);
+  }
+
+  public class ItemBridgeAdapter extends android.support.v7.widget.RecyclerView.Adapter implements android.support.v17.leanback.widget.FacetProviderAdapter {
+    ctor public ItemBridgeAdapter(android.support.v17.leanback.widget.ObjectAdapter, android.support.v17.leanback.widget.PresenterSelector);
+    ctor public ItemBridgeAdapter(android.support.v17.leanback.widget.ObjectAdapter);
+    ctor public ItemBridgeAdapter();
+    method public void clear();
+    method public android.support.v17.leanback.widget.FacetProvider getFacetProvider(int);
+    method public int getItemCount();
+    method public java.util.ArrayList<android.support.v17.leanback.widget.Presenter> getPresenterMapper();
+    method public android.support.v17.leanback.widget.ItemBridgeAdapter.Wrapper getWrapper();
+    method protected void onAddPresenter(android.support.v17.leanback.widget.Presenter, int);
+    method protected void onAttachedToWindow(android.support.v17.leanback.widget.ItemBridgeAdapter.ViewHolder);
+    method protected void onBind(android.support.v17.leanback.widget.ItemBridgeAdapter.ViewHolder);
+    method public final void onBindViewHolder(android.support.v7.widget.RecyclerView.ViewHolder, int);
+    method public final void onBindViewHolder(android.support.v7.widget.RecyclerView.ViewHolder, int, java.util.List);
+    method protected void onCreate(android.support.v17.leanback.widget.ItemBridgeAdapter.ViewHolder);
+    method public final android.support.v7.widget.RecyclerView.ViewHolder onCreateViewHolder(android.view.ViewGroup, int);
+    method protected void onDetachedFromWindow(android.support.v17.leanback.widget.ItemBridgeAdapter.ViewHolder);
+    method protected void onUnbind(android.support.v17.leanback.widget.ItemBridgeAdapter.ViewHolder);
+    method public final void onViewAttachedToWindow(android.support.v7.widget.RecyclerView.ViewHolder);
+    method public final void onViewDetachedFromWindow(android.support.v7.widget.RecyclerView.ViewHolder);
+    method public final void onViewRecycled(android.support.v7.widget.RecyclerView.ViewHolder);
+    method public void setAdapter(android.support.v17.leanback.widget.ObjectAdapter);
+    method public void setAdapterListener(android.support.v17.leanback.widget.ItemBridgeAdapter.AdapterListener);
+    method public void setPresenter(android.support.v17.leanback.widget.PresenterSelector);
+    method public void setPresenterMapper(java.util.ArrayList<android.support.v17.leanback.widget.Presenter>);
+    method public void setWrapper(android.support.v17.leanback.widget.ItemBridgeAdapter.Wrapper);
+  }
+
+  public static class ItemBridgeAdapter.AdapterListener {
+    ctor public ItemBridgeAdapter.AdapterListener();
+    method public void onAddPresenter(android.support.v17.leanback.widget.Presenter, int);
+    method public void onAttachedToWindow(android.support.v17.leanback.widget.ItemBridgeAdapter.ViewHolder);
+    method public void onBind(android.support.v17.leanback.widget.ItemBridgeAdapter.ViewHolder);
+    method public void onBind(android.support.v17.leanback.widget.ItemBridgeAdapter.ViewHolder, java.util.List);
+    method public void onCreate(android.support.v17.leanback.widget.ItemBridgeAdapter.ViewHolder);
+    method public void onDetachedFromWindow(android.support.v17.leanback.widget.ItemBridgeAdapter.ViewHolder);
+    method public void onUnbind(android.support.v17.leanback.widget.ItemBridgeAdapter.ViewHolder);
+  }
+
+  public class ItemBridgeAdapter.ViewHolder extends android.support.v7.widget.RecyclerView.ViewHolder implements android.support.v17.leanback.widget.FacetProvider {
+    method public final java.lang.Object getExtraObject();
+    method public java.lang.Object getFacet(java.lang.Class<?>);
+    method public final java.lang.Object getItem();
+    method public final android.support.v17.leanback.widget.Presenter getPresenter();
+    method public final android.support.v17.leanback.widget.Presenter.ViewHolder getViewHolder();
+    method public void setExtraObject(java.lang.Object);
+  }
+
+  public static abstract class ItemBridgeAdapter.Wrapper {
+    ctor public ItemBridgeAdapter.Wrapper();
+    method public abstract android.view.View createWrapper(android.view.View);
+    method public abstract void wrap(android.view.View, android.view.View);
+  }
+
+  public class ItemBridgeAdapterShadowOverlayWrapper extends android.support.v17.leanback.widget.ItemBridgeAdapter.Wrapper {
+    ctor public ItemBridgeAdapterShadowOverlayWrapper(android.support.v17.leanback.widget.ShadowOverlayHelper);
+    method public android.view.View createWrapper(android.view.View);
+    method public void wrap(android.view.View, android.view.View);
+  }
+
+  public class ListRow extends android.support.v17.leanback.widget.Row {
+    ctor public ListRow(android.support.v17.leanback.widget.HeaderItem, android.support.v17.leanback.widget.ObjectAdapter);
+    ctor public ListRow(long, android.support.v17.leanback.widget.HeaderItem, android.support.v17.leanback.widget.ObjectAdapter);
+    ctor public ListRow(android.support.v17.leanback.widget.ObjectAdapter);
+    method public final android.support.v17.leanback.widget.ObjectAdapter getAdapter();
+    method public java.lang.CharSequence getContentDescription();
+    method public void setContentDescription(java.lang.CharSequence);
+  }
+
+  public final class ListRowHoverCardView extends android.widget.LinearLayout {
+    ctor public ListRowHoverCardView(android.content.Context);
+    ctor public ListRowHoverCardView(android.content.Context, android.util.AttributeSet);
+    ctor public ListRowHoverCardView(android.content.Context, android.util.AttributeSet, int);
+    method public final java.lang.CharSequence getDescription();
+    method public final java.lang.CharSequence getTitle();
+    method public final void setDescription(java.lang.CharSequence);
+    method public final void setTitle(java.lang.CharSequence);
+  }
+
+  public class ListRowPresenter extends android.support.v17.leanback.widget.RowPresenter {
+    ctor public ListRowPresenter();
+    ctor public ListRowPresenter(int);
+    ctor public ListRowPresenter(int, boolean);
+    method protected void applySelectLevelToChild(android.support.v17.leanback.widget.ListRowPresenter.ViewHolder, android.view.View);
+    method public final boolean areChildRoundedCornersEnabled();
+    method protected android.support.v17.leanback.widget.RowPresenter.ViewHolder createRowViewHolder(android.view.ViewGroup);
+    method protected android.support.v17.leanback.widget.ShadowOverlayHelper.Options createShadowOverlayOptions();
+    method public final void enableChildRoundedCorners(boolean);
+    method public int getExpandedRowHeight();
+    method public final int getFocusZoomFactor();
+    method public final android.support.v17.leanback.widget.PresenterSelector getHoverCardPresenterSelector();
+    method public int getRecycledPoolSize(android.support.v17.leanback.widget.Presenter);
+    method public int getRowHeight();
+    method public final boolean getShadowEnabled();
+    method public final deprecated int getZoomFactor();
+    method public final boolean isFocusDimmerUsed();
+    method public final boolean isKeepChildForeground();
+    method public boolean isUsingDefaultListSelectEffect();
+    method public final boolean isUsingDefaultSelectEffect();
+    method public boolean isUsingDefaultShadow();
+    method public boolean isUsingOutlineClipping(android.content.Context);
+    method public boolean isUsingZOrder(android.content.Context);
+    method public void setExpandedRowHeight(int);
+    method public final void setHoverCardPresenterSelector(android.support.v17.leanback.widget.PresenterSelector);
+    method public final void setKeepChildForeground(boolean);
+    method public void setNumRows(int);
+    method public void setRecycledPoolSize(android.support.v17.leanback.widget.Presenter, int);
+    method public void setRowHeight(int);
+    method public final void setShadowEnabled(boolean);
+  }
+
+  public static class ListRowPresenter.SelectItemViewHolderTask extends android.support.v17.leanback.widget.Presenter.ViewHolderTask {
+    ctor public ListRowPresenter.SelectItemViewHolderTask(int);
+    method public int getItemPosition();
+    method public android.support.v17.leanback.widget.Presenter.ViewHolderTask getItemTask();
+    method public boolean isSmoothScroll();
+    method public void setItemPosition(int);
+    method public void setItemTask(android.support.v17.leanback.widget.Presenter.ViewHolderTask);
+    method public void setSmoothScroll(boolean);
+  }
+
+  public static class ListRowPresenter.ViewHolder extends android.support.v17.leanback.widget.RowPresenter.ViewHolder {
+    ctor public ListRowPresenter.ViewHolder(android.view.View, android.support.v17.leanback.widget.HorizontalGridView, android.support.v17.leanback.widget.ListRowPresenter);
+    method public final android.support.v17.leanback.widget.ItemBridgeAdapter getBridgeAdapter();
+    method public final android.support.v17.leanback.widget.HorizontalGridView getGridView();
+    method public android.support.v17.leanback.widget.Presenter.ViewHolder getItemViewHolder(int);
+    method public final android.support.v17.leanback.widget.ListRowPresenter getListRowPresenter();
+    method public int getSelectedPosition();
+  }
+
+  public final class ListRowView extends android.widget.LinearLayout {
+    ctor public ListRowView(android.content.Context);
+    ctor public ListRowView(android.content.Context, android.util.AttributeSet);
+    ctor public ListRowView(android.content.Context, android.util.AttributeSet, int);
+    method public android.support.v17.leanback.widget.HorizontalGridView getGridView();
+  }
+
+  public abstract interface MultiActionsProvider {
+    method public abstract android.support.v17.leanback.widget.MultiActionsProvider.MultiAction[] getActions();
+  }
+
+  public static class MultiActionsProvider.MultiAction {
+    ctor public MultiActionsProvider.MultiAction(long);
+    method public android.graphics.drawable.Drawable getCurrentDrawable();
+    method public android.graphics.drawable.Drawable[] getDrawables();
+    method public long getId();
+    method public int getIndex();
+    method public void incrementIndex();
+    method public void setDrawables(android.graphics.drawable.Drawable[]);
+    method public void setIndex(int);
+  }
+
+  public abstract class ObjectAdapter {
+    ctor public ObjectAdapter(android.support.v17.leanback.widget.PresenterSelector);
+    ctor public ObjectAdapter(android.support.v17.leanback.widget.Presenter);
+    ctor public ObjectAdapter();
+    method public abstract java.lang.Object get(int);
+    method public long getId(int);
+    method public final android.support.v17.leanback.widget.Presenter getPresenter(java.lang.Object);
+    method public final android.support.v17.leanback.widget.PresenterSelector getPresenterSelector();
+    method public final boolean hasStableIds();
+    method public boolean isImmediateNotifySupported();
+    method protected final void notifyChanged();
+    method protected final void notifyItemMoved(int, int);
+    method public final void notifyItemRangeChanged(int, int);
+    method public final void notifyItemRangeChanged(int, int, java.lang.Object);
+    method protected final void notifyItemRangeInserted(int, int);
+    method protected final void notifyItemRangeRemoved(int, int);
+    method protected void onHasStableIdsChanged();
+    method protected void onPresenterSelectorChanged();
+    method public final void registerObserver(android.support.v17.leanback.widget.ObjectAdapter.DataObserver);
+    method public final void setHasStableIds(boolean);
+    method public final void setPresenterSelector(android.support.v17.leanback.widget.PresenterSelector);
+    method public abstract int size();
+    method public final void unregisterAllObservers();
+    method public final void unregisterObserver(android.support.v17.leanback.widget.ObjectAdapter.DataObserver);
+    field public static final int NO_ID = -1; // 0xffffffff
+  }
+
+  public static abstract class ObjectAdapter.DataObserver {
+    ctor public ObjectAdapter.DataObserver();
+    method public void onChanged();
+    method public void onItemMoved(int, int);
+    method public void onItemRangeChanged(int, int);
+    method public void onItemRangeChanged(int, int, java.lang.Object);
+    method public void onItemRangeInserted(int, int);
+    method public void onItemRangeRemoved(int, int);
+  }
+
+  public abstract interface OnActionClickedListener {
+    method public abstract void onActionClicked(android.support.v17.leanback.widget.Action);
+  }
+
+  public abstract interface OnChildLaidOutListener {
+    method public abstract void onChildLaidOut(android.view.ViewGroup, android.view.View, int, long);
+  }
+
+  public abstract deprecated interface OnChildSelectedListener {
+    method public abstract void onChildSelected(android.view.ViewGroup, android.view.View, int, long);
+  }
+
+  public abstract class OnChildViewHolderSelectedListener {
+    ctor public OnChildViewHolderSelectedListener();
+    method public void onChildViewHolderSelected(android.support.v7.widget.RecyclerView, android.support.v7.widget.RecyclerView.ViewHolder, int, int);
+    method public void onChildViewHolderSelectedAndPositioned(android.support.v7.widget.RecyclerView, android.support.v7.widget.RecyclerView.ViewHolder, int, int);
+  }
+
+  public abstract interface OnItemViewClickedListener implements android.support.v17.leanback.widget.BaseOnItemViewClickedListener {
+  }
+
+  public abstract interface OnItemViewSelectedListener implements android.support.v17.leanback.widget.BaseOnItemViewSelectedListener {
+  }
+
+  public class PageRow extends android.support.v17.leanback.widget.Row {
+    ctor public PageRow(android.support.v17.leanback.widget.HeaderItem);
+    method public final boolean isRenderedAsRowView();
+  }
+
+  public abstract class Parallax<PropertyT extends android.util.Property> {
+    ctor public Parallax();
+    method public android.support.v17.leanback.widget.ParallaxEffect addEffect(android.support.v17.leanback.widget.Parallax.PropertyMarkerValue...);
+    method public final PropertyT addProperty(java.lang.String);
+    method public abstract PropertyT createProperty(java.lang.String, int);
+    method public java.util.List<android.support.v17.leanback.widget.ParallaxEffect> getEffects();
+    method public abstract float getMaxValue();
+    method public final java.util.List<PropertyT> getProperties();
+    method public void removeAllEffects();
+    method public void removeEffect(android.support.v17.leanback.widget.ParallaxEffect);
+    method public void updateValues();
+  }
+
+  public static class Parallax.FloatProperty extends android.util.Property {
+    ctor public Parallax.FloatProperty(java.lang.String, int);
+    method public final android.support.v17.leanback.widget.Parallax.PropertyMarkerValue at(float, float);
+    method public final android.support.v17.leanback.widget.Parallax.PropertyMarkerValue atAbsolute(float);
+    method public final android.support.v17.leanback.widget.Parallax.PropertyMarkerValue atFraction(float);
+    method public final android.support.v17.leanback.widget.Parallax.PropertyMarkerValue atMax();
+    method public final android.support.v17.leanback.widget.Parallax.PropertyMarkerValue atMin();
+    method public final java.lang.Float get(android.support.v17.leanback.widget.Parallax);
+    method public final int getIndex();
+    method public final float getValue(android.support.v17.leanback.widget.Parallax);
+    method public final void set(android.support.v17.leanback.widget.Parallax, java.lang.Float);
+    method public final void setValue(android.support.v17.leanback.widget.Parallax, float);
+    field public static final float UNKNOWN_AFTER = 3.4028235E38f;
+    field public static final float UNKNOWN_BEFORE = -3.4028235E38f;
+  }
+
+  public static class Parallax.IntProperty extends android.util.Property {
+    ctor public Parallax.IntProperty(java.lang.String, int);
+    method public final android.support.v17.leanback.widget.Parallax.PropertyMarkerValue at(int, float);
+    method public final android.support.v17.leanback.widget.Parallax.PropertyMarkerValue atAbsolute(int);
+    method public final android.support.v17.leanback.widget.Parallax.PropertyMarkerValue atFraction(float);
+    method public final android.support.v17.leanback.widget.Parallax.PropertyMarkerValue atMax();
+    method public final android.support.v17.leanback.widget.Parallax.PropertyMarkerValue atMin();
+    method public final java.lang.Integer get(android.support.v17.leanback.widget.Parallax);
+    method public final int getIndex();
+    method public final int getValue(android.support.v17.leanback.widget.Parallax);
+    method public final void set(android.support.v17.leanback.widget.Parallax, java.lang.Integer);
+    method public final void setValue(android.support.v17.leanback.widget.Parallax, int);
+    field public static final int UNKNOWN_AFTER = 2147483647; // 0x7fffffff
+    field public static final int UNKNOWN_BEFORE = -2147483648; // 0x80000000
+  }
+
+  public static class Parallax.PropertyMarkerValue<PropertyT> {
+    ctor public Parallax.PropertyMarkerValue(PropertyT);
+    method public PropertyT getProperty();
+  }
+
+  public abstract class ParallaxEffect {
+    method public final void addTarget(android.support.v17.leanback.widget.ParallaxTarget);
+    method public final java.util.List<android.support.v17.leanback.widget.Parallax.PropertyMarkerValue> getPropertyRanges();
+    method public final java.util.List<android.support.v17.leanback.widget.ParallaxTarget> getTargets();
+    method public final void performMapping(android.support.v17.leanback.widget.Parallax);
+    method public final void removeTarget(android.support.v17.leanback.widget.ParallaxTarget);
+    method public final void setPropertyRanges(android.support.v17.leanback.widget.Parallax.PropertyMarkerValue...);
+    method public final android.support.v17.leanback.widget.ParallaxEffect target(android.support.v17.leanback.widget.ParallaxTarget);
+    method public final android.support.v17.leanback.widget.ParallaxEffect target(java.lang.Object, android.animation.PropertyValuesHolder);
+    method public final <T, V extends java.lang.Number> android.support.v17.leanback.widget.ParallaxEffect target(T, android.util.Property<T, V>);
+  }
+
+  public abstract class ParallaxTarget {
+    ctor public ParallaxTarget();
+    method public void directUpdate(java.lang.Number);
+    method public boolean isDirectMapping();
+    method public void update(float);
+  }
+
+  public static final class ParallaxTarget.DirectPropertyTarget<T, V extends java.lang.Number> extends android.support.v17.leanback.widget.ParallaxTarget {
+    ctor public ParallaxTarget.DirectPropertyTarget(java.lang.Object, android.util.Property<T, V>);
+  }
+
+  public static final class ParallaxTarget.PropertyValuesHolderTarget extends android.support.v17.leanback.widget.ParallaxTarget {
+    ctor public ParallaxTarget.PropertyValuesHolderTarget(java.lang.Object, android.animation.PropertyValuesHolder);
+  }
+
+  public class PlaybackControlsRow extends android.support.v17.leanback.widget.Row {
+    ctor public PlaybackControlsRow(java.lang.Object);
+    ctor public PlaybackControlsRow();
+    method public android.support.v17.leanback.widget.Action getActionForKeyCode(int);
+    method public android.support.v17.leanback.widget.Action getActionForKeyCode(android.support.v17.leanback.widget.ObjectAdapter, int);
+    method public long getBufferedPosition();
+    method public deprecated int getBufferedProgress();
+    method public deprecated long getBufferedProgressLong();
+    method public long getCurrentPosition();
+    method public deprecated int getCurrentTime();
+    method public deprecated long getCurrentTimeLong();
+    method public long getDuration();
+    method public final android.graphics.drawable.Drawable getImageDrawable();
+    method public final java.lang.Object getItem();
+    method public final android.support.v17.leanback.widget.ObjectAdapter getPrimaryActionsAdapter();
+    method public final android.support.v17.leanback.widget.ObjectAdapter getSecondaryActionsAdapter();
+    method public deprecated int getTotalTime();
+    method public deprecated long getTotalTimeLong();
+    method public void setBufferedPosition(long);
+    method public deprecated void setBufferedProgress(int);
+    method public deprecated void setBufferedProgressLong(long);
+    method public void setCurrentPosition(long);
+    method public deprecated void setCurrentTime(int);
+    method public deprecated void setCurrentTimeLong(long);
+    method public void setDuration(long);
+    method public final void setImageBitmap(android.content.Context, android.graphics.Bitmap);
+    method public final void setImageDrawable(android.graphics.drawable.Drawable);
+    method public void setOnPlaybackProgressChangedListener(android.support.v17.leanback.widget.PlaybackControlsRow.OnPlaybackProgressCallback);
+    method public final void setPrimaryActionsAdapter(android.support.v17.leanback.widget.ObjectAdapter);
+    method public final void setSecondaryActionsAdapter(android.support.v17.leanback.widget.ObjectAdapter);
+    method public deprecated void setTotalTime(int);
+    method public deprecated void setTotalTimeLong(long);
+  }
+
+  public static class PlaybackControlsRow.ClosedCaptioningAction extends android.support.v17.leanback.widget.PlaybackControlsRow.MultiAction {
+    ctor public PlaybackControlsRow.ClosedCaptioningAction(android.content.Context);
+    ctor public PlaybackControlsRow.ClosedCaptioningAction(android.content.Context, int);
+    field public static final int INDEX_OFF = 0; // 0x0
+    field public static final int INDEX_ON = 1; // 0x1
+    field public static deprecated int OFF;
+    field public static deprecated int ON;
+  }
+
+  public static class PlaybackControlsRow.FastForwardAction extends android.support.v17.leanback.widget.PlaybackControlsRow.MultiAction {
+    ctor public PlaybackControlsRow.FastForwardAction(android.content.Context);
+    ctor public PlaybackControlsRow.FastForwardAction(android.content.Context, int);
+  }
+
+  public static class PlaybackControlsRow.HighQualityAction extends android.support.v17.leanback.widget.PlaybackControlsRow.MultiAction {
+    ctor public PlaybackControlsRow.HighQualityAction(android.content.Context);
+    ctor public PlaybackControlsRow.HighQualityAction(android.content.Context, int);
+    field public static final int INDEX_OFF = 0; // 0x0
+    field public static final int INDEX_ON = 1; // 0x1
+    field public static deprecated int OFF;
+    field public static deprecated int ON;
+  }
+
+  public static class PlaybackControlsRow.MoreActions extends android.support.v17.leanback.widget.Action {
+    ctor public PlaybackControlsRow.MoreActions(android.content.Context);
+  }
+
+  public static abstract class PlaybackControlsRow.MultiAction extends android.support.v17.leanback.widget.Action {
+    ctor public PlaybackControlsRow.MultiAction(int);
+    method public int getActionCount();
+    method public android.graphics.drawable.Drawable getDrawable(int);
+    method public int getIndex();
+    method public java.lang.String getLabel(int);
+    method public java.lang.String getSecondaryLabel(int);
+    method public void nextIndex();
+    method public void setDrawables(android.graphics.drawable.Drawable[]);
+    method public void setIndex(int);
+    method public void setLabels(java.lang.String[]);
+    method public void setSecondaryLabels(java.lang.String[]);
+  }
+
+  public static class PlaybackControlsRow.OnPlaybackProgressCallback {
+    ctor public PlaybackControlsRow.OnPlaybackProgressCallback();
+    method public void onBufferedPositionChanged(android.support.v17.leanback.widget.PlaybackControlsRow, long);
+    method public void onCurrentPositionChanged(android.support.v17.leanback.widget.PlaybackControlsRow, long);
+    method public void onDurationChanged(android.support.v17.leanback.widget.PlaybackControlsRow, long);
+  }
+
+  public static class PlaybackControlsRow.PictureInPictureAction extends android.support.v17.leanback.widget.Action {
+    ctor public PlaybackControlsRow.PictureInPictureAction(android.content.Context);
+  }
+
+  public static class PlaybackControlsRow.PlayPauseAction extends android.support.v17.leanback.widget.PlaybackControlsRow.MultiAction {
+    ctor public PlaybackControlsRow.PlayPauseAction(android.content.Context);
+    field public static final int INDEX_PAUSE = 1; // 0x1
+    field public static final int INDEX_PLAY = 0; // 0x0
+    field public static deprecated int PAUSE;
+    field public static deprecated int PLAY;
+  }
+
+  public static class PlaybackControlsRow.RepeatAction extends android.support.v17.leanback.widget.PlaybackControlsRow.MultiAction {
+    ctor public PlaybackControlsRow.RepeatAction(android.content.Context);
+    ctor public PlaybackControlsRow.RepeatAction(android.content.Context, int);
+    ctor public PlaybackControlsRow.RepeatAction(android.content.Context, int, int);
+    field public static deprecated int ALL;
+    field public static final int INDEX_ALL = 1; // 0x1
+    field public static final int INDEX_NONE = 0; // 0x0
+    field public static final int INDEX_ONE = 2; // 0x2
+    field public static deprecated int NONE;
+    field public static deprecated int ONE;
+  }
+
+  public static class PlaybackControlsRow.RewindAction extends android.support.v17.leanback.widget.PlaybackControlsRow.MultiAction {
+    ctor public PlaybackControlsRow.RewindAction(android.content.Context);
+    ctor public PlaybackControlsRow.RewindAction(android.content.Context, int);
+  }
+
+  public static class PlaybackControlsRow.ShuffleAction extends android.support.v17.leanback.widget.PlaybackControlsRow.MultiAction {
+    ctor public PlaybackControlsRow.ShuffleAction(android.content.Context);
+    ctor public PlaybackControlsRow.ShuffleAction(android.content.Context, int);
+    field public static final int INDEX_OFF = 0; // 0x0
+    field public static final int INDEX_ON = 1; // 0x1
+    field public static deprecated int OFF;
+    field public static deprecated int ON;
+  }
+
+  public static class PlaybackControlsRow.SkipNextAction extends android.support.v17.leanback.widget.Action {
+    ctor public PlaybackControlsRow.SkipNextAction(android.content.Context);
+  }
+
+  public static class PlaybackControlsRow.SkipPreviousAction extends android.support.v17.leanback.widget.Action {
+    ctor public PlaybackControlsRow.SkipPreviousAction(android.content.Context);
+  }
+
+  public static abstract class PlaybackControlsRow.ThumbsAction extends android.support.v17.leanback.widget.PlaybackControlsRow.MultiAction {
+    ctor public PlaybackControlsRow.ThumbsAction(int, android.content.Context, int, int);
+    field public static final int INDEX_OUTLINE = 1; // 0x1
+    field public static final int INDEX_SOLID = 0; // 0x0
+    field public static deprecated int OUTLINE;
+    field public static deprecated int SOLID;
+  }
+
+  public static class PlaybackControlsRow.ThumbsDownAction extends android.support.v17.leanback.widget.PlaybackControlsRow.ThumbsAction {
+    ctor public PlaybackControlsRow.ThumbsDownAction(android.content.Context);
+  }
+
+  public static class PlaybackControlsRow.ThumbsUpAction extends android.support.v17.leanback.widget.PlaybackControlsRow.ThumbsAction {
+    ctor public PlaybackControlsRow.ThumbsUpAction(android.content.Context);
+  }
+
+  public class PlaybackControlsRowPresenter extends android.support.v17.leanback.widget.PlaybackRowPresenter {
+    ctor public PlaybackControlsRowPresenter(android.support.v17.leanback.widget.Presenter);
+    ctor public PlaybackControlsRowPresenter();
+    method public boolean areSecondaryActionsHidden();
+    method protected android.support.v17.leanback.widget.RowPresenter.ViewHolder createRowViewHolder(android.view.ViewGroup);
+    method public int getBackgroundColor();
+    method public android.support.v17.leanback.widget.OnActionClickedListener getOnActionClickedListener();
+    method public int getProgressColor();
+    method public void setBackgroundColor(int);
+    method public void setOnActionClickedListener(android.support.v17.leanback.widget.OnActionClickedListener);
+    method public void setProgressColor(int);
+    method public void setSecondaryActionsHidden(boolean);
+    method public void showBottomSpace(android.support.v17.leanback.widget.PlaybackControlsRowPresenter.ViewHolder, boolean);
+    method public void showPrimaryActions(android.support.v17.leanback.widget.PlaybackControlsRowPresenter.ViewHolder);
+  }
+
+  public class PlaybackControlsRowPresenter.ViewHolder extends android.support.v17.leanback.widget.PlaybackRowPresenter.ViewHolder {
+    field public final android.support.v17.leanback.widget.Presenter.ViewHolder mDescriptionViewHolder;
+  }
+
+  public abstract class PlaybackRowPresenter extends android.support.v17.leanback.widget.RowPresenter {
+    ctor public PlaybackRowPresenter();
+    method public void onReappear(android.support.v17.leanback.widget.RowPresenter.ViewHolder);
+  }
+
+  public static class PlaybackRowPresenter.ViewHolder extends android.support.v17.leanback.widget.RowPresenter.ViewHolder {
+    ctor public PlaybackRowPresenter.ViewHolder(android.view.View);
+  }
+
+  public class PlaybackSeekDataProvider {
+    ctor public PlaybackSeekDataProvider();
+    method public long[] getSeekPositions();
+    method public void getThumbnail(int, android.support.v17.leanback.widget.PlaybackSeekDataProvider.ResultCallback);
+    method public void reset();
+  }
+
+  public static class PlaybackSeekDataProvider.ResultCallback {
+    ctor public PlaybackSeekDataProvider.ResultCallback();
+    method public void onThumbnailLoaded(android.graphics.Bitmap, int);
+  }
+
+  public abstract interface PlaybackSeekUi {
+    method public abstract void setPlaybackSeekUiClient(android.support.v17.leanback.widget.PlaybackSeekUi.Client);
+  }
+
+  public static class PlaybackSeekUi.Client {
+    ctor public PlaybackSeekUi.Client();
+    method public android.support.v17.leanback.widget.PlaybackSeekDataProvider getPlaybackSeekDataProvider();
+    method public boolean isSeekEnabled();
+    method public void onSeekFinished(boolean);
+    method public void onSeekPositionChanged(long);
+    method public void onSeekStarted();
+  }
+
+  public class PlaybackTransportRowPresenter extends android.support.v17.leanback.widget.PlaybackRowPresenter {
+    ctor public PlaybackTransportRowPresenter();
+    method protected android.support.v17.leanback.widget.RowPresenter.ViewHolder createRowViewHolder(android.view.ViewGroup);
+    method public float getDefaultSeekIncrement();
+    method public android.support.v17.leanback.widget.OnActionClickedListener getOnActionClickedListener();
+    method public int getProgressColor();
+    method protected void onProgressBarClicked(android.support.v17.leanback.widget.PlaybackTransportRowPresenter.ViewHolder);
+    method public void setDefaultSeekIncrement(float);
+    method public void setDescriptionPresenter(android.support.v17.leanback.widget.Presenter);
+    method public void setOnActionClickedListener(android.support.v17.leanback.widget.OnActionClickedListener);
+    method public void setProgressColor(int);
+  }
+
+  public class PlaybackTransportRowPresenter.ViewHolder extends android.support.v17.leanback.widget.PlaybackRowPresenter.ViewHolder implements android.support.v17.leanback.widget.PlaybackSeekUi {
+    ctor public PlaybackTransportRowPresenter.ViewHolder(android.view.View, android.support.v17.leanback.widget.Presenter);
+    method public final android.widget.TextView getCurrentPositionView();
+    method public final android.support.v17.leanback.widget.Presenter.ViewHolder getDescriptionViewHolder();
+    method public final android.widget.TextView getDurationView();
+    method protected void onSetCurrentPositionLabel(long);
+    method protected void onSetDurationLabel(long);
+    method public void setPlaybackSeekUiClient(android.support.v17.leanback.widget.PlaybackSeekUi.Client);
+  }
+
+  public abstract class Presenter implements android.support.v17.leanback.widget.FacetProvider {
+    ctor public Presenter();
+    method protected static void cancelAnimationsRecursive(android.view.View);
+    method public final java.lang.Object getFacet(java.lang.Class<?>);
+    method public abstract void onBindViewHolder(android.support.v17.leanback.widget.Presenter.ViewHolder, java.lang.Object);
+    method public void onBindViewHolder(android.support.v17.leanback.widget.Presenter.ViewHolder, java.lang.Object, java.util.List<java.lang.Object>);
+    method public abstract android.support.v17.leanback.widget.Presenter.ViewHolder onCreateViewHolder(android.view.ViewGroup);
+    method public abstract void onUnbindViewHolder(android.support.v17.leanback.widget.Presenter.ViewHolder);
+    method public void onViewAttachedToWindow(android.support.v17.leanback.widget.Presenter.ViewHolder);
+    method public void onViewDetachedFromWindow(android.support.v17.leanback.widget.Presenter.ViewHolder);
+    method public final void setFacet(java.lang.Class<?>, java.lang.Object);
+    method public void setOnClickListener(android.support.v17.leanback.widget.Presenter.ViewHolder, android.view.View.OnClickListener);
+  }
+
+  public static class Presenter.ViewHolder implements android.support.v17.leanback.widget.FacetProvider {
+    ctor public Presenter.ViewHolder(android.view.View);
+    method public final java.lang.Object getFacet(java.lang.Class<?>);
+    method public final void setFacet(java.lang.Class<?>, java.lang.Object);
+    field public final android.view.View view;
+  }
+
+  public static abstract class Presenter.ViewHolderTask {
+    ctor public Presenter.ViewHolderTask();
+    method public void run(android.support.v17.leanback.widget.Presenter.ViewHolder);
+  }
+
+  public abstract class PresenterSelector {
+    ctor public PresenterSelector();
+    method public abstract android.support.v17.leanback.widget.Presenter getPresenter(java.lang.Object);
+    method public android.support.v17.leanback.widget.Presenter[] getPresenters();
+  }
+
+  public abstract class PresenterSwitcher {
+    ctor public PresenterSwitcher();
+    method public void clear();
+    method public final android.view.ViewGroup getParentViewGroup();
+    method public void init(android.view.ViewGroup, android.support.v17.leanback.widget.PresenterSelector);
+    method protected abstract void insertView(android.view.View);
+    method protected void onViewSelected(android.view.View);
+    method public void select(java.lang.Object);
+    method protected void showView(android.view.View, boolean);
+    method public void unselect();
+  }
+
+  public class RecyclerViewParallax extends android.support.v17.leanback.widget.Parallax {
+    ctor public RecyclerViewParallax();
+    method public android.support.v17.leanback.widget.RecyclerViewParallax.ChildPositionProperty createProperty(java.lang.String, int);
+    method public float getMaxValue();
+    method public android.support.v7.widget.RecyclerView getRecyclerView();
+    method public void setRecyclerView(android.support.v7.widget.RecyclerView);
+  }
+
+  public static final class RecyclerViewParallax.ChildPositionProperty extends android.support.v17.leanback.widget.Parallax.IntProperty {
+    method public android.support.v17.leanback.widget.RecyclerViewParallax.ChildPositionProperty adapterPosition(int);
+    method public android.support.v17.leanback.widget.RecyclerViewParallax.ChildPositionProperty fraction(float);
+    method public int getAdapterPosition();
+    method public float getFraction();
+    method public int getOffset();
+    method public int getViewId();
+    method public android.support.v17.leanback.widget.RecyclerViewParallax.ChildPositionProperty offset(int);
+    method public android.support.v17.leanback.widget.RecyclerViewParallax.ChildPositionProperty viewId(int);
+  }
+
+  public class Row {
+    ctor public Row(long, android.support.v17.leanback.widget.HeaderItem);
+    ctor public Row(android.support.v17.leanback.widget.HeaderItem);
+    ctor public Row();
+    method public final android.support.v17.leanback.widget.HeaderItem getHeaderItem();
+    method public final long getId();
+    method public boolean isRenderedAsRowView();
+    method public final void setHeaderItem(android.support.v17.leanback.widget.HeaderItem);
+    method public final void setId(long);
+  }
+
+  public class RowHeaderPresenter extends android.support.v17.leanback.widget.Presenter {
+    ctor public RowHeaderPresenter();
+    method protected static float getFontDescent(android.widget.TextView, android.graphics.Paint);
+    method public int getSpaceUnderBaseline(android.support.v17.leanback.widget.RowHeaderPresenter.ViewHolder);
+    method public boolean isNullItemVisibilityGone();
+    method public void onBindViewHolder(android.support.v17.leanback.widget.Presenter.ViewHolder, java.lang.Object);
+    method public android.support.v17.leanback.widget.Presenter.ViewHolder onCreateViewHolder(android.view.ViewGroup);
+    method protected void onSelectLevelChanged(android.support.v17.leanback.widget.RowHeaderPresenter.ViewHolder);
+    method public void onUnbindViewHolder(android.support.v17.leanback.widget.Presenter.ViewHolder);
+    method public void setNullItemVisibilityGone(boolean);
+    method public final void setSelectLevel(android.support.v17.leanback.widget.RowHeaderPresenter.ViewHolder, float);
+  }
+
+  public static class RowHeaderPresenter.ViewHolder extends android.support.v17.leanback.widget.Presenter.ViewHolder {
+    ctor public RowHeaderPresenter.ViewHolder(android.view.View);
+    method public final float getSelectLevel();
+  }
+
+  public final class RowHeaderView extends android.widget.TextView {
+    ctor public RowHeaderView(android.content.Context);
+    ctor public RowHeaderView(android.content.Context, android.util.AttributeSet);
+    ctor public RowHeaderView(android.content.Context, android.util.AttributeSet, int);
+  }
+
+  public abstract class RowPresenter extends android.support.v17.leanback.widget.Presenter {
+    ctor public RowPresenter();
+    method protected abstract android.support.v17.leanback.widget.RowPresenter.ViewHolder createRowViewHolder(android.view.ViewGroup);
+    method protected void dispatchItemSelectedListener(android.support.v17.leanback.widget.RowPresenter.ViewHolder, boolean);
+    method public void freeze(android.support.v17.leanback.widget.RowPresenter.ViewHolder, boolean);
+    method public final android.support.v17.leanback.widget.RowHeaderPresenter getHeaderPresenter();
+    method public final android.support.v17.leanback.widget.RowPresenter.ViewHolder getRowViewHolder(android.support.v17.leanback.widget.Presenter.ViewHolder);
+    method public final boolean getSelectEffectEnabled();
+    method public final float getSelectLevel(android.support.v17.leanback.widget.Presenter.ViewHolder);
+    method public final int getSyncActivatePolicy();
+    method protected void initializeRowViewHolder(android.support.v17.leanback.widget.RowPresenter.ViewHolder);
+    method protected boolean isClippingChildren();
+    method public boolean isUsingDefaultSelectEffect();
+    method protected void onBindRowViewHolder(android.support.v17.leanback.widget.RowPresenter.ViewHolder, java.lang.Object);
+    method public final void onBindViewHolder(android.support.v17.leanback.widget.Presenter.ViewHolder, java.lang.Object);
+    method public final android.support.v17.leanback.widget.Presenter.ViewHolder onCreateViewHolder(android.view.ViewGroup);
+    method protected void onRowViewAttachedToWindow(android.support.v17.leanback.widget.RowPresenter.ViewHolder);
+    method protected void onRowViewDetachedFromWindow(android.support.v17.leanback.widget.RowPresenter.ViewHolder);
+    method protected void onRowViewExpanded(android.support.v17.leanback.widget.RowPresenter.ViewHolder, boolean);
+    method protected void onRowViewSelected(android.support.v17.leanback.widget.RowPresenter.ViewHolder, boolean);
+    method protected void onSelectLevelChanged(android.support.v17.leanback.widget.RowPresenter.ViewHolder);
+    method protected void onUnbindRowViewHolder(android.support.v17.leanback.widget.RowPresenter.ViewHolder);
+    method public final void onUnbindViewHolder(android.support.v17.leanback.widget.Presenter.ViewHolder);
+    method public final void onViewAttachedToWindow(android.support.v17.leanback.widget.Presenter.ViewHolder);
+    method public final void onViewDetachedFromWindow(android.support.v17.leanback.widget.Presenter.ViewHolder);
+    method public void setEntranceTransitionState(android.support.v17.leanback.widget.RowPresenter.ViewHolder, boolean);
+    method public final void setHeaderPresenter(android.support.v17.leanback.widget.RowHeaderPresenter);
+    method public final void setRowViewExpanded(android.support.v17.leanback.widget.Presenter.ViewHolder, boolean);
+    method public final void setRowViewSelected(android.support.v17.leanback.widget.Presenter.ViewHolder, boolean);
+    method public final void setSelectEffectEnabled(boolean);
+    method public final void setSelectLevel(android.support.v17.leanback.widget.Presenter.ViewHolder, float);
+    method public final void setSyncActivatePolicy(int);
+    field public static final int SYNC_ACTIVATED_CUSTOM = 0; // 0x0
+    field public static final int SYNC_ACTIVATED_TO_EXPANDED = 1; // 0x1
+    field public static final int SYNC_ACTIVATED_TO_EXPANDED_AND_SELECTED = 3; // 0x3
+    field public static final int SYNC_ACTIVATED_TO_SELECTED = 2; // 0x2
+  }
+
+  public static class RowPresenter.ViewHolder extends android.support.v17.leanback.widget.Presenter.ViewHolder {
+    ctor public RowPresenter.ViewHolder(android.view.View);
+    method public final android.support.v17.leanback.widget.RowHeaderPresenter.ViewHolder getHeaderViewHolder();
+    method public final android.support.v17.leanback.widget.BaseOnItemViewClickedListener getOnItemViewClickedListener();
+    method public final android.support.v17.leanback.widget.BaseOnItemViewSelectedListener getOnItemViewSelectedListener();
+    method public android.view.View.OnKeyListener getOnKeyListener();
+    method public final android.support.v17.leanback.widget.Row getRow();
+    method public final java.lang.Object getRowObject();
+    method public final float getSelectLevel();
+    method public java.lang.Object getSelectedItem();
+    method public android.support.v17.leanback.widget.Presenter.ViewHolder getSelectedItemViewHolder();
+    method public final boolean isExpanded();
+    method public final boolean isSelected();
+    method public final void setActivated(boolean);
+    method public final void setOnItemViewClickedListener(android.support.v17.leanback.widget.BaseOnItemViewClickedListener);
+    method public final void setOnItemViewSelectedListener(android.support.v17.leanback.widget.BaseOnItemViewSelectedListener);
+    method public void setOnKeyListener(android.view.View.OnKeyListener);
+    method public final void syncActivatedStatus(android.view.View);
+    field protected final android.support.v17.leanback.graphics.ColorOverlayDimmer mColorDimmer;
+  }
+
+  public class SearchBar extends android.widget.RelativeLayout {
+    ctor public SearchBar(android.content.Context);
+    ctor public SearchBar(android.content.Context, android.util.AttributeSet);
+    ctor public SearchBar(android.content.Context, android.util.AttributeSet, int);
+    method public void displayCompletions(java.util.List<java.lang.String>);
+    method public void displayCompletions(android.view.inputmethod.CompletionInfo[]);
+    method public android.graphics.drawable.Drawable getBadgeDrawable();
+    method public java.lang.CharSequence getHint();
+    method public java.lang.String getTitle();
+    method public boolean isRecognizing();
+    method public void setBadgeDrawable(android.graphics.drawable.Drawable);
+    method public void setPermissionListener(android.support.v17.leanback.widget.SearchBar.SearchBarPermissionListener);
+    method public void setSearchAffordanceColors(android.support.v17.leanback.widget.SearchOrbView.Colors);
+    method public void setSearchAffordanceColorsInListening(android.support.v17.leanback.widget.SearchOrbView.Colors);
+    method public void setSearchBarListener(android.support.v17.leanback.widget.SearchBar.SearchBarListener);
+    method public void setSearchQuery(java.lang.String);
+    method public deprecated void setSpeechRecognitionCallback(android.support.v17.leanback.widget.SpeechRecognitionCallback);
+    method public void setSpeechRecognizer(android.speech.SpeechRecognizer);
+    method public void setTitle(java.lang.String);
+    method public void startRecognition();
+    method public void stopRecognition();
+  }
+
+  public static abstract interface SearchBar.SearchBarListener {
+    method public abstract void onKeyboardDismiss(java.lang.String);
+    method public abstract void onSearchQueryChange(java.lang.String);
+    method public abstract void onSearchQuerySubmit(java.lang.String);
+  }
+
+  public static abstract interface SearchBar.SearchBarPermissionListener {
+    method public abstract void requestAudioPermission();
+  }
+
+  public class SearchEditText extends android.widget.EditText {
+    ctor public SearchEditText(android.content.Context);
+    ctor public SearchEditText(android.content.Context, android.util.AttributeSet);
+    ctor public SearchEditText(android.content.Context, android.util.AttributeSet, int);
+    method public void setOnKeyboardDismissListener(android.support.v17.leanback.widget.SearchEditText.OnKeyboardDismissListener);
+  }
+
+  public static abstract interface SearchEditText.OnKeyboardDismissListener {
+    method public abstract void onKeyboardDismiss();
+  }
+
+  public class SearchOrbView extends android.widget.FrameLayout implements android.view.View.OnClickListener {
+    ctor public SearchOrbView(android.content.Context);
+    ctor public SearchOrbView(android.content.Context, android.util.AttributeSet);
+    ctor public SearchOrbView(android.content.Context, android.util.AttributeSet, int);
+    method public void enableOrbColorAnimation(boolean);
+    method public int getOrbColor();
+    method public android.support.v17.leanback.widget.SearchOrbView.Colors getOrbColors();
+    method public android.graphics.drawable.Drawable getOrbIcon();
+    method public void onClick(android.view.View);
+    method public void setOnOrbClickedListener(android.view.View.OnClickListener);
+    method public void setOrbColor(int);
+    method public deprecated void setOrbColor(int, int);
+    method public void setOrbColors(android.support.v17.leanback.widget.SearchOrbView.Colors);
+    method public void setOrbIcon(android.graphics.drawable.Drawable);
+  }
+
+  public static class SearchOrbView.Colors {
+    ctor public SearchOrbView.Colors(int);
+    ctor public SearchOrbView.Colors(int, int);
+    ctor public SearchOrbView.Colors(int, int, int);
+    method public static int getBrightColor(int);
+    field public int brightColor;
+    field public int color;
+    field public int iconColor;
+  }
+
+  public class SectionRow extends android.support.v17.leanback.widget.Row {
+    ctor public SectionRow(android.support.v17.leanback.widget.HeaderItem);
+    ctor public SectionRow(long, java.lang.String);
+    ctor public SectionRow(java.lang.String);
+    method public final boolean isRenderedAsRowView();
+  }
+
+  public class ShadowOverlayContainer extends android.widget.FrameLayout {
+    ctor public ShadowOverlayContainer(android.content.Context);
+    ctor public ShadowOverlayContainer(android.content.Context, android.util.AttributeSet);
+    ctor public ShadowOverlayContainer(android.content.Context, android.util.AttributeSet, int);
+    method public int getShadowType();
+    method public android.view.View getWrappedView();
+    method public deprecated void initialize(boolean, boolean);
+    method public deprecated void initialize(boolean, boolean, boolean);
+    method public static void prepareParentForShadow(android.view.ViewGroup);
+    method public void setOverlayColor(int);
+    method public void setShadowFocusLevel(float);
+    method public static boolean supportsDynamicShadow();
+    method public static boolean supportsShadow();
+    method public void useDynamicShadow();
+    method public void useDynamicShadow(float, float);
+    method public void useStaticShadow();
+    method public void wrap(android.view.View);
+    field public static final int SHADOW_DYNAMIC = 3; // 0x3
+    field public static final int SHADOW_NONE = 1; // 0x1
+    field public static final int SHADOW_STATIC = 2; // 0x2
+  }
+
+  public final class ShadowOverlayHelper {
+    method public android.support.v17.leanback.widget.ShadowOverlayContainer createShadowOverlayContainer(android.content.Context);
+    method public int getShadowType();
+    method public boolean needsOverlay();
+    method public boolean needsRoundedCorner();
+    method public boolean needsWrapper();
+    method public void onViewCreated(android.view.View);
+    method public void prepareParentForShadow(android.view.ViewGroup);
+    method public static void setNoneWrapperOverlayColor(android.view.View, int);
+    method public static void setNoneWrapperShadowFocusLevel(android.view.View, float);
+    method public void setOverlayColor(android.view.View, int);
+    method public void setShadowFocusLevel(android.view.View, float);
+    method public static boolean supportsDynamicShadow();
+    method public static boolean supportsForeground();
+    method public static boolean supportsRoundedCorner();
+    method public static boolean supportsShadow();
+    field public static final int SHADOW_DYNAMIC = 3; // 0x3
+    field public static final int SHADOW_NONE = 1; // 0x1
+    field public static final int SHADOW_STATIC = 2; // 0x2
+  }
+
+  public static final class ShadowOverlayHelper.Builder {
+    ctor public ShadowOverlayHelper.Builder();
+    method public android.support.v17.leanback.widget.ShadowOverlayHelper build(android.content.Context);
+    method public android.support.v17.leanback.widget.ShadowOverlayHelper.Builder keepForegroundDrawable(boolean);
+    method public android.support.v17.leanback.widget.ShadowOverlayHelper.Builder needsOverlay(boolean);
+    method public android.support.v17.leanback.widget.ShadowOverlayHelper.Builder needsRoundedCorner(boolean);
+    method public android.support.v17.leanback.widget.ShadowOverlayHelper.Builder needsShadow(boolean);
+    method public android.support.v17.leanback.widget.ShadowOverlayHelper.Builder options(android.support.v17.leanback.widget.ShadowOverlayHelper.Options);
+    method public android.support.v17.leanback.widget.ShadowOverlayHelper.Builder preferZOrder(boolean);
+  }
+
+  public static final class ShadowOverlayHelper.Options {
+    ctor public ShadowOverlayHelper.Options();
+    method public android.support.v17.leanback.widget.ShadowOverlayHelper.Options dynamicShadowZ(float, float);
+    method public final float getDynamicShadowFocusedZ();
+    method public final float getDynamicShadowUnfocusedZ();
+    method public final int getRoundedCornerRadius();
+    method public android.support.v17.leanback.widget.ShadowOverlayHelper.Options roundedCornerRadius(int);
+    field public static final android.support.v17.leanback.widget.ShadowOverlayHelper.Options DEFAULT;
+  }
+
+  public final class SinglePresenterSelector extends android.support.v17.leanback.widget.PresenterSelector {
+    ctor public SinglePresenterSelector(android.support.v17.leanback.widget.Presenter);
+    method public android.support.v17.leanback.widget.Presenter getPresenter(java.lang.Object);
+  }
+
+  public class SparseArrayObjectAdapter extends android.support.v17.leanback.widget.ObjectAdapter {
+    ctor public SparseArrayObjectAdapter(android.support.v17.leanback.widget.PresenterSelector);
+    ctor public SparseArrayObjectAdapter(android.support.v17.leanback.widget.Presenter);
+    ctor public SparseArrayObjectAdapter();
+    method public void clear(int);
+    method public void clear();
+    method public java.lang.Object get(int);
+    method public int indexOf(java.lang.Object);
+    method public int indexOf(int);
+    method public java.lang.Object lookup(int);
+    method public void notifyArrayItemRangeChanged(int, int);
+    method public void set(int, java.lang.Object);
+    method public int size();
+  }
+
+  public class SpeechOrbView extends android.support.v17.leanback.widget.SearchOrbView {
+    ctor public SpeechOrbView(android.content.Context);
+    ctor public SpeechOrbView(android.content.Context, android.util.AttributeSet);
+    ctor public SpeechOrbView(android.content.Context, android.util.AttributeSet, int);
+    method public void setListeningOrbColors(android.support.v17.leanback.widget.SearchOrbView.Colors);
+    method public void setNotListeningOrbColors(android.support.v17.leanback.widget.SearchOrbView.Colors);
+    method public void setSoundLevel(int);
+    method public void showListening();
+    method public void showNotListening();
+  }
+
+  public abstract deprecated interface SpeechRecognitionCallback {
+    method public abstract void recognizeSpeech();
+  }
+
+  public class TitleHelper {
+    ctor public TitleHelper(android.view.ViewGroup, android.view.View);
+    method public android.support.v17.leanback.widget.BrowseFrameLayout.OnFocusSearchListener getOnFocusSearchListener();
+    method public android.view.ViewGroup getSceneRoot();
+    method public android.view.View getTitleView();
+    method public void showTitle(boolean);
+  }
+
+  public class TitleView extends android.widget.FrameLayout implements android.support.v17.leanback.widget.TitleViewAdapter.Provider {
+    ctor public TitleView(android.content.Context);
+    ctor public TitleView(android.content.Context, android.util.AttributeSet);
+    ctor public TitleView(android.content.Context, android.util.AttributeSet, int);
+    method public void enableAnimation(boolean);
+    method public android.graphics.drawable.Drawable getBadgeDrawable();
+    method public android.support.v17.leanback.widget.SearchOrbView.Colors getSearchAffordanceColors();
+    method public android.view.View getSearchAffordanceView();
+    method public java.lang.CharSequence getTitle();
+    method public android.support.v17.leanback.widget.TitleViewAdapter getTitleViewAdapter();
+    method public void setBadgeDrawable(android.graphics.drawable.Drawable);
+    method public void setOnSearchClickedListener(android.view.View.OnClickListener);
+    method public void setSearchAffordanceColors(android.support.v17.leanback.widget.SearchOrbView.Colors);
+    method public void setTitle(java.lang.CharSequence);
+    method public void updateComponentsVisibility(int);
+  }
+
+  public abstract class TitleViewAdapter {
+    ctor public TitleViewAdapter();
+    method public android.graphics.drawable.Drawable getBadgeDrawable();
+    method public android.support.v17.leanback.widget.SearchOrbView.Colors getSearchAffordanceColors();
+    method public abstract android.view.View getSearchAffordanceView();
+    method public java.lang.CharSequence getTitle();
+    method public void setAnimationEnabled(boolean);
+    method public void setBadgeDrawable(android.graphics.drawable.Drawable);
+    method public void setOnSearchClickedListener(android.view.View.OnClickListener);
+    method public void setSearchAffordanceColors(android.support.v17.leanback.widget.SearchOrbView.Colors);
+    method public void setTitle(java.lang.CharSequence);
+    method public void updateComponentsVisibility(int);
+    field public static final int BRANDING_VIEW_VISIBLE = 2; // 0x2
+    field public static final int FULL_VIEW_VISIBLE = 6; // 0x6
+    field public static final int SEARCH_VIEW_VISIBLE = 4; // 0x4
+  }
+
+  public static abstract interface TitleViewAdapter.Provider {
+    method public abstract android.support.v17.leanback.widget.TitleViewAdapter getTitleViewAdapter();
+  }
+
+  public class VerticalGridPresenter extends android.support.v17.leanback.widget.Presenter {
+    ctor public VerticalGridPresenter();
+    ctor public VerticalGridPresenter(int);
+    ctor public VerticalGridPresenter(int, boolean);
+    method public final boolean areChildRoundedCornersEnabled();
+    method protected android.support.v17.leanback.widget.VerticalGridPresenter.ViewHolder createGridViewHolder(android.view.ViewGroup);
+    method protected android.support.v17.leanback.widget.ShadowOverlayHelper.Options createShadowOverlayOptions();
+    method public final void enableChildRoundedCorners(boolean);
+    method public final int getFocusZoomFactor();
+    method public final boolean getKeepChildForeground();
+    method public int getNumberOfColumns();
+    method public final android.support.v17.leanback.widget.OnItemViewClickedListener getOnItemViewClickedListener();
+    method public final android.support.v17.leanback.widget.OnItemViewSelectedListener getOnItemViewSelectedListener();
+    method public final boolean getShadowEnabled();
+    method protected void initializeGridViewHolder(android.support.v17.leanback.widget.VerticalGridPresenter.ViewHolder);
+    method public final boolean isFocusDimmerUsed();
+    method public boolean isUsingDefaultShadow();
+    method public boolean isUsingZOrder(android.content.Context);
+    method public void onBindViewHolder(android.support.v17.leanback.widget.Presenter.ViewHolder, java.lang.Object);
+    method public final android.support.v17.leanback.widget.VerticalGridPresenter.ViewHolder onCreateViewHolder(android.view.ViewGroup);
+    method public void onUnbindViewHolder(android.support.v17.leanback.widget.Presenter.ViewHolder);
+    method public void setEntranceTransitionState(android.support.v17.leanback.widget.VerticalGridPresenter.ViewHolder, boolean);
+    method public final void setKeepChildForeground(boolean);
+    method public void setNumberOfColumns(int);
+    method public final void setOnItemViewClickedListener(android.support.v17.leanback.widget.OnItemViewClickedListener);
+    method public final void setOnItemViewSelectedListener(android.support.v17.leanback.widget.OnItemViewSelectedListener);
+    method public final void setShadowEnabled(boolean);
+  }
+
+  public static class VerticalGridPresenter.ViewHolder extends android.support.v17.leanback.widget.Presenter.ViewHolder {
+    ctor public VerticalGridPresenter.ViewHolder(android.support.v17.leanback.widget.VerticalGridView);
+    method public android.support.v17.leanback.widget.VerticalGridView getGridView();
+  }
+
+  public class VerticalGridView extends android.support.v17.leanback.widget.BaseGridView {
+    ctor public VerticalGridView(android.content.Context);
+    ctor public VerticalGridView(android.content.Context, android.util.AttributeSet);
+    ctor public VerticalGridView(android.content.Context, android.util.AttributeSet, int);
+    method protected void initAttributes(android.content.Context, android.util.AttributeSet);
+    method public void setColumnWidth(int);
+    method public void setNumColumns(int);
+  }
+
+  public abstract interface ViewHolderTask {
+    method public abstract void run(android.support.v7.widget.RecyclerView.ViewHolder);
+  }
+
+}
+
+package android.support.v17.leanback.widget.picker {
+
+  public class Picker extends android.widget.FrameLayout {
+    ctor public Picker(android.content.Context, android.util.AttributeSet, int);
+    method public void addOnValueChangedListener(android.support.v17.leanback.widget.picker.Picker.PickerValueListener);
+    method public float getActivatedVisibleItemCount();
+    method public android.support.v17.leanback.widget.picker.PickerColumn getColumnAt(int);
+    method public int getColumnsCount();
+    method protected int getPickerItemHeightPixels();
+    method public final int getPickerItemLayoutId();
+    method public final int getPickerItemTextViewId();
+    method public int getSelectedColumn();
+    method public final deprecated java.lang.CharSequence getSeparator();
+    method public final java.util.List<java.lang.CharSequence> getSeparators();
+    method public float getVisibleItemCount();
+    method public void onColumnValueChanged(int, int);
+    method public void removeOnValueChangedListener(android.support.v17.leanback.widget.picker.Picker.PickerValueListener);
+    method public void setActivatedVisibleItemCount(float);
+    method public void setColumnAt(int, android.support.v17.leanback.widget.picker.PickerColumn);
+    method public void setColumnValue(int, int, boolean);
+    method public void setColumns(java.util.List<android.support.v17.leanback.widget.picker.PickerColumn>);
+    method public final void setPickerItemTextViewId(int);
+    method public void setSelectedColumn(int);
+    method public final void setSeparator(java.lang.CharSequence);
+    method public final void setSeparators(java.util.List<java.lang.CharSequence>);
+    method public void setVisibleItemCount(float);
+  }
+
+  public static abstract interface Picker.PickerValueListener {
+    method public abstract void onValueChanged(android.support.v17.leanback.widget.picker.Picker, int);
+  }
+
+  public class PickerColumn {
+    ctor public PickerColumn();
+    method public int getCount();
+    method public int getCurrentValue();
+    method public java.lang.CharSequence getLabelFor(int);
+    method public java.lang.String getLabelFormat();
+    method public int getMaxValue();
+    method public int getMinValue();
+    method public java.lang.CharSequence[] getStaticLabels();
+    method public void setCurrentValue(int);
+    method public void setLabelFormat(java.lang.String);
+    method public void setMaxValue(int);
+    method public void setMinValue(int);
+    method public void setStaticLabels(java.lang.CharSequence[]);
+  }
+
+  public class TimePicker extends android.support.v17.leanback.widget.picker.Picker {
+    ctor public TimePicker(android.content.Context, android.util.AttributeSet);
+    ctor public TimePicker(android.content.Context, android.util.AttributeSet, int);
+    method public int getHour();
+    method public int getMinute();
+    method public boolean is24Hour();
+    method public boolean isPm();
+    method public void setHour(int);
+    method public void setIs24Hour(boolean);
+    method public void setMinute(int);
+  }
+
+}
+
diff --git a/v17/leanback/api21/android/support/v17/leanback/transition/FadeAndShortSlide.java b/leanback/api21/android/support/v17/leanback/transition/FadeAndShortSlide.java
similarity index 100%
rename from v17/leanback/api21/android/support/v17/leanback/transition/FadeAndShortSlide.java
rename to leanback/api21/android/support/v17/leanback/transition/FadeAndShortSlide.java
diff --git a/v17/leanback/api21/android/support/v17/leanback/transition/SlideNoPropagation.java b/leanback/api21/android/support/v17/leanback/transition/SlideNoPropagation.java
similarity index 100%
rename from v17/leanback/api21/android/support/v17/leanback/transition/SlideNoPropagation.java
rename to leanback/api21/android/support/v17/leanback/transition/SlideNoPropagation.java
diff --git a/v17/leanback/api21/android/support/v17/leanback/transition/TransitionHelperApi21.java b/leanback/api21/android/support/v17/leanback/transition/TransitionHelperApi21.java
similarity index 100%
rename from v17/leanback/api21/android/support/v17/leanback/transition/TransitionHelperApi21.java
rename to leanback/api21/android/support/v17/leanback/transition/TransitionHelperApi21.java
diff --git a/v17/leanback/api21/android/support/v17/leanback/transition/TranslationAnimationCreator.java b/leanback/api21/android/support/v17/leanback/transition/TranslationAnimationCreator.java
similarity index 100%
rename from v17/leanback/api21/android/support/v17/leanback/transition/TranslationAnimationCreator.java
rename to leanback/api21/android/support/v17/leanback/transition/TranslationAnimationCreator.java
diff --git a/v17/leanback/api21/android/support/v17/leanback/widget/RoundedRectHelperApi21.java b/leanback/api21/android/support/v17/leanback/widget/RoundedRectHelperApi21.java
similarity index 100%
rename from v17/leanback/api21/android/support/v17/leanback/widget/RoundedRectHelperApi21.java
rename to leanback/api21/android/support/v17/leanback/widget/RoundedRectHelperApi21.java
diff --git a/v17/leanback/api21/android/support/v17/leanback/widget/ShadowHelperApi21.java b/leanback/api21/android/support/v17/leanback/widget/ShadowHelperApi21.java
similarity index 100%
rename from v17/leanback/api21/android/support/v17/leanback/widget/ShadowHelperApi21.java
rename to leanback/api21/android/support/v17/leanback/widget/ShadowHelperApi21.java
diff --git a/leanback/build.gradle b/leanback/build.gradle
new file mode 100644
index 0000000..036f08f
--- /dev/null
+++ b/leanback/build.gradle
@@ -0,0 +1,47 @@
+import static android.support.dependencies.DependenciesKt.*
+import android.support.LibraryGroups
+import android.support.LibraryVersions
+
+plugins {
+    id("SupportAndroidLibraryPlugin")
+}
+
+dependencies {
+    api(project(":support-compat"))
+    api(project(":support-core-ui"))
+    api(project(":support-media-compat"))
+    api(project(":support-fragment"))
+    api(project(":recyclerview-v7"))
+
+    androidTestImplementation(TEST_RUNNER)
+    androidTestImplementation(ESPRESSO_CORE)
+    androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
+    androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
+}
+
+android {
+    defaultConfig {
+        minSdkVersion 17
+    }
+
+    sourceSets {
+        main.java.srcDirs = [
+                'common',
+                'jbmr2',
+                'kitkat',
+                'api21',
+                'src'
+        ]
+        main.res.srcDir 'res'
+    }
+}
+
+supportLibrary {
+    name = "Android Support Leanback v17"
+    publish = true
+    mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+    mavenGroup = LibraryGroups.SUPPORT
+    inceptionYear = "2014"
+    description = "Android Support Leanback v17"
+    legacySourceLocation = true
+}
diff --git a/v17/leanback/common/android/support/v17/leanback/transition/TransitionEpicenterCallback.java b/leanback/common/android/support/v17/leanback/transition/TransitionEpicenterCallback.java
similarity index 100%
rename from v17/leanback/common/android/support/v17/leanback/transition/TransitionEpicenterCallback.java
rename to leanback/common/android/support/v17/leanback/transition/TransitionEpicenterCallback.java
diff --git a/v17/leanback/common/android/support/v17/leanback/transition/TransitionListener.java b/leanback/common/android/support/v17/leanback/transition/TransitionListener.java
similarity index 100%
rename from v17/leanback/common/android/support/v17/leanback/transition/TransitionListener.java
rename to leanback/common/android/support/v17/leanback/transition/TransitionListener.java
diff --git a/leanback/generatef.py b/leanback/generatef.py
new file mode 100755
index 0000000..6364f09
--- /dev/null
+++ b/leanback/generatef.py
@@ -0,0 +1,108 @@
+#!/usr/bin/python
+
+# 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.
+
+import os
+import sys
+import re
+
+print "Generate framework fragment related code for leanback"
+
+cls = ['Base', 'BaseRow', 'Browse', 'Details', 'Error', 'Headers',
+      'Playback', 'Rows', 'Search', 'VerticalGrid', 'Branded',
+      'GuidedStep', 'Onboarding', 'Video']
+
+for w in cls:
+    print "copy {}SupportFragment to {}Fragment".format(w, w)
+
+    file = open('src/android/support/v17/leanback/app/{}SupportFragment.java'.format(w), 'r')
+    content = "// CHECKSTYLE:OFF Generated code\n"
+    content = content + "/* This file is auto-generated from {}SupportFragment.java.  DO NOT MODIFY. */\n\n".format(w)
+
+    for line in file:
+        line = line.replace('IS_FRAMEWORK_FRAGMENT = false', 'IS_FRAMEWORK_FRAGMENT = true');
+        for w2 in cls:
+            line = line.replace('{}SupportFragment'.format(w2), '{}Fragment'.format(w2))
+        line = line.replace('android.support.v4.app.FragmentActivity', 'android.app.Activity')
+        line = line.replace('android.support.v4.app.Fragment', 'android.app.Fragment')
+        line = line.replace('activity.getSupportFragmentManager()', 'activity.getFragmentManager()')
+        line = line.replace('FragmentActivity activity', 'Activity activity')
+        line = line.replace('(FragmentActivity', '(Activity')
+        # replace getContext() with FragmentUtil.getContext(XXXFragment.this), but dont match the case "view.getContext()"
+        line = re.sub(r'([^\.])getContext\(\)', r'\1FragmentUtil.getContext({}Fragment.this)'.format(w), line);
+        content = content + line
+    file.close()
+    # add deprecated tag to fragment class and inner classes/interfaces
+    content = re.sub(r'\*\/\n(@.*\n|)(public |abstract public |abstract |)class', '* @deprecated use {@link ' + w + 'SupportFragment}\n */\n@Deprecated\n\\1\\2class', content)
+    content = re.sub(r'\*\/\n    public (static class|interface|final static class|abstract static class)', '* @deprecated use {@link ' + w + 'SupportFragment}\n     */\n    @Deprecated\n    public \\1', content)
+    outfile = open('src/android/support/v17/leanback/app/{}Fragment.java'.format(w), 'w')
+    outfile.write(content)
+    outfile.close()
+
+
+
+print "copy VideoSupportFragmentGlueHost to VideoFragmentGlueHost"
+file = open('src/android/support/v17/leanback/app/VideoSupportFragmentGlueHost.java', 'r')
+content = "// CHECKSTYLE:OFF Generated code\n"
+content = content + "/* This file is auto-generated from VideoSupportFragmentGlueHost.java.  DO NOT MODIFY. */\n\n"
+for line in file:
+    line = line.replace('android.support.v4.app.Fragment', 'android.app.Fragment')
+    line = line.replace('VideoSupportFragment', 'VideoFragment')
+    line = line.replace('PlaybackSupportFragment', 'PlaybackFragment')
+    content = content + line
+file.close()
+# add deprecated tag to class
+content = re.sub(r'\*\/\npublic class', '* @deprecated use {@link VideoSupportFragmentGlueHost}\n */\n@Deprecated\npublic class', content)
+outfile = open('src/android/support/v17/leanback/app/VideoFragmentGlueHost.java', 'w')
+outfile.write(content)
+outfile.close()
+
+
+
+print "copy PlaybackSupportFragmentGlueHost to PlaybackFragmentGlueHost"
+file = open('src/android/support/v17/leanback/app/PlaybackSupportFragmentGlueHost.java', 'r')
+content = "// CHECKSTYLE:OFF Generated code\n"
+content = content + "/* This file is auto-generated from {}PlaybackSupportFragmentGlueHost.java.  DO NOT MODIFY. */\n\n"
+for line in file:
+    line = line.replace('VideoSupportFragment', 'VideoFragment')
+    line = line.replace('PlaybackSupportFragment', 'PlaybackFragment')
+    line = line.replace('android.support.v4.app.Fragment', 'android.app.Fragment')
+    content = content + line
+file.close()
+# add deprecated tag to class
+content = re.sub(r'\*\/\npublic class', '* @deprecated use {@link PlaybackSupportFragmentGlueHost}\n */\n@Deprecated\npublic class', content)
+outfile = open('src/android/support/v17/leanback/app/PlaybackFragmentGlueHost.java', 'w')
+outfile.write(content)
+outfile.close()
+
+
+
+print "copy DetailsSupportFragmentBackgroundController to DetailsFragmentBackgroundController"
+file = open('src/android/support/v17/leanback/app/DetailsSupportFragmentBackgroundController.java', 'r')
+content = "// CHECKSTYLE:OFF Generated code\n"
+content = content + "/* This file is auto-generated from {}DetailsSupportFragmentBackgroundController.java.  DO NOT MODIFY. */\n\n"
+for line in file:
+    line = line.replace('VideoSupportFragment', 'VideoFragment')
+    line = line.replace('DetailsSupportFragment', 'DetailsFragment')
+    line = line.replace('RowsSupportFragment', 'RowsFragment')
+    line = line.replace('android.support.v4.app.Fragment', 'android.app.Fragment')
+    line = line.replace('mFragment.getContext()', 'FragmentUtil.getContext(mFragment)')
+    content = content + line
+file.close()
+# add deprecated tag to class
+content = re.sub(r'\*\/\npublic class', '* @deprecated use {@link DetailsSupportFragmentBackgroundController}\n */\n@Deprecated\npublic class', content)
+outfile = open('src/android/support/v17/leanback/app/DetailsFragmentBackgroundController.java', 'w')
+outfile.write(content)
+outfile.close()
diff --git a/v17/leanback/jbmr2/android/support/v17/leanback/widget/ShadowHelperJbmr2.java b/leanback/jbmr2/android/support/v17/leanback/widget/ShadowHelperJbmr2.java
similarity index 100%
rename from v17/leanback/jbmr2/android/support/v17/leanback/widget/ShadowHelperJbmr2.java
rename to leanback/jbmr2/android/support/v17/leanback/widget/ShadowHelperJbmr2.java
diff --git a/v17/leanback/kitkat/android/support/v17/leanback/transition/LeanbackTransitionHelperKitKat.java b/leanback/kitkat/android/support/v17/leanback/transition/LeanbackTransitionHelperKitKat.java
similarity index 100%
rename from v17/leanback/kitkat/android/support/v17/leanback/transition/LeanbackTransitionHelperKitKat.java
rename to leanback/kitkat/android/support/v17/leanback/transition/LeanbackTransitionHelperKitKat.java
diff --git a/v17/leanback/kitkat/android/support/v17/leanback/transition/Scale.java b/leanback/kitkat/android/support/v17/leanback/transition/Scale.java
similarity index 100%
rename from v17/leanback/kitkat/android/support/v17/leanback/transition/Scale.java
rename to leanback/kitkat/android/support/v17/leanback/transition/Scale.java
diff --git a/v17/leanback/kitkat/android/support/v17/leanback/transition/SlideKitkat.java b/leanback/kitkat/android/support/v17/leanback/transition/SlideKitkat.java
similarity index 100%
rename from v17/leanback/kitkat/android/support/v17/leanback/transition/SlideKitkat.java
rename to leanback/kitkat/android/support/v17/leanback/transition/SlideKitkat.java
diff --git a/v17/leanback/kitkat/android/support/v17/leanback/transition/TransitionHelperKitkat.java b/leanback/kitkat/android/support/v17/leanback/transition/TransitionHelperKitkat.java
similarity index 100%
rename from v17/leanback/kitkat/android/support/v17/leanback/transition/TransitionHelperKitkat.java
rename to leanback/kitkat/android/support/v17/leanback/transition/TransitionHelperKitkat.java
diff --git a/v17/leanback/res/anim/lb_decelerator_2.xml b/leanback/res/anim/lb_decelerator_2.xml
similarity index 100%
rename from v17/leanback/res/anim/lb_decelerator_2.xml
rename to leanback/res/anim/lb_decelerator_2.xml
diff --git a/v17/leanback/res/anim/lb_decelerator_4.xml b/leanback/res/anim/lb_decelerator_4.xml
similarity index 100%
rename from v17/leanback/res/anim/lb_decelerator_4.xml
rename to leanback/res/anim/lb_decelerator_4.xml
diff --git a/v17/leanback/res/animator-v21/lb_onboarding_description_enter.xml b/leanback/res/animator-v21/lb_onboarding_description_enter.xml
similarity index 100%
rename from v17/leanback/res/animator-v21/lb_onboarding_description_enter.xml
rename to leanback/res/animator-v21/lb_onboarding_description_enter.xml
diff --git a/v17/leanback/res/animator-v21/lb_onboarding_logo_enter.xml b/leanback/res/animator-v21/lb_onboarding_logo_enter.xml
similarity index 100%
rename from v17/leanback/res/animator-v21/lb_onboarding_logo_enter.xml
rename to leanback/res/animator-v21/lb_onboarding_logo_enter.xml
diff --git a/v17/leanback/res/animator-v21/lb_onboarding_logo_exit.xml b/leanback/res/animator-v21/lb_onboarding_logo_exit.xml
similarity index 100%
rename from v17/leanback/res/animator-v21/lb_onboarding_logo_exit.xml
rename to leanback/res/animator-v21/lb_onboarding_logo_exit.xml
diff --git a/v17/leanback/res/animator-v21/lb_onboarding_page_indicator_enter.xml b/leanback/res/animator-v21/lb_onboarding_page_indicator_enter.xml
similarity index 100%
rename from v17/leanback/res/animator-v21/lb_onboarding_page_indicator_enter.xml
rename to leanback/res/animator-v21/lb_onboarding_page_indicator_enter.xml
diff --git a/v17/leanback/res/animator-v21/lb_onboarding_title_enter.xml b/leanback/res/animator-v21/lb_onboarding_title_enter.xml
similarity index 100%
rename from v17/leanback/res/animator-v21/lb_onboarding_title_enter.xml
rename to leanback/res/animator-v21/lb_onboarding_title_enter.xml
diff --git a/v17/leanback/res/animator-v21/lb_playback_bg_fade_in.xml b/leanback/res/animator-v21/lb_playback_bg_fade_in.xml
similarity index 100%
rename from v17/leanback/res/animator-v21/lb_playback_bg_fade_in.xml
rename to leanback/res/animator-v21/lb_playback_bg_fade_in.xml
diff --git a/v17/leanback/res/animator-v21/lb_playback_bg_fade_out.xml b/leanback/res/animator-v21/lb_playback_bg_fade_out.xml
similarity index 100%
rename from v17/leanback/res/animator-v21/lb_playback_bg_fade_out.xml
rename to leanback/res/animator-v21/lb_playback_bg_fade_out.xml
diff --git a/v17/leanback/res/animator-v21/lb_playback_description_fade_out.xml b/leanback/res/animator-v21/lb_playback_description_fade_out.xml
similarity index 100%
rename from v17/leanback/res/animator-v21/lb_playback_description_fade_out.xml
rename to leanback/res/animator-v21/lb_playback_description_fade_out.xml
diff --git a/v17/leanback/res/animator/lb_guidedactions_item_pressed.xml b/leanback/res/animator/lb_guidedactions_item_pressed.xml
similarity index 100%
rename from v17/leanback/res/animator/lb_guidedactions_item_pressed.xml
rename to leanback/res/animator/lb_guidedactions_item_pressed.xml
diff --git a/v17/leanback/res/animator/lb_guidedactions_item_unpressed.xml b/leanback/res/animator/lb_guidedactions_item_unpressed.xml
similarity index 100%
rename from v17/leanback/res/animator/lb_guidedactions_item_unpressed.xml
rename to leanback/res/animator/lb_guidedactions_item_unpressed.xml
diff --git a/v17/leanback/res/animator/lb_guidedstep_slide_down.xml b/leanback/res/animator/lb_guidedstep_slide_down.xml
similarity index 100%
rename from v17/leanback/res/animator/lb_guidedstep_slide_down.xml
rename to leanback/res/animator/lb_guidedstep_slide_down.xml
diff --git a/v17/leanback/res/animator/lb_guidedstep_slide_up.xml b/leanback/res/animator/lb_guidedstep_slide_up.xml
similarity index 100%
rename from v17/leanback/res/animator/lb_guidedstep_slide_up.xml
rename to leanback/res/animator/lb_guidedstep_slide_up.xml
diff --git a/v17/leanback/res/animator/lb_onboarding_description_enter.xml b/leanback/res/animator/lb_onboarding_description_enter.xml
similarity index 100%
rename from v17/leanback/res/animator/lb_onboarding_description_enter.xml
rename to leanback/res/animator/lb_onboarding_description_enter.xml
diff --git a/v17/leanback/res/animator/lb_onboarding_logo_enter.xml b/leanback/res/animator/lb_onboarding_logo_enter.xml
similarity index 100%
rename from v17/leanback/res/animator/lb_onboarding_logo_enter.xml
rename to leanback/res/animator/lb_onboarding_logo_enter.xml
diff --git a/v17/leanback/res/animator/lb_onboarding_logo_exit.xml b/leanback/res/animator/lb_onboarding_logo_exit.xml
similarity index 100%
rename from v17/leanback/res/animator/lb_onboarding_logo_exit.xml
rename to leanback/res/animator/lb_onboarding_logo_exit.xml
diff --git a/v17/leanback/res/animator/lb_onboarding_page_indicator_enter.xml b/leanback/res/animator/lb_onboarding_page_indicator_enter.xml
similarity index 100%
rename from v17/leanback/res/animator/lb_onboarding_page_indicator_enter.xml
rename to leanback/res/animator/lb_onboarding_page_indicator_enter.xml
diff --git a/v17/leanback/res/animator/lb_onboarding_page_indicator_fade_in.xml b/leanback/res/animator/lb_onboarding_page_indicator_fade_in.xml
similarity index 100%
rename from v17/leanback/res/animator/lb_onboarding_page_indicator_fade_in.xml
rename to leanback/res/animator/lb_onboarding_page_indicator_fade_in.xml
diff --git a/v17/leanback/res/animator/lb_onboarding_page_indicator_fade_out.xml b/leanback/res/animator/lb_onboarding_page_indicator_fade_out.xml
similarity index 100%
rename from v17/leanback/res/animator/lb_onboarding_page_indicator_fade_out.xml
rename to leanback/res/animator/lb_onboarding_page_indicator_fade_out.xml
diff --git a/v17/leanback/res/animator/lb_onboarding_start_button_fade_in.xml b/leanback/res/animator/lb_onboarding_start_button_fade_in.xml
similarity index 100%
rename from v17/leanback/res/animator/lb_onboarding_start_button_fade_in.xml
rename to leanback/res/animator/lb_onboarding_start_button_fade_in.xml
diff --git a/v17/leanback/res/animator/lb_onboarding_start_button_fade_out.xml b/leanback/res/animator/lb_onboarding_start_button_fade_out.xml
similarity index 100%
rename from v17/leanback/res/animator/lb_onboarding_start_button_fade_out.xml
rename to leanback/res/animator/lb_onboarding_start_button_fade_out.xml
diff --git a/v17/leanback/res/animator/lb_onboarding_title_enter.xml b/leanback/res/animator/lb_onboarding_title_enter.xml
similarity index 100%
rename from v17/leanback/res/animator/lb_onboarding_title_enter.xml
rename to leanback/res/animator/lb_onboarding_title_enter.xml
diff --git a/v17/leanback/res/animator/lb_playback_bg_fade_in.xml b/leanback/res/animator/lb_playback_bg_fade_in.xml
similarity index 100%
rename from v17/leanback/res/animator/lb_playback_bg_fade_in.xml
rename to leanback/res/animator/lb_playback_bg_fade_in.xml
diff --git a/v17/leanback/res/animator/lb_playback_bg_fade_out.xml b/leanback/res/animator/lb_playback_bg_fade_out.xml
similarity index 100%
rename from v17/leanback/res/animator/lb_playback_bg_fade_out.xml
rename to leanback/res/animator/lb_playback_bg_fade_out.xml
diff --git a/v17/leanback/res/animator/lb_playback_controls_fade_in.xml b/leanback/res/animator/lb_playback_controls_fade_in.xml
similarity index 100%
rename from v17/leanback/res/animator/lb_playback_controls_fade_in.xml
rename to leanback/res/animator/lb_playback_controls_fade_in.xml
diff --git a/v17/leanback/res/animator/lb_playback_controls_fade_out.xml b/leanback/res/animator/lb_playback_controls_fade_out.xml
similarity index 100%
rename from v17/leanback/res/animator/lb_playback_controls_fade_out.xml
rename to leanback/res/animator/lb_playback_controls_fade_out.xml
diff --git a/v17/leanback/res/animator/lb_playback_description_fade_in.xml b/leanback/res/animator/lb_playback_description_fade_in.xml
similarity index 100%
rename from v17/leanback/res/animator/lb_playback_description_fade_in.xml
rename to leanback/res/animator/lb_playback_description_fade_in.xml
diff --git a/v17/leanback/res/animator/lb_playback_description_fade_out.xml b/leanback/res/animator/lb_playback_description_fade_out.xml
similarity index 100%
rename from v17/leanback/res/animator/lb_playback_description_fade_out.xml
rename to leanback/res/animator/lb_playback_description_fade_out.xml
diff --git a/v17/leanback/res/animator/lb_playback_rows_fade_in.xml b/leanback/res/animator/lb_playback_rows_fade_in.xml
similarity index 100%
rename from v17/leanback/res/animator/lb_playback_rows_fade_in.xml
rename to leanback/res/animator/lb_playback_rows_fade_in.xml
diff --git a/v17/leanback/res/animator/lb_playback_rows_fade_out.xml b/leanback/res/animator/lb_playback_rows_fade_out.xml
similarity index 100%
rename from v17/leanback/res/animator/lb_playback_rows_fade_out.xml
rename to leanback/res/animator/lb_playback_rows_fade_out.xml
diff --git a/v17/leanback/res/drawable-hdpi/lb_action_bg_focused.9.png b/leanback/res/drawable-hdpi/lb_action_bg_focused.9.png
similarity index 100%
rename from v17/leanback/res/drawable-hdpi/lb_action_bg_focused.9.png
rename to leanback/res/drawable-hdpi/lb_action_bg_focused.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-hdpi/lb_ic_actions_right_arrow.png b/leanback/res/drawable-hdpi/lb_ic_actions_right_arrow.png
similarity index 100%
rename from v17/leanback/res/drawable-hdpi/lb_ic_actions_right_arrow.png
rename to leanback/res/drawable-hdpi/lb_ic_actions_right_arrow.png
Binary files differ
diff --git a/v17/leanback/res/drawable-hdpi/lb_ic_in_app_search.png b/leanback/res/drawable-hdpi/lb_ic_in_app_search.png
similarity index 100%
rename from v17/leanback/res/drawable-hdpi/lb_ic_in_app_search.png
rename to leanback/res/drawable-hdpi/lb_ic_in_app_search.png
Binary files differ
diff --git a/v17/leanback/res/drawable-hdpi/lb_ic_sad_cloud.png b/leanback/res/drawable-hdpi/lb_ic_sad_cloud.png
similarity index 100%
rename from v17/leanback/res/drawable-hdpi/lb_ic_sad_cloud.png
rename to leanback/res/drawable-hdpi/lb_ic_sad_cloud.png
Binary files differ
diff --git a/v17/leanback/res/drawable-hdpi/lb_ic_search_mic.png b/leanback/res/drawable-hdpi/lb_ic_search_mic.png
similarity index 100%
rename from v17/leanback/res/drawable-hdpi/lb_ic_search_mic.png
rename to leanback/res/drawable-hdpi/lb_ic_search_mic.png
Binary files differ
diff --git a/v17/leanback/res/drawable-hdpi/lb_ic_search_mic_out.png b/leanback/res/drawable-hdpi/lb_ic_search_mic_out.png
similarity index 100%
rename from v17/leanback/res/drawable-hdpi/lb_ic_search_mic_out.png
rename to leanback/res/drawable-hdpi/lb_ic_search_mic_out.png
Binary files differ
diff --git a/v17/leanback/res/drawable-hdpi/lb_in_app_search_bg.9.png b/leanback/res/drawable-hdpi/lb_in_app_search_bg.9.png
similarity index 100%
rename from v17/leanback/res/drawable-hdpi/lb_in_app_search_bg.9.png
rename to leanback/res/drawable-hdpi/lb_in_app_search_bg.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-hdpi/lb_in_app_search_shadow_focused.9.png b/leanback/res/drawable-hdpi/lb_in_app_search_shadow_focused.9.png
similarity index 100%
rename from v17/leanback/res/drawable-hdpi/lb_in_app_search_shadow_focused.9.png
rename to leanback/res/drawable-hdpi/lb_in_app_search_shadow_focused.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-hdpi/lb_in_app_search_shadow_normal.9.png b/leanback/res/drawable-hdpi/lb_in_app_search_shadow_normal.9.png
similarity index 100%
rename from v17/leanback/res/drawable-hdpi/lb_in_app_search_shadow_normal.9.png
rename to leanback/res/drawable-hdpi/lb_in_app_search_shadow_normal.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-mdpi/lb_action_bg_focused.9.png b/leanback/res/drawable-mdpi/lb_action_bg_focused.9.png
similarity index 100%
rename from v17/leanback/res/drawable-mdpi/lb_action_bg_focused.9.png
rename to leanback/res/drawable-mdpi/lb_action_bg_focused.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-mdpi/lb_ic_actions_right_arrow.png b/leanback/res/drawable-mdpi/lb_ic_actions_right_arrow.png
similarity index 100%
rename from v17/leanback/res/drawable-mdpi/lb_ic_actions_right_arrow.png
rename to leanback/res/drawable-mdpi/lb_ic_actions_right_arrow.png
Binary files differ
diff --git a/v17/leanback/res/drawable-mdpi/lb_ic_in_app_search.png b/leanback/res/drawable-mdpi/lb_ic_in_app_search.png
similarity index 100%
rename from v17/leanback/res/drawable-mdpi/lb_ic_in_app_search.png
rename to leanback/res/drawable-mdpi/lb_ic_in_app_search.png
Binary files differ
diff --git a/v17/leanback/res/drawable-mdpi/lb_ic_sad_cloud.png b/leanback/res/drawable-mdpi/lb_ic_sad_cloud.png
similarity index 100%
rename from v17/leanback/res/drawable-mdpi/lb_ic_sad_cloud.png
rename to leanback/res/drawable-mdpi/lb_ic_sad_cloud.png
Binary files differ
diff --git a/v17/leanback/res/drawable-mdpi/lb_ic_search_mic.png b/leanback/res/drawable-mdpi/lb_ic_search_mic.png
similarity index 100%
rename from v17/leanback/res/drawable-mdpi/lb_ic_search_mic.png
rename to leanback/res/drawable-mdpi/lb_ic_search_mic.png
Binary files differ
diff --git a/v17/leanback/res/drawable-mdpi/lb_ic_search_mic_out.png b/leanback/res/drawable-mdpi/lb_ic_search_mic_out.png
similarity index 100%
rename from v17/leanback/res/drawable-mdpi/lb_ic_search_mic_out.png
rename to leanback/res/drawable-mdpi/lb_ic_search_mic_out.png
Binary files differ
diff --git a/v17/leanback/res/drawable-mdpi/lb_in_app_search_bg.9.png b/leanback/res/drawable-mdpi/lb_in_app_search_bg.9.png
similarity index 100%
rename from v17/leanback/res/drawable-mdpi/lb_in_app_search_bg.9.png
rename to leanback/res/drawable-mdpi/lb_in_app_search_bg.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-mdpi/lb_in_app_search_shadow_focused.9.png b/leanback/res/drawable-mdpi/lb_in_app_search_shadow_focused.9.png
similarity index 100%
rename from v17/leanback/res/drawable-mdpi/lb_in_app_search_shadow_focused.9.png
rename to leanback/res/drawable-mdpi/lb_in_app_search_shadow_focused.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-mdpi/lb_in_app_search_shadow_normal.9.png b/leanback/res/drawable-mdpi/lb_in_app_search_shadow_normal.9.png
similarity index 100%
rename from v17/leanback/res/drawable-mdpi/lb_in_app_search_shadow_normal.9.png
rename to leanback/res/drawable-mdpi/lb_in_app_search_shadow_normal.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-v21/lb_action_bg.xml b/leanback/res/drawable-v21/lb_action_bg.xml
similarity index 100%
rename from v17/leanback/res/drawable-v21/lb_action_bg.xml
rename to leanback/res/drawable-v21/lb_action_bg.xml
diff --git a/v17/leanback/res/drawable-v21/lb_card_foreground.xml b/leanback/res/drawable-v21/lb_card_foreground.xml
similarity index 100%
rename from v17/leanback/res/drawable-v21/lb_card_foreground.xml
rename to leanback/res/drawable-v21/lb_card_foreground.xml
diff --git a/v17/leanback/res/drawable-v21/lb_control_button_primary.xml b/leanback/res/drawable-v21/lb_control_button_primary.xml
similarity index 100%
rename from v17/leanback/res/drawable-v21/lb_control_button_primary.xml
rename to leanback/res/drawable-v21/lb_control_button_primary.xml
diff --git a/v17/leanback/res/drawable-v21/lb_control_button_secondary.xml b/leanback/res/drawable-v21/lb_control_button_secondary.xml
similarity index 100%
rename from v17/leanback/res/drawable-v21/lb_control_button_secondary.xml
rename to leanback/res/drawable-v21/lb_control_button_secondary.xml
diff --git a/v17/leanback/res/drawable-v21/lb_selectable_item_rounded_rect.xml b/leanback/res/drawable-v21/lb_selectable_item_rounded_rect.xml
similarity index 100%
rename from v17/leanback/res/drawable-v21/lb_selectable_item_rounded_rect.xml
rename to leanback/res/drawable-v21/lb_selectable_item_rounded_rect.xml
diff --git a/v17/leanback/res/drawable-xhdpi/lb_action_bg_focused.9.png b/leanback/res/drawable-xhdpi/lb_action_bg_focused.9.png
similarity index 100%
rename from v17/leanback/res/drawable-xhdpi/lb_action_bg_focused.9.png
rename to leanback/res/drawable-xhdpi/lb_action_bg_focused.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_card_shadow_focused.9.png b/leanback/res/drawable-xhdpi/lb_card_shadow_focused.9.png
similarity index 100%
rename from v17/leanback/res/drawable-xhdpi/lb_card_shadow_focused.9.png
rename to leanback/res/drawable-xhdpi/lb_card_shadow_focused.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_card_shadow_normal.9.png b/leanback/res/drawable-xhdpi/lb_card_shadow_normal.9.png
similarity index 100%
rename from v17/leanback/res/drawable-xhdpi/lb_card_shadow_normal.9.png
rename to leanback/res/drawable-xhdpi/lb_card_shadow_normal.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_actions_right_arrow.png b/leanback/res/drawable-xhdpi/lb_ic_actions_right_arrow.png
similarity index 100%
rename from v17/leanback/res/drawable-xhdpi/lb_ic_actions_right_arrow.png
rename to leanback/res/drawable-xhdpi/lb_ic_actions_right_arrow.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_cc.png b/leanback/res/drawable-xhdpi/lb_ic_cc.png
similarity index 100%
rename from v17/leanback/res/drawable-xhdpi/lb_ic_cc.png
rename to leanback/res/drawable-xhdpi/lb_ic_cc.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_fast_forward.png b/leanback/res/drawable-xhdpi/lb_ic_fast_forward.png
similarity index 100%
rename from v17/leanback/res/drawable-xhdpi/lb_ic_fast_forward.png
rename to leanback/res/drawable-xhdpi/lb_ic_fast_forward.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_fast_rewind.png b/leanback/res/drawable-xhdpi/lb_ic_fast_rewind.png
similarity index 100%
rename from v17/leanback/res/drawable-xhdpi/lb_ic_fast_rewind.png
rename to leanback/res/drawable-xhdpi/lb_ic_fast_rewind.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_guidedactions_item_chevron.png b/leanback/res/drawable-xhdpi/lb_ic_guidedactions_item_chevron.png
similarity index 100%
rename from v17/leanback/res/drawable-xhdpi/lb_ic_guidedactions_item_chevron.png
rename to leanback/res/drawable-xhdpi/lb_ic_guidedactions_item_chevron.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_hq.png b/leanback/res/drawable-xhdpi/lb_ic_hq.png
similarity index 100%
rename from v17/leanback/res/drawable-xhdpi/lb_ic_hq.png
rename to leanback/res/drawable-xhdpi/lb_ic_hq.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_in_app_search.png b/leanback/res/drawable-xhdpi/lb_ic_in_app_search.png
similarity index 100%
rename from v17/leanback/res/drawable-xhdpi/lb_ic_in_app_search.png
rename to leanback/res/drawable-xhdpi/lb_ic_in_app_search.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_loop.png b/leanback/res/drawable-xhdpi/lb_ic_loop.png
similarity index 100%
rename from v17/leanback/res/drawable-xhdpi/lb_ic_loop.png
rename to leanback/res/drawable-xhdpi/lb_ic_loop.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_loop_one.png b/leanback/res/drawable-xhdpi/lb_ic_loop_one.png
similarity index 100%
rename from v17/leanback/res/drawable-xhdpi/lb_ic_loop_one.png
rename to leanback/res/drawable-xhdpi/lb_ic_loop_one.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_more.png b/leanback/res/drawable-xhdpi/lb_ic_more.png
similarity index 100%
rename from v17/leanback/res/drawable-xhdpi/lb_ic_more.png
rename to leanback/res/drawable-xhdpi/lb_ic_more.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_nav_arrow.png b/leanback/res/drawable-xhdpi/lb_ic_nav_arrow.png
similarity index 100%
rename from v17/leanback/res/drawable-xhdpi/lb_ic_nav_arrow.png
rename to leanback/res/drawable-xhdpi/lb_ic_nav_arrow.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_pause.png b/leanback/res/drawable-xhdpi/lb_ic_pause.png
similarity index 100%
rename from v17/leanback/res/drawable-xhdpi/lb_ic_pause.png
rename to leanback/res/drawable-xhdpi/lb_ic_pause.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_pip.png b/leanback/res/drawable-xhdpi/lb_ic_pip.png
similarity index 100%
rename from v17/leanback/res/drawable-xhdpi/lb_ic_pip.png
rename to leanback/res/drawable-xhdpi/lb_ic_pip.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_play.png b/leanback/res/drawable-xhdpi/lb_ic_play.png
similarity index 100%
rename from v17/leanback/res/drawable-xhdpi/lb_ic_play.png
rename to leanback/res/drawable-xhdpi/lb_ic_play.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_play_fit.png b/leanback/res/drawable-xhdpi/lb_ic_play_fit.png
similarity index 100%
rename from v17/leanback/res/drawable-xhdpi/lb_ic_play_fit.png
rename to leanback/res/drawable-xhdpi/lb_ic_play_fit.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_playback_loop.png b/leanback/res/drawable-xhdpi/lb_ic_playback_loop.png
similarity index 100%
rename from v17/leanback/res/drawable-xhdpi/lb_ic_playback_loop.png
rename to leanback/res/drawable-xhdpi/lb_ic_playback_loop.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_replay.png b/leanback/res/drawable-xhdpi/lb_ic_replay.png
similarity index 100%
rename from v17/leanback/res/drawable-xhdpi/lb_ic_replay.png
rename to leanback/res/drawable-xhdpi/lb_ic_replay.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_sad_cloud.png b/leanback/res/drawable-xhdpi/lb_ic_sad_cloud.png
similarity index 100%
rename from v17/leanback/res/drawable-xhdpi/lb_ic_sad_cloud.png
rename to leanback/res/drawable-xhdpi/lb_ic_sad_cloud.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_search_mic.png b/leanback/res/drawable-xhdpi/lb_ic_search_mic.png
similarity index 100%
rename from v17/leanback/res/drawable-xhdpi/lb_ic_search_mic.png
rename to leanback/res/drawable-xhdpi/lb_ic_search_mic.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_search_mic_out.png b/leanback/res/drawable-xhdpi/lb_ic_search_mic_out.png
similarity index 100%
rename from v17/leanback/res/drawable-xhdpi/lb_ic_search_mic_out.png
rename to leanback/res/drawable-xhdpi/lb_ic_search_mic_out.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_shuffle.png b/leanback/res/drawable-xhdpi/lb_ic_shuffle.png
similarity index 100%
rename from v17/leanback/res/drawable-xhdpi/lb_ic_shuffle.png
rename to leanback/res/drawable-xhdpi/lb_ic_shuffle.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_skip_next.png b/leanback/res/drawable-xhdpi/lb_ic_skip_next.png
similarity index 100%
rename from v17/leanback/res/drawable-xhdpi/lb_ic_skip_next.png
rename to leanback/res/drawable-xhdpi/lb_ic_skip_next.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_skip_previous.png b/leanback/res/drawable-xhdpi/lb_ic_skip_previous.png
similarity index 100%
rename from v17/leanback/res/drawable-xhdpi/lb_ic_skip_previous.png
rename to leanback/res/drawable-xhdpi/lb_ic_skip_previous.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_stop.png b/leanback/res/drawable-xhdpi/lb_ic_stop.png
similarity index 100%
rename from v17/leanback/res/drawable-xhdpi/lb_ic_stop.png
rename to leanback/res/drawable-xhdpi/lb_ic_stop.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_thumb_down.png b/leanback/res/drawable-xhdpi/lb_ic_thumb_down.png
similarity index 100%
rename from v17/leanback/res/drawable-xhdpi/lb_ic_thumb_down.png
rename to leanback/res/drawable-xhdpi/lb_ic_thumb_down.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_thumb_down_outline.png b/leanback/res/drawable-xhdpi/lb_ic_thumb_down_outline.png
similarity index 100%
rename from v17/leanback/res/drawable-xhdpi/lb_ic_thumb_down_outline.png
rename to leanback/res/drawable-xhdpi/lb_ic_thumb_down_outline.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_thumb_up.png b/leanback/res/drawable-xhdpi/lb_ic_thumb_up.png
similarity index 100%
rename from v17/leanback/res/drawable-xhdpi/lb_ic_thumb_up.png
rename to leanback/res/drawable-xhdpi/lb_ic_thumb_up.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_thumb_up_outline.png b/leanback/res/drawable-xhdpi/lb_ic_thumb_up_outline.png
similarity index 100%
rename from v17/leanback/res/drawable-xhdpi/lb_ic_thumb_up_outline.png
rename to leanback/res/drawable-xhdpi/lb_ic_thumb_up_outline.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_in_app_search_bg.9.png b/leanback/res/drawable-xhdpi/lb_in_app_search_bg.9.png
similarity index 100%
rename from v17/leanback/res/drawable-xhdpi/lb_in_app_search_bg.9.png
rename to leanback/res/drawable-xhdpi/lb_in_app_search_bg.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_in_app_search_shadow_focused.9.png b/leanback/res/drawable-xhdpi/lb_in_app_search_shadow_focused.9.png
similarity index 100%
rename from v17/leanback/res/drawable-xhdpi/lb_in_app_search_shadow_focused.9.png
rename to leanback/res/drawable-xhdpi/lb_in_app_search_shadow_focused.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_in_app_search_shadow_normal.9.png b/leanback/res/drawable-xhdpi/lb_in_app_search_shadow_normal.9.png
similarity index 100%
rename from v17/leanback/res/drawable-xhdpi/lb_in_app_search_shadow_normal.9.png
rename to leanback/res/drawable-xhdpi/lb_in_app_search_shadow_normal.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_text_dot_one.png b/leanback/res/drawable-xhdpi/lb_text_dot_one.png
similarity index 100%
rename from v17/leanback/res/drawable-xhdpi/lb_text_dot_one.png
rename to leanback/res/drawable-xhdpi/lb_text_dot_one.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_text_dot_one_small.png b/leanback/res/drawable-xhdpi/lb_text_dot_one_small.png
similarity index 100%
rename from v17/leanback/res/drawable-xhdpi/lb_text_dot_one_small.png
rename to leanback/res/drawable-xhdpi/lb_text_dot_one_small.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_text_dot_two.png b/leanback/res/drawable-xhdpi/lb_text_dot_two.png
similarity index 100%
rename from v17/leanback/res/drawable-xhdpi/lb_text_dot_two.png
rename to leanback/res/drawable-xhdpi/lb_text_dot_two.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_text_dot_two_small.png b/leanback/res/drawable-xhdpi/lb_text_dot_two_small.png
similarity index 100%
rename from v17/leanback/res/drawable-xhdpi/lb_text_dot_two_small.png
rename to leanback/res/drawable-xhdpi/lb_text_dot_two_small.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xxhdpi/lb_action_bg_focused.9.png b/leanback/res/drawable-xxhdpi/lb_action_bg_focused.9.png
similarity index 100%
rename from v17/leanback/res/drawable-xxhdpi/lb_action_bg_focused.9.png
rename to leanback/res/drawable-xxhdpi/lb_action_bg_focused.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xxhdpi/lb_ic_actions_right_arrow.png b/leanback/res/drawable-xxhdpi/lb_ic_actions_right_arrow.png
similarity index 100%
rename from v17/leanback/res/drawable-xxhdpi/lb_ic_actions_right_arrow.png
rename to leanback/res/drawable-xxhdpi/lb_ic_actions_right_arrow.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xxhdpi/lb_ic_in_app_search.png b/leanback/res/drawable-xxhdpi/lb_ic_in_app_search.png
similarity index 100%
rename from v17/leanback/res/drawable-xxhdpi/lb_ic_in_app_search.png
rename to leanback/res/drawable-xxhdpi/lb_ic_in_app_search.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xxhdpi/lb_ic_sad_cloud.png b/leanback/res/drawable-xxhdpi/lb_ic_sad_cloud.png
similarity index 100%
rename from v17/leanback/res/drawable-xxhdpi/lb_ic_sad_cloud.png
rename to leanback/res/drawable-xxhdpi/lb_ic_sad_cloud.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xxhdpi/lb_ic_search_mic.png b/leanback/res/drawable-xxhdpi/lb_ic_search_mic.png
similarity index 100%
rename from v17/leanback/res/drawable-xxhdpi/lb_ic_search_mic.png
rename to leanback/res/drawable-xxhdpi/lb_ic_search_mic.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xxhdpi/lb_ic_search_mic_out.png b/leanback/res/drawable-xxhdpi/lb_ic_search_mic_out.png
similarity index 100%
rename from v17/leanback/res/drawable-xxhdpi/lb_ic_search_mic_out.png
rename to leanback/res/drawable-xxhdpi/lb_ic_search_mic_out.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xxhdpi/lb_in_app_search_bg.9.png b/leanback/res/drawable-xxhdpi/lb_in_app_search_bg.9.png
similarity index 100%
rename from v17/leanback/res/drawable-xxhdpi/lb_in_app_search_bg.9.png
rename to leanback/res/drawable-xxhdpi/lb_in_app_search_bg.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xxhdpi/lb_in_app_search_shadow_focused.9.png b/leanback/res/drawable-xxhdpi/lb_in_app_search_shadow_focused.9.png
similarity index 100%
rename from v17/leanback/res/drawable-xxhdpi/lb_in_app_search_shadow_focused.9.png
rename to leanback/res/drawable-xxhdpi/lb_in_app_search_shadow_focused.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xxhdpi/lb_in_app_search_shadow_normal.9.png b/leanback/res/drawable-xxhdpi/lb_in_app_search_shadow_normal.9.png
similarity index 100%
rename from v17/leanback/res/drawable-xxhdpi/lb_in_app_search_shadow_normal.9.png
rename to leanback/res/drawable-xxhdpi/lb_in_app_search_shadow_normal.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable/lb_background.xml b/leanback/res/drawable/lb_background.xml
similarity index 100%
rename from v17/leanback/res/drawable/lb_background.xml
rename to leanback/res/drawable/lb_background.xml
diff --git a/v17/leanback/res/drawable/lb_card_foreground.xml b/leanback/res/drawable/lb_card_foreground.xml
similarity index 100%
rename from v17/leanback/res/drawable/lb_card_foreground.xml
rename to leanback/res/drawable/lb_card_foreground.xml
diff --git a/v17/leanback/res/drawable/lb_control_button_primary.xml b/leanback/res/drawable/lb_control_button_primary.xml
similarity index 100%
rename from v17/leanback/res/drawable/lb_control_button_primary.xml
rename to leanback/res/drawable/lb_control_button_primary.xml
diff --git a/v17/leanback/res/drawable/lb_control_button_secondary.xml b/leanback/res/drawable/lb_control_button_secondary.xml
similarity index 100%
rename from v17/leanback/res/drawable/lb_control_button_secondary.xml
rename to leanback/res/drawable/lb_control_button_secondary.xml
diff --git a/v17/leanback/res/drawable/lb_headers_right_fading.xml b/leanback/res/drawable/lb_headers_right_fading.xml
similarity index 100%
rename from v17/leanback/res/drawable/lb_headers_right_fading.xml
rename to leanback/res/drawable/lb_headers_right_fading.xml
diff --git a/v17/leanback/res/drawable/lb_onboarding_start_button_background.xml b/leanback/res/drawable/lb_onboarding_start_button_background.xml
similarity index 100%
rename from v17/leanback/res/drawable/lb_onboarding_start_button_background.xml
rename to leanback/res/drawable/lb_onboarding_start_button_background.xml
diff --git a/v17/leanback/res/drawable/lb_playback_now_playing_bar.xml b/leanback/res/drawable/lb_playback_now_playing_bar.xml
similarity index 100%
rename from v17/leanback/res/drawable/lb_playback_now_playing_bar.xml
rename to leanback/res/drawable/lb_playback_now_playing_bar.xml
diff --git a/v17/leanback/res/drawable/lb_playback_progress_bar.xml b/leanback/res/drawable/lb_playback_progress_bar.xml
similarity index 100%
rename from v17/leanback/res/drawable/lb_playback_progress_bar.xml
rename to leanback/res/drawable/lb_playback_progress_bar.xml
diff --git a/v17/leanback/res/drawable/lb_search_orb.xml b/leanback/res/drawable/lb_search_orb.xml
similarity index 100%
rename from v17/leanback/res/drawable/lb_search_orb.xml
rename to leanback/res/drawable/lb_search_orb.xml
diff --git a/v17/leanback/res/drawable/lb_speech_orb.xml b/leanback/res/drawable/lb_speech_orb.xml
similarity index 100%
rename from v17/leanback/res/drawable/lb_speech_orb.xml
rename to leanback/res/drawable/lb_speech_orb.xml
diff --git a/v17/leanback/res/layout/lb_action_1_line.xml b/leanback/res/layout/lb_action_1_line.xml
similarity index 100%
rename from v17/leanback/res/layout/lb_action_1_line.xml
rename to leanback/res/layout/lb_action_1_line.xml
diff --git a/v17/leanback/res/layout/lb_action_2_lines.xml b/leanback/res/layout/lb_action_2_lines.xml
similarity index 100%
rename from v17/leanback/res/layout/lb_action_2_lines.xml
rename to leanback/res/layout/lb_action_2_lines.xml
diff --git a/v17/leanback/res/layout/lb_background_window.xml b/leanback/res/layout/lb_background_window.xml
similarity index 100%
rename from v17/leanback/res/layout/lb_background_window.xml
rename to leanback/res/layout/lb_background_window.xml
diff --git a/v17/leanback/res/layout/lb_browse_fragment.xml b/leanback/res/layout/lb_browse_fragment.xml
similarity index 100%
rename from v17/leanback/res/layout/lb_browse_fragment.xml
rename to leanback/res/layout/lb_browse_fragment.xml
diff --git a/v17/leanback/res/layout/lb_browse_title.xml b/leanback/res/layout/lb_browse_title.xml
similarity index 100%
rename from v17/leanback/res/layout/lb_browse_title.xml
rename to leanback/res/layout/lb_browse_title.xml
diff --git a/v17/leanback/res/layout/lb_control_bar.xml b/leanback/res/layout/lb_control_bar.xml
similarity index 100%
rename from v17/leanback/res/layout/lb_control_bar.xml
rename to leanback/res/layout/lb_control_bar.xml
diff --git a/v17/leanback/res/layout/lb_control_button_primary.xml b/leanback/res/layout/lb_control_button_primary.xml
similarity index 100%
rename from v17/leanback/res/layout/lb_control_button_primary.xml
rename to leanback/res/layout/lb_control_button_primary.xml
diff --git a/v17/leanback/res/layout/lb_control_button_secondary.xml b/leanback/res/layout/lb_control_button_secondary.xml
similarity index 100%
rename from v17/leanback/res/layout/lb_control_button_secondary.xml
rename to leanback/res/layout/lb_control_button_secondary.xml
diff --git a/v17/leanback/res/layout/lb_details_description.xml b/leanback/res/layout/lb_details_description.xml
similarity index 100%
rename from v17/leanback/res/layout/lb_details_description.xml
rename to leanback/res/layout/lb_details_description.xml
diff --git a/v17/leanback/res/layout/lb_details_fragment.xml b/leanback/res/layout/lb_details_fragment.xml
similarity index 100%
rename from v17/leanback/res/layout/lb_details_fragment.xml
rename to leanback/res/layout/lb_details_fragment.xml
diff --git a/v17/leanback/res/layout/lb_details_overview.xml b/leanback/res/layout/lb_details_overview.xml
similarity index 100%
rename from v17/leanback/res/layout/lb_details_overview.xml
rename to leanback/res/layout/lb_details_overview.xml
diff --git a/v17/leanback/res/layout/lb_divider.xml b/leanback/res/layout/lb_divider.xml
similarity index 100%
rename from v17/leanback/res/layout/lb_divider.xml
rename to leanback/res/layout/lb_divider.xml
diff --git a/v17/leanback/res/layout/lb_error_fragment.xml b/leanback/res/layout/lb_error_fragment.xml
similarity index 100%
rename from v17/leanback/res/layout/lb_error_fragment.xml
rename to leanback/res/layout/lb_error_fragment.xml
diff --git a/v17/leanback/res/layout/lb_fullwidth_details_overview.xml b/leanback/res/layout/lb_fullwidth_details_overview.xml
similarity index 100%
rename from v17/leanback/res/layout/lb_fullwidth_details_overview.xml
rename to leanback/res/layout/lb_fullwidth_details_overview.xml
diff --git a/v17/leanback/res/layout/lb_fullwidth_details_overview_logo.xml b/leanback/res/layout/lb_fullwidth_details_overview_logo.xml
similarity index 100%
rename from v17/leanback/res/layout/lb_fullwidth_details_overview_logo.xml
rename to leanback/res/layout/lb_fullwidth_details_overview_logo.xml
diff --git a/v17/leanback/res/layout/lb_guidance.xml b/leanback/res/layout/lb_guidance.xml
similarity index 100%
rename from v17/leanback/res/layout/lb_guidance.xml
rename to leanback/res/layout/lb_guidance.xml
diff --git a/v17/leanback/res/layout/lb_guidedactions.xml b/leanback/res/layout/lb_guidedactions.xml
similarity index 100%
rename from v17/leanback/res/layout/lb_guidedactions.xml
rename to leanback/res/layout/lb_guidedactions.xml
diff --git a/v17/leanback/res/layout/lb_guidedactions_datepicker_item.xml b/leanback/res/layout/lb_guidedactions_datepicker_item.xml
similarity index 100%
rename from v17/leanback/res/layout/lb_guidedactions_datepicker_item.xml
rename to leanback/res/layout/lb_guidedactions_datepicker_item.xml
diff --git a/v17/leanback/res/layout/lb_guidedactions_item.xml b/leanback/res/layout/lb_guidedactions_item.xml
similarity index 100%
rename from v17/leanback/res/layout/lb_guidedactions_item.xml
rename to leanback/res/layout/lb_guidedactions_item.xml
diff --git a/v17/leanback/res/layout/lb_guidedbuttonactions.xml b/leanback/res/layout/lb_guidedbuttonactions.xml
similarity index 100%
rename from v17/leanback/res/layout/lb_guidedbuttonactions.xml
rename to leanback/res/layout/lb_guidedbuttonactions.xml
diff --git a/v17/leanback/res/layout/lb_guidedstep_background.xml b/leanback/res/layout/lb_guidedstep_background.xml
similarity index 100%
rename from v17/leanback/res/layout/lb_guidedstep_background.xml
rename to leanback/res/layout/lb_guidedstep_background.xml
diff --git a/v17/leanback/res/layout/lb_guidedstep_fragment.xml b/leanback/res/layout/lb_guidedstep_fragment.xml
similarity index 100%
rename from v17/leanback/res/layout/lb_guidedstep_fragment.xml
rename to leanback/res/layout/lb_guidedstep_fragment.xml
diff --git a/v17/leanback/res/layout/lb_header.xml b/leanback/res/layout/lb_header.xml
similarity index 100%
rename from v17/leanback/res/layout/lb_header.xml
rename to leanback/res/layout/lb_header.xml
diff --git a/v17/leanback/res/layout/lb_headers_fragment.xml b/leanback/res/layout/lb_headers_fragment.xml
similarity index 100%
rename from v17/leanback/res/layout/lb_headers_fragment.xml
rename to leanback/res/layout/lb_headers_fragment.xml
diff --git a/v17/leanback/res/layout/lb_image_card_view.xml b/leanback/res/layout/lb_image_card_view.xml
similarity index 100%
rename from v17/leanback/res/layout/lb_image_card_view.xml
rename to leanback/res/layout/lb_image_card_view.xml
diff --git a/v17/leanback/res/layout/lb_image_card_view_themed_badge_left.xml b/leanback/res/layout/lb_image_card_view_themed_badge_left.xml
similarity index 100%
rename from v17/leanback/res/layout/lb_image_card_view_themed_badge_left.xml
rename to leanback/res/layout/lb_image_card_view_themed_badge_left.xml
diff --git a/v17/leanback/res/layout/lb_image_card_view_themed_badge_right.xml b/leanback/res/layout/lb_image_card_view_themed_badge_right.xml
similarity index 100%
rename from v17/leanback/res/layout/lb_image_card_view_themed_badge_right.xml
rename to leanback/res/layout/lb_image_card_view_themed_badge_right.xml
diff --git a/v17/leanback/res/layout/lb_image_card_view_themed_content.xml b/leanback/res/layout/lb_image_card_view_themed_content.xml
similarity index 100%
rename from v17/leanback/res/layout/lb_image_card_view_themed_content.xml
rename to leanback/res/layout/lb_image_card_view_themed_content.xml
diff --git a/v17/leanback/res/layout/lb_image_card_view_themed_title.xml b/leanback/res/layout/lb_image_card_view_themed_title.xml
similarity index 100%
rename from v17/leanback/res/layout/lb_image_card_view_themed_title.xml
rename to leanback/res/layout/lb_image_card_view_themed_title.xml
diff --git a/v17/leanback/res/layout/lb_list_row.xml b/leanback/res/layout/lb_list_row.xml
similarity index 100%
rename from v17/leanback/res/layout/lb_list_row.xml
rename to leanback/res/layout/lb_list_row.xml
diff --git a/v17/leanback/res/layout/lb_list_row_hovercard.xml b/leanback/res/layout/lb_list_row_hovercard.xml
similarity index 100%
rename from v17/leanback/res/layout/lb_list_row_hovercard.xml
rename to leanback/res/layout/lb_list_row_hovercard.xml
diff --git a/v17/leanback/res/layout/lb_media_item_number_view_flipper.xml b/leanback/res/layout/lb_media_item_number_view_flipper.xml
similarity index 100%
rename from v17/leanback/res/layout/lb_media_item_number_view_flipper.xml
rename to leanback/res/layout/lb_media_item_number_view_flipper.xml
diff --git a/v17/leanback/res/layout/lb_media_list_header.xml b/leanback/res/layout/lb_media_list_header.xml
similarity index 100%
rename from v17/leanback/res/layout/lb_media_list_header.xml
rename to leanback/res/layout/lb_media_list_header.xml
diff --git a/v17/leanback/res/layout/lb_onboarding_fragment.xml b/leanback/res/layout/lb_onboarding_fragment.xml
similarity index 100%
rename from v17/leanback/res/layout/lb_onboarding_fragment.xml
rename to leanback/res/layout/lb_onboarding_fragment.xml
diff --git a/v17/leanback/res/layout/lb_picker.xml b/leanback/res/layout/lb_picker.xml
similarity index 100%
rename from v17/leanback/res/layout/lb_picker.xml
rename to leanback/res/layout/lb_picker.xml
diff --git a/v17/leanback/res/layout/lb_picker_column.xml b/leanback/res/layout/lb_picker_column.xml
similarity index 100%
rename from v17/leanback/res/layout/lb_picker_column.xml
rename to leanback/res/layout/lb_picker_column.xml
diff --git a/v17/leanback/res/layout/lb_picker_item.xml b/leanback/res/layout/lb_picker_item.xml
similarity index 100%
rename from v17/leanback/res/layout/lb_picker_item.xml
rename to leanback/res/layout/lb_picker_item.xml
diff --git a/v17/leanback/res/layout/lb_picker_separator.xml b/leanback/res/layout/lb_picker_separator.xml
similarity index 100%
rename from v17/leanback/res/layout/lb_picker_separator.xml
rename to leanback/res/layout/lb_picker_separator.xml
diff --git a/v17/leanback/res/layout/lb_playback_controls.xml b/leanback/res/layout/lb_playback_controls.xml
similarity index 100%
rename from v17/leanback/res/layout/lb_playback_controls.xml
rename to leanback/res/layout/lb_playback_controls.xml
diff --git a/v17/leanback/res/layout/lb_playback_controls_row.xml b/leanback/res/layout/lb_playback_controls_row.xml
similarity index 100%
rename from v17/leanback/res/layout/lb_playback_controls_row.xml
rename to leanback/res/layout/lb_playback_controls_row.xml
diff --git a/v17/leanback/res/layout/lb_playback_fragment.xml b/leanback/res/layout/lb_playback_fragment.xml
similarity index 100%
rename from v17/leanback/res/layout/lb_playback_fragment.xml
rename to leanback/res/layout/lb_playback_fragment.xml
diff --git a/v17/leanback/res/layout/lb_playback_now_playing_bars.xml b/leanback/res/layout/lb_playback_now_playing_bars.xml
similarity index 100%
rename from v17/leanback/res/layout/lb_playback_now_playing_bars.xml
rename to leanback/res/layout/lb_playback_now_playing_bars.xml
diff --git a/v17/leanback/res/layout/lb_playback_transport_controls.xml b/leanback/res/layout/lb_playback_transport_controls.xml
similarity index 100%
rename from v17/leanback/res/layout/lb_playback_transport_controls.xml
rename to leanback/res/layout/lb_playback_transport_controls.xml
diff --git a/v17/leanback/res/layout/lb_playback_transport_controls_row.xml b/leanback/res/layout/lb_playback_transport_controls_row.xml
similarity index 100%
rename from v17/leanback/res/layout/lb_playback_transport_controls_row.xml
rename to leanback/res/layout/lb_playback_transport_controls_row.xml
diff --git a/v17/leanback/res/layout/lb_row_container.xml b/leanback/res/layout/lb_row_container.xml
similarity index 100%
rename from v17/leanback/res/layout/lb_row_container.xml
rename to leanback/res/layout/lb_row_container.xml
diff --git a/v17/leanback/res/layout/lb_row_header.xml b/leanback/res/layout/lb_row_header.xml
similarity index 100%
rename from v17/leanback/res/layout/lb_row_header.xml
rename to leanback/res/layout/lb_row_header.xml
diff --git a/v17/leanback/res/layout/lb_row_media_item.xml b/leanback/res/layout/lb_row_media_item.xml
similarity index 100%
rename from v17/leanback/res/layout/lb_row_media_item.xml
rename to leanback/res/layout/lb_row_media_item.xml
diff --git a/v17/leanback/res/layout/lb_row_media_item_action.xml b/leanback/res/layout/lb_row_media_item_action.xml
similarity index 100%
rename from v17/leanback/res/layout/lb_row_media_item_action.xml
rename to leanback/res/layout/lb_row_media_item_action.xml
diff --git a/v17/leanback/res/layout/lb_rows_fragment.xml b/leanback/res/layout/lb_rows_fragment.xml
similarity index 100%
rename from v17/leanback/res/layout/lb_rows_fragment.xml
rename to leanback/res/layout/lb_rows_fragment.xml
diff --git a/v17/leanback/res/layout/lb_search_bar.xml b/leanback/res/layout/lb_search_bar.xml
similarity index 100%
rename from v17/leanback/res/layout/lb_search_bar.xml
rename to leanback/res/layout/lb_search_bar.xml
diff --git a/v17/leanback/res/layout/lb_search_fragment.xml b/leanback/res/layout/lb_search_fragment.xml
similarity index 100%
rename from v17/leanback/res/layout/lb_search_fragment.xml
rename to leanback/res/layout/lb_search_fragment.xml
diff --git a/v17/leanback/res/layout/lb_search_orb.xml b/leanback/res/layout/lb_search_orb.xml
similarity index 100%
rename from v17/leanback/res/layout/lb_search_orb.xml
rename to leanback/res/layout/lb_search_orb.xml
diff --git a/v17/leanback/res/layout/lb_section_header.xml b/leanback/res/layout/lb_section_header.xml
similarity index 100%
rename from v17/leanback/res/layout/lb_section_header.xml
rename to leanback/res/layout/lb_section_header.xml
diff --git a/v17/leanback/res/layout/lb_shadow.xml b/leanback/res/layout/lb_shadow.xml
similarity index 100%
rename from v17/leanback/res/layout/lb_shadow.xml
rename to leanback/res/layout/lb_shadow.xml
diff --git a/v17/leanback/res/layout/lb_speech_orb.xml b/leanback/res/layout/lb_speech_orb.xml
similarity index 100%
rename from v17/leanback/res/layout/lb_speech_orb.xml
rename to leanback/res/layout/lb_speech_orb.xml
diff --git a/v17/leanback/res/layout/lb_title_view.xml b/leanback/res/layout/lb_title_view.xml
similarity index 100%
rename from v17/leanback/res/layout/lb_title_view.xml
rename to leanback/res/layout/lb_title_view.xml
diff --git a/v17/leanback/res/layout/lb_vertical_grid.xml b/leanback/res/layout/lb_vertical_grid.xml
similarity index 100%
rename from v17/leanback/res/layout/lb_vertical_grid.xml
rename to leanback/res/layout/lb_vertical_grid.xml
diff --git a/v17/leanback/res/layout/lb_vertical_grid_fragment.xml b/leanback/res/layout/lb_vertical_grid_fragment.xml
similarity index 100%
rename from v17/leanback/res/layout/lb_vertical_grid_fragment.xml
rename to leanback/res/layout/lb_vertical_grid_fragment.xml
diff --git a/v17/leanback/res/layout/lb_video_surface.xml b/leanback/res/layout/lb_video_surface.xml
similarity index 100%
rename from v17/leanback/res/layout/lb_video_surface.xml
rename to leanback/res/layout/lb_video_surface.xml
diff --git a/v17/leanback/res/layout/video_surface_fragment.xml b/leanback/res/layout/video_surface_fragment.xml
similarity index 100%
rename from v17/leanback/res/layout/video_surface_fragment.xml
rename to leanback/res/layout/video_surface_fragment.xml
diff --git a/v17/leanback/res/raw/lb_voice_failure.ogg b/leanback/res/raw/lb_voice_failure.ogg
similarity index 100%
rename from v17/leanback/res/raw/lb_voice_failure.ogg
rename to leanback/res/raw/lb_voice_failure.ogg
Binary files differ
diff --git a/v17/leanback/res/raw/lb_voice_no_input.ogg b/leanback/res/raw/lb_voice_no_input.ogg
similarity index 100%
rename from v17/leanback/res/raw/lb_voice_no_input.ogg
rename to leanback/res/raw/lb_voice_no_input.ogg
Binary files differ
diff --git a/v17/leanback/res/raw/lb_voice_open.ogg b/leanback/res/raw/lb_voice_open.ogg
similarity index 100%
rename from v17/leanback/res/raw/lb_voice_open.ogg
rename to leanback/res/raw/lb_voice_open.ogg
Binary files differ
diff --git a/v17/leanback/res/raw/lb_voice_success.ogg b/leanback/res/raw/lb_voice_success.ogg
similarity index 100%
rename from v17/leanback/res/raw/lb_voice_success.ogg
rename to leanback/res/raw/lb_voice_success.ogg
Binary files differ
diff --git a/v17/leanback/res/transition-v19/lb_browse_headers_in.xml b/leanback/res/transition-v19/lb_browse_headers_in.xml
similarity index 100%
rename from v17/leanback/res/transition-v19/lb_browse_headers_in.xml
rename to leanback/res/transition-v19/lb_browse_headers_in.xml
diff --git a/v17/leanback/res/transition-v19/lb_browse_headers_out.xml b/leanback/res/transition-v19/lb_browse_headers_out.xml
similarity index 100%
rename from v17/leanback/res/transition-v19/lb_browse_headers_out.xml
rename to leanback/res/transition-v19/lb_browse_headers_out.xml
diff --git a/v17/leanback/res/transition-v21/lb_browse_enter_transition.xml b/leanback/res/transition-v21/lb_browse_enter_transition.xml
similarity index 100%
rename from v17/leanback/res/transition-v21/lb_browse_enter_transition.xml
rename to leanback/res/transition-v21/lb_browse_enter_transition.xml
diff --git a/v17/leanback/res/transition-v21/lb_browse_entrance_transition.xml b/leanback/res/transition-v21/lb_browse_entrance_transition.xml
similarity index 100%
rename from v17/leanback/res/transition-v21/lb_browse_entrance_transition.xml
rename to leanback/res/transition-v21/lb_browse_entrance_transition.xml
diff --git a/v17/leanback/res/transition-v21/lb_browse_headers_in.xml b/leanback/res/transition-v21/lb_browse_headers_in.xml
similarity index 100%
rename from v17/leanback/res/transition-v21/lb_browse_headers_in.xml
rename to leanback/res/transition-v21/lb_browse_headers_in.xml
diff --git a/v17/leanback/res/transition-v21/lb_browse_headers_out.xml b/leanback/res/transition-v21/lb_browse_headers_out.xml
similarity index 100%
rename from v17/leanback/res/transition-v21/lb_browse_headers_out.xml
rename to leanback/res/transition-v21/lb_browse_headers_out.xml
diff --git a/v17/leanback/res/transition-v21/lb_browse_return_transition.xml b/leanback/res/transition-v21/lb_browse_return_transition.xml
similarity index 100%
rename from v17/leanback/res/transition-v21/lb_browse_return_transition.xml
rename to leanback/res/transition-v21/lb_browse_return_transition.xml
diff --git a/v17/leanback/res/transition-v21/lb_details_enter_transition.xml b/leanback/res/transition-v21/lb_details_enter_transition.xml
similarity index 100%
rename from v17/leanback/res/transition-v21/lb_details_enter_transition.xml
rename to leanback/res/transition-v21/lb_details_enter_transition.xml
diff --git a/v17/leanback/res/transition-v21/lb_details_return_transition.xml b/leanback/res/transition-v21/lb_details_return_transition.xml
similarity index 100%
rename from v17/leanback/res/transition-v21/lb_details_return_transition.xml
rename to leanback/res/transition-v21/lb_details_return_transition.xml
diff --git a/v17/leanback/res/transition-v21/lb_enter_transition.xml b/leanback/res/transition-v21/lb_enter_transition.xml
similarity index 100%
rename from v17/leanback/res/transition-v21/lb_enter_transition.xml
rename to leanback/res/transition-v21/lb_enter_transition.xml
diff --git a/v17/leanback/res/transition-v21/lb_guidedstep_activity_enter.xml b/leanback/res/transition-v21/lb_guidedstep_activity_enter.xml
similarity index 100%
rename from v17/leanback/res/transition-v21/lb_guidedstep_activity_enter.xml
rename to leanback/res/transition-v21/lb_guidedstep_activity_enter.xml
diff --git a/v17/leanback/res/transition-v21/lb_guidedstep_activity_enter_bottom.xml b/leanback/res/transition-v21/lb_guidedstep_activity_enter_bottom.xml
similarity index 100%
rename from v17/leanback/res/transition-v21/lb_guidedstep_activity_enter_bottom.xml
rename to leanback/res/transition-v21/lb_guidedstep_activity_enter_bottom.xml
diff --git a/v17/leanback/res/transition-v21/lb_return_transition.xml b/leanback/res/transition-v21/lb_return_transition.xml
similarity index 100%
rename from v17/leanback/res/transition-v21/lb_return_transition.xml
rename to leanback/res/transition-v21/lb_return_transition.xml
diff --git a/v17/leanback/res/transition-v21/lb_shared_element_enter_transition.xml b/leanback/res/transition-v21/lb_shared_element_enter_transition.xml
similarity index 100%
rename from v17/leanback/res/transition-v21/lb_shared_element_enter_transition.xml
rename to leanback/res/transition-v21/lb_shared_element_enter_transition.xml
diff --git a/v17/leanback/res/transition-v21/lb_shared_element_return_transition.xml b/leanback/res/transition-v21/lb_shared_element_return_transition.xml
similarity index 100%
rename from v17/leanback/res/transition-v21/lb_shared_element_return_transition.xml
rename to leanback/res/transition-v21/lb_shared_element_return_transition.xml
diff --git a/v17/leanback/res/transition-v21/lb_title_in.xml b/leanback/res/transition-v21/lb_title_in.xml
similarity index 100%
rename from v17/leanback/res/transition-v21/lb_title_in.xml
rename to leanback/res/transition-v21/lb_title_in.xml
diff --git a/v17/leanback/res/transition-v21/lb_title_out.xml b/leanback/res/transition-v21/lb_title_out.xml
similarity index 100%
rename from v17/leanback/res/transition-v21/lb_title_out.xml
rename to leanback/res/transition-v21/lb_title_out.xml
diff --git a/v17/leanback/res/transition-v21/lb_vertical_grid_enter_transition.xml b/leanback/res/transition-v21/lb_vertical_grid_enter_transition.xml
similarity index 100%
rename from v17/leanback/res/transition-v21/lb_vertical_grid_enter_transition.xml
rename to leanback/res/transition-v21/lb_vertical_grid_enter_transition.xml
diff --git a/v17/leanback/res/transition-v21/lb_vertical_grid_entrance_transition.xml b/leanback/res/transition-v21/lb_vertical_grid_entrance_transition.xml
similarity index 100%
rename from v17/leanback/res/transition-v21/lb_vertical_grid_entrance_transition.xml
rename to leanback/res/transition-v21/lb_vertical_grid_entrance_transition.xml
diff --git a/v17/leanback/res/transition-v21/lb_vertical_grid_return_transition.xml b/leanback/res/transition-v21/lb_vertical_grid_return_transition.xml
similarity index 100%
rename from v17/leanback/res/transition-v21/lb_vertical_grid_return_transition.xml
rename to leanback/res/transition-v21/lb_vertical_grid_return_transition.xml
diff --git a/v17/leanback/res/values-af/strings.xml b/leanback/res/values-af/strings.xml
similarity index 100%
rename from v17/leanback/res/values-af/strings.xml
rename to leanback/res/values-af/strings.xml
diff --git a/v17/leanback/res/values-am/strings.xml b/leanback/res/values-am/strings.xml
similarity index 100%
rename from v17/leanback/res/values-am/strings.xml
rename to leanback/res/values-am/strings.xml
diff --git a/v17/leanback/res/values-ar/strings.xml b/leanback/res/values-ar/strings.xml
similarity index 100%
rename from v17/leanback/res/values-ar/strings.xml
rename to leanback/res/values-ar/strings.xml
diff --git a/v17/leanback/res/values-az/strings.xml b/leanback/res/values-az/strings.xml
similarity index 100%
rename from v17/leanback/res/values-az/strings.xml
rename to leanback/res/values-az/strings.xml
diff --git a/v17/leanback/res/values-b+sr+Latn/strings.xml b/leanback/res/values-b+sr+Latn/strings.xml
similarity index 100%
rename from v17/leanback/res/values-b+sr+Latn/strings.xml
rename to leanback/res/values-b+sr+Latn/strings.xml
diff --git a/v17/leanback/res/values-be/strings.xml b/leanback/res/values-be/strings.xml
similarity index 100%
rename from v17/leanback/res/values-be/strings.xml
rename to leanback/res/values-be/strings.xml
diff --git a/v17/leanback/res/values-bg/strings.xml b/leanback/res/values-bg/strings.xml
similarity index 100%
rename from v17/leanback/res/values-bg/strings.xml
rename to leanback/res/values-bg/strings.xml
diff --git a/v17/leanback/res/values-bn/strings.xml b/leanback/res/values-bn/strings.xml
similarity index 100%
rename from v17/leanback/res/values-bn/strings.xml
rename to leanback/res/values-bn/strings.xml
diff --git a/v17/leanback/res/values-bs/strings.xml b/leanback/res/values-bs/strings.xml
similarity index 100%
rename from v17/leanback/res/values-bs/strings.xml
rename to leanback/res/values-bs/strings.xml
diff --git a/v17/leanback/res/values-ca/strings.xml b/leanback/res/values-ca/strings.xml
similarity index 100%
rename from v17/leanback/res/values-ca/strings.xml
rename to leanback/res/values-ca/strings.xml
diff --git a/v17/leanback/res/values-cs/strings.xml b/leanback/res/values-cs/strings.xml
similarity index 100%
rename from v17/leanback/res/values-cs/strings.xml
rename to leanback/res/values-cs/strings.xml
diff --git a/v17/leanback/res/values-da/strings.xml b/leanback/res/values-da/strings.xml
similarity index 100%
rename from v17/leanback/res/values-da/strings.xml
rename to leanback/res/values-da/strings.xml
diff --git a/v17/leanback/res/values-de/strings.xml b/leanback/res/values-de/strings.xml
similarity index 100%
rename from v17/leanback/res/values-de/strings.xml
rename to leanback/res/values-de/strings.xml
diff --git a/v17/leanback/res/values-el/strings.xml b/leanback/res/values-el/strings.xml
similarity index 100%
rename from v17/leanback/res/values-el/strings.xml
rename to leanback/res/values-el/strings.xml
diff --git a/v17/leanback/res/values-en-rAU/strings.xml b/leanback/res/values-en-rAU/strings.xml
similarity index 100%
rename from v17/leanback/res/values-en-rAU/strings.xml
rename to leanback/res/values-en-rAU/strings.xml
diff --git a/v17/leanback/res/values-en-rCA/strings.xml b/leanback/res/values-en-rCA/strings.xml
similarity index 100%
rename from v17/leanback/res/values-en-rCA/strings.xml
rename to leanback/res/values-en-rCA/strings.xml
diff --git a/v17/leanback/res/values-en-rGB/strings.xml b/leanback/res/values-en-rGB/strings.xml
similarity index 100%
rename from v17/leanback/res/values-en-rGB/strings.xml
rename to leanback/res/values-en-rGB/strings.xml
diff --git a/v17/leanback/res/values-en-rIN/strings.xml b/leanback/res/values-en-rIN/strings.xml
similarity index 100%
rename from v17/leanback/res/values-en-rIN/strings.xml
rename to leanback/res/values-en-rIN/strings.xml
diff --git a/v17/leanback/res/values-en-rXC/strings.xml b/leanback/res/values-en-rXC/strings.xml
similarity index 100%
rename from v17/leanback/res/values-en-rXC/strings.xml
rename to leanback/res/values-en-rXC/strings.xml
diff --git a/v17/leanback/res/values-es-rUS/strings.xml b/leanback/res/values-es-rUS/strings.xml
similarity index 100%
rename from v17/leanback/res/values-es-rUS/strings.xml
rename to leanback/res/values-es-rUS/strings.xml
diff --git a/v17/leanback/res/values-es/strings.xml b/leanback/res/values-es/strings.xml
similarity index 100%
rename from v17/leanback/res/values-es/strings.xml
rename to leanback/res/values-es/strings.xml
diff --git a/v17/leanback/res/values-et/strings.xml b/leanback/res/values-et/strings.xml
similarity index 100%
rename from v17/leanback/res/values-et/strings.xml
rename to leanback/res/values-et/strings.xml
diff --git a/v17/leanback/res/values-eu/strings.xml b/leanback/res/values-eu/strings.xml
similarity index 100%
rename from v17/leanback/res/values-eu/strings.xml
rename to leanback/res/values-eu/strings.xml
diff --git a/v17/leanback/res/values-fa/strings.xml b/leanback/res/values-fa/strings.xml
similarity index 100%
rename from v17/leanback/res/values-fa/strings.xml
rename to leanback/res/values-fa/strings.xml
diff --git a/v17/leanback/res/values-fi/strings.xml b/leanback/res/values-fi/strings.xml
similarity index 100%
rename from v17/leanback/res/values-fi/strings.xml
rename to leanback/res/values-fi/strings.xml
diff --git a/v17/leanback/res/values-fr-rCA/strings.xml b/leanback/res/values-fr-rCA/strings.xml
similarity index 100%
rename from v17/leanback/res/values-fr-rCA/strings.xml
rename to leanback/res/values-fr-rCA/strings.xml
diff --git a/v17/leanback/res/values-fr/strings.xml b/leanback/res/values-fr/strings.xml
similarity index 100%
rename from v17/leanback/res/values-fr/strings.xml
rename to leanback/res/values-fr/strings.xml
diff --git a/v17/leanback/res/values-gl/strings.xml b/leanback/res/values-gl/strings.xml
similarity index 100%
rename from v17/leanback/res/values-gl/strings.xml
rename to leanback/res/values-gl/strings.xml
diff --git a/v17/leanback/res/values-gu/strings.xml b/leanback/res/values-gu/strings.xml
similarity index 100%
rename from v17/leanback/res/values-gu/strings.xml
rename to leanback/res/values-gu/strings.xml
diff --git a/v17/leanback/res/values-hi/strings.xml b/leanback/res/values-hi/strings.xml
similarity index 100%
rename from v17/leanback/res/values-hi/strings.xml
rename to leanback/res/values-hi/strings.xml
diff --git a/v17/leanback/res/values-hr/strings.xml b/leanback/res/values-hr/strings.xml
similarity index 100%
rename from v17/leanback/res/values-hr/strings.xml
rename to leanback/res/values-hr/strings.xml
diff --git a/v17/leanback/res/values-hu/strings.xml b/leanback/res/values-hu/strings.xml
similarity index 100%
rename from v17/leanback/res/values-hu/strings.xml
rename to leanback/res/values-hu/strings.xml
diff --git a/v17/leanback/res/values-hy/strings.xml b/leanback/res/values-hy/strings.xml
similarity index 100%
rename from v17/leanback/res/values-hy/strings.xml
rename to leanback/res/values-hy/strings.xml
diff --git a/v17/leanback/res/values-in/strings.xml b/leanback/res/values-in/strings.xml
similarity index 100%
rename from v17/leanback/res/values-in/strings.xml
rename to leanback/res/values-in/strings.xml
diff --git a/v17/leanback/res/values-is/strings.xml b/leanback/res/values-is/strings.xml
similarity index 100%
rename from v17/leanback/res/values-is/strings.xml
rename to leanback/res/values-is/strings.xml
diff --git a/v17/leanback/res/values-it/strings.xml b/leanback/res/values-it/strings.xml
similarity index 100%
rename from v17/leanback/res/values-it/strings.xml
rename to leanback/res/values-it/strings.xml
diff --git a/v17/leanback/res/values-iw/strings.xml b/leanback/res/values-iw/strings.xml
similarity index 100%
rename from v17/leanback/res/values-iw/strings.xml
rename to leanback/res/values-iw/strings.xml
diff --git a/v17/leanback/res/values-ja/strings.xml b/leanback/res/values-ja/strings.xml
similarity index 100%
rename from v17/leanback/res/values-ja/strings.xml
rename to leanback/res/values-ja/strings.xml
diff --git a/v17/leanback/res/values-ka/strings.xml b/leanback/res/values-ka/strings.xml
similarity index 100%
rename from v17/leanback/res/values-ka/strings.xml
rename to leanback/res/values-ka/strings.xml
diff --git a/v17/leanback/res/values-kk/strings.xml b/leanback/res/values-kk/strings.xml
similarity index 100%
rename from v17/leanback/res/values-kk/strings.xml
rename to leanback/res/values-kk/strings.xml
diff --git a/v17/leanback/res/values-km/strings.xml b/leanback/res/values-km/strings.xml
similarity index 100%
rename from v17/leanback/res/values-km/strings.xml
rename to leanback/res/values-km/strings.xml
diff --git a/v17/leanback/res/values-kn/strings.xml b/leanback/res/values-kn/strings.xml
similarity index 100%
rename from v17/leanback/res/values-kn/strings.xml
rename to leanback/res/values-kn/strings.xml
diff --git a/v17/leanback/res/values-ko/strings.xml b/leanback/res/values-ko/strings.xml
similarity index 100%
rename from v17/leanback/res/values-ko/strings.xml
rename to leanback/res/values-ko/strings.xml
diff --git a/v17/leanback/res/values-ky/strings.xml b/leanback/res/values-ky/strings.xml
similarity index 100%
rename from v17/leanback/res/values-ky/strings.xml
rename to leanback/res/values-ky/strings.xml
diff --git a/v17/leanback/res/values-ldrtl/dimens.xml b/leanback/res/values-ldrtl/dimens.xml
similarity index 100%
rename from v17/leanback/res/values-ldrtl/dimens.xml
rename to leanback/res/values-ldrtl/dimens.xml
diff --git a/v17/leanback/res/values-ldrtl/integers.xml b/leanback/res/values-ldrtl/integers.xml
similarity index 100%
rename from v17/leanback/res/values-ldrtl/integers.xml
rename to leanback/res/values-ldrtl/integers.xml
diff --git a/v17/leanback/res/values-lo/strings.xml b/leanback/res/values-lo/strings.xml
similarity index 100%
rename from v17/leanback/res/values-lo/strings.xml
rename to leanback/res/values-lo/strings.xml
diff --git a/v17/leanback/res/values-lt/strings.xml b/leanback/res/values-lt/strings.xml
similarity index 100%
rename from v17/leanback/res/values-lt/strings.xml
rename to leanback/res/values-lt/strings.xml
diff --git a/v17/leanback/res/values-lv/strings.xml b/leanback/res/values-lv/strings.xml
similarity index 100%
rename from v17/leanback/res/values-lv/strings.xml
rename to leanback/res/values-lv/strings.xml
diff --git a/v17/leanback/res/values-mk/strings.xml b/leanback/res/values-mk/strings.xml
similarity index 100%
rename from v17/leanback/res/values-mk/strings.xml
rename to leanback/res/values-mk/strings.xml
diff --git a/v17/leanback/res/values-ml/strings.xml b/leanback/res/values-ml/strings.xml
similarity index 100%
rename from v17/leanback/res/values-ml/strings.xml
rename to leanback/res/values-ml/strings.xml
diff --git a/v17/leanback/res/values-mn/strings.xml b/leanback/res/values-mn/strings.xml
similarity index 100%
rename from v17/leanback/res/values-mn/strings.xml
rename to leanback/res/values-mn/strings.xml
diff --git a/v17/leanback/res/values-mr/strings.xml b/leanback/res/values-mr/strings.xml
similarity index 100%
rename from v17/leanback/res/values-mr/strings.xml
rename to leanback/res/values-mr/strings.xml
diff --git a/v17/leanback/res/values-ms/strings.xml b/leanback/res/values-ms/strings.xml
similarity index 100%
rename from v17/leanback/res/values-ms/strings.xml
rename to leanback/res/values-ms/strings.xml
diff --git a/v17/leanback/res/values-my/strings.xml b/leanback/res/values-my/strings.xml
similarity index 100%
rename from v17/leanback/res/values-my/strings.xml
rename to leanback/res/values-my/strings.xml
diff --git a/v17/leanback/res/values-nb/strings.xml b/leanback/res/values-nb/strings.xml
similarity index 100%
rename from v17/leanback/res/values-nb/strings.xml
rename to leanback/res/values-nb/strings.xml
diff --git a/v17/leanback/res/values-ne/strings.xml b/leanback/res/values-ne/strings.xml
similarity index 100%
rename from v17/leanback/res/values-ne/strings.xml
rename to leanback/res/values-ne/strings.xml
diff --git a/v17/leanback/res/values-nl/strings.xml b/leanback/res/values-nl/strings.xml
similarity index 100%
rename from v17/leanback/res/values-nl/strings.xml
rename to leanback/res/values-nl/strings.xml
diff --git a/v17/leanback/res/values-pa/strings.xml b/leanback/res/values-pa/strings.xml
similarity index 100%
rename from v17/leanback/res/values-pa/strings.xml
rename to leanback/res/values-pa/strings.xml
diff --git a/v17/leanback/res/values-pl/strings.xml b/leanback/res/values-pl/strings.xml
similarity index 100%
rename from v17/leanback/res/values-pl/strings.xml
rename to leanback/res/values-pl/strings.xml
diff --git a/v17/leanback/res/values-pt-rBR/strings.xml b/leanback/res/values-pt-rBR/strings.xml
similarity index 100%
rename from v17/leanback/res/values-pt-rBR/strings.xml
rename to leanback/res/values-pt-rBR/strings.xml
diff --git a/v17/leanback/res/values-pt-rPT/strings.xml b/leanback/res/values-pt-rPT/strings.xml
similarity index 100%
rename from v17/leanback/res/values-pt-rPT/strings.xml
rename to leanback/res/values-pt-rPT/strings.xml
diff --git a/v17/leanback/res/values-pt/strings.xml b/leanback/res/values-pt/strings.xml
similarity index 100%
rename from v17/leanback/res/values-pt/strings.xml
rename to leanback/res/values-pt/strings.xml
diff --git a/v17/leanback/res/values-ro/strings.xml b/leanback/res/values-ro/strings.xml
similarity index 100%
rename from v17/leanback/res/values-ro/strings.xml
rename to leanback/res/values-ro/strings.xml
diff --git a/v17/leanback/res/values-ru/strings.xml b/leanback/res/values-ru/strings.xml
similarity index 100%
rename from v17/leanback/res/values-ru/strings.xml
rename to leanback/res/values-ru/strings.xml
diff --git a/v17/leanback/res/values-si/strings.xml b/leanback/res/values-si/strings.xml
similarity index 100%
rename from v17/leanback/res/values-si/strings.xml
rename to leanback/res/values-si/strings.xml
diff --git a/v17/leanback/res/values-sk/strings.xml b/leanback/res/values-sk/strings.xml
similarity index 100%
rename from v17/leanback/res/values-sk/strings.xml
rename to leanback/res/values-sk/strings.xml
diff --git a/v17/leanback/res/values-sl/strings.xml b/leanback/res/values-sl/strings.xml
similarity index 100%
rename from v17/leanback/res/values-sl/strings.xml
rename to leanback/res/values-sl/strings.xml
diff --git a/v17/leanback/res/values-sq/strings.xml b/leanback/res/values-sq/strings.xml
similarity index 100%
rename from v17/leanback/res/values-sq/strings.xml
rename to leanback/res/values-sq/strings.xml
diff --git a/v17/leanback/res/values-sr/strings.xml b/leanback/res/values-sr/strings.xml
similarity index 100%
rename from v17/leanback/res/values-sr/strings.xml
rename to leanback/res/values-sr/strings.xml
diff --git a/v17/leanback/res/values-sv/strings.xml b/leanback/res/values-sv/strings.xml
similarity index 100%
rename from v17/leanback/res/values-sv/strings.xml
rename to leanback/res/values-sv/strings.xml
diff --git a/v17/leanback/res/values-sw/strings.xml b/leanback/res/values-sw/strings.xml
similarity index 100%
rename from v17/leanback/res/values-sw/strings.xml
rename to leanback/res/values-sw/strings.xml
diff --git a/v17/leanback/res/values-ta/strings.xml b/leanback/res/values-ta/strings.xml
similarity index 100%
rename from v17/leanback/res/values-ta/strings.xml
rename to leanback/res/values-ta/strings.xml
diff --git a/v17/leanback/res/values-te/strings.xml b/leanback/res/values-te/strings.xml
similarity index 100%
rename from v17/leanback/res/values-te/strings.xml
rename to leanback/res/values-te/strings.xml
diff --git a/v17/leanback/res/values-th/strings.xml b/leanback/res/values-th/strings.xml
similarity index 100%
rename from v17/leanback/res/values-th/strings.xml
rename to leanback/res/values-th/strings.xml
diff --git a/v17/leanback/res/values-tl/strings.xml b/leanback/res/values-tl/strings.xml
similarity index 100%
rename from v17/leanback/res/values-tl/strings.xml
rename to leanback/res/values-tl/strings.xml
diff --git a/v17/leanback/res/values-tr/strings.xml b/leanback/res/values-tr/strings.xml
similarity index 100%
rename from v17/leanback/res/values-tr/strings.xml
rename to leanback/res/values-tr/strings.xml
diff --git a/v17/leanback/res/values-uk/strings.xml b/leanback/res/values-uk/strings.xml
similarity index 100%
rename from v17/leanback/res/values-uk/strings.xml
rename to leanback/res/values-uk/strings.xml
diff --git a/v17/leanback/res/values-ur/strings.xml b/leanback/res/values-ur/strings.xml
similarity index 100%
rename from v17/leanback/res/values-ur/strings.xml
rename to leanback/res/values-ur/strings.xml
diff --git a/v17/leanback/res/values-uz/strings.xml b/leanback/res/values-uz/strings.xml
similarity index 100%
rename from v17/leanback/res/values-uz/strings.xml
rename to leanback/res/values-uz/strings.xml
diff --git a/v17/leanback/res/values-v18/themes.xml b/leanback/res/values-v18/themes.xml
similarity index 100%
rename from v17/leanback/res/values-v18/themes.xml
rename to leanback/res/values-v18/themes.xml
diff --git a/v17/leanback/res/values-v19/themes.xml b/leanback/res/values-v19/themes.xml
similarity index 100%
rename from v17/leanback/res/values-v19/themes.xml
rename to leanback/res/values-v19/themes.xml
diff --git a/v17/leanback/res/values-v21/styles.xml b/leanback/res/values-v21/styles.xml
similarity index 100%
rename from v17/leanback/res/values-v21/styles.xml
rename to leanback/res/values-v21/styles.xml
diff --git a/v17/leanback/res/values-v21/themes.xml b/leanback/res/values-v21/themes.xml
similarity index 100%
rename from v17/leanback/res/values-v21/themes.xml
rename to leanback/res/values-v21/themes.xml
diff --git a/v17/leanback/res/values-v22/integers.xml b/leanback/res/values-v22/integers.xml
similarity index 100%
rename from v17/leanback/res/values-v22/integers.xml
rename to leanback/res/values-v22/integers.xml
diff --git a/v17/leanback/res/values-vi/strings.xml b/leanback/res/values-vi/strings.xml
similarity index 100%
rename from v17/leanback/res/values-vi/strings.xml
rename to leanback/res/values-vi/strings.xml
diff --git a/v17/leanback/res/values-zh-rCN/strings.xml b/leanback/res/values-zh-rCN/strings.xml
similarity index 100%
rename from v17/leanback/res/values-zh-rCN/strings.xml
rename to leanback/res/values-zh-rCN/strings.xml
diff --git a/v17/leanback/res/values-zh-rHK/strings.xml b/leanback/res/values-zh-rHK/strings.xml
similarity index 100%
rename from v17/leanback/res/values-zh-rHK/strings.xml
rename to leanback/res/values-zh-rHK/strings.xml
diff --git a/v17/leanback/res/values-zh-rTW/strings.xml b/leanback/res/values-zh-rTW/strings.xml
similarity index 100%
rename from v17/leanback/res/values-zh-rTW/strings.xml
rename to leanback/res/values-zh-rTW/strings.xml
diff --git a/v17/leanback/res/values-zu/strings.xml b/leanback/res/values-zu/strings.xml
similarity index 100%
rename from v17/leanback/res/values-zu/strings.xml
rename to leanback/res/values-zu/strings.xml
diff --git a/v17/leanback/res/values/attrs.xml b/leanback/res/values/attrs.xml
similarity index 100%
rename from v17/leanback/res/values/attrs.xml
rename to leanback/res/values/attrs.xml
diff --git a/v17/leanback/res/values/colors.xml b/leanback/res/values/colors.xml
similarity index 100%
rename from v17/leanback/res/values/colors.xml
rename to leanback/res/values/colors.xml
diff --git a/v17/leanback/res/values/dimens.xml b/leanback/res/values/dimens.xml
similarity index 100%
rename from v17/leanback/res/values/dimens.xml
rename to leanback/res/values/dimens.xml
diff --git a/v17/leanback/res/values/ids.xml b/leanback/res/values/ids.xml
similarity index 100%
rename from v17/leanback/res/values/ids.xml
rename to leanback/res/values/ids.xml
diff --git a/v17/leanback/res/values/integers.xml b/leanback/res/values/integers.xml
similarity index 100%
rename from v17/leanback/res/values/integers.xml
rename to leanback/res/values/integers.xml
diff --git a/v17/leanback/res/values/strings.xml b/leanback/res/values/strings.xml
similarity index 100%
rename from v17/leanback/res/values/strings.xml
rename to leanback/res/values/strings.xml
diff --git a/v17/leanback/res/values/styles.xml b/leanback/res/values/styles.xml
similarity index 100%
rename from v17/leanback/res/values/styles.xml
rename to leanback/res/values/styles.xml
diff --git a/v17/leanback/res/values/themes.xml b/leanback/res/values/themes.xml
similarity index 100%
rename from v17/leanback/res/values/themes.xml
rename to leanback/res/values/themes.xml
diff --git a/v17/leanback/src/android/support/v17/leanback/animation/LogAccelerateInterpolator.java b/leanback/src/android/support/v17/leanback/animation/LogAccelerateInterpolator.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/animation/LogAccelerateInterpolator.java
rename to leanback/src/android/support/v17/leanback/animation/LogAccelerateInterpolator.java
diff --git a/v17/leanback/src/android/support/v17/leanback/animation/LogDecelerateInterpolator.java b/leanback/src/android/support/v17/leanback/animation/LogDecelerateInterpolator.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/animation/LogDecelerateInterpolator.java
rename to leanback/src/android/support/v17/leanback/animation/LogDecelerateInterpolator.java
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BackgroundFragment.java b/leanback/src/android/support/v17/leanback/app/BackgroundFragment.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/app/BackgroundFragment.java
rename to leanback/src/android/support/v17/leanback/app/BackgroundFragment.java
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BackgroundManager.java b/leanback/src/android/support/v17/leanback/app/BackgroundManager.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/app/BackgroundManager.java
rename to leanback/src/android/support/v17/leanback/app/BackgroundManager.java
diff --git a/leanback/src/android/support/v17/leanback/app/BaseFragment.java b/leanback/src/android/support/v17/leanback/app/BaseFragment.java
new file mode 100644
index 0000000..ea46011
--- /dev/null
+++ b/leanback/src/android/support/v17/leanback/app/BaseFragment.java
@@ -0,0 +1,323 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from BaseSupportFragment.java.  DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.annotation.SuppressLint;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v17.leanback.transition.TransitionHelper;
+import android.support.v17.leanback.transition.TransitionListener;
+import android.support.v17.leanback.util.StateMachine;
+import android.support.v17.leanback.util.StateMachine.Condition;
+import android.support.v17.leanback.util.StateMachine.Event;
+import android.support.v17.leanback.util.StateMachine.State;
+import android.view.View;
+import android.view.ViewTreeObserver;
+
+/**
+ * Base class for leanback Fragments. This class is not intended to be subclassed by apps.
+ * @deprecated use {@link BaseSupportFragment}
+ */
+@Deprecated
+@SuppressWarnings("FragmentNotInstantiable")
+public class BaseFragment extends BrandedFragment {
+
+    /**
+     * The start state for all
+     */
+    final State STATE_START = new State("START", true, false);
+
+    /**
+     * Initial State for ENTRNACE transition.
+     */
+    final State STATE_ENTRANCE_INIT = new State("ENTRANCE_INIT");
+
+    /**
+     * prepareEntranceTransition is just called, but view not ready yet. We can enable the
+     * busy spinner.
+     */
+    final State STATE_ENTRANCE_ON_PREPARED = new State("ENTRANCE_ON_PREPARED", true, false) {
+        @Override
+        public void run() {
+            mProgressBarManager.show();
+        }
+    };
+
+    /**
+     * prepareEntranceTransition is called and main content view to slide in was created, so we can
+     * call {@link #onEntranceTransitionPrepare}. Note that we dont set initial content to invisible
+     * in this State, the process is very different in subclass, e.g. BrowseFragment hide header
+     * views and hide main fragment view in two steps.
+     */
+    final State STATE_ENTRANCE_ON_PREPARED_ON_CREATEVIEW = new State(
+            "ENTRANCE_ON_PREPARED_ON_CREATEVIEW") {
+        @Override
+        public void run() {
+            onEntranceTransitionPrepare();
+        }
+    };
+
+    /**
+     * execute the entrance transition.
+     */
+    final State STATE_ENTRANCE_PERFORM = new State("STATE_ENTRANCE_PERFORM") {
+        @Override
+        public void run() {
+            mProgressBarManager.hide();
+            onExecuteEntranceTransition();
+        }
+    };
+
+    /**
+     * execute onEntranceTransitionEnd.
+     */
+    final State STATE_ENTRANCE_ON_ENDED = new State("ENTRANCE_ON_ENDED") {
+        @Override
+        public void run() {
+            onEntranceTransitionEnd();
+        }
+    };
+
+    /**
+     * either entrance transition completed or skipped
+     */
+    final State STATE_ENTRANCE_COMPLETE = new State("ENTRANCE_COMPLETE", true, false);
+
+    /**
+     * Event fragment.onCreate()
+     */
+    final Event EVT_ON_CREATE = new Event("onCreate");
+
+    /**
+     * Event fragment.onViewCreated()
+     */
+    final Event EVT_ON_CREATEVIEW = new Event("onCreateView");
+
+    /**
+     * Event for {@link #prepareEntranceTransition()} is called.
+     */
+    final Event EVT_PREPARE_ENTRANCE = new Event("prepareEntranceTransition");
+
+    /**
+     * Event for {@link #startEntranceTransition()} is called.
+     */
+    final Event EVT_START_ENTRANCE = new Event("startEntranceTransition");
+
+    /**
+     * Event for entrance transition is ended through Transition listener.
+     */
+    final Event EVT_ENTRANCE_END = new Event("onEntranceTransitionEnd");
+
+    /**
+     * Event for skipping entrance transition if not supported.
+     */
+    final Condition COND_TRANSITION_NOT_SUPPORTED = new Condition("EntranceTransitionNotSupport") {
+        @Override
+        public boolean canProceed() {
+            return !TransitionHelper.systemSupportsEntranceTransitions();
+        }
+    };
+
+    final StateMachine mStateMachine = new StateMachine();
+
+    Object mEntranceTransition;
+    final ProgressBarManager mProgressBarManager = new ProgressBarManager();
+
+    @SuppressLint("ValidFragment")
+    BaseFragment() {
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        createStateMachineStates();
+        createStateMachineTransitions();
+        mStateMachine.start();
+        super.onCreate(savedInstanceState);
+        mStateMachine.fireEvent(EVT_ON_CREATE);
+    }
+
+    void createStateMachineStates() {
+        mStateMachine.addState(STATE_START);
+        mStateMachine.addState(STATE_ENTRANCE_INIT);
+        mStateMachine.addState(STATE_ENTRANCE_ON_PREPARED);
+        mStateMachine.addState(STATE_ENTRANCE_ON_PREPARED_ON_CREATEVIEW);
+        mStateMachine.addState(STATE_ENTRANCE_PERFORM);
+        mStateMachine.addState(STATE_ENTRANCE_ON_ENDED);
+        mStateMachine.addState(STATE_ENTRANCE_COMPLETE);
+    }
+
+    void createStateMachineTransitions() {
+        mStateMachine.addTransition(STATE_START, STATE_ENTRANCE_INIT, EVT_ON_CREATE);
+        mStateMachine.addTransition(STATE_ENTRANCE_INIT, STATE_ENTRANCE_COMPLETE,
+                COND_TRANSITION_NOT_SUPPORTED);
+        mStateMachine.addTransition(STATE_ENTRANCE_INIT, STATE_ENTRANCE_COMPLETE,
+                EVT_ON_CREATEVIEW);
+        mStateMachine.addTransition(STATE_ENTRANCE_INIT, STATE_ENTRANCE_ON_PREPARED,
+                EVT_PREPARE_ENTRANCE);
+        mStateMachine.addTransition(STATE_ENTRANCE_ON_PREPARED,
+                STATE_ENTRANCE_ON_PREPARED_ON_CREATEVIEW,
+                EVT_ON_CREATEVIEW);
+        mStateMachine.addTransition(STATE_ENTRANCE_ON_PREPARED,
+                STATE_ENTRANCE_PERFORM,
+                EVT_START_ENTRANCE);
+        mStateMachine.addTransition(STATE_ENTRANCE_ON_PREPARED_ON_CREATEVIEW,
+                STATE_ENTRANCE_PERFORM);
+        mStateMachine.addTransition(STATE_ENTRANCE_PERFORM,
+                STATE_ENTRANCE_ON_ENDED,
+                EVT_ENTRANCE_END);
+        mStateMachine.addTransition(STATE_ENTRANCE_ON_ENDED, STATE_ENTRANCE_COMPLETE);
+    }
+
+    @Override
+    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+        mStateMachine.fireEvent(EVT_ON_CREATEVIEW);
+    }
+
+    /**
+     * Enables entrance transition.<p>
+     * Entrance transition is the standard slide-in transition that shows rows of data in
+     * browse screen and details screen.
+     * <p>
+     * The method is ignored before LOLLIPOP (API21).
+     * <p>
+     * This method must be called in or
+     * before onCreate().  Typically entrance transition should be enabled when savedInstance is
+     * null so that fragment restored from instanceState does not run an extra entrance transition.
+     * When the entrance transition is enabled, the fragment will make headers and content
+     * hidden initially.
+     * When data of rows are ready, app must call {@link #startEntranceTransition()} to kick off
+     * the transition, otherwise the rows will be invisible forever.
+     * <p>
+     * It is similar to android:windowsEnterTransition and can be considered a late-executed
+     * android:windowsEnterTransition controlled by app.  There are two reasons that app needs it:
+     * <li> Workaround the problem that activity transition is not available between launcher and
+     * app.  Browse activity must programmatically start the slide-in transition.</li>
+     * <li> Separates DetailsOverviewRow transition from other rows transition.  So that
+     * the DetailsOverviewRow transition can be executed earlier without waiting for all rows
+     * to be loaded.</li>
+     * <p>
+     * Transition object is returned by createEntranceTransition().  Typically the app does not need
+     * override the default transition that browse and details provides.
+     */
+    public void prepareEntranceTransition() {
+        mStateMachine.fireEvent(EVT_PREPARE_ENTRANCE);
+    }
+
+    /**
+     * Create entrance transition.  Subclass can override to load transition from
+     * resource or construct manually.  Typically app does not need to
+     * override the default transition that browse and details provides.
+     */
+    protected Object createEntranceTransition() {
+        return null;
+    }
+
+    /**
+     * Run entrance transition.  Subclass may use TransitionManager to perform
+     * go(Scene) or beginDelayedTransition().  App should not override the default
+     * implementation of browse and details fragment.
+     */
+    protected void runEntranceTransition(Object entranceTransition) {
+    }
+
+    /**
+     * Callback when entrance transition is prepared.  This is when fragment should
+     * stop user input and animations.
+     */
+    protected void onEntranceTransitionPrepare() {
+    }
+
+    /**
+     * Callback when entrance transition is started.  This is when fragment should
+     * stop processing layout.
+     */
+    protected void onEntranceTransitionStart() {
+    }
+
+    /**
+     * Callback when entrance transition is ended.
+     */
+    protected void onEntranceTransitionEnd() {
+    }
+
+    /**
+     * When fragment finishes loading data, it should call startEntranceTransition()
+     * to execute the entrance transition.
+     * startEntranceTransition() will start transition only if both two conditions
+     * are satisfied:
+     * <li> prepareEntranceTransition() was called.</li>
+     * <li> has not executed entrance transition yet.</li>
+     * <p>
+     * If startEntranceTransition() is called before onViewCreated(), it will be pending
+     * and executed when view is created.
+     */
+    public void startEntranceTransition() {
+        mStateMachine.fireEvent(EVT_START_ENTRANCE);
+    }
+
+    void onExecuteEntranceTransition() {
+        // wait till views get their initial position before start transition
+        final View view = getView();
+        if (view == null) {
+            // fragment view destroyed, transition not needed
+            return;
+        }
+        view.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
+            @Override
+            public boolean onPreDraw() {
+                view.getViewTreeObserver().removeOnPreDrawListener(this);
+                if (FragmentUtil.getContext(BaseFragment.this) == null || getView() == null) {
+                    // bail out if fragment is destroyed immediately after startEntranceTransition
+                    return true;
+                }
+                internalCreateEntranceTransition();
+                onEntranceTransitionStart();
+                if (mEntranceTransition != null) {
+                    runEntranceTransition(mEntranceTransition);
+                } else {
+                    mStateMachine.fireEvent(EVT_ENTRANCE_END);
+                }
+                return false;
+            }
+        });
+        view.invalidate();
+    }
+
+    void internalCreateEntranceTransition() {
+        mEntranceTransition = createEntranceTransition();
+        if (mEntranceTransition == null) {
+            return;
+        }
+        TransitionHelper.addTransitionListener(mEntranceTransition, new TransitionListener() {
+            @Override
+            public void onTransitionEnd(Object transition) {
+                mEntranceTransition = null;
+                mStateMachine.fireEvent(EVT_ENTRANCE_END);
+            }
+        });
+    }
+
+    /**
+     * Returns the {@link ProgressBarManager}.
+     * @return The {@link ProgressBarManager}.
+     */
+    public final ProgressBarManager getProgressBarManager() {
+        return mProgressBarManager;
+    }
+}
diff --git a/leanback/src/android/support/v17/leanback/app/BaseRowFragment.java b/leanback/src/android/support/v17/leanback/app/BaseRowFragment.java
new file mode 100644
index 0000000..97a5b84
--- /dev/null
+++ b/leanback/src/android/support/v17/leanback/app/BaseRowFragment.java
@@ -0,0 +1,308 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from BaseRowSupportFragment.java.  DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v17.leanback.widget.ItemBridgeAdapter;
+import android.support.v17.leanback.widget.ListRow;
+import android.support.v17.leanback.widget.ObjectAdapter;
+import android.support.v17.leanback.widget.OnChildViewHolderSelectedListener;
+import android.support.v17.leanback.widget.PresenterSelector;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.app.Fragment;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * An internal base class for a fragment containing a list of rows.
+ * @deprecated use {@link BaseRowSupportFragment}
+ */
+@Deprecated
+abstract class BaseRowFragment extends Fragment {
+    private static final String CURRENT_SELECTED_POSITION = "currentSelectedPosition";
+    private ObjectAdapter mAdapter;
+    VerticalGridView mVerticalGridView;
+    private PresenterSelector mPresenterSelector;
+    final ItemBridgeAdapter mBridgeAdapter = new ItemBridgeAdapter();
+    int mSelectedPosition = -1;
+    private boolean mPendingTransitionPrepare;
+    private LateSelectionObserver mLateSelectionObserver = new LateSelectionObserver();
+
+    abstract int getLayoutResourceId();
+
+    private final OnChildViewHolderSelectedListener mRowSelectedListener =
+            new OnChildViewHolderSelectedListener() {
+                @Override
+                public void onChildViewHolderSelected(RecyclerView parent,
+                        RecyclerView.ViewHolder view, int position, int subposition) {
+                    if (!mLateSelectionObserver.mIsLateSelection) {
+                        mSelectedPosition = position;
+                        onRowSelected(parent, view, position, subposition);
+                    }
+                }
+            };
+
+    void onRowSelected(RecyclerView parent, RecyclerView.ViewHolder view,
+            int position, int subposition) {
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        View view = inflater.inflate(getLayoutResourceId(), container, false);
+        mVerticalGridView = findGridViewFromRoot(view);
+        if (mPendingTransitionPrepare) {
+            mPendingTransitionPrepare = false;
+            onTransitionPrepare();
+        }
+        return view;
+    }
+
+    VerticalGridView findGridViewFromRoot(View view) {
+        return (VerticalGridView) view;
+    }
+
+    @Override
+    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+        if (savedInstanceState != null) {
+            mSelectedPosition = savedInstanceState.getInt(CURRENT_SELECTED_POSITION, -1);
+        }
+        setAdapterAndSelection();
+        mVerticalGridView.setOnChildViewHolderSelectedListener(mRowSelectedListener);
+    }
+
+    /**
+     * This class waits for the adapter to be updated before setting the selected
+     * row.
+     */
+    private class LateSelectionObserver extends RecyclerView.AdapterDataObserver {
+        boolean mIsLateSelection = false;
+
+        LateSelectionObserver() {
+        }
+
+        @Override
+        public void onChanged() {
+            performLateSelection();
+        }
+
+        @Override
+        public void onItemRangeInserted(int positionStart, int itemCount) {
+            performLateSelection();
+        }
+
+        void startLateSelection() {
+            mIsLateSelection = true;
+            mBridgeAdapter.registerAdapterDataObserver(this);
+        }
+
+        void performLateSelection() {
+            clear();
+            if (mVerticalGridView != null) {
+                mVerticalGridView.setSelectedPosition(mSelectedPosition);
+            }
+        }
+
+        void clear() {
+            if (mIsLateSelection) {
+                mIsLateSelection = false;
+                mBridgeAdapter.unregisterAdapterDataObserver(this);
+            }
+        }
+    }
+
+    void setAdapterAndSelection() {
+        if (mAdapter == null) {
+            // delay until ItemBridgeAdapter has wrappedAdapter. Once we assign ItemBridgeAdapter
+            // to RecyclerView, it will not be allowed to change "hasStableId" to true.
+            return;
+        }
+        if (mVerticalGridView.getAdapter() != mBridgeAdapter) {
+            // avoid extra layout if ItemBridgeAdapter was already set.
+            mVerticalGridView.setAdapter(mBridgeAdapter);
+        }
+        // We don't set the selected position unless we've data in the adapter.
+        boolean lateSelection = mBridgeAdapter.getItemCount() == 0 && mSelectedPosition >= 0;
+        if (lateSelection) {
+            mLateSelectionObserver.startLateSelection();
+        } else if (mSelectedPosition >= 0) {
+            mVerticalGridView.setSelectedPosition(mSelectedPosition);
+        }
+    }
+
+    @Override
+    public void onDestroyView() {
+        super.onDestroyView();
+        mLateSelectionObserver.clear();
+        mVerticalGridView = null;
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putInt(CURRENT_SELECTED_POSITION, mSelectedPosition);
+    }
+
+    /**
+     * Set the presenter selector used to create and bind views.
+     */
+    public final void setPresenterSelector(PresenterSelector presenterSelector) {
+        if (mPresenterSelector != presenterSelector) {
+            mPresenterSelector = presenterSelector;
+            updateAdapter();
+        }
+    }
+
+    /**
+     * Get the presenter selector used to create and bind views.
+     */
+    public final PresenterSelector getPresenterSelector() {
+        return mPresenterSelector;
+    }
+
+    /**
+     * Sets the adapter that represents a list of rows.
+     * @param rowsAdapter Adapter that represents list of rows.
+     */
+    public final void setAdapter(ObjectAdapter rowsAdapter) {
+        if (mAdapter != rowsAdapter) {
+            mAdapter = rowsAdapter;
+            updateAdapter();
+        }
+    }
+
+    /**
+     * Returns the Adapter that represents list of rows.
+     * @return Adapter that represents list of rows.
+     */
+    public final ObjectAdapter getAdapter() {
+        return mAdapter;
+    }
+
+    /**
+     * Returns the RecyclerView.Adapter that wraps {@link #getAdapter()}.
+     * @return The RecyclerView.Adapter that wraps {@link #getAdapter()}.
+     */
+    public final ItemBridgeAdapter getBridgeAdapter() {
+        return mBridgeAdapter;
+    }
+
+    /**
+     * Sets the selected row position with smooth animation.
+     */
+    public void setSelectedPosition(int position) {
+        setSelectedPosition(position, true);
+    }
+
+    /**
+     * Gets position of currently selected row.
+     * @return Position of currently selected row.
+     */
+    public int getSelectedPosition() {
+        return mSelectedPosition;
+    }
+
+    /**
+     * Sets the selected row position.
+     */
+    public void setSelectedPosition(int position, boolean smooth) {
+        if (mSelectedPosition == position) {
+            return;
+        }
+        mSelectedPosition = position;
+        if (mVerticalGridView != null) {
+            if (mLateSelectionObserver.mIsLateSelection) {
+                return;
+            }
+            if (smooth) {
+                mVerticalGridView.setSelectedPositionSmooth(position);
+            } else {
+                mVerticalGridView.setSelectedPosition(position);
+            }
+        }
+    }
+
+    public final VerticalGridView getVerticalGridView() {
+        return mVerticalGridView;
+    }
+
+    void updateAdapter() {
+        mBridgeAdapter.setAdapter(mAdapter);
+        mBridgeAdapter.setPresenter(mPresenterSelector);
+
+        if (mVerticalGridView != null) {
+            setAdapterAndSelection();
+        }
+    }
+
+    Object getItem(Row row, int position) {
+        if (row instanceof ListRow) {
+            return ((ListRow) row).getAdapter().get(position);
+        } else {
+            return null;
+        }
+    }
+
+    public boolean onTransitionPrepare() {
+        if (mVerticalGridView != null) {
+            mVerticalGridView.setAnimateChildLayout(false);
+            mVerticalGridView.setScrollEnabled(false);
+            return true;
+        }
+        mPendingTransitionPrepare = true;
+        return false;
+    }
+
+    public void onTransitionStart() {
+        if (mVerticalGridView != null) {
+            mVerticalGridView.setPruneChild(false);
+            mVerticalGridView.setLayoutFrozen(true);
+            mVerticalGridView.setFocusSearchDisabled(true);
+        }
+    }
+
+    public void onTransitionEnd() {
+        // be careful that fragment might be destroyed before header transition ends.
+        if (mVerticalGridView != null) {
+            mVerticalGridView.setLayoutFrozen(false);
+            mVerticalGridView.setAnimateChildLayout(true);
+            mVerticalGridView.setPruneChild(true);
+            mVerticalGridView.setFocusSearchDisabled(false);
+            mVerticalGridView.setScrollEnabled(true);
+        }
+    }
+
+    public void setAlignment(int windowAlignOffsetTop) {
+        if (mVerticalGridView != null) {
+            // align the top edge of item
+            mVerticalGridView.setItemAlignmentOffset(0);
+            mVerticalGridView.setItemAlignmentOffsetPercent(
+                    VerticalGridView.ITEM_ALIGN_OFFSET_PERCENT_DISABLED);
+
+            // align to a fixed position from top
+            mVerticalGridView.setWindowAlignmentOffset(windowAlignOffsetTop);
+            mVerticalGridView.setWindowAlignmentOffsetPercent(
+                    VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
+            mVerticalGridView.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE);
+        }
+    }
+}
diff --git a/leanback/src/android/support/v17/leanback/app/BaseRowSupportFragment.java b/leanback/src/android/support/v17/leanback/app/BaseRowSupportFragment.java
new file mode 100644
index 0000000..6a477ab
--- /dev/null
+++ b/leanback/src/android/support/v17/leanback/app/BaseRowSupportFragment.java
@@ -0,0 +1,303 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v17.leanback.widget.ItemBridgeAdapter;
+import android.support.v17.leanback.widget.ListRow;
+import android.support.v17.leanback.widget.ObjectAdapter;
+import android.support.v17.leanback.widget.OnChildViewHolderSelectedListener;
+import android.support.v17.leanback.widget.PresenterSelector;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.support.v4.app.Fragment;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * An internal base class for a fragment containing a list of rows.
+ */
+abstract class BaseRowSupportFragment extends Fragment {
+    private static final String CURRENT_SELECTED_POSITION = "currentSelectedPosition";
+    private ObjectAdapter mAdapter;
+    VerticalGridView mVerticalGridView;
+    private PresenterSelector mPresenterSelector;
+    final ItemBridgeAdapter mBridgeAdapter = new ItemBridgeAdapter();
+    int mSelectedPosition = -1;
+    private boolean mPendingTransitionPrepare;
+    private LateSelectionObserver mLateSelectionObserver = new LateSelectionObserver();
+
+    abstract int getLayoutResourceId();
+
+    private final OnChildViewHolderSelectedListener mRowSelectedListener =
+            new OnChildViewHolderSelectedListener() {
+                @Override
+                public void onChildViewHolderSelected(RecyclerView parent,
+                        RecyclerView.ViewHolder view, int position, int subposition) {
+                    if (!mLateSelectionObserver.mIsLateSelection) {
+                        mSelectedPosition = position;
+                        onRowSelected(parent, view, position, subposition);
+                    }
+                }
+            };
+
+    void onRowSelected(RecyclerView parent, RecyclerView.ViewHolder view,
+            int position, int subposition) {
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        View view = inflater.inflate(getLayoutResourceId(), container, false);
+        mVerticalGridView = findGridViewFromRoot(view);
+        if (mPendingTransitionPrepare) {
+            mPendingTransitionPrepare = false;
+            onTransitionPrepare();
+        }
+        return view;
+    }
+
+    VerticalGridView findGridViewFromRoot(View view) {
+        return (VerticalGridView) view;
+    }
+
+    @Override
+    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+        if (savedInstanceState != null) {
+            mSelectedPosition = savedInstanceState.getInt(CURRENT_SELECTED_POSITION, -1);
+        }
+        setAdapterAndSelection();
+        mVerticalGridView.setOnChildViewHolderSelectedListener(mRowSelectedListener);
+    }
+
+    /**
+     * This class waits for the adapter to be updated before setting the selected
+     * row.
+     */
+    private class LateSelectionObserver extends RecyclerView.AdapterDataObserver {
+        boolean mIsLateSelection = false;
+
+        LateSelectionObserver() {
+        }
+
+        @Override
+        public void onChanged() {
+            performLateSelection();
+        }
+
+        @Override
+        public void onItemRangeInserted(int positionStart, int itemCount) {
+            performLateSelection();
+        }
+
+        void startLateSelection() {
+            mIsLateSelection = true;
+            mBridgeAdapter.registerAdapterDataObserver(this);
+        }
+
+        void performLateSelection() {
+            clear();
+            if (mVerticalGridView != null) {
+                mVerticalGridView.setSelectedPosition(mSelectedPosition);
+            }
+        }
+
+        void clear() {
+            if (mIsLateSelection) {
+                mIsLateSelection = false;
+                mBridgeAdapter.unregisterAdapterDataObserver(this);
+            }
+        }
+    }
+
+    void setAdapterAndSelection() {
+        if (mAdapter == null) {
+            // delay until ItemBridgeAdapter has wrappedAdapter. Once we assign ItemBridgeAdapter
+            // to RecyclerView, it will not be allowed to change "hasStableId" to true.
+            return;
+        }
+        if (mVerticalGridView.getAdapter() != mBridgeAdapter) {
+            // avoid extra layout if ItemBridgeAdapter was already set.
+            mVerticalGridView.setAdapter(mBridgeAdapter);
+        }
+        // We don't set the selected position unless we've data in the adapter.
+        boolean lateSelection = mBridgeAdapter.getItemCount() == 0 && mSelectedPosition >= 0;
+        if (lateSelection) {
+            mLateSelectionObserver.startLateSelection();
+        } else if (mSelectedPosition >= 0) {
+            mVerticalGridView.setSelectedPosition(mSelectedPosition);
+        }
+    }
+
+    @Override
+    public void onDestroyView() {
+        super.onDestroyView();
+        mLateSelectionObserver.clear();
+        mVerticalGridView = null;
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putInt(CURRENT_SELECTED_POSITION, mSelectedPosition);
+    }
+
+    /**
+     * Set the presenter selector used to create and bind views.
+     */
+    public final void setPresenterSelector(PresenterSelector presenterSelector) {
+        if (mPresenterSelector != presenterSelector) {
+            mPresenterSelector = presenterSelector;
+            updateAdapter();
+        }
+    }
+
+    /**
+     * Get the presenter selector used to create and bind views.
+     */
+    public final PresenterSelector getPresenterSelector() {
+        return mPresenterSelector;
+    }
+
+    /**
+     * Sets the adapter that represents a list of rows.
+     * @param rowsAdapter Adapter that represents list of rows.
+     */
+    public final void setAdapter(ObjectAdapter rowsAdapter) {
+        if (mAdapter != rowsAdapter) {
+            mAdapter = rowsAdapter;
+            updateAdapter();
+        }
+    }
+
+    /**
+     * Returns the Adapter that represents list of rows.
+     * @return Adapter that represents list of rows.
+     */
+    public final ObjectAdapter getAdapter() {
+        return mAdapter;
+    }
+
+    /**
+     * Returns the RecyclerView.Adapter that wraps {@link #getAdapter()}.
+     * @return The RecyclerView.Adapter that wraps {@link #getAdapter()}.
+     */
+    public final ItemBridgeAdapter getBridgeAdapter() {
+        return mBridgeAdapter;
+    }
+
+    /**
+     * Sets the selected row position with smooth animation.
+     */
+    public void setSelectedPosition(int position) {
+        setSelectedPosition(position, true);
+    }
+
+    /**
+     * Gets position of currently selected row.
+     * @return Position of currently selected row.
+     */
+    public int getSelectedPosition() {
+        return mSelectedPosition;
+    }
+
+    /**
+     * Sets the selected row position.
+     */
+    public void setSelectedPosition(int position, boolean smooth) {
+        if (mSelectedPosition == position) {
+            return;
+        }
+        mSelectedPosition = position;
+        if (mVerticalGridView != null) {
+            if (mLateSelectionObserver.mIsLateSelection) {
+                return;
+            }
+            if (smooth) {
+                mVerticalGridView.setSelectedPositionSmooth(position);
+            } else {
+                mVerticalGridView.setSelectedPosition(position);
+            }
+        }
+    }
+
+    public final VerticalGridView getVerticalGridView() {
+        return mVerticalGridView;
+    }
+
+    void updateAdapter() {
+        mBridgeAdapter.setAdapter(mAdapter);
+        mBridgeAdapter.setPresenter(mPresenterSelector);
+
+        if (mVerticalGridView != null) {
+            setAdapterAndSelection();
+        }
+    }
+
+    Object getItem(Row row, int position) {
+        if (row instanceof ListRow) {
+            return ((ListRow) row).getAdapter().get(position);
+        } else {
+            return null;
+        }
+    }
+
+    public boolean onTransitionPrepare() {
+        if (mVerticalGridView != null) {
+            mVerticalGridView.setAnimateChildLayout(false);
+            mVerticalGridView.setScrollEnabled(false);
+            return true;
+        }
+        mPendingTransitionPrepare = true;
+        return false;
+    }
+
+    public void onTransitionStart() {
+        if (mVerticalGridView != null) {
+            mVerticalGridView.setPruneChild(false);
+            mVerticalGridView.setLayoutFrozen(true);
+            mVerticalGridView.setFocusSearchDisabled(true);
+        }
+    }
+
+    public void onTransitionEnd() {
+        // be careful that fragment might be destroyed before header transition ends.
+        if (mVerticalGridView != null) {
+            mVerticalGridView.setLayoutFrozen(false);
+            mVerticalGridView.setAnimateChildLayout(true);
+            mVerticalGridView.setPruneChild(true);
+            mVerticalGridView.setFocusSearchDisabled(false);
+            mVerticalGridView.setScrollEnabled(true);
+        }
+    }
+
+    public void setAlignment(int windowAlignOffsetTop) {
+        if (mVerticalGridView != null) {
+            // align the top edge of item
+            mVerticalGridView.setItemAlignmentOffset(0);
+            mVerticalGridView.setItemAlignmentOffsetPercent(
+                    VerticalGridView.ITEM_ALIGN_OFFSET_PERCENT_DISABLED);
+
+            // align to a fixed position from top
+            mVerticalGridView.setWindowAlignmentOffset(windowAlignOffsetTop);
+            mVerticalGridView.setWindowAlignmentOffsetPercent(
+                    VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
+            mVerticalGridView.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE);
+        }
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BaseSupportFragment.java b/leanback/src/android/support/v17/leanback/app/BaseSupportFragment.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/app/BaseSupportFragment.java
rename to leanback/src/android/support/v17/leanback/app/BaseSupportFragment.java
diff --git a/leanback/src/android/support/v17/leanback/app/BrandedFragment.java b/leanback/src/android/support/v17/leanback/app/BrandedFragment.java
new file mode 100644
index 0000000..415c13e
--- /dev/null
+++ b/leanback/src/android/support/v17/leanback/app/BrandedFragment.java
@@ -0,0 +1,340 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from BrandedSupportFragment.java.  DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v17.leanback.R;
+import android.support.v17.leanback.widget.SearchOrbView;
+import android.support.v17.leanback.widget.TitleHelper;
+import android.support.v17.leanback.widget.TitleViewAdapter;
+import android.app.Fragment;
+import android.util.TypedValue;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * Fragment class for managing search and branding using a view that implements
+ * {@link TitleViewAdapter.Provider}.
+ * @deprecated use {@link BrandedSupportFragment}
+ */
+@Deprecated
+public class BrandedFragment extends Fragment {
+
+    // BUNDLE attribute for title is showing
+    private static final String TITLE_SHOW = "titleShow";
+
+    private boolean mShowingTitle = true;
+    private CharSequence mTitle;
+    private Drawable mBadgeDrawable;
+    private View mTitleView;
+    private TitleViewAdapter mTitleViewAdapter;
+    private SearchOrbView.Colors mSearchAffordanceColors;
+    private boolean mSearchAffordanceColorSet;
+    private View.OnClickListener mExternalOnSearchClickedListener;
+    private TitleHelper mTitleHelper;
+
+    /**
+     * Called by {@link #installTitleView(LayoutInflater, ViewGroup, Bundle)} to inflate
+     * title view.  Default implementation uses layout file lb_browse_title.
+     * Subclass may override and use its own layout, the layout must have a descendant with id
+     * browse_title_group that implements {@link TitleViewAdapter.Provider}. Subclass may return
+     * null if no title is needed.
+     *
+     * @param inflater           The LayoutInflater object that can be used to inflate
+     *                           any views in the fragment,
+     * @param parent             Parent of title view.
+     * @param savedInstanceState If non-null, this fragment is being re-constructed
+     *                           from a previous saved state as given here.
+     * @return Title view which must have a descendant with id browse_title_group that implements
+     *         {@link TitleViewAdapter.Provider}, or null for no title view.
+     */
+    public View onInflateTitleView(LayoutInflater inflater, ViewGroup parent,
+                                Bundle savedInstanceState) {
+        TypedValue typedValue = new TypedValue();
+        boolean found = parent.getContext().getTheme().resolveAttribute(
+                R.attr.browseTitleViewLayout, typedValue, true);
+        return inflater.inflate(found ? typedValue.resourceId : R.layout.lb_browse_title,
+                parent, false);
+    }
+
+    /**
+     * Inflate title view and add to parent.  This method should be called in
+     * {@link Fragment#onCreateView(LayoutInflater, ViewGroup, Bundle)}.
+     * @param inflater The LayoutInflater object that can be used to inflate
+     * any views in the fragment,
+     * @param parent Parent of title view.
+     * @param savedInstanceState If non-null, this fragment is being re-constructed
+     * from a previous saved state as given here.
+     */
+    public void installTitleView(LayoutInflater inflater, ViewGroup parent,
+                            Bundle savedInstanceState) {
+        View titleLayoutRoot = onInflateTitleView(inflater, parent, savedInstanceState);
+        if (titleLayoutRoot != null) {
+            parent.addView(titleLayoutRoot);
+            setTitleView(titleLayoutRoot.findViewById(R.id.browse_title_group));
+        } else {
+            setTitleView(null);
+        }
+    }
+
+    /**
+     * Sets the view that implemented {@link TitleViewAdapter}.
+     * @param titleView The view that implemented {@link TitleViewAdapter.Provider}.
+     */
+    public void setTitleView(View titleView) {
+        mTitleView = titleView;
+        if (mTitleView == null) {
+            mTitleViewAdapter = null;
+            mTitleHelper = null;
+        } else {
+            mTitleViewAdapter = ((TitleViewAdapter.Provider) mTitleView).getTitleViewAdapter();
+            mTitleViewAdapter.setTitle(mTitle);
+            mTitleViewAdapter.setBadgeDrawable(mBadgeDrawable);
+            if (mSearchAffordanceColorSet) {
+                mTitleViewAdapter.setSearchAffordanceColors(mSearchAffordanceColors);
+            }
+            if (mExternalOnSearchClickedListener != null) {
+                setOnSearchClickedListener(mExternalOnSearchClickedListener);
+            }
+            if (getView() instanceof ViewGroup) {
+                mTitleHelper = new TitleHelper((ViewGroup) getView(), mTitleView);
+            }
+        }
+    }
+
+    /**
+     * Returns the view that implements {@link TitleViewAdapter.Provider}.
+     * @return The view that implements {@link TitleViewAdapter.Provider}.
+     */
+    public View getTitleView() {
+        return mTitleView;
+    }
+
+    /**
+     * Returns the {@link TitleViewAdapter} implemented by title view.
+     * @return The {@link TitleViewAdapter} implemented by title view.
+     */
+    public TitleViewAdapter getTitleViewAdapter() {
+        return mTitleViewAdapter;
+    }
+
+    /**
+     * Returns the {@link TitleHelper}.
+     */
+    TitleHelper getTitleHelper() {
+        return mTitleHelper;
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putBoolean(TITLE_SHOW, mShowingTitle);
+    }
+
+    @Override
+    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+        if (savedInstanceState != null) {
+            mShowingTitle = savedInstanceState.getBoolean(TITLE_SHOW);
+        }
+        if (mTitleView != null && view instanceof ViewGroup) {
+            mTitleHelper = new TitleHelper((ViewGroup) view, mTitleView);
+            mTitleHelper.showTitle(mShowingTitle);
+        }
+    }
+
+    @Override
+    public void onDestroyView() {
+        super.onDestroyView();
+        mTitleHelper = null;
+    }
+
+    /**
+     * Shows or hides the title view.
+     * @param show True to show title view, false to hide title view.
+     */
+    public void showTitle(boolean show) {
+        // TODO: handle interruptions?
+        if (show == mShowingTitle) {
+            return;
+        }
+        mShowingTitle = show;
+        if (mTitleHelper != null) {
+            mTitleHelper.showTitle(show);
+        }
+    }
+
+    /**
+     * Changes title view's components visibility and shows title.
+     * @param flags Flags representing the visibility of components inside title view.
+     * @see TitleViewAdapter#SEARCH_VIEW_VISIBLE
+     * @see TitleViewAdapter#BRANDING_VIEW_VISIBLE
+     * @see TitleViewAdapter#FULL_VIEW_VISIBLE
+     * @see TitleViewAdapter#updateComponentsVisibility(int)
+     */
+    public void showTitle(int flags) {
+        if (mTitleViewAdapter != null) {
+            mTitleViewAdapter.updateComponentsVisibility(flags);
+        }
+        showTitle(true);
+    }
+
+    /**
+     * Sets the drawable displayed in the fragment title.
+     *
+     * @param drawable The Drawable to display in the fragment title.
+     */
+    public void setBadgeDrawable(Drawable drawable) {
+        if (mBadgeDrawable != drawable) {
+            mBadgeDrawable = drawable;
+            if (mTitleViewAdapter != null) {
+                mTitleViewAdapter.setBadgeDrawable(drawable);
+            }
+        }
+    }
+
+    /**
+     * Returns the badge drawable used in the fragment title.
+     * @return The badge drawable used in the fragment title.
+     */
+    public Drawable getBadgeDrawable() {
+        return mBadgeDrawable;
+    }
+
+    /**
+     * Sets title text for the fragment.
+     *
+     * @param title The title text of the fragment.
+     */
+    public void setTitle(CharSequence title) {
+        mTitle = title;
+        if (mTitleViewAdapter != null) {
+            mTitleViewAdapter.setTitle(title);
+        }
+    }
+
+    /**
+     * Returns the title text for the fragment.
+     * @return Title text for the fragment.
+     */
+    public CharSequence getTitle() {
+        return mTitle;
+    }
+
+    /**
+     * Sets a click listener for the search affordance.
+     *
+     * <p>The presence of a listener will change the visibility of the search
+     * affordance in the fragment title. When set to non-null, the title will
+     * contain an element that a user may click to begin a search.
+     *
+     * <p>The listener's {@link View.OnClickListener#onClick onClick} method
+     * will be invoked when the user clicks on the search element.
+     *
+     * @param listener The listener to call when the search element is clicked.
+     */
+    public void setOnSearchClickedListener(View.OnClickListener listener) {
+        mExternalOnSearchClickedListener = listener;
+        if (mTitleViewAdapter != null) {
+            mTitleViewAdapter.setOnSearchClickedListener(listener);
+        }
+    }
+
+    /**
+     * Sets the {@link android.support.v17.leanback.widget.SearchOrbView.Colors} used to draw the
+     * search affordance.
+     *
+     * @param colors Colors used to draw search affordance.
+     */
+    public void setSearchAffordanceColors(SearchOrbView.Colors colors) {
+        mSearchAffordanceColors = colors;
+        mSearchAffordanceColorSet = true;
+        if (mTitleViewAdapter != null) {
+            mTitleViewAdapter.setSearchAffordanceColors(mSearchAffordanceColors);
+        }
+    }
+
+    /**
+     * Returns the {@link android.support.v17.leanback.widget.SearchOrbView.Colors}
+     * used to draw the search affordance.
+     */
+    public SearchOrbView.Colors getSearchAffordanceColors() {
+        if (mSearchAffordanceColorSet) {
+            return mSearchAffordanceColors;
+        }
+        if (mTitleViewAdapter == null) {
+            throw new IllegalStateException("Fragment views not yet created");
+        }
+        return mTitleViewAdapter.getSearchAffordanceColors();
+    }
+
+    /**
+     * Sets the color used to draw the search affordance.
+     * A default brighter color will be set by the framework.
+     *
+     * @param color The color to use for the search affordance.
+     */
+    public void setSearchAffordanceColor(int color) {
+        setSearchAffordanceColors(new SearchOrbView.Colors(color));
+    }
+
+    /**
+     * Returns the color used to draw the search affordance.
+     */
+    public int getSearchAffordanceColor() {
+        return getSearchAffordanceColors().color;
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        if (mTitleViewAdapter != null) {
+            showTitle(mShowingTitle);
+            mTitleViewAdapter.setAnimationEnabled(true);
+        }
+    }
+
+    @Override
+    public void onPause() {
+        if (mTitleViewAdapter != null) {
+            mTitleViewAdapter.setAnimationEnabled(false);
+        }
+        super.onPause();
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        if (mTitleViewAdapter != null) {
+            mTitleViewAdapter.setAnimationEnabled(true);
+        }
+    }
+
+    /**
+     * Returns true/false to indicate the visibility of TitleView.
+     *
+     * @return boolean to indicate whether or not it's showing the title.
+     */
+    public final boolean isShowingTitle() {
+        return mShowingTitle;
+    }
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BrandedSupportFragment.java b/leanback/src/android/support/v17/leanback/app/BrandedSupportFragment.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/app/BrandedSupportFragment.java
rename to leanback/src/android/support/v17/leanback/app/BrandedSupportFragment.java
diff --git a/leanback/src/android/support/v17/leanback/app/BrowseFragment.java b/leanback/src/android/support/v17/leanback/app/BrowseFragment.java
new file mode 100644
index 0000000..c561ea9
--- /dev/null
+++ b/leanback/src/android/support/v17/leanback/app/BrowseFragment.java
@@ -0,0 +1,1868 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from BrowseSupportFragment.java.  DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import static android.support.v7.widget.RecyclerView.NO_POSITION;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.support.annotation.ColorInt;
+import android.support.v17.leanback.R;
+import android.support.v17.leanback.transition.TransitionHelper;
+import android.support.v17.leanback.transition.TransitionListener;
+import android.support.v17.leanback.util.StateMachine.Event;
+import android.support.v17.leanback.util.StateMachine.State;
+import android.support.v17.leanback.widget.BrowseFrameLayout;
+import android.support.v17.leanback.widget.InvisibleRowPresenter;
+import android.support.v17.leanback.widget.ListRow;
+import android.support.v17.leanback.widget.ObjectAdapter;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.OnItemViewSelectedListener;
+import android.support.v17.leanback.widget.PageRow;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.PresenterSelector;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.RowHeaderPresenter;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.support.v17.leanback.widget.ScaleFrameLayout;
+import android.support.v17.leanback.widget.TitleViewAdapter;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.app.FragmentManager.BackStackEntry;
+import android.app.FragmentTransaction;
+import android.support.v4.view.ViewCompat;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.MarginLayoutParams;
+import android.view.ViewTreeObserver;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A fragment for creating Leanback browse screens. It is composed of a
+ * RowsFragment and a HeadersFragment.
+ * <p>
+ * A BrowseFragment renders the elements of its {@link ObjectAdapter} as a set
+ * of rows in a vertical list. The elements in this adapter must be subclasses
+ * of {@link Row}.
+ * <p>
+ * The HeadersFragment can be set to be either shown or hidden by default, or
+ * may be disabled entirely. See {@link #setHeadersState} for details.
+ * <p>
+ * By default the BrowseFragment includes support for returning to the headers
+ * when the user presses Back. For Activities that customize {@link
+ * android.app.Activity#onBackPressed()}, you must disable this default Back key support by
+ * calling {@link #setHeadersTransitionOnBackEnabled(boolean)} with false and
+ * use {@link BrowseFragment.BrowseTransitionListener} and
+ * {@link #startHeadersTransition(boolean)}.
+ * <p>
+ * The recommended theme to use with a BrowseFragment is
+ * {@link android.support.v17.leanback.R.style#Theme_Leanback_Browse}.
+ * </p>
+ * @deprecated use {@link BrowseSupportFragment}
+ */
+@Deprecated
+public class BrowseFragment extends BaseFragment {
+
+    // BUNDLE attribute for saving header show/hide status when backstack is used:
+    static final String HEADER_STACK_INDEX = "headerStackIndex";
+    // BUNDLE attribute for saving header show/hide status when backstack is not used:
+    static final String HEADER_SHOW = "headerShow";
+    private static final String IS_PAGE_ROW = "isPageRow";
+    private static final String CURRENT_SELECTED_POSITION = "currentSelectedPosition";
+
+    /**
+     * State to hide headers fragment.
+     */
+    final State STATE_SET_ENTRANCE_START_STATE = new State("SET_ENTRANCE_START_STATE") {
+        @Override
+        public void run() {
+            setEntranceTransitionStartState();
+        }
+    };
+
+    /**
+     * Event for Header fragment view is created, we could perform
+     * {@link #setEntranceTransitionStartState()} to hide headers fragment initially.
+     */
+    final Event EVT_HEADER_VIEW_CREATED = new Event("headerFragmentViewCreated");
+
+    /**
+     * Event for {@link #getMainFragment()} view is created, it's additional requirement to execute
+     * {@link #onEntranceTransitionPrepare()}.
+     */
+    final Event EVT_MAIN_FRAGMENT_VIEW_CREATED = new Event("mainFragmentViewCreated");
+
+    /**
+     * Event that data for the screen is ready, this is additional requirement to launch entrance
+     * transition.
+     */
+    final Event EVT_SCREEN_DATA_READY = new Event("screenDataReady");
+
+    @Override
+    void createStateMachineStates() {
+        super.createStateMachineStates();
+        mStateMachine.addState(STATE_SET_ENTRANCE_START_STATE);
+    }
+
+    @Override
+    void createStateMachineTransitions() {
+        super.createStateMachineTransitions();
+        // when headers fragment view is created we could setEntranceTransitionStartState()
+        mStateMachine.addTransition(STATE_ENTRANCE_ON_PREPARED, STATE_SET_ENTRANCE_START_STATE,
+                EVT_HEADER_VIEW_CREATED);
+
+        // add additional requirement for onEntranceTransitionPrepare()
+        mStateMachine.addTransition(STATE_ENTRANCE_ON_PREPARED,
+                STATE_ENTRANCE_ON_PREPARED_ON_CREATEVIEW,
+                EVT_MAIN_FRAGMENT_VIEW_CREATED);
+        // add additional requirement to launch entrance transition.
+        mStateMachine.addTransition(STATE_ENTRANCE_ON_PREPARED,  STATE_ENTRANCE_PERFORM,
+                EVT_SCREEN_DATA_READY);
+    }
+
+    final class BackStackListener implements FragmentManager.OnBackStackChangedListener {
+        int mLastEntryCount;
+        int mIndexOfHeadersBackStack;
+
+        BackStackListener() {
+            mLastEntryCount = getFragmentManager().getBackStackEntryCount();
+            mIndexOfHeadersBackStack = -1;
+        }
+
+        void load(Bundle savedInstanceState) {
+            if (savedInstanceState != null) {
+                mIndexOfHeadersBackStack = savedInstanceState.getInt(HEADER_STACK_INDEX, -1);
+                mShowingHeaders = mIndexOfHeadersBackStack == -1;
+            } else {
+                if (!mShowingHeaders) {
+                    getFragmentManager().beginTransaction()
+                            .addToBackStack(mWithHeadersBackStackName).commit();
+                }
+            }
+        }
+
+        void save(Bundle outState) {
+            outState.putInt(HEADER_STACK_INDEX, mIndexOfHeadersBackStack);
+        }
+
+
+        @Override
+        public void onBackStackChanged() {
+            if (getFragmentManager() == null) {
+                Log.w(TAG, "getFragmentManager() is null, stack:", new Exception());
+                return;
+            }
+            int count = getFragmentManager().getBackStackEntryCount();
+            // if backstack is growing and last pushed entry is "headers" backstack,
+            // remember the index of the entry.
+            if (count > mLastEntryCount) {
+                BackStackEntry entry = getFragmentManager().getBackStackEntryAt(count - 1);
+                if (mWithHeadersBackStackName.equals(entry.getName())) {
+                    mIndexOfHeadersBackStack = count - 1;
+                }
+            } else if (count < mLastEntryCount) {
+                // if popped "headers" backstack, initiate the show header transition if needed
+                if (mIndexOfHeadersBackStack >= count) {
+                    if (!isHeadersDataReady()) {
+                        // if main fragment was restored first before BrowseFragment's adapter gets
+                        // restored: don't start header transition, but add the entry back.
+                        getFragmentManager().beginTransaction()
+                                .addToBackStack(mWithHeadersBackStackName).commit();
+                        return;
+                    }
+                    mIndexOfHeadersBackStack = -1;
+                    if (!mShowingHeaders) {
+                        startHeadersTransitionInternal(true);
+                    }
+                }
+            }
+            mLastEntryCount = count;
+        }
+    }
+
+    /**
+     * Listener for transitions between browse headers and rows.
+     * @deprecated use {@link BrowseSupportFragment}
+     */
+    @Deprecated
+    public static class BrowseTransitionListener {
+        /**
+         * Callback when headers transition starts.
+         *
+         * @param withHeaders True if the transition will result in headers
+         *        being shown, false otherwise.
+         */
+        public void onHeadersTransitionStart(boolean withHeaders) {
+        }
+        /**
+         * Callback when headers transition stops.
+         *
+         * @param withHeaders True if the transition will result in headers
+         *        being shown, false otherwise.
+         */
+        public void onHeadersTransitionStop(boolean withHeaders) {
+        }
+    }
+
+    private class SetSelectionRunnable implements Runnable {
+        static final int TYPE_INVALID = -1;
+        static final int TYPE_INTERNAL_SYNC = 0;
+        static final int TYPE_USER_REQUEST = 1;
+
+        private int mPosition;
+        private int mType;
+        private boolean mSmooth;
+
+        SetSelectionRunnable() {
+            reset();
+        }
+
+        void post(int position, int type, boolean smooth) {
+            // Posting the set selection, rather than calling it immediately, prevents an issue
+            // with adapter changes.  Example: a row is added before the current selected row;
+            // first the fast lane view updates its selection, then the rows fragment has that
+            // new selection propagated immediately; THEN the rows view processes the same adapter
+            // change and moves the selection again.
+            if (type >= mType) {
+                mPosition = position;
+                mType = type;
+                mSmooth = smooth;
+                mBrowseFrame.removeCallbacks(this);
+                mBrowseFrame.post(this);
+            }
+        }
+
+        @Override
+        public void run() {
+            setSelection(mPosition, mSmooth);
+            reset();
+        }
+
+        private void reset() {
+            mPosition = -1;
+            mType = TYPE_INVALID;
+            mSmooth = false;
+        }
+    }
+
+    /**
+     * Possible set of actions that {@link BrowseFragment} exposes to clients. Custom
+     * fragments can interact with {@link BrowseFragment} using this interface.
+     * @deprecated use {@link BrowseSupportFragment}
+     */
+    @Deprecated
+    public interface FragmentHost {
+        /**
+         * Fragments are required to invoke this callback once their view is created
+         * inside {@link Fragment#onViewCreated} method. {@link BrowseFragment} starts the entrance
+         * animation only after receiving this callback. Failure to invoke this method
+         * will lead to fragment not showing up.
+         *
+         * @param fragmentAdapter {@link MainFragmentAdapter} used by the current fragment.
+         */
+        void notifyViewCreated(MainFragmentAdapter fragmentAdapter);
+
+        /**
+         * Fragments mapped to {@link PageRow} are required to invoke this callback once their data
+         * is created for transition, the entrance animation only after receiving this callback.
+         * Failure to invoke this method will lead to fragment not showing up.
+         *
+         * @param fragmentAdapter {@link MainFragmentAdapter} used by the current fragment.
+         */
+        void notifyDataReady(MainFragmentAdapter fragmentAdapter);
+
+        /**
+         * Show or hide title view in {@link BrowseFragment} for fragments mapped to
+         * {@link PageRow}.  Otherwise the request is ignored, in that case BrowseFragment is fully
+         * in control of showing/hiding title view.
+         * <p>
+         * When HeadersFragment is visible, BrowseFragment will hide search affordance view if
+         * there are other focusable rows above currently focused row.
+         *
+         * @param show Boolean indicating whether or not to show the title view.
+         */
+        void showTitleView(boolean show);
+    }
+
+    /**
+     * Default implementation of {@link FragmentHost} that is used only by
+     * {@link BrowseFragment}.
+     */
+    private final class FragmentHostImpl implements FragmentHost {
+        boolean mShowTitleView = true;
+
+        FragmentHostImpl() {
+        }
+
+        @Override
+        public void notifyViewCreated(MainFragmentAdapter fragmentAdapter) {
+            mStateMachine.fireEvent(EVT_MAIN_FRAGMENT_VIEW_CREATED);
+            if (!mIsPageRow) {
+                // If it's not a PageRow: it's a ListRow, so we already have data ready.
+                mStateMachine.fireEvent(EVT_SCREEN_DATA_READY);
+            }
+        }
+
+        @Override
+        public void notifyDataReady(MainFragmentAdapter fragmentAdapter) {
+            // If fragment host is not the currently active fragment (in BrowseFragment), then
+            // ignore the request.
+            if (mMainFragmentAdapter == null || mMainFragmentAdapter.getFragmentHost() != this) {
+                return;
+            }
+
+            // We only honor showTitle request for PageRows.
+            if (!mIsPageRow) {
+                return;
+            }
+
+            mStateMachine.fireEvent(EVT_SCREEN_DATA_READY);
+        }
+
+        @Override
+        public void showTitleView(boolean show) {
+            mShowTitleView = show;
+
+            // If fragment host is not the currently active fragment (in BrowseFragment), then
+            // ignore the request.
+            if (mMainFragmentAdapter == null || mMainFragmentAdapter.getFragmentHost() != this) {
+                return;
+            }
+
+            // We only honor showTitle request for PageRows.
+            if (!mIsPageRow) {
+                return;
+            }
+
+            updateTitleViewVisibility();
+        }
+    }
+
+    /**
+     * Interface that defines the interaction between {@link BrowseFragment} and its main
+     * content fragment. The key method is {@link MainFragmentAdapter#getFragment()},
+     * it will be used to get the fragment to be shown in the content section. Clients can
+     * provide any implementation of fragment and customize its interaction with
+     * {@link BrowseFragment} by overriding the necessary methods.
+     *
+     * <p>
+     * Clients are expected to provide
+     * an instance of {@link MainFragmentAdapterRegistry} which will be responsible for providing
+     * implementations of {@link MainFragmentAdapter} for given content types. Currently
+     * we support different types of content - {@link ListRow}, {@link PageRow} or any subtype
+     * of {@link Row}. We provide an out of the box adapter implementation for any rows other than
+     * {@link PageRow} - {@link android.support.v17.leanback.app.RowsFragment.MainFragmentAdapter}.
+     *
+     * <p>
+     * {@link PageRow} is intended to give full flexibility to developers in terms of Fragment
+     * design. Users will have to provide an implementation of {@link MainFragmentAdapter}
+     * and provide that through {@link MainFragmentAdapterRegistry}.
+     * {@link MainFragmentAdapter} implementation can supply any fragment and override
+     * just those interactions that makes sense.
+     * @deprecated use {@link BrowseSupportFragment}
+     */
+    @Deprecated
+    public static class MainFragmentAdapter<T extends Fragment> {
+        private boolean mScalingEnabled;
+        private final T mFragment;
+        FragmentHostImpl mFragmentHost;
+
+        public MainFragmentAdapter(T fragment) {
+            this.mFragment = fragment;
+        }
+
+        public final T getFragment() {
+            return mFragment;
+        }
+
+        /**
+         * Returns whether its scrolling.
+         */
+        public boolean isScrolling() {
+            return false;
+        }
+
+        /**
+         * Set the visibility of titles/hover card of browse rows.
+         */
+        public void setExpand(boolean expand) {
+        }
+
+        /**
+         * For rows that willing to participate entrance transition,  this function
+         * hide views if afterTransition is true,  show views if afterTransition is false.
+         */
+        public void setEntranceTransitionState(boolean state) {
+        }
+
+        /**
+         * Sets the window alignment and also the pivots for scale operation.
+         */
+        public void setAlignment(int windowAlignOffsetFromTop) {
+        }
+
+        /**
+         * Callback indicating transition prepare start.
+         */
+        public boolean onTransitionPrepare() {
+            return false;
+        }
+
+        /**
+         * Callback indicating transition start.
+         */
+        public void onTransitionStart() {
+        }
+
+        /**
+         * Callback indicating transition end.
+         */
+        public void onTransitionEnd() {
+        }
+
+        /**
+         * Returns whether row scaling is enabled.
+         */
+        public boolean isScalingEnabled() {
+            return mScalingEnabled;
+        }
+
+        /**
+         * Sets the row scaling property.
+         */
+        public void setScalingEnabled(boolean scalingEnabled) {
+            this.mScalingEnabled = scalingEnabled;
+        }
+
+        /**
+         * Returns the current host interface so that main fragment can interact with
+         * {@link BrowseFragment}.
+         */
+        public final FragmentHost getFragmentHost() {
+            return mFragmentHost;
+        }
+
+        void setFragmentHost(FragmentHostImpl fragmentHost) {
+            this.mFragmentHost = fragmentHost;
+        }
+    }
+
+    /**
+     * Interface to be implemented by all fragments for providing an instance of
+     * {@link MainFragmentAdapter}. Both {@link RowsFragment} and custom fragment provided
+     * against {@link PageRow} will need to implement this interface.
+     * @deprecated use {@link BrowseSupportFragment}
+     */
+    @Deprecated
+    public interface MainFragmentAdapterProvider {
+        /**
+         * Returns an instance of {@link MainFragmentAdapter} that {@link BrowseFragment}
+         * would use to communicate with the target fragment.
+         */
+        MainFragmentAdapter getMainFragmentAdapter();
+    }
+
+    /**
+     * Interface to be implemented by {@link RowsFragment} and its subclasses for providing
+     * an instance of {@link MainFragmentRowsAdapter}.
+     * @deprecated use {@link BrowseSupportFragment}
+     */
+    @Deprecated
+    public interface MainFragmentRowsAdapterProvider {
+        /**
+         * Returns an instance of {@link MainFragmentRowsAdapter} that {@link BrowseFragment}
+         * would use to communicate with the target fragment.
+         */
+        MainFragmentRowsAdapter getMainFragmentRowsAdapter();
+    }
+
+    /**
+     * This is used to pass information to {@link RowsFragment} or its subclasses.
+     * {@link BrowseFragment} uses this interface to pass row based interaction events to
+     * the target fragment.
+     * @deprecated use {@link BrowseSupportFragment}
+     */
+    @Deprecated
+    public static class MainFragmentRowsAdapter<T extends Fragment> {
+        private final T mFragment;
+
+        public MainFragmentRowsAdapter(T fragment) {
+            if (fragment == null) {
+                throw new IllegalArgumentException("Fragment can't be null");
+            }
+            this.mFragment = fragment;
+        }
+
+        public final T getFragment() {
+            return mFragment;
+        }
+        /**
+         * Set the visibility titles/hover of browse rows.
+         */
+        public void setAdapter(ObjectAdapter adapter) {
+        }
+
+        /**
+         * Sets an item clicked listener on the fragment.
+         */
+        public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
+        }
+
+        /**
+         * Sets an item selection listener.
+         */
+        public void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
+        }
+
+        /**
+         * Selects a Row and perform an optional task on the Row.
+         */
+        public void setSelectedPosition(int rowPosition,
+                                        boolean smooth,
+                                        final Presenter.ViewHolderTask rowHolderTask) {
+        }
+
+        /**
+         * Selects a Row.
+         */
+        public void setSelectedPosition(int rowPosition, boolean smooth) {
+        }
+
+        /**
+         * @return The position of selected row.
+         */
+        public int getSelectedPosition() {
+            return 0;
+        }
+
+        /**
+         * @param position Position of Row.
+         * @return Row ViewHolder.
+         */
+        public RowPresenter.ViewHolder findRowViewHolderByPosition(int position) {
+            return null;
+        }
+    }
+
+    private boolean createMainFragment(ObjectAdapter adapter, int position) {
+        Object item = null;
+        if (!mCanShowHeaders) {
+            // when header is disabled, we can decide to use RowsFragment even no data.
+        } else if (adapter == null || adapter.size() == 0) {
+            return false;
+        } else {
+            if (position < 0) {
+                position = 0;
+            } else if (position >= adapter.size()) {
+                throw new IllegalArgumentException(
+                        String.format("Invalid position %d requested", position));
+            }
+            item = adapter.get(position);
+        }
+
+        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) {
+                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;
+            }
+        }
+
+        if (swap) {
+            mMainFragment = mMainFragmentAdapterRegistry.createFragment(item);
+            if (!(mMainFragment instanceof MainFragmentAdapterProvider)) {
+                throw new IllegalArgumentException(
+                        "Fragment must implement MainFragmentAdapterProvider");
+            }
+
+            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}
+     * can return any fragment class.
+     * @deprecated use {@link BrowseSupportFragment}
+     */
+    @Deprecated
+    public abstract static class FragmentFactory<T extends Fragment> {
+        public abstract T createFragment(Object row);
+    }
+
+    /**
+     * FragmentFactory implementation for {@link ListRow}.
+     * @deprecated use {@link BrowseSupportFragment}
+     */
+    @Deprecated
+    public static class ListRowFragmentFactory extends FragmentFactory<RowsFragment> {
+        @Override
+        public RowsFragment createFragment(Object row) {
+            return new RowsFragment();
+        }
+    }
+
+    /**
+     * Registry class maintaining the mapping of {@link Row} subclasses to {@link FragmentFactory}.
+     * BrowseRowFragment automatically registers {@link ListRowFragmentFactory} for
+     * handling {@link ListRow}. Developers can override that and also if they want to
+     * use custom fragment, they can register a custom {@link FragmentFactory}
+     * against {@link PageRow}.
+     * @deprecated use {@link BrowseSupportFragment}
+     */
+    @Deprecated
+    public final static class MainFragmentAdapterRegistry {
+        private final Map<Class, FragmentFactory> mItemToFragmentFactoryMapping = new HashMap<>();
+        private final static FragmentFactory sDefaultFragmentFactory = new ListRowFragmentFactory();
+
+        public MainFragmentAdapterRegistry() {
+            registerFragment(ListRow.class, sDefaultFragmentFactory);
+        }
+
+        public void registerFragment(Class rowClass, FragmentFactory factory) {
+            mItemToFragmentFactoryMapping.put(rowClass, factory);
+        }
+
+        public Fragment createFragment(Object item) {
+            FragmentFactory fragmentFactory = item == null ? sDefaultFragmentFactory :
+                    mItemToFragmentFactoryMapping.get(item.getClass());
+            if (fragmentFactory == null && !(item instanceof PageRow)) {
+                fragmentFactory = sDefaultFragmentFactory;
+            }
+
+            return fragmentFactory.createFragment(item);
+        }
+    }
+
+    static final String TAG = "BrowseFragment";
+
+    private static final String LB_HEADERS_BACKSTACK = "lbHeadersBackStack_";
+
+    static boolean DEBUG = false;
+
+    /** The headers fragment is enabled and shown by default. */
+    public static final int HEADERS_ENABLED = 1;
+
+    /** The headers fragment is enabled and hidden by default. */
+    public static final int HEADERS_HIDDEN = 2;
+
+    /** The headers fragment is disabled and will never be shown. */
+    public static final int HEADERS_DISABLED = 3;
+
+    private MainFragmentAdapterRegistry mMainFragmentAdapterRegistry =
+            new MainFragmentAdapterRegistry();
+    MainFragmentAdapter mMainFragmentAdapter;
+    Fragment mMainFragment;
+    HeadersFragment mHeadersFragment;
+    MainFragmentRowsAdapter mMainFragmentRowsAdapter;
+    ListRowDataAdapter mMainFragmentListRowDataAdapter;
+
+    private ObjectAdapter mAdapter;
+    private PresenterSelector mAdapterPresenter;
+
+    private int mHeadersState = HEADERS_ENABLED;
+    private int mBrandColor = Color.TRANSPARENT;
+    private boolean mBrandColorSet;
+
+    BrowseFrameLayout mBrowseFrame;
+    private ScaleFrameLayout mScaleFrameLayout;
+    boolean mHeadersBackStackEnabled = true;
+    String mWithHeadersBackStackName;
+    boolean mShowingHeaders = true;
+    boolean mCanShowHeaders = true;
+    private int mContainerListMarginStart;
+    private int mContainerListAlignTop;
+    private boolean mMainFragmentScaleEnabled = true;
+    OnItemViewSelectedListener mExternalOnItemViewSelectedListener;
+    private OnItemViewClickedListener mOnItemViewClickedListener;
+    private int mSelectedPosition = -1;
+    private float mScaleFactor;
+    boolean mIsPageRow;
+    Object mPageRow;
+
+    private PresenterSelector mHeaderPresenterSelector;
+    private final SetSelectionRunnable mSetSelectionRunnable = new SetSelectionRunnable();
+
+    // transition related:
+    Object mSceneWithHeaders;
+    Object mSceneWithoutHeaders;
+    private Object mSceneAfterEntranceTransition;
+    Object mHeadersTransition;
+    BackStackListener mBackStackChangedListener;
+    BrowseTransitionListener mBrowseTransitionListener;
+
+    private static final String ARG_TITLE = BrowseFragment.class.getCanonicalName() + ".title";
+    private static final String ARG_HEADERS_STATE =
+        BrowseFragment.class.getCanonicalName() + ".headersState";
+
+    /**
+     * Creates arguments for a browse fragment.
+     *
+     * @param args The Bundle to place arguments into, or null if the method
+     *        should return a new Bundle.
+     * @param title The title of the BrowseFragment.
+     * @param headersState The initial state of the headers of the
+     *        BrowseFragment. Must be one of {@link #HEADERS_ENABLED}, {@link
+     *        #HEADERS_HIDDEN}, or {@link #HEADERS_DISABLED}.
+     * @return A Bundle with the given arguments for creating a BrowseFragment.
+     */
+    public static Bundle createArgs(Bundle args, String title, int headersState) {
+        if (args == null) {
+            args = new Bundle();
+        }
+        args.putString(ARG_TITLE, title);
+        args.putInt(ARG_HEADERS_STATE, headersState);
+        return args;
+    }
+
+    /**
+     * Sets the brand color for the browse fragment. The brand color is used as
+     * the primary color for UI elements in the browse fragment. For example,
+     * the background color of the headers fragment uses the brand color.
+     *
+     * @param color The color to use as the brand color of the fragment.
+     */
+    public void setBrandColor(@ColorInt int color) {
+        mBrandColor = color;
+        mBrandColorSet = true;
+
+        if (mHeadersFragment != null) {
+            mHeadersFragment.setBackgroundColor(mBrandColor);
+        }
+    }
+
+    /**
+     * Returns the brand color for the browse fragment.
+     * The default is transparent.
+     */
+    @ColorInt
+    public int getBrandColor() {
+        return mBrandColor;
+    }
+
+    /**
+     * Wrapping app provided PresenterSelector to support InvisibleRowPresenter for SectionRow
+     * DividerRow and PageRow.
+     */
+    private void updateWrapperPresenter() {
+        if (mAdapter == null) {
+            mAdapterPresenter = null;
+            return;
+        }
+        final PresenterSelector adapterPresenter = mAdapter.getPresenterSelector();
+        if (adapterPresenter == null) {
+            throw new IllegalArgumentException("Adapter.getPresenterSelector() is null");
+        }
+        if (adapterPresenter == mAdapterPresenter) {
+            return;
+        }
+        mAdapterPresenter = adapterPresenter;
+
+        Presenter[] presenters = adapterPresenter.getPresenters();
+        final Presenter invisibleRowPresenter = new InvisibleRowPresenter();
+        final Presenter[] allPresenters = new Presenter[presenters.length + 1];
+        System.arraycopy(allPresenters, 0, presenters, 0, presenters.length);
+        allPresenters[allPresenters.length - 1] = invisibleRowPresenter;
+        mAdapter.setPresenterSelector(new PresenterSelector() {
+            @Override
+            public Presenter getPresenter(Object item) {
+                Row row = (Row) item;
+                if (row.isRenderedAsRowView()) {
+                    return adapterPresenter.getPresenter(item);
+                } else {
+                    return invisibleRowPresenter;
+                }
+            }
+
+            @Override
+            public Presenter[] getPresenters() {
+                return allPresenters;
+            }
+        });
+    }
+
+    /**
+     * Sets the adapter containing the rows for the fragment.
+     *
+     * <p>The items referenced by the adapter must be be derived from
+     * {@link Row}. These rows will be used by the rows fragment and the headers
+     * fragment (if not disabled) to render the browse rows.
+     *
+     * @param adapter An ObjectAdapter for the browse rows. All items must
+     *        derive from {@link Row}.
+     */
+    public void setAdapter(ObjectAdapter adapter) {
+        mAdapter = adapter;
+        updateWrapperPresenter();
+        if (getView() == null) {
+            return;
+        }
+
+        updateMainFragmentRowsAdapter();
+        mHeadersFragment.setAdapter(mAdapter);
+    }
+
+    void setMainFragmentRowsAdapter(MainFragmentRowsAdapter mainFragmentRowsAdapter) {
+        if (mainFragmentRowsAdapter == mMainFragmentRowsAdapter) {
+            return;
+        }
+        // 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() {
+        return mMainFragmentAdapterRegistry;
+    }
+
+    /**
+     * Returns the adapter containing the rows for the fragment.
+     */
+    public ObjectAdapter getAdapter() {
+        return mAdapter;
+    }
+
+    /**
+     * Sets an item selection listener.
+     */
+    public void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
+        mExternalOnItemViewSelectedListener = listener;
+    }
+
+    /**
+     * Returns an item selection listener.
+     */
+    public OnItemViewSelectedListener getOnItemViewSelectedListener() {
+        return mExternalOnItemViewSelectedListener;
+    }
+
+    /**
+     * Get RowsFragment if it's bound to BrowseFragment or null if either BrowseFragment has
+     * not been created yet or a different fragment is bound to it.
+     *
+     * @return RowsFragment if it's bound to BrowseFragment or null otherwise.
+     */
+    public RowsFragment getRowsFragment() {
+        if (mMainFragment instanceof RowsFragment) {
+            return (RowsFragment) mMainFragment;
+        }
+
+        return null;
+    }
+
+    /**
+     * @return Current main fragment or null if not created.
+     */
+    public Fragment getMainFragment() {
+        return mMainFragment;
+    }
+
+    /**
+     * Get currently bound HeadersFragment or null if HeadersFragment has not been created yet.
+     * @return Currently bound HeadersFragment or null if HeadersFragment has not been created yet.
+     */
+    public HeadersFragment getHeadersFragment() {
+        return mHeadersFragment;
+    }
+
+    /**
+     * Sets an item clicked listener on the fragment.
+     * OnItemViewClickedListener will override {@link View.OnClickListener} that
+     * item presenter sets during {@link Presenter#onCreateViewHolder(ViewGroup)}.
+     * So in general, developer should choose one of the listeners but not both.
+     */
+    public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
+        mOnItemViewClickedListener = listener;
+        if (mMainFragmentRowsAdapter != null) {
+            mMainFragmentRowsAdapter.setOnItemViewClickedListener(listener);
+        }
+    }
+
+    /**
+     * Returns the item Clicked listener.
+     */
+    public OnItemViewClickedListener getOnItemViewClickedListener() {
+        return mOnItemViewClickedListener;
+    }
+
+    /**
+     * Starts a headers transition.
+     *
+     * <p>This method will begin a transition to either show or hide the
+     * headers, depending on the value of withHeaders. If headers are disabled
+     * for this browse fragment, this method will throw an exception.
+     *
+     * @param withHeaders True if the headers should transition to being shown,
+     *        false if the transition should result in headers being hidden.
+     */
+    public void startHeadersTransition(boolean withHeaders) {
+        if (!mCanShowHeaders) {
+            throw new IllegalStateException("Cannot start headers transition");
+        }
+        if (isInHeadersTransition() || mShowingHeaders == withHeaders) {
+            return;
+        }
+        startHeadersTransitionInternal(withHeaders);
+    }
+
+    /**
+     * Returns true if the headers transition is currently running.
+     */
+    public boolean isInHeadersTransition() {
+        return mHeadersTransition != null;
+    }
+
+    /**
+     * Returns true if headers are shown.
+     */
+    public boolean isShowingHeaders() {
+        return mShowingHeaders;
+    }
+
+    /**
+     * Sets a listener for browse fragment transitions.
+     *
+     * @param listener The listener to call when a browse headers transition
+     *        begins or ends.
+     */
+    public void setBrowseTransitionListener(BrowseTransitionListener listener) {
+        mBrowseTransitionListener = listener;
+    }
+
+    /**
+     * @deprecated use {@link BrowseFragment#enableMainFragmentScaling(boolean)} instead.
+     *
+     * @param enable true to enable row scaling
+     */
+    @Deprecated
+    public void enableRowScaling(boolean enable) {
+        enableMainFragmentScaling(enable);
+    }
+
+    /**
+     * Enables scaling of main fragment when headers are present. For the page/row fragment,
+     * scaling is enabled only when both this method and
+     * {@link MainFragmentAdapter#isScalingEnabled()} are enabled.
+     *
+     * @param enable true to enable row scaling
+     */
+    public void enableMainFragmentScaling(boolean enable) {
+        mMainFragmentScaleEnabled = enable;
+    }
+
+    void startHeadersTransitionInternal(final boolean withHeaders) {
+        if (getFragmentManager().isDestroyed()) {
+            return;
+        }
+        if (!isHeadersDataReady()) {
+            return;
+        }
+        mShowingHeaders = withHeaders;
+        mMainFragmentAdapter.onTransitionPrepare();
+        mMainFragmentAdapter.onTransitionStart();
+        onExpandTransitionStart(!withHeaders, new Runnable() {
+            @Override
+            public void run() {
+                mHeadersFragment.onTransitionPrepare();
+                mHeadersFragment.onTransitionStart();
+                createHeadersTransition();
+                if (mBrowseTransitionListener != null) {
+                    mBrowseTransitionListener.onHeadersTransitionStart(withHeaders);
+                }
+                TransitionHelper.runTransition(
+                        withHeaders ? mSceneWithHeaders : mSceneWithoutHeaders, mHeadersTransition);
+                if (mHeadersBackStackEnabled) {
+                    if (!withHeaders) {
+                        getFragmentManager().beginTransaction()
+                                .addToBackStack(mWithHeadersBackStackName).commit();
+                    } else {
+                        int index = mBackStackChangedListener.mIndexOfHeadersBackStack;
+                        if (index >= 0) {
+                            BackStackEntry entry = getFragmentManager().getBackStackEntryAt(index);
+                            getFragmentManager().popBackStackImmediate(entry.getId(),
+                                    FragmentManager.POP_BACK_STACK_INCLUSIVE);
+                        }
+                    }
+                }
+            }
+        });
+    }
+
+    boolean isVerticalScrolling() {
+        // don't run transition
+        return mHeadersFragment.isScrolling() || mMainFragmentAdapter.isScrolling();
+    }
+
+
+    private final BrowseFrameLayout.OnFocusSearchListener mOnFocusSearchListener =
+            new BrowseFrameLayout.OnFocusSearchListener() {
+        @Override
+        public View onFocusSearch(View focused, int direction) {
+            // if headers is running transition,  focus stays
+            if (mCanShowHeaders && isInHeadersTransition()) {
+                return focused;
+            }
+            if (DEBUG) Log.v(TAG, "onFocusSearch focused " + focused + " + direction " + direction);
+
+            if (getTitleView() != null && focused != getTitleView()
+                    && direction == View.FOCUS_UP) {
+                return getTitleView();
+            }
+            if (getTitleView() != null && getTitleView().hasFocus()
+                    && direction == View.FOCUS_DOWN) {
+                return mCanShowHeaders && mShowingHeaders
+                        ? mHeadersFragment.getVerticalGridView() : mMainFragment.getView();
+            }
+
+            boolean isRtl = ViewCompat.getLayoutDirection(focused)
+                    == ViewCompat.LAYOUT_DIRECTION_RTL;
+            int towardStart = isRtl ? View.FOCUS_RIGHT : View.FOCUS_LEFT;
+            int towardEnd = isRtl ? View.FOCUS_LEFT : View.FOCUS_RIGHT;
+            if (mCanShowHeaders && direction == towardStart) {
+                if (isVerticalScrolling() || mShowingHeaders || !isHeadersDataReady()) {
+                    return focused;
+                }
+                return mHeadersFragment.getVerticalGridView();
+            } else if (direction == towardEnd) {
+                if (isVerticalScrolling()) {
+                    return focused;
+                } else if (mMainFragment != null && mMainFragment.getView() != null) {
+                    return mMainFragment.getView();
+                }
+                return focused;
+            } else if (direction == View.FOCUS_DOWN && mShowingHeaders) {
+                // disable focus_down moving into PageFragment.
+                return focused;
+            } else {
+                return null;
+            }
+        }
+    };
+
+    final boolean isHeadersDataReady() {
+        return mAdapter != null && mAdapter.size() != 0;
+    }
+
+    private final BrowseFrameLayout.OnChildFocusListener mOnChildFocusListener =
+            new BrowseFrameLayout.OnChildFocusListener() {
+
+        @Override
+        public boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
+            if (getChildFragmentManager().isDestroyed()) {
+                return true;
+            }
+            // Make sure not changing focus when requestFocus() is called.
+            if (mCanShowHeaders && mShowingHeaders) {
+                if (mHeadersFragment != null && mHeadersFragment.getView() != null
+                        && mHeadersFragment.getView().requestFocus(
+                                direction, previouslyFocusedRect)) {
+                    return true;
+                }
+            }
+            if (mMainFragment != null && mMainFragment.getView() != null
+                    && mMainFragment.getView().requestFocus(direction, previouslyFocusedRect)) {
+                return true;
+            }
+            return getTitleView() != null
+                    && getTitleView().requestFocus(direction, previouslyFocusedRect);
+        }
+
+        @Override
+        public void onRequestChildFocus(View child, View focused) {
+            if (getChildFragmentManager().isDestroyed()) {
+                return;
+            }
+            if (!mCanShowHeaders || isInHeadersTransition()) return;
+            int childId = child.getId();
+            if (childId == R.id.browse_container_dock && mShowingHeaders) {
+                startHeadersTransitionInternal(false);
+            } else if (childId == R.id.browse_headers_dock && !mShowingHeaders) {
+                startHeadersTransitionInternal(true);
+            }
+        }
+    };
+
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putInt(CURRENT_SELECTED_POSITION, mSelectedPosition);
+        outState.putBoolean(IS_PAGE_ROW, mIsPageRow);
+
+        if (mBackStackChangedListener != null) {
+            mBackStackChangedListener.save(outState);
+        } else {
+            outState.putBoolean(HEADER_SHOW, mShowingHeaders);
+        }
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        final Context context = FragmentUtil.getContext(BrowseFragment.this);
+        TypedArray ta = context.obtainStyledAttributes(R.styleable.LeanbackTheme);
+        mContainerListMarginStart = (int) ta.getDimension(
+                R.styleable.LeanbackTheme_browseRowsMarginStart, context.getResources()
+                .getDimensionPixelSize(R.dimen.lb_browse_rows_margin_start));
+        mContainerListAlignTop = (int) ta.getDimension(
+                R.styleable.LeanbackTheme_browseRowsMarginTop, context.getResources()
+                .getDimensionPixelSize(R.dimen.lb_browse_rows_margin_top));
+        ta.recycle();
+
+        readArguments(getArguments());
+
+        if (mCanShowHeaders) {
+            if (mHeadersBackStackEnabled) {
+                mWithHeadersBackStackName = LB_HEADERS_BACKSTACK + this;
+                mBackStackChangedListener = new BackStackListener();
+                getFragmentManager().addOnBackStackChangedListener(mBackStackChangedListener);
+                mBackStackChangedListener.load(savedInstanceState);
+            } else {
+                if (savedInstanceState != null) {
+                    mShowingHeaders = savedInstanceState.getBoolean(HEADER_SHOW);
+                }
+            }
+        }
+
+        mScaleFactor = getResources().getFraction(R.fraction.lb_browse_rows_scale, 1, 1);
+    }
+
+    @Override
+    public void onDestroyView() {
+        setMainFragmentRowsAdapter(null);
+        mPageRow = null;
+        mMainFragmentAdapter = null;
+        mMainFragment = null;
+        mHeadersFragment = null;
+        super.onDestroyView();
+    }
+
+    @Override
+    public void onDestroy() {
+        if (mBackStackChangedListener != null) {
+            getFragmentManager().removeOnBackStackChangedListener(mBackStackChangedListener);
+        }
+        super.onDestroy();
+    }
+
+    /**
+     * Creates a new {@link HeadersFragment} instance. Subclass of BrowseFragment may override and
+     * return an instance of subclass of HeadersFragment, e.g. when app wants to replace presenter
+     * to render HeaderItem.
+     *
+     * @return A new instance of {@link HeadersFragment} or its subclass.
+     */
+    public HeadersFragment onCreateHeadersFragment() {
+        return new HeadersFragment();
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+
+        if (getChildFragmentManager().findFragmentById(R.id.scale_frame) == null) {
+            mHeadersFragment = onCreateHeadersFragment();
+
+            createMainFragment(mAdapter, mSelectedPosition);
+            FragmentTransaction ft = getChildFragmentManager().beginTransaction()
+                    .replace(R.id.browse_headers_dock, mHeadersFragment);
+
+            if (mMainFragment != null) {
+                ft.replace(R.id.scale_frame, mMainFragment);
+            } else {
+                // Empty adapter used to guard against lazy adapter loading. When this
+                // fragment is instantiated, mAdapter might not have the data or might not
+                // have been set. In either of those cases mFragmentAdapter will be null.
+                // This way we can maintain the invariant that mMainFragmentAdapter is never
+                // null and it avoids doing null checks all over the code.
+                mMainFragmentAdapter = new MainFragmentAdapter(null);
+                mMainFragmentAdapter.setFragmentHost(new FragmentHostImpl());
+            }
+
+            ft.commit();
+        } else {
+            mHeadersFragment = (HeadersFragment) getChildFragmentManager()
+                    .findFragmentById(R.id.browse_headers_dock);
+            mMainFragment = getChildFragmentManager().findFragmentById(R.id.scale_frame);
+
+            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;
+
+            setMainFragmentAdapter();
+        }
+
+        mHeadersFragment.setHeadersGone(!mCanShowHeaders);
+        if (mHeaderPresenterSelector != null) {
+            mHeadersFragment.setPresenterSelector(mHeaderPresenterSelector);
+        }
+        mHeadersFragment.setAdapter(mAdapter);
+        mHeadersFragment.setOnHeaderViewSelectedListener(mHeaderViewSelectedListener);
+        mHeadersFragment.setOnHeaderClickedListener(mHeaderClickedListener);
+
+        View root = inflater.inflate(R.layout.lb_browse_fragment, container, false);
+
+        getProgressBarManager().setRootView((ViewGroup)root);
+
+        mBrowseFrame = (BrowseFrameLayout) root.findViewById(R.id.browse_frame);
+        mBrowseFrame.setOnChildFocusListener(mOnChildFocusListener);
+        mBrowseFrame.setOnFocusSearchListener(mOnFocusSearchListener);
+
+        installTitleView(inflater, mBrowseFrame, savedInstanceState);
+
+        mScaleFrameLayout = (ScaleFrameLayout) root.findViewById(R.id.scale_frame);
+        mScaleFrameLayout.setPivotX(0);
+        mScaleFrameLayout.setPivotY(mContainerListAlignTop);
+
+        if (mBrandColorSet) {
+            mHeadersFragment.setBackgroundColor(mBrandColor);
+        }
+
+        mSceneWithHeaders = TransitionHelper.createScene(mBrowseFrame, new Runnable() {
+            @Override
+            public void run() {
+                showHeaders(true);
+            }
+        });
+        mSceneWithoutHeaders =  TransitionHelper.createScene(mBrowseFrame, new Runnable() {
+            @Override
+            public void run() {
+                showHeaders(false);
+            }
+        });
+        mSceneAfterEntranceTransition = TransitionHelper.createScene(mBrowseFrame, new Runnable() {
+            @Override
+            public void run() {
+                setEntranceTransitionEndState();
+            }
+        });
+
+        return root;
+    }
+
+    void createHeadersTransition() {
+        mHeadersTransition = TransitionHelper.loadTransition(FragmentUtil.getContext(BrowseFragment.this),
+                mShowingHeaders
+                        ? R.transition.lb_browse_headers_in : R.transition.lb_browse_headers_out);
+
+        TransitionHelper.addTransitionListener(mHeadersTransition, new TransitionListener() {
+            @Override
+            public void onTransitionStart(Object transition) {
+            }
+            @Override
+            public void onTransitionEnd(Object transition) {
+                mHeadersTransition = null;
+                if (mMainFragmentAdapter != null) {
+                    mMainFragmentAdapter.onTransitionEnd();
+                    if (!mShowingHeaders && mMainFragment != null) {
+                        View mainFragmentView = mMainFragment.getView();
+                        if (mainFragmentView != null && !mainFragmentView.hasFocus()) {
+                            mainFragmentView.requestFocus();
+                        }
+                    }
+                }
+                if (mHeadersFragment != null) {
+                    mHeadersFragment.onTransitionEnd();
+                    if (mShowingHeaders) {
+                        VerticalGridView headerGridView = mHeadersFragment.getVerticalGridView();
+                        if (headerGridView != null && !headerGridView.hasFocus()) {
+                            headerGridView.requestFocus();
+                        }
+                    }
+                }
+
+                // Animate TitleView once header animation is complete.
+                updateTitleViewVisibility();
+
+                if (mBrowseTransitionListener != null) {
+                    mBrowseTransitionListener.onHeadersTransitionStop(mShowingHeaders);
+                }
+            }
+        });
+    }
+
+    void updateTitleViewVisibility() {
+        if (!mShowingHeaders) {
+            boolean showTitleView;
+            if (mIsPageRow && mMainFragmentAdapter != null) {
+                // page fragment case:
+                showTitleView = mMainFragmentAdapter.mFragmentHost.mShowTitleView;
+            } else {
+                // regular row view case:
+                showTitleView = isFirstRowWithContent(mSelectedPosition);
+            }
+            if (showTitleView) {
+                showTitle(TitleViewAdapter.FULL_VIEW_VISIBLE);
+            } else {
+                showTitle(false);
+            }
+        } else {
+            // when HeaderFragment is showing,  showBranding and showSearch are slightly different
+            boolean showBranding;
+            boolean showSearch;
+            if (mIsPageRow && mMainFragmentAdapter != null) {
+                showBranding = mMainFragmentAdapter.mFragmentHost.mShowTitleView;
+            } else {
+                showBranding = isFirstRowWithContent(mSelectedPosition);
+            }
+            showSearch = isFirstRowWithContentOrPageRow(mSelectedPosition);
+            int flags = 0;
+            if (showBranding) flags |= TitleViewAdapter.BRANDING_VIEW_VISIBLE;
+            if (showSearch) flags |= TitleViewAdapter.SEARCH_VIEW_VISIBLE;
+            if (flags != 0) {
+                showTitle(flags);
+            } else {
+                showTitle(false);
+            }
+        }
+    }
+
+    boolean isFirstRowWithContentOrPageRow(int rowPosition) {
+        if (mAdapter == null || mAdapter.size() == 0) {
+            return true;
+        }
+        for (int i = 0; i < mAdapter.size(); i++) {
+            final Row row = (Row) mAdapter.get(i);
+            if (row.isRenderedAsRowView() || row instanceof PageRow) {
+                return rowPosition == i;
+            }
+        }
+        return true;
+    }
+
+    boolean isFirstRowWithContent(int rowPosition) {
+        if (mAdapter == null || mAdapter.size() == 0) {
+            return true;
+        }
+        for (int i = 0; i < mAdapter.size(); i++) {
+            final Row row = (Row) mAdapter.get(i);
+            if (row.isRenderedAsRowView()) {
+                return rowPosition == i;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Sets the {@link PresenterSelector} used to render the row headers.
+     *
+     * @param headerPresenterSelector The PresenterSelector that will determine
+     *        the Presenter for each row header.
+     */
+    public void setHeaderPresenterSelector(PresenterSelector headerPresenterSelector) {
+        mHeaderPresenterSelector = headerPresenterSelector;
+        if (mHeadersFragment != null) {
+            mHeadersFragment.setPresenterSelector(mHeaderPresenterSelector);
+        }
+    }
+
+    private void setHeadersOnScreen(boolean onScreen) {
+        MarginLayoutParams lp;
+        View containerList;
+        containerList = mHeadersFragment.getView();
+        lp = (MarginLayoutParams) containerList.getLayoutParams();
+        lp.setMarginStart(onScreen ? 0 : -mContainerListMarginStart);
+        containerList.setLayoutParams(lp);
+    }
+
+    void showHeaders(boolean show) {
+        if (DEBUG) Log.v(TAG, "showHeaders " + show);
+        mHeadersFragment.setHeadersEnabled(show);
+        setHeadersOnScreen(show);
+        expandMainFragment(!show);
+    }
+
+    private void expandMainFragment(boolean expand) {
+        MarginLayoutParams params = (MarginLayoutParams) mScaleFrameLayout.getLayoutParams();
+        params.setMarginStart(!expand ? mContainerListMarginStart : 0);
+        mScaleFrameLayout.setLayoutParams(params);
+        mMainFragmentAdapter.setExpand(expand);
+
+        setMainFragmentAlignment();
+        final float scaleFactor = !expand
+                && mMainFragmentScaleEnabled
+                && mMainFragmentAdapter.isScalingEnabled() ? mScaleFactor : 1;
+        mScaleFrameLayout.setLayoutScaleY(scaleFactor);
+        mScaleFrameLayout.setChildScale(scaleFactor);
+    }
+
+    private HeadersFragment.OnHeaderClickedListener mHeaderClickedListener =
+        new HeadersFragment.OnHeaderClickedListener() {
+            @Override
+            public void onHeaderClicked(RowHeaderPresenter.ViewHolder viewHolder, Row row) {
+                if (!mCanShowHeaders || !mShowingHeaders || isInHeadersTransition()) {
+                    return;
+                }
+                startHeadersTransitionInternal(false);
+                mMainFragment.getView().requestFocus();
+            }
+        };
+
+    class MainFragmentItemViewSelectedListener implements OnItemViewSelectedListener {
+        MainFragmentRowsAdapter mMainFragmentRowsAdapter;
+
+        public MainFragmentItemViewSelectedListener(MainFragmentRowsAdapter fragmentRowsAdapter) {
+            mMainFragmentRowsAdapter = fragmentRowsAdapter;
+        }
+
+        @Override
+        public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
+                RowPresenter.ViewHolder rowViewHolder, Row row) {
+            int position = mMainFragmentRowsAdapter.getSelectedPosition();
+            if (DEBUG) Log.v(TAG, "row selected position " + position);
+            onRowSelected(position);
+            if (mExternalOnItemViewSelectedListener != null) {
+                mExternalOnItemViewSelectedListener.onItemSelected(itemViewHolder, item,
+                        rowViewHolder, row);
+            }
+        }
+    };
+
+    private HeadersFragment.OnHeaderViewSelectedListener mHeaderViewSelectedListener =
+            new HeadersFragment.OnHeaderViewSelectedListener() {
+        @Override
+        public void onHeaderSelected(RowHeaderPresenter.ViewHolder viewHolder, Row row) {
+            int position = mHeadersFragment.getSelectedPosition();
+            if (DEBUG) Log.v(TAG, "header selected position " + position);
+            onRowSelected(position);
+        }
+    };
+
+    void onRowSelected(int position) {
+        // 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) {
+        if (position == NO_POSITION) {
+            return;
+        }
+
+        mSelectedPosition = position;
+        if (mHeadersFragment == null || mMainFragmentAdapter == null) {
+            // onDestroyView() called
+            return;
+        }
+        mHeadersFragment.setSelectedPosition(position, smooth);
+        replaceMainFragment(position);
+
+        if (mMainFragmentRowsAdapter != null) {
+            mMainFragmentRowsAdapter.setSelectedPosition(position, smooth);
+        }
+
+        updateTitleViewVisibility();
+    }
+
+    private void replaceMainFragment(int position) {
+        if (createMainFragment(mAdapter, position)) {
+            swapToMainFragment();
+            expandMainFragment(!(mCanShowHeaders && mShowingHeaders));
+        }
+    }
+
+    private void swapToMainFragment() {
+        final VerticalGridView gridView = mHeadersFragment.getVerticalGridView();
+        if (isShowingHeaders() && gridView != null
+                && gridView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) {
+            // if user is scrolling HeadersFragment,  swap to empty fragment and wait scrolling
+            // finishes.
+            getChildFragmentManager().beginTransaction()
+                    .replace(R.id.scale_frame, new Fragment()).commit();
+            gridView.addOnScrollListener(new RecyclerView.OnScrollListener() {
+                @SuppressWarnings("ReferenceEquality")
+                @Override
+                public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
+                    if (newState == RecyclerView.SCROLL_STATE_IDLE) {
+                        gridView.removeOnScrollListener(this);
+                        FragmentManager fm = getChildFragmentManager();
+                        Fragment currentFragment = fm.findFragmentById(R.id.scale_frame);
+                        if (currentFragment != mMainFragment) {
+                            fm.beginTransaction().replace(R.id.scale_frame, mMainFragment).commit();
+                        }
+                    }
+                }
+            });
+        } else {
+            // Otherwise swap immediately
+            getChildFragmentManager().beginTransaction()
+                    .replace(R.id.scale_frame, mMainFragment).commit();
+        }
+    }
+
+    /**
+     * Sets the selected row position with smooth animation.
+     */
+    public void setSelectedPosition(int position) {
+        setSelectedPosition(position, true);
+    }
+
+    /**
+     * Gets position of currently selected row.
+     * @return Position of currently selected row.
+     */
+    public int getSelectedPosition() {
+        return mSelectedPosition;
+    }
+
+    /**
+     * @return selected row ViewHolder inside fragment created by {@link MainFragmentRowsAdapter}.
+     */
+    public RowPresenter.ViewHolder getSelectedRowViewHolder() {
+        if (mMainFragmentRowsAdapter != null) {
+            int rowPos = mMainFragmentRowsAdapter.getSelectedPosition();
+            return mMainFragmentRowsAdapter.findRowViewHolderByPosition(rowPos);
+        }
+        return null;
+    }
+
+    /**
+     * Sets the selected row position.
+     */
+    public void setSelectedPosition(int position, boolean smooth) {
+        mSetSelectionRunnable.post(
+                position, SetSelectionRunnable.TYPE_USER_REQUEST, smooth);
+    }
+
+    /**
+     * Selects a Row and perform an optional task on the Row. For example
+     * <code>setSelectedPosition(10, true, new ListRowPresenterSelectItemViewHolderTask(5))</code>
+     * scrolls to 11th row and selects 6th item on that row.  The method will be ignored if
+     * RowsFragment has not been created (i.e. before {@link #onCreateView(LayoutInflater,
+     * ViewGroup, Bundle)}).
+     *
+     * @param rowPosition Which row to select.
+     * @param smooth True to scroll to the row, false for no animation.
+     * @param rowHolderTask Optional task to perform on the Row.  When the task is not null, headers
+     * fragment will be collapsed.
+     */
+    public void setSelectedPosition(int rowPosition, boolean smooth,
+            final Presenter.ViewHolderTask rowHolderTask) {
+        if (mMainFragmentAdapterRegistry == null) {
+            return;
+        }
+        if (rowHolderTask != null) {
+            startHeadersTransition(false);
+        }
+        if (mMainFragmentRowsAdapter != null) {
+            mMainFragmentRowsAdapter.setSelectedPosition(rowPosition, smooth, rowHolderTask);
+        }
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        mHeadersFragment.setAlignment(mContainerListAlignTop);
+        setMainFragmentAlignment();
+
+        if (mCanShowHeaders && mShowingHeaders && mHeadersFragment != null
+                && mHeadersFragment.getView() != null) {
+            mHeadersFragment.getView().requestFocus();
+        } else if ((!mCanShowHeaders || !mShowingHeaders) && mMainFragment != null
+                && mMainFragment.getView() != null) {
+            mMainFragment.getView().requestFocus();
+        }
+
+        if (mCanShowHeaders) {
+            showHeaders(mShowingHeaders);
+        }
+
+        mStateMachine.fireEvent(EVT_HEADER_VIEW_CREATED);
+    }
+
+    private void onExpandTransitionStart(boolean expand, final Runnable callback) {
+        if (expand) {
+            callback.run();
+            return;
+        }
+        // Run a "pre" layout when we go non-expand, in order to get the initial
+        // positions of added rows.
+        new ExpandPreLayout(callback, mMainFragmentAdapter, getView()).execute();
+    }
+
+    private void setMainFragmentAlignment() {
+        int alignOffset = mContainerListAlignTop;
+        if (mMainFragmentScaleEnabled
+                && mMainFragmentAdapter.isScalingEnabled()
+                && mShowingHeaders) {
+            alignOffset = (int) (alignOffset / mScaleFactor + 0.5f);
+        }
+        mMainFragmentAdapter.setAlignment(alignOffset);
+    }
+
+    /**
+     * Enables/disables headers transition on back key support. This is enabled by
+     * default. The BrowseFragment will add a back stack entry when headers are
+     * showing. Running a headers transition when the back key is pressed only
+     * works when the headers state is {@link #HEADERS_ENABLED} or
+     * {@link #HEADERS_HIDDEN}.
+     * <p>
+     * NOTE: If an Activity has its own onBackPressed() handling, you must
+     * disable this feature. You may use {@link #startHeadersTransition(boolean)}
+     * and {@link BrowseTransitionListener} in your own back stack handling.
+     */
+    public final void setHeadersTransitionOnBackEnabled(boolean headersBackStackEnabled) {
+        mHeadersBackStackEnabled = headersBackStackEnabled;
+    }
+
+    /**
+     * Returns true if headers transition on back key support is enabled.
+     */
+    public final boolean isHeadersTransitionOnBackEnabled() {
+        return mHeadersBackStackEnabled;
+    }
+
+    private void readArguments(Bundle args) {
+        if (args == null) {
+            return;
+        }
+        if (args.containsKey(ARG_TITLE)) {
+            setTitle(args.getString(ARG_TITLE));
+        }
+        if (args.containsKey(ARG_HEADERS_STATE)) {
+            setHeadersState(args.getInt(ARG_HEADERS_STATE));
+        }
+    }
+
+    /**
+     * Sets the state for the headers column in the browse fragment. Must be one
+     * of {@link #HEADERS_ENABLED}, {@link #HEADERS_HIDDEN}, or
+     * {@link #HEADERS_DISABLED}.
+     *
+     * @param headersState The state of the headers for the browse fragment.
+     */
+    public void setHeadersState(int headersState) {
+        if (headersState < HEADERS_ENABLED || headersState > HEADERS_DISABLED) {
+            throw new IllegalArgumentException("Invalid headers state: " + headersState);
+        }
+        if (DEBUG) Log.v(TAG, "setHeadersState " + headersState);
+
+        if (headersState != mHeadersState) {
+            mHeadersState = headersState;
+            switch (headersState) {
+                case HEADERS_ENABLED:
+                    mCanShowHeaders = true;
+                    mShowingHeaders = true;
+                    break;
+                case HEADERS_HIDDEN:
+                    mCanShowHeaders = true;
+                    mShowingHeaders = false;
+                    break;
+                case HEADERS_DISABLED:
+                    mCanShowHeaders = false;
+                    mShowingHeaders = false;
+                    break;
+                default:
+                    Log.w(TAG, "Unknown headers state: " + headersState);
+                    break;
+            }
+            if (mHeadersFragment != null) {
+                mHeadersFragment.setHeadersGone(!mCanShowHeaders);
+            }
+        }
+    }
+
+    /**
+     * Returns the state of the headers column in the browse fragment.
+     */
+    public int getHeadersState() {
+        return mHeadersState;
+    }
+
+    @Override
+    protected Object createEntranceTransition() {
+        return TransitionHelper.loadTransition(FragmentUtil.getContext(BrowseFragment.this),
+                R.transition.lb_browse_entrance_transition);
+    }
+
+    @Override
+    protected void runEntranceTransition(Object entranceTransition) {
+        TransitionHelper.runTransition(mSceneAfterEntranceTransition, entranceTransition);
+    }
+
+    @Override
+    protected void onEntranceTransitionPrepare() {
+        mHeadersFragment.onTransitionPrepare();
+        mMainFragmentAdapter.setEntranceTransitionState(false);
+        mMainFragmentAdapter.onTransitionPrepare();
+    }
+
+    @Override
+    protected void onEntranceTransitionStart() {
+        mHeadersFragment.onTransitionStart();
+        mMainFragmentAdapter.onTransitionStart();
+    }
+
+    @Override
+    protected void onEntranceTransitionEnd() {
+        if (mMainFragmentAdapter != null) {
+            mMainFragmentAdapter.onTransitionEnd();
+        }
+
+        if (mHeadersFragment != null) {
+            mHeadersFragment.onTransitionEnd();
+        }
+    }
+
+    void setSearchOrbViewOnScreen(boolean onScreen) {
+        View searchOrbView = getTitleViewAdapter().getSearchAffordanceView();
+        if (searchOrbView != null) {
+            MarginLayoutParams lp = (MarginLayoutParams) searchOrbView.getLayoutParams();
+            lp.setMarginStart(onScreen ? 0 : -mContainerListMarginStart);
+            searchOrbView.setLayoutParams(lp);
+        }
+    }
+
+    void setEntranceTransitionStartState() {
+        setHeadersOnScreen(false);
+        setSearchOrbViewOnScreen(false);
+        // NOTE that mMainFragmentAdapter.setEntranceTransitionState(false) will be called
+        // in onEntranceTransitionPrepare() because mMainFragmentAdapter is still the dummy
+        // one when setEntranceTransitionStartState() is called.
+    }
+
+    void setEntranceTransitionEndState() {
+        setHeadersOnScreen(mShowingHeaders);
+        setSearchOrbViewOnScreen(true);
+        mMainFragmentAdapter.setEntranceTransitionState(true);
+    }
+
+    private class ExpandPreLayout implements ViewTreeObserver.OnPreDrawListener {
+
+        private final View mView;
+        private final Runnable mCallback;
+        private int mState;
+        private MainFragmentAdapter mainFragmentAdapter;
+
+        final static int STATE_INIT = 0;
+        final static int STATE_FIRST_DRAW = 1;
+        final static int STATE_SECOND_DRAW = 2;
+
+        ExpandPreLayout(Runnable callback, MainFragmentAdapter adapter, View view) {
+            mView = view;
+            mCallback = callback;
+            mainFragmentAdapter = adapter;
+        }
+
+        void execute() {
+            mView.getViewTreeObserver().addOnPreDrawListener(this);
+            mainFragmentAdapter.setExpand(false);
+            // always trigger onPreDraw even adapter setExpand() does nothing.
+            mView.invalidate();
+            mState = STATE_INIT;
+        }
+
+        @Override
+        public boolean onPreDraw() {
+            if (getView() == null || FragmentUtil.getContext(BrowseFragment.this) == null) {
+                mView.getViewTreeObserver().removeOnPreDrawListener(this);
+                return true;
+            }
+            if (mState == STATE_INIT) {
+                mainFragmentAdapter.setExpand(true);
+                // always trigger onPreDraw even adapter setExpand() does nothing.
+                mView.invalidate();
+                mState = STATE_FIRST_DRAW;
+            } else if (mState == STATE_FIRST_DRAW) {
+                mCallback.run();
+                mView.getViewTreeObserver().removeOnPreDrawListener(this);
+                mState = STATE_SECOND_DRAW;
+            }
+            return false;
+        }
+    }
+}
diff --git a/leanback/src/android/support/v17/leanback/app/BrowseSupportFragment.java b/leanback/src/android/support/v17/leanback/app/BrowseSupportFragment.java
new file mode 100644
index 0000000..c28064c
--- /dev/null
+++ b/leanback/src/android/support/v17/leanback/app/BrowseSupportFragment.java
@@ -0,0 +1,1845 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import static android.support.v7.widget.RecyclerView.NO_POSITION;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.support.annotation.ColorInt;
+import android.support.v17.leanback.R;
+import android.support.v17.leanback.transition.TransitionHelper;
+import android.support.v17.leanback.transition.TransitionListener;
+import android.support.v17.leanback.util.StateMachine.Event;
+import android.support.v17.leanback.util.StateMachine.State;
+import android.support.v17.leanback.widget.BrowseFrameLayout;
+import android.support.v17.leanback.widget.InvisibleRowPresenter;
+import android.support.v17.leanback.widget.ListRow;
+import android.support.v17.leanback.widget.ObjectAdapter;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.OnItemViewSelectedListener;
+import android.support.v17.leanback.widget.PageRow;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.PresenterSelector;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.RowHeaderPresenter;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.support.v17.leanback.widget.ScaleFrameLayout;
+import android.support.v17.leanback.widget.TitleViewAdapter;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentManager.BackStackEntry;
+import android.support.v4.app.FragmentTransaction;
+import android.support.v4.view.ViewCompat;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.MarginLayoutParams;
+import android.view.ViewTreeObserver;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A fragment for creating Leanback browse screens. It is composed of a
+ * RowsSupportFragment and a HeadersSupportFragment.
+ * <p>
+ * A BrowseSupportFragment renders the elements of its {@link ObjectAdapter} as a set
+ * of rows in a vertical list. The elements in this adapter must be subclasses
+ * of {@link Row}.
+ * <p>
+ * The HeadersSupportFragment can be set to be either shown or hidden by default, or
+ * may be disabled entirely. See {@link #setHeadersState} for details.
+ * <p>
+ * By default the BrowseSupportFragment includes support for returning to the headers
+ * when the user presses Back. For Activities that customize {@link
+ * android.support.v4.app.FragmentActivity#onBackPressed()}, you must disable this default Back key support by
+ * calling {@link #setHeadersTransitionOnBackEnabled(boolean)} with false and
+ * use {@link BrowseSupportFragment.BrowseTransitionListener} and
+ * {@link #startHeadersTransition(boolean)}.
+ * <p>
+ * The recommended theme to use with a BrowseSupportFragment is
+ * {@link android.support.v17.leanback.R.style#Theme_Leanback_Browse}.
+ * </p>
+ */
+public class BrowseSupportFragment extends BaseSupportFragment {
+
+    // BUNDLE attribute for saving header show/hide status when backstack is used:
+    static final String HEADER_STACK_INDEX = "headerStackIndex";
+    // BUNDLE attribute for saving header show/hide status when backstack is not used:
+    static final String HEADER_SHOW = "headerShow";
+    private static final String IS_PAGE_ROW = "isPageRow";
+    private static final String CURRENT_SELECTED_POSITION = "currentSelectedPosition";
+
+    /**
+     * State to hide headers fragment.
+     */
+    final State STATE_SET_ENTRANCE_START_STATE = new State("SET_ENTRANCE_START_STATE") {
+        @Override
+        public void run() {
+            setEntranceTransitionStartState();
+        }
+    };
+
+    /**
+     * Event for Header fragment view is created, we could perform
+     * {@link #setEntranceTransitionStartState()} to hide headers fragment initially.
+     */
+    final Event EVT_HEADER_VIEW_CREATED = new Event("headerFragmentViewCreated");
+
+    /**
+     * Event for {@link #getMainFragment()} view is created, it's additional requirement to execute
+     * {@link #onEntranceTransitionPrepare()}.
+     */
+    final Event EVT_MAIN_FRAGMENT_VIEW_CREATED = new Event("mainFragmentViewCreated");
+
+    /**
+     * Event that data for the screen is ready, this is additional requirement to launch entrance
+     * transition.
+     */
+    final Event EVT_SCREEN_DATA_READY = new Event("screenDataReady");
+
+    @Override
+    void createStateMachineStates() {
+        super.createStateMachineStates();
+        mStateMachine.addState(STATE_SET_ENTRANCE_START_STATE);
+    }
+
+    @Override
+    void createStateMachineTransitions() {
+        super.createStateMachineTransitions();
+        // when headers fragment view is created we could setEntranceTransitionStartState()
+        mStateMachine.addTransition(STATE_ENTRANCE_ON_PREPARED, STATE_SET_ENTRANCE_START_STATE,
+                EVT_HEADER_VIEW_CREATED);
+
+        // add additional requirement for onEntranceTransitionPrepare()
+        mStateMachine.addTransition(STATE_ENTRANCE_ON_PREPARED,
+                STATE_ENTRANCE_ON_PREPARED_ON_CREATEVIEW,
+                EVT_MAIN_FRAGMENT_VIEW_CREATED);
+        // add additional requirement to launch entrance transition.
+        mStateMachine.addTransition(STATE_ENTRANCE_ON_PREPARED,  STATE_ENTRANCE_PERFORM,
+                EVT_SCREEN_DATA_READY);
+    }
+
+    final class BackStackListener implements FragmentManager.OnBackStackChangedListener {
+        int mLastEntryCount;
+        int mIndexOfHeadersBackStack;
+
+        BackStackListener() {
+            mLastEntryCount = getFragmentManager().getBackStackEntryCount();
+            mIndexOfHeadersBackStack = -1;
+        }
+
+        void load(Bundle savedInstanceState) {
+            if (savedInstanceState != null) {
+                mIndexOfHeadersBackStack = savedInstanceState.getInt(HEADER_STACK_INDEX, -1);
+                mShowingHeaders = mIndexOfHeadersBackStack == -1;
+            } else {
+                if (!mShowingHeaders) {
+                    getFragmentManager().beginTransaction()
+                            .addToBackStack(mWithHeadersBackStackName).commit();
+                }
+            }
+        }
+
+        void save(Bundle outState) {
+            outState.putInt(HEADER_STACK_INDEX, mIndexOfHeadersBackStack);
+        }
+
+
+        @Override
+        public void onBackStackChanged() {
+            if (getFragmentManager() == null) {
+                Log.w(TAG, "getFragmentManager() is null, stack:", new Exception());
+                return;
+            }
+            int count = getFragmentManager().getBackStackEntryCount();
+            // if backstack is growing and last pushed entry is "headers" backstack,
+            // remember the index of the entry.
+            if (count > mLastEntryCount) {
+                BackStackEntry entry = getFragmentManager().getBackStackEntryAt(count - 1);
+                if (mWithHeadersBackStackName.equals(entry.getName())) {
+                    mIndexOfHeadersBackStack = count - 1;
+                }
+            } else if (count < mLastEntryCount) {
+                // if popped "headers" backstack, initiate the show header transition if needed
+                if (mIndexOfHeadersBackStack >= count) {
+                    if (!isHeadersDataReady()) {
+                        // if main fragment was restored first before BrowseSupportFragment's adapter gets
+                        // restored: don't start header transition, but add the entry back.
+                        getFragmentManager().beginTransaction()
+                                .addToBackStack(mWithHeadersBackStackName).commit();
+                        return;
+                    }
+                    mIndexOfHeadersBackStack = -1;
+                    if (!mShowingHeaders) {
+                        startHeadersTransitionInternal(true);
+                    }
+                }
+            }
+            mLastEntryCount = count;
+        }
+    }
+
+    /**
+     * Listener for transitions between browse headers and rows.
+     */
+    public static class BrowseTransitionListener {
+        /**
+         * Callback when headers transition starts.
+         *
+         * @param withHeaders True if the transition will result in headers
+         *        being shown, false otherwise.
+         */
+        public void onHeadersTransitionStart(boolean withHeaders) {
+        }
+        /**
+         * Callback when headers transition stops.
+         *
+         * @param withHeaders True if the transition will result in headers
+         *        being shown, false otherwise.
+         */
+        public void onHeadersTransitionStop(boolean withHeaders) {
+        }
+    }
+
+    private class SetSelectionRunnable implements Runnable {
+        static final int TYPE_INVALID = -1;
+        static final int TYPE_INTERNAL_SYNC = 0;
+        static final int TYPE_USER_REQUEST = 1;
+
+        private int mPosition;
+        private int mType;
+        private boolean mSmooth;
+
+        SetSelectionRunnable() {
+            reset();
+        }
+
+        void post(int position, int type, boolean smooth) {
+            // Posting the set selection, rather than calling it immediately, prevents an issue
+            // with adapter changes.  Example: a row is added before the current selected row;
+            // first the fast lane view updates its selection, then the rows fragment has that
+            // new selection propagated immediately; THEN the rows view processes the same adapter
+            // change and moves the selection again.
+            if (type >= mType) {
+                mPosition = position;
+                mType = type;
+                mSmooth = smooth;
+                mBrowseFrame.removeCallbacks(this);
+                mBrowseFrame.post(this);
+            }
+        }
+
+        @Override
+        public void run() {
+            setSelection(mPosition, mSmooth);
+            reset();
+        }
+
+        private void reset() {
+            mPosition = -1;
+            mType = TYPE_INVALID;
+            mSmooth = false;
+        }
+    }
+
+    /**
+     * Possible set of actions that {@link BrowseSupportFragment} exposes to clients. Custom
+     * fragments can interact with {@link BrowseSupportFragment} using this interface.
+     */
+    public interface FragmentHost {
+        /**
+         * Fragments are required to invoke this callback once their view is created
+         * inside {@link Fragment#onViewCreated} method. {@link BrowseSupportFragment} starts the entrance
+         * animation only after receiving this callback. Failure to invoke this method
+         * will lead to fragment not showing up.
+         *
+         * @param fragmentAdapter {@link MainFragmentAdapter} used by the current fragment.
+         */
+        void notifyViewCreated(MainFragmentAdapter fragmentAdapter);
+
+        /**
+         * Fragments mapped to {@link PageRow} are required to invoke this callback once their data
+         * is created for transition, the entrance animation only after receiving this callback.
+         * Failure to invoke this method will lead to fragment not showing up.
+         *
+         * @param fragmentAdapter {@link MainFragmentAdapter} used by the current fragment.
+         */
+        void notifyDataReady(MainFragmentAdapter fragmentAdapter);
+
+        /**
+         * Show or hide title view in {@link BrowseSupportFragment} for fragments mapped to
+         * {@link PageRow}.  Otherwise the request is ignored, in that case BrowseSupportFragment is fully
+         * in control of showing/hiding title view.
+         * <p>
+         * When HeadersSupportFragment is visible, BrowseSupportFragment will hide search affordance view if
+         * there are other focusable rows above currently focused row.
+         *
+         * @param show Boolean indicating whether or not to show the title view.
+         */
+        void showTitleView(boolean show);
+    }
+
+    /**
+     * Default implementation of {@link FragmentHost} that is used only by
+     * {@link BrowseSupportFragment}.
+     */
+    private final class FragmentHostImpl implements FragmentHost {
+        boolean mShowTitleView = true;
+
+        FragmentHostImpl() {
+        }
+
+        @Override
+        public void notifyViewCreated(MainFragmentAdapter fragmentAdapter) {
+            mStateMachine.fireEvent(EVT_MAIN_FRAGMENT_VIEW_CREATED);
+            if (!mIsPageRow) {
+                // If it's not a PageRow: it's a ListRow, so we already have data ready.
+                mStateMachine.fireEvent(EVT_SCREEN_DATA_READY);
+            }
+        }
+
+        @Override
+        public void notifyDataReady(MainFragmentAdapter fragmentAdapter) {
+            // If fragment host is not the currently active fragment (in BrowseSupportFragment), then
+            // ignore the request.
+            if (mMainFragmentAdapter == null || mMainFragmentAdapter.getFragmentHost() != this) {
+                return;
+            }
+
+            // We only honor showTitle request for PageRows.
+            if (!mIsPageRow) {
+                return;
+            }
+
+            mStateMachine.fireEvent(EVT_SCREEN_DATA_READY);
+        }
+
+        @Override
+        public void showTitleView(boolean show) {
+            mShowTitleView = show;
+
+            // If fragment host is not the currently active fragment (in BrowseSupportFragment), then
+            // ignore the request.
+            if (mMainFragmentAdapter == null || mMainFragmentAdapter.getFragmentHost() != this) {
+                return;
+            }
+
+            // We only honor showTitle request for PageRows.
+            if (!mIsPageRow) {
+                return;
+            }
+
+            updateTitleViewVisibility();
+        }
+    }
+
+    /**
+     * Interface that defines the interaction between {@link BrowseSupportFragment} and its main
+     * content fragment. The key method is {@link MainFragmentAdapter#getFragment()},
+     * it will be used to get the fragment to be shown in the content section. Clients can
+     * provide any implementation of fragment and customize its interaction with
+     * {@link BrowseSupportFragment} by overriding the necessary methods.
+     *
+     * <p>
+     * Clients are expected to provide
+     * an instance of {@link MainFragmentAdapterRegistry} which will be responsible for providing
+     * implementations of {@link MainFragmentAdapter} for given content types. Currently
+     * we support different types of content - {@link ListRow}, {@link PageRow} or any subtype
+     * of {@link Row}. We provide an out of the box adapter implementation for any rows other than
+     * {@link PageRow} - {@link android.support.v17.leanback.app.RowsSupportFragment.MainFragmentAdapter}.
+     *
+     * <p>
+     * {@link PageRow} is intended to give full flexibility to developers in terms of Fragment
+     * design. Users will have to provide an implementation of {@link MainFragmentAdapter}
+     * and provide that through {@link MainFragmentAdapterRegistry}.
+     * {@link MainFragmentAdapter} implementation can supply any fragment and override
+     * just those interactions that makes sense.
+     */
+    public static class MainFragmentAdapter<T extends Fragment> {
+        private boolean mScalingEnabled;
+        private final T mFragment;
+        FragmentHostImpl mFragmentHost;
+
+        public MainFragmentAdapter(T fragment) {
+            this.mFragment = fragment;
+        }
+
+        public final T getFragment() {
+            return mFragment;
+        }
+
+        /**
+         * Returns whether its scrolling.
+         */
+        public boolean isScrolling() {
+            return false;
+        }
+
+        /**
+         * Set the visibility of titles/hover card of browse rows.
+         */
+        public void setExpand(boolean expand) {
+        }
+
+        /**
+         * For rows that willing to participate entrance transition,  this function
+         * hide views if afterTransition is true,  show views if afterTransition is false.
+         */
+        public void setEntranceTransitionState(boolean state) {
+        }
+
+        /**
+         * Sets the window alignment and also the pivots for scale operation.
+         */
+        public void setAlignment(int windowAlignOffsetFromTop) {
+        }
+
+        /**
+         * Callback indicating transition prepare start.
+         */
+        public boolean onTransitionPrepare() {
+            return false;
+        }
+
+        /**
+         * Callback indicating transition start.
+         */
+        public void onTransitionStart() {
+        }
+
+        /**
+         * Callback indicating transition end.
+         */
+        public void onTransitionEnd() {
+        }
+
+        /**
+         * Returns whether row scaling is enabled.
+         */
+        public boolean isScalingEnabled() {
+            return mScalingEnabled;
+        }
+
+        /**
+         * Sets the row scaling property.
+         */
+        public void setScalingEnabled(boolean scalingEnabled) {
+            this.mScalingEnabled = scalingEnabled;
+        }
+
+        /**
+         * Returns the current host interface so that main fragment can interact with
+         * {@link BrowseSupportFragment}.
+         */
+        public final FragmentHost getFragmentHost() {
+            return mFragmentHost;
+        }
+
+        void setFragmentHost(FragmentHostImpl fragmentHost) {
+            this.mFragmentHost = fragmentHost;
+        }
+    }
+
+    /**
+     * Interface to be implemented by all fragments for providing an instance of
+     * {@link MainFragmentAdapter}. Both {@link RowsSupportFragment} and custom fragment provided
+     * against {@link PageRow} will need to implement this interface.
+     */
+    public interface MainFragmentAdapterProvider {
+        /**
+         * Returns an instance of {@link MainFragmentAdapter} that {@link BrowseSupportFragment}
+         * would use to communicate with the target fragment.
+         */
+        MainFragmentAdapter getMainFragmentAdapter();
+    }
+
+    /**
+     * Interface to be implemented by {@link RowsSupportFragment} and its subclasses for providing
+     * an instance of {@link MainFragmentRowsAdapter}.
+     */
+    public interface MainFragmentRowsAdapterProvider {
+        /**
+         * Returns an instance of {@link MainFragmentRowsAdapter} that {@link BrowseSupportFragment}
+         * would use to communicate with the target fragment.
+         */
+        MainFragmentRowsAdapter getMainFragmentRowsAdapter();
+    }
+
+    /**
+     * This is used to pass information to {@link RowsSupportFragment} or its subclasses.
+     * {@link BrowseSupportFragment} uses this interface to pass row based interaction events to
+     * the target fragment.
+     */
+    public static class MainFragmentRowsAdapter<T extends Fragment> {
+        private final T mFragment;
+
+        public MainFragmentRowsAdapter(T fragment) {
+            if (fragment == null) {
+                throw new IllegalArgumentException("Fragment can't be null");
+            }
+            this.mFragment = fragment;
+        }
+
+        public final T getFragment() {
+            return mFragment;
+        }
+        /**
+         * Set the visibility titles/hover of browse rows.
+         */
+        public void setAdapter(ObjectAdapter adapter) {
+        }
+
+        /**
+         * Sets an item clicked listener on the fragment.
+         */
+        public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
+        }
+
+        /**
+         * Sets an item selection listener.
+         */
+        public void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
+        }
+
+        /**
+         * Selects a Row and perform an optional task on the Row.
+         */
+        public void setSelectedPosition(int rowPosition,
+                                        boolean smooth,
+                                        final Presenter.ViewHolderTask rowHolderTask) {
+        }
+
+        /**
+         * Selects a Row.
+         */
+        public void setSelectedPosition(int rowPosition, boolean smooth) {
+        }
+
+        /**
+         * @return The position of selected row.
+         */
+        public int getSelectedPosition() {
+            return 0;
+        }
+
+        /**
+         * @param position Position of Row.
+         * @return Row ViewHolder.
+         */
+        public RowPresenter.ViewHolder findRowViewHolderByPosition(int position) {
+            return null;
+        }
+    }
+
+    private boolean createMainFragment(ObjectAdapter adapter, int position) {
+        Object item = null;
+        if (!mCanShowHeaders) {
+            // when header is disabled, we can decide to use RowsSupportFragment even no data.
+        } else if (adapter == null || adapter.size() == 0) {
+            return false;
+        } else {
+            if (position < 0) {
+                position = 0;
+            } else if (position >= adapter.size()) {
+                throw new IllegalArgumentException(
+                        String.format("Invalid position %d requested", position));
+            }
+            item = adapter.get(position);
+        }
+
+        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) {
+                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;
+            }
+        }
+
+        if (swap) {
+            mMainFragment = mMainFragmentAdapterRegistry.createFragment(item);
+            if (!(mMainFragment instanceof MainFragmentAdapterProvider)) {
+                throw new IllegalArgumentException(
+                        "Fragment must implement MainFragmentAdapterProvider");
+            }
+
+            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}
+     * can return any fragment class.
+     */
+    public abstract static class FragmentFactory<T extends Fragment> {
+        public abstract T createFragment(Object row);
+    }
+
+    /**
+     * FragmentFactory implementation for {@link ListRow}.
+     */
+    public static class ListRowFragmentFactory extends FragmentFactory<RowsSupportFragment> {
+        @Override
+        public RowsSupportFragment createFragment(Object row) {
+            return new RowsSupportFragment();
+        }
+    }
+
+    /**
+     * Registry class maintaining the mapping of {@link Row} subclasses to {@link FragmentFactory}.
+     * BrowseRowFragment automatically registers {@link ListRowFragmentFactory} for
+     * handling {@link ListRow}. Developers can override that and also if they want to
+     * use custom fragment, they can register a custom {@link FragmentFactory}
+     * against {@link PageRow}.
+     */
+    public final static class MainFragmentAdapterRegistry {
+        private final Map<Class, FragmentFactory> mItemToFragmentFactoryMapping = new HashMap<>();
+        private final static FragmentFactory sDefaultFragmentFactory = new ListRowFragmentFactory();
+
+        public MainFragmentAdapterRegistry() {
+            registerFragment(ListRow.class, sDefaultFragmentFactory);
+        }
+
+        public void registerFragment(Class rowClass, FragmentFactory factory) {
+            mItemToFragmentFactoryMapping.put(rowClass, factory);
+        }
+
+        public Fragment createFragment(Object item) {
+            FragmentFactory fragmentFactory = item == null ? sDefaultFragmentFactory :
+                    mItemToFragmentFactoryMapping.get(item.getClass());
+            if (fragmentFactory == null && !(item instanceof PageRow)) {
+                fragmentFactory = sDefaultFragmentFactory;
+            }
+
+            return fragmentFactory.createFragment(item);
+        }
+    }
+
+    static final String TAG = "BrowseSupportFragment";
+
+    private static final String LB_HEADERS_BACKSTACK = "lbHeadersBackStack_";
+
+    static boolean DEBUG = false;
+
+    /** The headers fragment is enabled and shown by default. */
+    public static final int HEADERS_ENABLED = 1;
+
+    /** The headers fragment is enabled and hidden by default. */
+    public static final int HEADERS_HIDDEN = 2;
+
+    /** The headers fragment is disabled and will never be shown. */
+    public static final int HEADERS_DISABLED = 3;
+
+    private MainFragmentAdapterRegistry mMainFragmentAdapterRegistry =
+            new MainFragmentAdapterRegistry();
+    MainFragmentAdapter mMainFragmentAdapter;
+    Fragment mMainFragment;
+    HeadersSupportFragment mHeadersSupportFragment;
+    MainFragmentRowsAdapter mMainFragmentRowsAdapter;
+    ListRowDataAdapter mMainFragmentListRowDataAdapter;
+
+    private ObjectAdapter mAdapter;
+    private PresenterSelector mAdapterPresenter;
+
+    private int mHeadersState = HEADERS_ENABLED;
+    private int mBrandColor = Color.TRANSPARENT;
+    private boolean mBrandColorSet;
+
+    BrowseFrameLayout mBrowseFrame;
+    private ScaleFrameLayout mScaleFrameLayout;
+    boolean mHeadersBackStackEnabled = true;
+    String mWithHeadersBackStackName;
+    boolean mShowingHeaders = true;
+    boolean mCanShowHeaders = true;
+    private int mContainerListMarginStart;
+    private int mContainerListAlignTop;
+    private boolean mMainFragmentScaleEnabled = true;
+    OnItemViewSelectedListener mExternalOnItemViewSelectedListener;
+    private OnItemViewClickedListener mOnItemViewClickedListener;
+    private int mSelectedPosition = -1;
+    private float mScaleFactor;
+    boolean mIsPageRow;
+    Object mPageRow;
+
+    private PresenterSelector mHeaderPresenterSelector;
+    private final SetSelectionRunnable mSetSelectionRunnable = new SetSelectionRunnable();
+
+    // transition related:
+    Object mSceneWithHeaders;
+    Object mSceneWithoutHeaders;
+    private Object mSceneAfterEntranceTransition;
+    Object mHeadersTransition;
+    BackStackListener mBackStackChangedListener;
+    BrowseTransitionListener mBrowseTransitionListener;
+
+    private static final String ARG_TITLE = BrowseSupportFragment.class.getCanonicalName() + ".title";
+    private static final String ARG_HEADERS_STATE =
+        BrowseSupportFragment.class.getCanonicalName() + ".headersState";
+
+    /**
+     * Creates arguments for a browse fragment.
+     *
+     * @param args The Bundle to place arguments into, or null if the method
+     *        should return a new Bundle.
+     * @param title The title of the BrowseSupportFragment.
+     * @param headersState The initial state of the headers of the
+     *        BrowseSupportFragment. Must be one of {@link #HEADERS_ENABLED}, {@link
+     *        #HEADERS_HIDDEN}, or {@link #HEADERS_DISABLED}.
+     * @return A Bundle with the given arguments for creating a BrowseSupportFragment.
+     */
+    public static Bundle createArgs(Bundle args, String title, int headersState) {
+        if (args == null) {
+            args = new Bundle();
+        }
+        args.putString(ARG_TITLE, title);
+        args.putInt(ARG_HEADERS_STATE, headersState);
+        return args;
+    }
+
+    /**
+     * Sets the brand color for the browse fragment. The brand color is used as
+     * the primary color for UI elements in the browse fragment. For example,
+     * the background color of the headers fragment uses the brand color.
+     *
+     * @param color The color to use as the brand color of the fragment.
+     */
+    public void setBrandColor(@ColorInt int color) {
+        mBrandColor = color;
+        mBrandColorSet = true;
+
+        if (mHeadersSupportFragment != null) {
+            mHeadersSupportFragment.setBackgroundColor(mBrandColor);
+        }
+    }
+
+    /**
+     * Returns the brand color for the browse fragment.
+     * The default is transparent.
+     */
+    @ColorInt
+    public int getBrandColor() {
+        return mBrandColor;
+    }
+
+    /**
+     * Wrapping app provided PresenterSelector to support InvisibleRowPresenter for SectionRow
+     * DividerRow and PageRow.
+     */
+    private void updateWrapperPresenter() {
+        if (mAdapter == null) {
+            mAdapterPresenter = null;
+            return;
+        }
+        final PresenterSelector adapterPresenter = mAdapter.getPresenterSelector();
+        if (adapterPresenter == null) {
+            throw new IllegalArgumentException("Adapter.getPresenterSelector() is null");
+        }
+        if (adapterPresenter == mAdapterPresenter) {
+            return;
+        }
+        mAdapterPresenter = adapterPresenter;
+
+        Presenter[] presenters = adapterPresenter.getPresenters();
+        final Presenter invisibleRowPresenter = new InvisibleRowPresenter();
+        final Presenter[] allPresenters = new Presenter[presenters.length + 1];
+        System.arraycopy(allPresenters, 0, presenters, 0, presenters.length);
+        allPresenters[allPresenters.length - 1] = invisibleRowPresenter;
+        mAdapter.setPresenterSelector(new PresenterSelector() {
+            @Override
+            public Presenter getPresenter(Object item) {
+                Row row = (Row) item;
+                if (row.isRenderedAsRowView()) {
+                    return adapterPresenter.getPresenter(item);
+                } else {
+                    return invisibleRowPresenter;
+                }
+            }
+
+            @Override
+            public Presenter[] getPresenters() {
+                return allPresenters;
+            }
+        });
+    }
+
+    /**
+     * Sets the adapter containing the rows for the fragment.
+     *
+     * <p>The items referenced by the adapter must be be derived from
+     * {@link Row}. These rows will be used by the rows fragment and the headers
+     * fragment (if not disabled) to render the browse rows.
+     *
+     * @param adapter An ObjectAdapter for the browse rows. All items must
+     *        derive from {@link Row}.
+     */
+    public void setAdapter(ObjectAdapter adapter) {
+        mAdapter = adapter;
+        updateWrapperPresenter();
+        if (getView() == null) {
+            return;
+        }
+
+        updateMainFragmentRowsAdapter();
+        mHeadersSupportFragment.setAdapter(mAdapter);
+    }
+
+    void setMainFragmentRowsAdapter(MainFragmentRowsAdapter mainFragmentRowsAdapter) {
+        if (mainFragmentRowsAdapter == mMainFragmentRowsAdapter) {
+            return;
+        }
+        // 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() {
+        return mMainFragmentAdapterRegistry;
+    }
+
+    /**
+     * Returns the adapter containing the rows for the fragment.
+     */
+    public ObjectAdapter getAdapter() {
+        return mAdapter;
+    }
+
+    /**
+     * Sets an item selection listener.
+     */
+    public void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
+        mExternalOnItemViewSelectedListener = listener;
+    }
+
+    /**
+     * Returns an item selection listener.
+     */
+    public OnItemViewSelectedListener getOnItemViewSelectedListener() {
+        return mExternalOnItemViewSelectedListener;
+    }
+
+    /**
+     * Get RowsSupportFragment if it's bound to BrowseSupportFragment or null if either BrowseSupportFragment has
+     * not been created yet or a different fragment is bound to it.
+     *
+     * @return RowsSupportFragment if it's bound to BrowseSupportFragment or null otherwise.
+     */
+    public RowsSupportFragment getRowsSupportFragment() {
+        if (mMainFragment instanceof RowsSupportFragment) {
+            return (RowsSupportFragment) mMainFragment;
+        }
+
+        return null;
+    }
+
+    /**
+     * @return Current main fragment or null if not created.
+     */
+    public Fragment getMainFragment() {
+        return mMainFragment;
+    }
+
+    /**
+     * Get currently bound HeadersSupportFragment or null if HeadersSupportFragment has not been created yet.
+     * @return Currently bound HeadersSupportFragment or null if HeadersSupportFragment has not been created yet.
+     */
+    public HeadersSupportFragment getHeadersSupportFragment() {
+        return mHeadersSupportFragment;
+    }
+
+    /**
+     * Sets an item clicked listener on the fragment.
+     * OnItemViewClickedListener will override {@link View.OnClickListener} that
+     * item presenter sets during {@link Presenter#onCreateViewHolder(ViewGroup)}.
+     * So in general, developer should choose one of the listeners but not both.
+     */
+    public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
+        mOnItemViewClickedListener = listener;
+        if (mMainFragmentRowsAdapter != null) {
+            mMainFragmentRowsAdapter.setOnItemViewClickedListener(listener);
+        }
+    }
+
+    /**
+     * Returns the item Clicked listener.
+     */
+    public OnItemViewClickedListener getOnItemViewClickedListener() {
+        return mOnItemViewClickedListener;
+    }
+
+    /**
+     * Starts a headers transition.
+     *
+     * <p>This method will begin a transition to either show or hide the
+     * headers, depending on the value of withHeaders. If headers are disabled
+     * for this browse fragment, this method will throw an exception.
+     *
+     * @param withHeaders True if the headers should transition to being shown,
+     *        false if the transition should result in headers being hidden.
+     */
+    public void startHeadersTransition(boolean withHeaders) {
+        if (!mCanShowHeaders) {
+            throw new IllegalStateException("Cannot start headers transition");
+        }
+        if (isInHeadersTransition() || mShowingHeaders == withHeaders) {
+            return;
+        }
+        startHeadersTransitionInternal(withHeaders);
+    }
+
+    /**
+     * Returns true if the headers transition is currently running.
+     */
+    public boolean isInHeadersTransition() {
+        return mHeadersTransition != null;
+    }
+
+    /**
+     * Returns true if headers are shown.
+     */
+    public boolean isShowingHeaders() {
+        return mShowingHeaders;
+    }
+
+    /**
+     * Sets a listener for browse fragment transitions.
+     *
+     * @param listener The listener to call when a browse headers transition
+     *        begins or ends.
+     */
+    public void setBrowseTransitionListener(BrowseTransitionListener listener) {
+        mBrowseTransitionListener = listener;
+    }
+
+    /**
+     * @deprecated use {@link BrowseSupportFragment#enableMainFragmentScaling(boolean)} instead.
+     *
+     * @param enable true to enable row scaling
+     */
+    @Deprecated
+    public void enableRowScaling(boolean enable) {
+        enableMainFragmentScaling(enable);
+    }
+
+    /**
+     * Enables scaling of main fragment when headers are present. For the page/row fragment,
+     * scaling is enabled only when both this method and
+     * {@link MainFragmentAdapter#isScalingEnabled()} are enabled.
+     *
+     * @param enable true to enable row scaling
+     */
+    public void enableMainFragmentScaling(boolean enable) {
+        mMainFragmentScaleEnabled = enable;
+    }
+
+    void startHeadersTransitionInternal(final boolean withHeaders) {
+        if (getFragmentManager().isDestroyed()) {
+            return;
+        }
+        if (!isHeadersDataReady()) {
+            return;
+        }
+        mShowingHeaders = withHeaders;
+        mMainFragmentAdapter.onTransitionPrepare();
+        mMainFragmentAdapter.onTransitionStart();
+        onExpandTransitionStart(!withHeaders, new Runnable() {
+            @Override
+            public void run() {
+                mHeadersSupportFragment.onTransitionPrepare();
+                mHeadersSupportFragment.onTransitionStart();
+                createHeadersTransition();
+                if (mBrowseTransitionListener != null) {
+                    mBrowseTransitionListener.onHeadersTransitionStart(withHeaders);
+                }
+                TransitionHelper.runTransition(
+                        withHeaders ? mSceneWithHeaders : mSceneWithoutHeaders, mHeadersTransition);
+                if (mHeadersBackStackEnabled) {
+                    if (!withHeaders) {
+                        getFragmentManager().beginTransaction()
+                                .addToBackStack(mWithHeadersBackStackName).commit();
+                    } else {
+                        int index = mBackStackChangedListener.mIndexOfHeadersBackStack;
+                        if (index >= 0) {
+                            BackStackEntry entry = getFragmentManager().getBackStackEntryAt(index);
+                            getFragmentManager().popBackStackImmediate(entry.getId(),
+                                    FragmentManager.POP_BACK_STACK_INCLUSIVE);
+                        }
+                    }
+                }
+            }
+        });
+    }
+
+    boolean isVerticalScrolling() {
+        // don't run transition
+        return mHeadersSupportFragment.isScrolling() || mMainFragmentAdapter.isScrolling();
+    }
+
+
+    private final BrowseFrameLayout.OnFocusSearchListener mOnFocusSearchListener =
+            new BrowseFrameLayout.OnFocusSearchListener() {
+        @Override
+        public View onFocusSearch(View focused, int direction) {
+            // if headers is running transition,  focus stays
+            if (mCanShowHeaders && isInHeadersTransition()) {
+                return focused;
+            }
+            if (DEBUG) Log.v(TAG, "onFocusSearch focused " + focused + " + direction " + direction);
+
+            if (getTitleView() != null && focused != getTitleView()
+                    && direction == View.FOCUS_UP) {
+                return getTitleView();
+            }
+            if (getTitleView() != null && getTitleView().hasFocus()
+                    && direction == View.FOCUS_DOWN) {
+                return mCanShowHeaders && mShowingHeaders
+                        ? mHeadersSupportFragment.getVerticalGridView() : mMainFragment.getView();
+            }
+
+            boolean isRtl = ViewCompat.getLayoutDirection(focused)
+                    == ViewCompat.LAYOUT_DIRECTION_RTL;
+            int towardStart = isRtl ? View.FOCUS_RIGHT : View.FOCUS_LEFT;
+            int towardEnd = isRtl ? View.FOCUS_LEFT : View.FOCUS_RIGHT;
+            if (mCanShowHeaders && direction == towardStart) {
+                if (isVerticalScrolling() || mShowingHeaders || !isHeadersDataReady()) {
+                    return focused;
+                }
+                return mHeadersSupportFragment.getVerticalGridView();
+            } else if (direction == towardEnd) {
+                if (isVerticalScrolling()) {
+                    return focused;
+                } else if (mMainFragment != null && mMainFragment.getView() != null) {
+                    return mMainFragment.getView();
+                }
+                return focused;
+            } else if (direction == View.FOCUS_DOWN && mShowingHeaders) {
+                // disable focus_down moving into PageFragment.
+                return focused;
+            } else {
+                return null;
+            }
+        }
+    };
+
+    final boolean isHeadersDataReady() {
+        return mAdapter != null && mAdapter.size() != 0;
+    }
+
+    private final BrowseFrameLayout.OnChildFocusListener mOnChildFocusListener =
+            new BrowseFrameLayout.OnChildFocusListener() {
+
+        @Override
+        public boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
+            if (getChildFragmentManager().isDestroyed()) {
+                return true;
+            }
+            // Make sure not changing focus when requestFocus() is called.
+            if (mCanShowHeaders && mShowingHeaders) {
+                if (mHeadersSupportFragment != null && mHeadersSupportFragment.getView() != null
+                        && mHeadersSupportFragment.getView().requestFocus(
+                                direction, previouslyFocusedRect)) {
+                    return true;
+                }
+            }
+            if (mMainFragment != null && mMainFragment.getView() != null
+                    && mMainFragment.getView().requestFocus(direction, previouslyFocusedRect)) {
+                return true;
+            }
+            return getTitleView() != null
+                    && getTitleView().requestFocus(direction, previouslyFocusedRect);
+        }
+
+        @Override
+        public void onRequestChildFocus(View child, View focused) {
+            if (getChildFragmentManager().isDestroyed()) {
+                return;
+            }
+            if (!mCanShowHeaders || isInHeadersTransition()) return;
+            int childId = child.getId();
+            if (childId == R.id.browse_container_dock && mShowingHeaders) {
+                startHeadersTransitionInternal(false);
+            } else if (childId == R.id.browse_headers_dock && !mShowingHeaders) {
+                startHeadersTransitionInternal(true);
+            }
+        }
+    };
+
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putInt(CURRENT_SELECTED_POSITION, mSelectedPosition);
+        outState.putBoolean(IS_PAGE_ROW, mIsPageRow);
+
+        if (mBackStackChangedListener != null) {
+            mBackStackChangedListener.save(outState);
+        } else {
+            outState.putBoolean(HEADER_SHOW, mShowingHeaders);
+        }
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        final Context context = getContext();
+        TypedArray ta = context.obtainStyledAttributes(R.styleable.LeanbackTheme);
+        mContainerListMarginStart = (int) ta.getDimension(
+                R.styleable.LeanbackTheme_browseRowsMarginStart, context.getResources()
+                .getDimensionPixelSize(R.dimen.lb_browse_rows_margin_start));
+        mContainerListAlignTop = (int) ta.getDimension(
+                R.styleable.LeanbackTheme_browseRowsMarginTop, context.getResources()
+                .getDimensionPixelSize(R.dimen.lb_browse_rows_margin_top));
+        ta.recycle();
+
+        readArguments(getArguments());
+
+        if (mCanShowHeaders) {
+            if (mHeadersBackStackEnabled) {
+                mWithHeadersBackStackName = LB_HEADERS_BACKSTACK + this;
+                mBackStackChangedListener = new BackStackListener();
+                getFragmentManager().addOnBackStackChangedListener(mBackStackChangedListener);
+                mBackStackChangedListener.load(savedInstanceState);
+            } else {
+                if (savedInstanceState != null) {
+                    mShowingHeaders = savedInstanceState.getBoolean(HEADER_SHOW);
+                }
+            }
+        }
+
+        mScaleFactor = getResources().getFraction(R.fraction.lb_browse_rows_scale, 1, 1);
+    }
+
+    @Override
+    public void onDestroyView() {
+        setMainFragmentRowsAdapter(null);
+        mPageRow = null;
+        mMainFragmentAdapter = null;
+        mMainFragment = null;
+        mHeadersSupportFragment = null;
+        super.onDestroyView();
+    }
+
+    @Override
+    public void onDestroy() {
+        if (mBackStackChangedListener != null) {
+            getFragmentManager().removeOnBackStackChangedListener(mBackStackChangedListener);
+        }
+        super.onDestroy();
+    }
+
+    /**
+     * Creates a new {@link HeadersSupportFragment} instance. Subclass of BrowseSupportFragment may override and
+     * return an instance of subclass of HeadersSupportFragment, e.g. when app wants to replace presenter
+     * to render HeaderItem.
+     *
+     * @return A new instance of {@link HeadersSupportFragment} or its subclass.
+     */
+    public HeadersSupportFragment onCreateHeadersSupportFragment() {
+        return new HeadersSupportFragment();
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+
+        if (getChildFragmentManager().findFragmentById(R.id.scale_frame) == null) {
+            mHeadersSupportFragment = onCreateHeadersSupportFragment();
+
+            createMainFragment(mAdapter, mSelectedPosition);
+            FragmentTransaction ft = getChildFragmentManager().beginTransaction()
+                    .replace(R.id.browse_headers_dock, mHeadersSupportFragment);
+
+            if (mMainFragment != null) {
+                ft.replace(R.id.scale_frame, mMainFragment);
+            } else {
+                // Empty adapter used to guard against lazy adapter loading. When this
+                // fragment is instantiated, mAdapter might not have the data or might not
+                // have been set. In either of those cases mFragmentAdapter will be null.
+                // This way we can maintain the invariant that mMainFragmentAdapter is never
+                // null and it avoids doing null checks all over the code.
+                mMainFragmentAdapter = new MainFragmentAdapter(null);
+                mMainFragmentAdapter.setFragmentHost(new FragmentHostImpl());
+            }
+
+            ft.commit();
+        } else {
+            mHeadersSupportFragment = (HeadersSupportFragment) getChildFragmentManager()
+                    .findFragmentById(R.id.browse_headers_dock);
+            mMainFragment = getChildFragmentManager().findFragmentById(R.id.scale_frame);
+
+            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;
+
+            setMainFragmentAdapter();
+        }
+
+        mHeadersSupportFragment.setHeadersGone(!mCanShowHeaders);
+        if (mHeaderPresenterSelector != null) {
+            mHeadersSupportFragment.setPresenterSelector(mHeaderPresenterSelector);
+        }
+        mHeadersSupportFragment.setAdapter(mAdapter);
+        mHeadersSupportFragment.setOnHeaderViewSelectedListener(mHeaderViewSelectedListener);
+        mHeadersSupportFragment.setOnHeaderClickedListener(mHeaderClickedListener);
+
+        View root = inflater.inflate(R.layout.lb_browse_fragment, container, false);
+
+        getProgressBarManager().setRootView((ViewGroup)root);
+
+        mBrowseFrame = (BrowseFrameLayout) root.findViewById(R.id.browse_frame);
+        mBrowseFrame.setOnChildFocusListener(mOnChildFocusListener);
+        mBrowseFrame.setOnFocusSearchListener(mOnFocusSearchListener);
+
+        installTitleView(inflater, mBrowseFrame, savedInstanceState);
+
+        mScaleFrameLayout = (ScaleFrameLayout) root.findViewById(R.id.scale_frame);
+        mScaleFrameLayout.setPivotX(0);
+        mScaleFrameLayout.setPivotY(mContainerListAlignTop);
+
+        if (mBrandColorSet) {
+            mHeadersSupportFragment.setBackgroundColor(mBrandColor);
+        }
+
+        mSceneWithHeaders = TransitionHelper.createScene(mBrowseFrame, new Runnable() {
+            @Override
+            public void run() {
+                showHeaders(true);
+            }
+        });
+        mSceneWithoutHeaders =  TransitionHelper.createScene(mBrowseFrame, new Runnable() {
+            @Override
+            public void run() {
+                showHeaders(false);
+            }
+        });
+        mSceneAfterEntranceTransition = TransitionHelper.createScene(mBrowseFrame, new Runnable() {
+            @Override
+            public void run() {
+                setEntranceTransitionEndState();
+            }
+        });
+
+        return root;
+    }
+
+    void createHeadersTransition() {
+        mHeadersTransition = TransitionHelper.loadTransition(getContext(),
+                mShowingHeaders
+                        ? R.transition.lb_browse_headers_in : R.transition.lb_browse_headers_out);
+
+        TransitionHelper.addTransitionListener(mHeadersTransition, new TransitionListener() {
+            @Override
+            public void onTransitionStart(Object transition) {
+            }
+            @Override
+            public void onTransitionEnd(Object transition) {
+                mHeadersTransition = null;
+                if (mMainFragmentAdapter != null) {
+                    mMainFragmentAdapter.onTransitionEnd();
+                    if (!mShowingHeaders && mMainFragment != null) {
+                        View mainFragmentView = mMainFragment.getView();
+                        if (mainFragmentView != null && !mainFragmentView.hasFocus()) {
+                            mainFragmentView.requestFocus();
+                        }
+                    }
+                }
+                if (mHeadersSupportFragment != null) {
+                    mHeadersSupportFragment.onTransitionEnd();
+                    if (mShowingHeaders) {
+                        VerticalGridView headerGridView = mHeadersSupportFragment.getVerticalGridView();
+                        if (headerGridView != null && !headerGridView.hasFocus()) {
+                            headerGridView.requestFocus();
+                        }
+                    }
+                }
+
+                // Animate TitleView once header animation is complete.
+                updateTitleViewVisibility();
+
+                if (mBrowseTransitionListener != null) {
+                    mBrowseTransitionListener.onHeadersTransitionStop(mShowingHeaders);
+                }
+            }
+        });
+    }
+
+    void updateTitleViewVisibility() {
+        if (!mShowingHeaders) {
+            boolean showTitleView;
+            if (mIsPageRow && mMainFragmentAdapter != null) {
+                // page fragment case:
+                showTitleView = mMainFragmentAdapter.mFragmentHost.mShowTitleView;
+            } else {
+                // regular row view case:
+                showTitleView = isFirstRowWithContent(mSelectedPosition);
+            }
+            if (showTitleView) {
+                showTitle(TitleViewAdapter.FULL_VIEW_VISIBLE);
+            } else {
+                showTitle(false);
+            }
+        } else {
+            // when HeaderFragment is showing,  showBranding and showSearch are slightly different
+            boolean showBranding;
+            boolean showSearch;
+            if (mIsPageRow && mMainFragmentAdapter != null) {
+                showBranding = mMainFragmentAdapter.mFragmentHost.mShowTitleView;
+            } else {
+                showBranding = isFirstRowWithContent(mSelectedPosition);
+            }
+            showSearch = isFirstRowWithContentOrPageRow(mSelectedPosition);
+            int flags = 0;
+            if (showBranding) flags |= TitleViewAdapter.BRANDING_VIEW_VISIBLE;
+            if (showSearch) flags |= TitleViewAdapter.SEARCH_VIEW_VISIBLE;
+            if (flags != 0) {
+                showTitle(flags);
+            } else {
+                showTitle(false);
+            }
+        }
+    }
+
+    boolean isFirstRowWithContentOrPageRow(int rowPosition) {
+        if (mAdapter == null || mAdapter.size() == 0) {
+            return true;
+        }
+        for (int i = 0; i < mAdapter.size(); i++) {
+            final Row row = (Row) mAdapter.get(i);
+            if (row.isRenderedAsRowView() || row instanceof PageRow) {
+                return rowPosition == i;
+            }
+        }
+        return true;
+    }
+
+    boolean isFirstRowWithContent(int rowPosition) {
+        if (mAdapter == null || mAdapter.size() == 0) {
+            return true;
+        }
+        for (int i = 0; i < mAdapter.size(); i++) {
+            final Row row = (Row) mAdapter.get(i);
+            if (row.isRenderedAsRowView()) {
+                return rowPosition == i;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Sets the {@link PresenterSelector} used to render the row headers.
+     *
+     * @param headerPresenterSelector The PresenterSelector that will determine
+     *        the Presenter for each row header.
+     */
+    public void setHeaderPresenterSelector(PresenterSelector headerPresenterSelector) {
+        mHeaderPresenterSelector = headerPresenterSelector;
+        if (mHeadersSupportFragment != null) {
+            mHeadersSupportFragment.setPresenterSelector(mHeaderPresenterSelector);
+        }
+    }
+
+    private void setHeadersOnScreen(boolean onScreen) {
+        MarginLayoutParams lp;
+        View containerList;
+        containerList = mHeadersSupportFragment.getView();
+        lp = (MarginLayoutParams) containerList.getLayoutParams();
+        lp.setMarginStart(onScreen ? 0 : -mContainerListMarginStart);
+        containerList.setLayoutParams(lp);
+    }
+
+    void showHeaders(boolean show) {
+        if (DEBUG) Log.v(TAG, "showHeaders " + show);
+        mHeadersSupportFragment.setHeadersEnabled(show);
+        setHeadersOnScreen(show);
+        expandMainFragment(!show);
+    }
+
+    private void expandMainFragment(boolean expand) {
+        MarginLayoutParams params = (MarginLayoutParams) mScaleFrameLayout.getLayoutParams();
+        params.setMarginStart(!expand ? mContainerListMarginStart : 0);
+        mScaleFrameLayout.setLayoutParams(params);
+        mMainFragmentAdapter.setExpand(expand);
+
+        setMainFragmentAlignment();
+        final float scaleFactor = !expand
+                && mMainFragmentScaleEnabled
+                && mMainFragmentAdapter.isScalingEnabled() ? mScaleFactor : 1;
+        mScaleFrameLayout.setLayoutScaleY(scaleFactor);
+        mScaleFrameLayout.setChildScale(scaleFactor);
+    }
+
+    private HeadersSupportFragment.OnHeaderClickedListener mHeaderClickedListener =
+        new HeadersSupportFragment.OnHeaderClickedListener() {
+            @Override
+            public void onHeaderClicked(RowHeaderPresenter.ViewHolder viewHolder, Row row) {
+                if (!mCanShowHeaders || !mShowingHeaders || isInHeadersTransition()) {
+                    return;
+                }
+                startHeadersTransitionInternal(false);
+                mMainFragment.getView().requestFocus();
+            }
+        };
+
+    class MainFragmentItemViewSelectedListener implements OnItemViewSelectedListener {
+        MainFragmentRowsAdapter mMainFragmentRowsAdapter;
+
+        public MainFragmentItemViewSelectedListener(MainFragmentRowsAdapter fragmentRowsAdapter) {
+            mMainFragmentRowsAdapter = fragmentRowsAdapter;
+        }
+
+        @Override
+        public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
+                RowPresenter.ViewHolder rowViewHolder, Row row) {
+            int position = mMainFragmentRowsAdapter.getSelectedPosition();
+            if (DEBUG) Log.v(TAG, "row selected position " + position);
+            onRowSelected(position);
+            if (mExternalOnItemViewSelectedListener != null) {
+                mExternalOnItemViewSelectedListener.onItemSelected(itemViewHolder, item,
+                        rowViewHolder, row);
+            }
+        }
+    };
+
+    private HeadersSupportFragment.OnHeaderViewSelectedListener mHeaderViewSelectedListener =
+            new HeadersSupportFragment.OnHeaderViewSelectedListener() {
+        @Override
+        public void onHeaderSelected(RowHeaderPresenter.ViewHolder viewHolder, Row row) {
+            int position = mHeadersSupportFragment.getSelectedPosition();
+            if (DEBUG) Log.v(TAG, "header selected position " + position);
+            onRowSelected(position);
+        }
+    };
+
+    void onRowSelected(int position) {
+        // 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) {
+        if (position == NO_POSITION) {
+            return;
+        }
+
+        mSelectedPosition = position;
+        if (mHeadersSupportFragment == null || mMainFragmentAdapter == null) {
+            // onDestroyView() called
+            return;
+        }
+        mHeadersSupportFragment.setSelectedPosition(position, smooth);
+        replaceMainFragment(position);
+
+        if (mMainFragmentRowsAdapter != null) {
+            mMainFragmentRowsAdapter.setSelectedPosition(position, smooth);
+        }
+
+        updateTitleViewVisibility();
+    }
+
+    private void replaceMainFragment(int position) {
+        if (createMainFragment(mAdapter, position)) {
+            swapToMainFragment();
+            expandMainFragment(!(mCanShowHeaders && mShowingHeaders));
+        }
+    }
+
+    private void swapToMainFragment() {
+        final VerticalGridView gridView = mHeadersSupportFragment.getVerticalGridView();
+        if (isShowingHeaders() && gridView != null
+                && gridView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) {
+            // if user is scrolling HeadersSupportFragment,  swap to empty fragment and wait scrolling
+            // finishes.
+            getChildFragmentManager().beginTransaction()
+                    .replace(R.id.scale_frame, new Fragment()).commit();
+            gridView.addOnScrollListener(new RecyclerView.OnScrollListener() {
+                @SuppressWarnings("ReferenceEquality")
+                @Override
+                public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
+                    if (newState == RecyclerView.SCROLL_STATE_IDLE) {
+                        gridView.removeOnScrollListener(this);
+                        FragmentManager fm = getChildFragmentManager();
+                        Fragment currentFragment = fm.findFragmentById(R.id.scale_frame);
+                        if (currentFragment != mMainFragment) {
+                            fm.beginTransaction().replace(R.id.scale_frame, mMainFragment).commit();
+                        }
+                    }
+                }
+            });
+        } else {
+            // Otherwise swap immediately
+            getChildFragmentManager().beginTransaction()
+                    .replace(R.id.scale_frame, mMainFragment).commit();
+        }
+    }
+
+    /**
+     * Sets the selected row position with smooth animation.
+     */
+    public void setSelectedPosition(int position) {
+        setSelectedPosition(position, true);
+    }
+
+    /**
+     * Gets position of currently selected row.
+     * @return Position of currently selected row.
+     */
+    public int getSelectedPosition() {
+        return mSelectedPosition;
+    }
+
+    /**
+     * @return selected row ViewHolder inside fragment created by {@link MainFragmentRowsAdapter}.
+     */
+    public RowPresenter.ViewHolder getSelectedRowViewHolder() {
+        if (mMainFragmentRowsAdapter != null) {
+            int rowPos = mMainFragmentRowsAdapter.getSelectedPosition();
+            return mMainFragmentRowsAdapter.findRowViewHolderByPosition(rowPos);
+        }
+        return null;
+    }
+
+    /**
+     * Sets the selected row position.
+     */
+    public void setSelectedPosition(int position, boolean smooth) {
+        mSetSelectionRunnable.post(
+                position, SetSelectionRunnable.TYPE_USER_REQUEST, smooth);
+    }
+
+    /**
+     * Selects a Row and perform an optional task on the Row. For example
+     * <code>setSelectedPosition(10, true, new ListRowPresenterSelectItemViewHolderTask(5))</code>
+     * scrolls to 11th row and selects 6th item on that row.  The method will be ignored if
+     * RowsSupportFragment has not been created (i.e. before {@link #onCreateView(LayoutInflater,
+     * ViewGroup, Bundle)}).
+     *
+     * @param rowPosition Which row to select.
+     * @param smooth True to scroll to the row, false for no animation.
+     * @param rowHolderTask Optional task to perform on the Row.  When the task is not null, headers
+     * fragment will be collapsed.
+     */
+    public void setSelectedPosition(int rowPosition, boolean smooth,
+            final Presenter.ViewHolderTask rowHolderTask) {
+        if (mMainFragmentAdapterRegistry == null) {
+            return;
+        }
+        if (rowHolderTask != null) {
+            startHeadersTransition(false);
+        }
+        if (mMainFragmentRowsAdapter != null) {
+            mMainFragmentRowsAdapter.setSelectedPosition(rowPosition, smooth, rowHolderTask);
+        }
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        mHeadersSupportFragment.setAlignment(mContainerListAlignTop);
+        setMainFragmentAlignment();
+
+        if (mCanShowHeaders && mShowingHeaders && mHeadersSupportFragment != null
+                && mHeadersSupportFragment.getView() != null) {
+            mHeadersSupportFragment.getView().requestFocus();
+        } else if ((!mCanShowHeaders || !mShowingHeaders) && mMainFragment != null
+                && mMainFragment.getView() != null) {
+            mMainFragment.getView().requestFocus();
+        }
+
+        if (mCanShowHeaders) {
+            showHeaders(mShowingHeaders);
+        }
+
+        mStateMachine.fireEvent(EVT_HEADER_VIEW_CREATED);
+    }
+
+    private void onExpandTransitionStart(boolean expand, final Runnable callback) {
+        if (expand) {
+            callback.run();
+            return;
+        }
+        // Run a "pre" layout when we go non-expand, in order to get the initial
+        // positions of added rows.
+        new ExpandPreLayout(callback, mMainFragmentAdapter, getView()).execute();
+    }
+
+    private void setMainFragmentAlignment() {
+        int alignOffset = mContainerListAlignTop;
+        if (mMainFragmentScaleEnabled
+                && mMainFragmentAdapter.isScalingEnabled()
+                && mShowingHeaders) {
+            alignOffset = (int) (alignOffset / mScaleFactor + 0.5f);
+        }
+        mMainFragmentAdapter.setAlignment(alignOffset);
+    }
+
+    /**
+     * Enables/disables headers transition on back key support. This is enabled by
+     * default. The BrowseSupportFragment will add a back stack entry when headers are
+     * showing. Running a headers transition when the back key is pressed only
+     * works when the headers state is {@link #HEADERS_ENABLED} or
+     * {@link #HEADERS_HIDDEN}.
+     * <p>
+     * NOTE: If an Activity has its own onBackPressed() handling, you must
+     * disable this feature. You may use {@link #startHeadersTransition(boolean)}
+     * and {@link BrowseTransitionListener} in your own back stack handling.
+     */
+    public final void setHeadersTransitionOnBackEnabled(boolean headersBackStackEnabled) {
+        mHeadersBackStackEnabled = headersBackStackEnabled;
+    }
+
+    /**
+     * Returns true if headers transition on back key support is enabled.
+     */
+    public final boolean isHeadersTransitionOnBackEnabled() {
+        return mHeadersBackStackEnabled;
+    }
+
+    private void readArguments(Bundle args) {
+        if (args == null) {
+            return;
+        }
+        if (args.containsKey(ARG_TITLE)) {
+            setTitle(args.getString(ARG_TITLE));
+        }
+        if (args.containsKey(ARG_HEADERS_STATE)) {
+            setHeadersState(args.getInt(ARG_HEADERS_STATE));
+        }
+    }
+
+    /**
+     * Sets the state for the headers column in the browse fragment. Must be one
+     * of {@link #HEADERS_ENABLED}, {@link #HEADERS_HIDDEN}, or
+     * {@link #HEADERS_DISABLED}.
+     *
+     * @param headersState The state of the headers for the browse fragment.
+     */
+    public void setHeadersState(int headersState) {
+        if (headersState < HEADERS_ENABLED || headersState > HEADERS_DISABLED) {
+            throw new IllegalArgumentException("Invalid headers state: " + headersState);
+        }
+        if (DEBUG) Log.v(TAG, "setHeadersState " + headersState);
+
+        if (headersState != mHeadersState) {
+            mHeadersState = headersState;
+            switch (headersState) {
+                case HEADERS_ENABLED:
+                    mCanShowHeaders = true;
+                    mShowingHeaders = true;
+                    break;
+                case HEADERS_HIDDEN:
+                    mCanShowHeaders = true;
+                    mShowingHeaders = false;
+                    break;
+                case HEADERS_DISABLED:
+                    mCanShowHeaders = false;
+                    mShowingHeaders = false;
+                    break;
+                default:
+                    Log.w(TAG, "Unknown headers state: " + headersState);
+                    break;
+            }
+            if (mHeadersSupportFragment != null) {
+                mHeadersSupportFragment.setHeadersGone(!mCanShowHeaders);
+            }
+        }
+    }
+
+    /**
+     * Returns the state of the headers column in the browse fragment.
+     */
+    public int getHeadersState() {
+        return mHeadersState;
+    }
+
+    @Override
+    protected Object createEntranceTransition() {
+        return TransitionHelper.loadTransition(getContext(),
+                R.transition.lb_browse_entrance_transition);
+    }
+
+    @Override
+    protected void runEntranceTransition(Object entranceTransition) {
+        TransitionHelper.runTransition(mSceneAfterEntranceTransition, entranceTransition);
+    }
+
+    @Override
+    protected void onEntranceTransitionPrepare() {
+        mHeadersSupportFragment.onTransitionPrepare();
+        mMainFragmentAdapter.setEntranceTransitionState(false);
+        mMainFragmentAdapter.onTransitionPrepare();
+    }
+
+    @Override
+    protected void onEntranceTransitionStart() {
+        mHeadersSupportFragment.onTransitionStart();
+        mMainFragmentAdapter.onTransitionStart();
+    }
+
+    @Override
+    protected void onEntranceTransitionEnd() {
+        if (mMainFragmentAdapter != null) {
+            mMainFragmentAdapter.onTransitionEnd();
+        }
+
+        if (mHeadersSupportFragment != null) {
+            mHeadersSupportFragment.onTransitionEnd();
+        }
+    }
+
+    void setSearchOrbViewOnScreen(boolean onScreen) {
+        View searchOrbView = getTitleViewAdapter().getSearchAffordanceView();
+        if (searchOrbView != null) {
+            MarginLayoutParams lp = (MarginLayoutParams) searchOrbView.getLayoutParams();
+            lp.setMarginStart(onScreen ? 0 : -mContainerListMarginStart);
+            searchOrbView.setLayoutParams(lp);
+        }
+    }
+
+    void setEntranceTransitionStartState() {
+        setHeadersOnScreen(false);
+        setSearchOrbViewOnScreen(false);
+        // NOTE that mMainFragmentAdapter.setEntranceTransitionState(false) will be called
+        // in onEntranceTransitionPrepare() because mMainFragmentAdapter is still the dummy
+        // one when setEntranceTransitionStartState() is called.
+    }
+
+    void setEntranceTransitionEndState() {
+        setHeadersOnScreen(mShowingHeaders);
+        setSearchOrbViewOnScreen(true);
+        mMainFragmentAdapter.setEntranceTransitionState(true);
+    }
+
+    private class ExpandPreLayout implements ViewTreeObserver.OnPreDrawListener {
+
+        private final View mView;
+        private final Runnable mCallback;
+        private int mState;
+        private MainFragmentAdapter mainFragmentAdapter;
+
+        final static int STATE_INIT = 0;
+        final static int STATE_FIRST_DRAW = 1;
+        final static int STATE_SECOND_DRAW = 2;
+
+        ExpandPreLayout(Runnable callback, MainFragmentAdapter adapter, View view) {
+            mView = view;
+            mCallback = callback;
+            mainFragmentAdapter = adapter;
+        }
+
+        void execute() {
+            mView.getViewTreeObserver().addOnPreDrawListener(this);
+            mainFragmentAdapter.setExpand(false);
+            // always trigger onPreDraw even adapter setExpand() does nothing.
+            mView.invalidate();
+            mState = STATE_INIT;
+        }
+
+        @Override
+        public boolean onPreDraw() {
+            if (getView() == null || getContext() == null) {
+                mView.getViewTreeObserver().removeOnPreDrawListener(this);
+                return true;
+            }
+            if (mState == STATE_INIT) {
+                mainFragmentAdapter.setExpand(true);
+                // always trigger onPreDraw even adapter setExpand() does nothing.
+                mView.invalidate();
+                mState = STATE_FIRST_DRAW;
+            } else if (mState == STATE_FIRST_DRAW) {
+                mCallback.run();
+                mView.getViewTreeObserver().removeOnPreDrawListener(this);
+                mState = STATE_SECOND_DRAW;
+            }
+            return false;
+        }
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/DetailsBackgroundVideoHelper.java b/leanback/src/android/support/v17/leanback/app/DetailsBackgroundVideoHelper.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/app/DetailsBackgroundVideoHelper.java
rename to leanback/src/android/support/v17/leanback/app/DetailsBackgroundVideoHelper.java
diff --git a/leanback/src/android/support/v17/leanback/app/DetailsFragment.java b/leanback/src/android/support/v17/leanback/app/DetailsFragment.java
new file mode 100644
index 0000000..18934f4
--- /dev/null
+++ b/leanback/src/android/support/v17/leanback/app/DetailsFragment.java
@@ -0,0 +1,934 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from DetailsSupportFragment.java.  DO NOT MODIFY. */
+
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from DetailsFragment.java.  DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.FragmentTransaction;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.annotation.CallSuper;
+import android.support.v17.leanback.R;
+import android.support.v17.leanback.transition.TransitionHelper;
+import android.support.v17.leanback.transition.TransitionListener;
+import android.support.v17.leanback.util.StateMachine.Event;
+import android.support.v17.leanback.util.StateMachine.State;
+import android.support.v17.leanback.widget.BaseOnItemViewClickedListener;
+import android.support.v17.leanback.widget.BaseOnItemViewSelectedListener;
+import android.support.v17.leanback.widget.BrowseFrameLayout;
+import android.support.v17.leanback.widget.DetailsParallax;
+import android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter;
+import android.support.v17.leanback.widget.ItemAlignmentFacet;
+import android.support.v17.leanback.widget.ItemBridgeAdapter;
+import android.support.v17.leanback.widget.ObjectAdapter;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.PresenterSelector;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * A fragment for creating Leanback details screens.
+ *
+ * <p>
+ * A DetailsFragment renders the elements of its {@link ObjectAdapter} as a set
+ * of rows in a vertical list.The Adapter's {@link PresenterSelector} must maintain subclasses
+ * of {@link RowPresenter}.
+ * </p>
+ *
+ * When {@link FullWidthDetailsOverviewRowPresenter} is found in adapter,  DetailsFragment will
+ * setup default behavior of the DetailsOverviewRow:
+ * <li>
+ * The alignment of FullWidthDetailsOverviewRowPresenter is setup in
+ * {@link #setupDetailsOverviewRowPresenter(FullWidthDetailsOverviewRowPresenter)}.
+ * </li>
+ * <li>
+ * The view status switching of FullWidthDetailsOverviewRowPresenter is done in
+ * {@link #onSetDetailsOverviewRowStatus(FullWidthDetailsOverviewRowPresenter,
+ * FullWidthDetailsOverviewRowPresenter.ViewHolder, int, int, int)}.
+ * </li>
+ *
+ * <p>
+ * The recommended activity themes to use with a DetailsFragment are
+ * <li>
+ * {@link android.support.v17.leanback.R.style#Theme_Leanback_Details} with activity
+ * shared element transition for {@link FullWidthDetailsOverviewRowPresenter}.
+ * </li>
+ * <li>
+ * {@link android.support.v17.leanback.R.style#Theme_Leanback_Details_NoSharedElementTransition}
+ * if shared element transition is not needed, for example if first row is not rendered by
+ * {@link FullWidthDetailsOverviewRowPresenter}.
+ * </li>
+ * </p>
+ *
+ * <p>
+ * DetailsFragment can use {@link DetailsFragmentBackgroundController} to add a parallax drawable
+ * background and embedded video playing fragment.
+ * </p>
+ * @deprecated use {@link DetailsSupportFragment}
+ */
+@Deprecated
+public class DetailsFragment extends BaseFragment {
+    static final String TAG = "DetailsFragment";
+    static boolean DEBUG = false;
+
+    final State STATE_SET_ENTRANCE_START_STATE = new State("STATE_SET_ENTRANCE_START_STATE") {
+        @Override
+        public void run() {
+            mRowsFragment.setEntranceTransitionState(false);
+        }
+    };
+
+    final State STATE_ENTER_TRANSITION_INIT = new State("STATE_ENTER_TRANSIITON_INIT");
+
+    void switchToVideoBeforeVideoFragmentCreated() {
+        // if the video fragment is not ready: immediately fade out covering drawable,
+        // hide title and mark mPendingFocusOnVideo and set focus on it later.
+        mDetailsBackgroundController.switchToVideoBeforeCreate();
+        showTitle(false);
+        mPendingFocusOnVideo = true;
+        slideOutGridView();
+    }
+
+    final State STATE_SWITCH_TO_VIDEO_IN_ON_CREATE = new State("STATE_SWITCH_TO_VIDEO_IN_ON_CREATE",
+            false, false) {
+        @Override
+        public void run() {
+            switchToVideoBeforeVideoFragmentCreated();
+        }
+    };
+
+    final State STATE_ENTER_TRANSITION_CANCEL = new State("STATE_ENTER_TRANSITION_CANCEL",
+            false, false) {
+        @Override
+        public void run() {
+            if (mWaitEnterTransitionTimeout != null) {
+                mWaitEnterTransitionTimeout.mRef.clear();
+            }
+            // clear the activity enter/sharedElement transition, return transitions are kept.
+            // keep the return transitions and clear enter transition
+            if (getActivity() != null) {
+                Window window = getActivity().getWindow();
+                Object returnTransition = TransitionHelper.getReturnTransition(window);
+                Object sharedReturnTransition = TransitionHelper
+                        .getSharedElementReturnTransition(window);
+                TransitionHelper.setEnterTransition(window, null);
+                TransitionHelper.setSharedElementEnterTransition(window, null);
+                TransitionHelper.setReturnTransition(window, returnTransition);
+                TransitionHelper.setSharedElementReturnTransition(window, sharedReturnTransition);
+            }
+        }
+    };
+
+    final State STATE_ENTER_TRANSITION_COMPLETE = new State("STATE_ENTER_TRANSIITON_COMPLETE",
+            true, false);
+
+    final State STATE_ENTER_TRANSITION_ADDLISTENER = new State("STATE_ENTER_TRANSITION_PENDING") {
+        @Override
+        public void run() {
+            Object transition = TransitionHelper.getEnterTransition(getActivity().getWindow());
+            TransitionHelper.addTransitionListener(transition, mEnterTransitionListener);
+        }
+    };
+
+    final State STATE_ENTER_TRANSITION_PENDING = new State("STATE_ENTER_TRANSITION_PENDING") {
+        @Override
+        public void run() {
+            if (mWaitEnterTransitionTimeout == null) {
+                new WaitEnterTransitionTimeout(DetailsFragment.this);
+            }
+        }
+    };
+
+    /**
+     * Start this task when first DetailsOverviewRow is created, if there is no entrance transition
+     * started, it will clear PF_ENTRANCE_TRANSITION_PENDING.
+     */
+    static class WaitEnterTransitionTimeout implements Runnable {
+        static final long WAIT_ENTERTRANSITION_START = 200;
+
+        final WeakReference<DetailsFragment> mRef;
+
+        WaitEnterTransitionTimeout(DetailsFragment f) {
+            mRef = new WeakReference<>(f);
+            f.getView().postDelayed(this, WAIT_ENTERTRANSITION_START);
+        }
+
+        @Override
+        public void run() {
+            DetailsFragment f = mRef.get();
+            if (f != null) {
+                f.mStateMachine.fireEvent(f.EVT_ENTER_TRANSIITON_DONE);
+            }
+        }
+    }
+
+    final State STATE_ON_SAFE_START = new State("STATE_ON_SAFE_START") {
+        @Override
+        public void run() {
+            onSafeStart();
+        }
+    };
+
+    final Event EVT_ONSTART = new Event("onStart");
+
+    final Event EVT_NO_ENTER_TRANSITION = new Event("EVT_NO_ENTER_TRANSITION");
+
+    final Event EVT_DETAILS_ROW_LOADED = new Event("onFirstRowLoaded");
+
+    final Event EVT_ENTER_TRANSIITON_DONE = new Event("onEnterTransitionDone");
+
+    final Event EVT_SWITCH_TO_VIDEO = new Event("switchToVideo");
+
+    @Override
+    void createStateMachineStates() {
+        super.createStateMachineStates();
+        mStateMachine.addState(STATE_SET_ENTRANCE_START_STATE);
+        mStateMachine.addState(STATE_ON_SAFE_START);
+        mStateMachine.addState(STATE_SWITCH_TO_VIDEO_IN_ON_CREATE);
+        mStateMachine.addState(STATE_ENTER_TRANSITION_INIT);
+        mStateMachine.addState(STATE_ENTER_TRANSITION_ADDLISTENER);
+        mStateMachine.addState(STATE_ENTER_TRANSITION_CANCEL);
+        mStateMachine.addState(STATE_ENTER_TRANSITION_PENDING);
+        mStateMachine.addState(STATE_ENTER_TRANSITION_COMPLETE);
+    }
+
+    @Override
+    void createStateMachineTransitions() {
+        super.createStateMachineTransitions();
+        /**
+         * Part 1: Processing enter transitions after fragment.onCreate
+         */
+        mStateMachine.addTransition(STATE_START, STATE_ENTER_TRANSITION_INIT, EVT_ON_CREATE);
+        // if transition is not supported, skip to complete
+        mStateMachine.addTransition(STATE_ENTER_TRANSITION_INIT, STATE_ENTER_TRANSITION_COMPLETE,
+                COND_TRANSITION_NOT_SUPPORTED);
+        // if transition is not set on Activity, skip to complete
+        mStateMachine.addTransition(STATE_ENTER_TRANSITION_INIT, STATE_ENTER_TRANSITION_COMPLETE,
+                EVT_NO_ENTER_TRANSITION);
+        // if switchToVideo is called before EVT_ON_CREATEVIEW, clear enter transition and skip to
+        // complete.
+        mStateMachine.addTransition(STATE_ENTER_TRANSITION_INIT, STATE_ENTER_TRANSITION_CANCEL,
+                EVT_SWITCH_TO_VIDEO);
+        mStateMachine.addTransition(STATE_ENTER_TRANSITION_CANCEL, STATE_ENTER_TRANSITION_COMPLETE);
+        // once after onCreateView, we cannot skip the enter transition, add a listener and wait
+        // it to finish
+        mStateMachine.addTransition(STATE_ENTER_TRANSITION_INIT, STATE_ENTER_TRANSITION_ADDLISTENER,
+                EVT_ON_CREATEVIEW);
+        // when enter transition finishes, go to complete, however this might never happen if
+        // the activity is not giving transition options in startActivity, there is no API to query
+        // if this activity is started in a enter transition mode. So we rely on a timer below:
+        mStateMachine.addTransition(STATE_ENTER_TRANSITION_ADDLISTENER,
+                STATE_ENTER_TRANSITION_COMPLETE, EVT_ENTER_TRANSIITON_DONE);
+        // we are expecting app to start delayed enter transition shortly after details row is
+        // loaded, so create a timer and wait for enter transition start.
+        mStateMachine.addTransition(STATE_ENTER_TRANSITION_ADDLISTENER,
+                STATE_ENTER_TRANSITION_PENDING, EVT_DETAILS_ROW_LOADED);
+        // if enter transition not started in the timer, skip to DONE, this can be also true when
+        // startActivity is not giving transition option.
+        mStateMachine.addTransition(STATE_ENTER_TRANSITION_PENDING, STATE_ENTER_TRANSITION_COMPLETE,
+                EVT_ENTER_TRANSIITON_DONE);
+
+        /**
+         * Part 2: modification to the entrance transition defined in BaseFragment
+         */
+        // Must finish enter transition before perform entrance transition.
+        mStateMachine.addTransition(STATE_ENTER_TRANSITION_COMPLETE, STATE_ENTRANCE_PERFORM);
+        // Calling switch to video would hide immediately and skip entrance transition
+        mStateMachine.addTransition(STATE_ENTRANCE_INIT, STATE_SWITCH_TO_VIDEO_IN_ON_CREATE,
+                EVT_SWITCH_TO_VIDEO);
+        mStateMachine.addTransition(STATE_SWITCH_TO_VIDEO_IN_ON_CREATE, STATE_ENTRANCE_COMPLETE);
+        // if the entrance transition is skipped to complete by COND_TRANSITION_NOT_SUPPORTED, we
+        // still need to do the switchToVideo.
+        mStateMachine.addTransition(STATE_ENTRANCE_COMPLETE, STATE_SWITCH_TO_VIDEO_IN_ON_CREATE,
+                EVT_SWITCH_TO_VIDEO);
+
+        // for once the view is created in onStart and prepareEntranceTransition was called, we
+        // could setEntranceStartState:
+        mStateMachine.addTransition(STATE_ENTRANCE_ON_PREPARED,
+                STATE_SET_ENTRANCE_START_STATE, EVT_ONSTART);
+
+        /**
+         * Part 3: onSafeStart()
+         */
+        // for onSafeStart: the condition is onStart called, entrance transition complete
+        mStateMachine.addTransition(STATE_START, STATE_ON_SAFE_START, EVT_ONSTART);
+        mStateMachine.addTransition(STATE_ENTRANCE_COMPLETE, STATE_ON_SAFE_START);
+        mStateMachine.addTransition(STATE_ENTER_TRANSITION_COMPLETE, STATE_ON_SAFE_START);
+    }
+
+    private class SetSelectionRunnable implements Runnable {
+        int mPosition;
+        boolean mSmooth = true;
+
+        SetSelectionRunnable() {
+        }
+
+        @Override
+        public void run() {
+            if (mRowsFragment == null) {
+                return;
+            }
+            mRowsFragment.setSelectedPosition(mPosition, mSmooth);
+        }
+    }
+
+    TransitionListener mEnterTransitionListener = new TransitionListener() {
+        @Override
+        public void onTransitionStart(Object transition) {
+            if (mWaitEnterTransitionTimeout != null) {
+                // cancel task of WaitEnterTransitionTimeout, we will clearPendingEnterTransition
+                // when transition finishes.
+                mWaitEnterTransitionTimeout.mRef.clear();
+            }
+        }
+
+        @Override
+        public void onTransitionCancel(Object transition) {
+            mStateMachine.fireEvent(EVT_ENTER_TRANSIITON_DONE);
+        }
+
+        @Override
+        public void onTransitionEnd(Object transition) {
+            mStateMachine.fireEvent(EVT_ENTER_TRANSIITON_DONE);
+        }
+    };
+
+    TransitionListener mReturnTransitionListener = new TransitionListener() {
+        @Override
+        public void onTransitionStart(Object transition) {
+            onReturnTransitionStart();
+        }
+    };
+
+    BrowseFrameLayout mRootView;
+    View mBackgroundView;
+    Drawable mBackgroundDrawable;
+    Fragment mVideoFragment;
+    DetailsParallax mDetailsParallax;
+    RowsFragment mRowsFragment;
+    ObjectAdapter mAdapter;
+    int mContainerListAlignTop;
+    BaseOnItemViewSelectedListener mExternalOnItemViewSelectedListener;
+    BaseOnItemViewClickedListener mOnItemViewClickedListener;
+    DetailsFragmentBackgroundController mDetailsBackgroundController;
+
+    // A temporarily flag when switchToVideo() is called in onCreate(), if mPendingFocusOnVideo is
+    // true, we will focus to VideoFragment immediately after video fragment's view is created.
+    boolean mPendingFocusOnVideo = false;
+
+    WaitEnterTransitionTimeout mWaitEnterTransitionTimeout;
+
+    Object mSceneAfterEntranceTransition;
+
+    final SetSelectionRunnable mSetSelectionRunnable = new SetSelectionRunnable();
+
+    final BaseOnItemViewSelectedListener<Object> mOnItemViewSelectedListener =
+            new BaseOnItemViewSelectedListener<Object>() {
+        @Override
+        public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
+                                   RowPresenter.ViewHolder rowViewHolder, Object row) {
+            int position = mRowsFragment.getVerticalGridView().getSelectedPosition();
+            int subposition = mRowsFragment.getVerticalGridView().getSelectedSubPosition();
+            if (DEBUG) Log.v(TAG, "row selected position " + position
+                    + " subposition " + subposition);
+            onRowSelected(position, subposition);
+            if (mExternalOnItemViewSelectedListener != null) {
+                mExternalOnItemViewSelectedListener.onItemSelected(itemViewHolder, item,
+                        rowViewHolder, row);
+            }
+        }
+    };
+
+    /**
+     * Sets the list of rows for the fragment.
+     */
+    public void setAdapter(ObjectAdapter adapter) {
+        mAdapter = adapter;
+        Presenter[] presenters = adapter.getPresenterSelector().getPresenters();
+        if (presenters != null) {
+            for (int i = 0; i < presenters.length; i++) {
+                setupPresenter(presenters[i]);
+            }
+        } else {
+            Log.e(TAG, "PresenterSelector.getPresenters() not implemented");
+        }
+        if (mRowsFragment != null) {
+            mRowsFragment.setAdapter(adapter);
+        }
+    }
+
+    /**
+     * Returns the list of rows.
+     */
+    public ObjectAdapter getAdapter() {
+        return mAdapter;
+    }
+
+    /**
+     * Sets an item selection listener.
+     */
+    public void setOnItemViewSelectedListener(BaseOnItemViewSelectedListener listener) {
+        mExternalOnItemViewSelectedListener = listener;
+    }
+
+    /**
+     * Sets an item clicked listener.
+     */
+    public void setOnItemViewClickedListener(BaseOnItemViewClickedListener listener) {
+        if (mOnItemViewClickedListener != listener) {
+            mOnItemViewClickedListener = listener;
+            if (mRowsFragment != null) {
+                mRowsFragment.setOnItemViewClickedListener(listener);
+            }
+        }
+    }
+
+    /**
+     * Returns the item clicked listener.
+     */
+    public BaseOnItemViewClickedListener getOnItemViewClickedListener() {
+        return mOnItemViewClickedListener;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mContainerListAlignTop =
+            getResources().getDimensionPixelSize(R.dimen.lb_details_rows_align_top);
+
+        Activity activity = getActivity();
+        if (activity != null) {
+            Object transition = TransitionHelper.getEnterTransition(activity.getWindow());
+            if (transition == null) {
+                mStateMachine.fireEvent(EVT_NO_ENTER_TRANSITION);
+            }
+            transition = TransitionHelper.getReturnTransition(activity.getWindow());
+            if (transition != null) {
+                TransitionHelper.addTransitionListener(transition, mReturnTransitionListener);
+            }
+        } else {
+            mStateMachine.fireEvent(EVT_NO_ENTER_TRANSITION);
+        }
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        mRootView = (BrowseFrameLayout) inflater.inflate(
+                R.layout.lb_details_fragment, container, false);
+        mBackgroundView = mRootView.findViewById(R.id.details_background_view);
+        if (mBackgroundView != null) {
+            mBackgroundView.setBackground(mBackgroundDrawable);
+        }
+        mRowsFragment = (RowsFragment) getChildFragmentManager().findFragmentById(
+                R.id.details_rows_dock);
+        if (mRowsFragment == null) {
+            mRowsFragment = new RowsFragment();
+            getChildFragmentManager().beginTransaction()
+                    .replace(R.id.details_rows_dock, mRowsFragment).commit();
+        }
+        installTitleView(inflater, mRootView, savedInstanceState);
+        mRowsFragment.setAdapter(mAdapter);
+        mRowsFragment.setOnItemViewSelectedListener(mOnItemViewSelectedListener);
+        mRowsFragment.setOnItemViewClickedListener(mOnItemViewClickedListener);
+
+        mSceneAfterEntranceTransition = TransitionHelper.createScene(mRootView, new Runnable() {
+            @Override
+            public void run() {
+                mRowsFragment.setEntranceTransitionState(true);
+            }
+        });
+
+        setupDpadNavigation();
+
+        if (Build.VERSION.SDK_INT >= 21) {
+            // Setup adapter listener to work with ParallaxTransition (>= API 21).
+            mRowsFragment.setExternalAdapterListener(new ItemBridgeAdapter.AdapterListener() {
+                @Override
+                public void onCreate(ItemBridgeAdapter.ViewHolder vh) {
+                    if (mDetailsParallax != null && vh.getViewHolder()
+                            instanceof FullWidthDetailsOverviewRowPresenter.ViewHolder) {
+                        FullWidthDetailsOverviewRowPresenter.ViewHolder rowVh =
+                                (FullWidthDetailsOverviewRowPresenter.ViewHolder)
+                                        vh.getViewHolder();
+                        rowVh.getOverviewView().setTag(R.id.lb_parallax_source,
+                                mDetailsParallax);
+                    }
+                }
+            });
+        }
+        return mRootView;
+    }
+
+    /**
+     * @deprecated override {@link #onInflateTitleView(LayoutInflater,ViewGroup,Bundle)} instead.
+     */
+    @Deprecated
+    protected View inflateTitle(LayoutInflater inflater, ViewGroup parent,
+            Bundle savedInstanceState) {
+        return super.onInflateTitleView(inflater, parent, savedInstanceState);
+    }
+
+    @Override
+    public View onInflateTitleView(LayoutInflater inflater, ViewGroup parent,
+                                   Bundle savedInstanceState) {
+        return inflateTitle(inflater, parent, savedInstanceState);
+    }
+
+    void setVerticalGridViewLayout(VerticalGridView listview) {
+        // align the top edge of item to a fixed position
+        listview.setItemAlignmentOffset(-mContainerListAlignTop);
+        listview.setItemAlignmentOffsetPercent(VerticalGridView.ITEM_ALIGN_OFFSET_PERCENT_DISABLED);
+        listview.setWindowAlignmentOffset(0);
+        listview.setWindowAlignmentOffsetPercent(VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
+        listview.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE);
+    }
+
+    /**
+     * Called to setup each Presenter of Adapter passed in {@link #setAdapter(ObjectAdapter)}.Note
+     * that setup should only change the Presenter behavior that is meaningful in DetailsFragment.
+     * For example how a row is aligned in details Fragment.   The default implementation invokes
+     * {@link #setupDetailsOverviewRowPresenter(FullWidthDetailsOverviewRowPresenter)}
+     *
+     */
+    protected void setupPresenter(Presenter rowPresenter) {
+        if (rowPresenter instanceof FullWidthDetailsOverviewRowPresenter) {
+            setupDetailsOverviewRowPresenter((FullWidthDetailsOverviewRowPresenter) rowPresenter);
+        }
+    }
+
+    /**
+     * Called to setup {@link FullWidthDetailsOverviewRowPresenter}.  The default implementation
+     * adds two alignment positions({@link ItemAlignmentFacet}) for ViewHolder of
+     * FullWidthDetailsOverviewRowPresenter to align in fragment.
+     */
+    protected void setupDetailsOverviewRowPresenter(FullWidthDetailsOverviewRowPresenter presenter) {
+        ItemAlignmentFacet facet = new ItemAlignmentFacet();
+        // by default align details_frame to half window height
+        ItemAlignmentFacet.ItemAlignmentDef alignDef1 = new ItemAlignmentFacet.ItemAlignmentDef();
+        alignDef1.setItemAlignmentViewId(R.id.details_frame);
+        alignDef1.setItemAlignmentOffset(- getResources()
+                .getDimensionPixelSize(R.dimen.lb_details_v2_align_pos_for_actions));
+        alignDef1.setItemAlignmentOffsetPercent(0);
+        // when description is selected, align details_frame to top edge
+        ItemAlignmentFacet.ItemAlignmentDef alignDef2 = new ItemAlignmentFacet.ItemAlignmentDef();
+        alignDef2.setItemAlignmentViewId(R.id.details_frame);
+        alignDef2.setItemAlignmentFocusViewId(R.id.details_overview_description);
+        alignDef2.setItemAlignmentOffset(- getResources()
+                .getDimensionPixelSize(R.dimen.lb_details_v2_align_pos_for_description));
+        alignDef2.setItemAlignmentOffsetPercent(0);
+        ItemAlignmentFacet.ItemAlignmentDef[] defs =
+                new ItemAlignmentFacet.ItemAlignmentDef[] {alignDef1, alignDef2};
+        facet.setAlignmentDefs(defs);
+        presenter.setFacet(ItemAlignmentFacet.class, facet);
+    }
+
+    VerticalGridView getVerticalGridView() {
+        return mRowsFragment == null ? null : mRowsFragment.getVerticalGridView();
+    }
+
+    /**
+     * Gets embedded RowsFragment showing multiple rows for DetailsFragment.  If view of
+     * DetailsFragment is not created, the method returns null.
+     * @return Embedded RowsFragment showing multiple rows for DetailsFragment.
+     */
+    public RowsFragment getRowsFragment() {
+        return mRowsFragment;
+    }
+
+    /**
+     * Setup dimensions that are only meaningful when the child Fragments are inside
+     * DetailsFragment.
+     */
+    private void setupChildFragmentLayout() {
+        setVerticalGridViewLayout(mRowsFragment.getVerticalGridView());
+    }
+
+    /**
+     * Sets the selected row position with smooth animation.
+     */
+    public void setSelectedPosition(int position) {
+        setSelectedPosition(position, true);
+    }
+
+    /**
+     * Sets the selected row position.
+     */
+    public void setSelectedPosition(int position, boolean smooth) {
+        mSetSelectionRunnable.mPosition = position;
+        mSetSelectionRunnable.mSmooth = smooth;
+        if (getView() != null && getView().getHandler() != null) {
+            getView().getHandler().post(mSetSelectionRunnable);
+        }
+    }
+
+    void switchToVideo() {
+        if (mVideoFragment != null && mVideoFragment.getView() != null) {
+            mVideoFragment.getView().requestFocus();
+        } else {
+            mStateMachine.fireEvent(EVT_SWITCH_TO_VIDEO);
+        }
+    }
+
+    void switchToRows() {
+        mPendingFocusOnVideo = false;
+        VerticalGridView verticalGridView = getVerticalGridView();
+        if (verticalGridView != null && verticalGridView.getChildCount() > 0) {
+            verticalGridView.requestFocus();
+        }
+    }
+
+    /**
+     * This method asks DetailsFragmentBackgroundController to add a fragment for rendering video.
+     * In case the fragment is already there, it will return the existing one. The method must be
+     * called after calling super.onCreate(). App usually does not call this method directly.
+     *
+     * @return Fragment the added or restored fragment responsible for rendering video.
+     * @see DetailsFragmentBackgroundController#onCreateVideoFragment()
+     */
+    final Fragment findOrCreateVideoFragment() {
+        if (mVideoFragment != null) {
+            return mVideoFragment;
+        }
+        Fragment fragment = getChildFragmentManager()
+                .findFragmentById(R.id.video_surface_container);
+        if (fragment == null && mDetailsBackgroundController != null) {
+            FragmentTransaction ft2 = getChildFragmentManager().beginTransaction();
+            ft2.add(android.support.v17.leanback.R.id.video_surface_container,
+                    fragment = mDetailsBackgroundController.onCreateVideoFragment());
+            ft2.commit();
+            if (mPendingFocusOnVideo) {
+                // wait next cycle for Fragment view created so we can focus on it.
+                // This is a bit hack eventually we will do commitNow() which get view immediately.
+                getView().post(new Runnable() {
+                    @Override
+                    public void run() {
+                        if (getView() != null) {
+                            switchToVideo();
+                        }
+                        mPendingFocusOnVideo = false;
+                    }
+                });
+            }
+        }
+        mVideoFragment = fragment;
+        return mVideoFragment;
+    }
+
+    void onRowSelected(int selectedPosition, int selectedSubPosition) {
+        ObjectAdapter adapter = getAdapter();
+        if (( mRowsFragment != null && mRowsFragment.getView() != null
+                && mRowsFragment.getView().hasFocus() && !mPendingFocusOnVideo)
+                && (adapter == null || adapter.size() == 0
+                || (getVerticalGridView().getSelectedPosition() == 0
+                && getVerticalGridView().getSelectedSubPosition() == 0))) {
+            showTitle(true);
+        } else {
+            showTitle(false);
+        }
+        if (adapter != null && adapter.size() > selectedPosition) {
+            final VerticalGridView gridView = getVerticalGridView();
+            final int count = gridView.getChildCount();
+            if (count > 0) {
+                mStateMachine.fireEvent(EVT_DETAILS_ROW_LOADED);
+            }
+            for (int i = 0; i < count; i++) {
+                ItemBridgeAdapter.ViewHolder bridgeViewHolder = (ItemBridgeAdapter.ViewHolder)
+                        gridView.getChildViewHolder(gridView.getChildAt(i));
+                RowPresenter rowPresenter = (RowPresenter) bridgeViewHolder.getPresenter();
+                onSetRowStatus(rowPresenter,
+                        rowPresenter.getRowViewHolder(bridgeViewHolder.getViewHolder()),
+                        bridgeViewHolder.getAdapterPosition(),
+                        selectedPosition, selectedSubPosition);
+            }
+        }
+    }
+
+    /**
+     * Called when onStart and enter transition (postponed/none postponed) and entrance transition
+     * are all finished.
+     */
+    @CallSuper
+    void onSafeStart() {
+        if (mDetailsBackgroundController != null) {
+            mDetailsBackgroundController.onStart();
+        }
+    }
+
+    @CallSuper
+    void onReturnTransitionStart() {
+        if (mDetailsBackgroundController != null) {
+            // first disable parallax effect that auto-start PlaybackGlue.
+            boolean isVideoVisible = mDetailsBackgroundController.disableVideoParallax();
+            // if video is not visible we can safely remove VideoFragment,
+            // otherwise let video playing during return transition.
+            if (!isVideoVisible && mVideoFragment != null) {
+                FragmentTransaction ft2 = getChildFragmentManager().beginTransaction();
+                ft2.remove(mVideoFragment);
+                ft2.commit();
+                mVideoFragment = null;
+            }
+        }
+    }
+
+    @Override
+    public void onStop() {
+        if (mDetailsBackgroundController != null) {
+            mDetailsBackgroundController.onStop();
+        }
+        super.onStop();
+    }
+
+    /**
+     * Called on every visible row to change view status when current selected row position
+     * or selected sub position changed.  Subclass may override.   The default
+     * implementation calls {@link #onSetDetailsOverviewRowStatus(FullWidthDetailsOverviewRowPresenter,
+     * FullWidthDetailsOverviewRowPresenter.ViewHolder, int, int, int)} if presenter is
+     * instance of {@link FullWidthDetailsOverviewRowPresenter}.
+     *
+     * @param presenter   The presenter used to create row ViewHolder.
+     * @param viewHolder  The visible (attached) row ViewHolder, note that it may or may not
+     *                    be selected.
+     * @param adapterPosition  The adapter position of viewHolder inside adapter.
+     * @param selectedPosition The adapter position of currently selected row.
+     * @param selectedSubPosition The sub position within currently selected row.  This is used
+     *                            When a row has multiple alignment positions.
+     */
+    protected void onSetRowStatus(RowPresenter presenter, RowPresenter.ViewHolder viewHolder, int
+            adapterPosition, int selectedPosition, int selectedSubPosition) {
+        if (presenter instanceof FullWidthDetailsOverviewRowPresenter) {
+            onSetDetailsOverviewRowStatus((FullWidthDetailsOverviewRowPresenter) presenter,
+                    (FullWidthDetailsOverviewRowPresenter.ViewHolder) viewHolder,
+                    adapterPosition, selectedPosition, selectedSubPosition);
+        }
+    }
+
+    /**
+     * Called to change DetailsOverviewRow view status when current selected row position
+     * or selected sub position changed.  Subclass may override.   The default
+     * implementation switches between three states based on the positions:
+     * {@link FullWidthDetailsOverviewRowPresenter#STATE_HALF},
+     * {@link FullWidthDetailsOverviewRowPresenter#STATE_FULL} and
+     * {@link FullWidthDetailsOverviewRowPresenter#STATE_SMALL}.
+     *
+     * @param presenter   The presenter used to create row ViewHolder.
+     * @param viewHolder  The visible (attached) row ViewHolder, note that it may or may not
+     *                    be selected.
+     * @param adapterPosition  The adapter position of viewHolder inside adapter.
+     * @param selectedPosition The adapter position of currently selected row.
+     * @param selectedSubPosition The sub position within currently selected row.  This is used
+     *                            When a row has multiple alignment positions.
+     */
+    protected void onSetDetailsOverviewRowStatus(FullWidthDetailsOverviewRowPresenter presenter,
+            FullWidthDetailsOverviewRowPresenter.ViewHolder viewHolder, int adapterPosition,
+            int selectedPosition, int selectedSubPosition) {
+        if (selectedPosition > adapterPosition) {
+            presenter.setState(viewHolder, FullWidthDetailsOverviewRowPresenter.STATE_HALF);
+        } else if (selectedPosition == adapterPosition && selectedSubPosition == 1) {
+            presenter.setState(viewHolder, FullWidthDetailsOverviewRowPresenter.STATE_HALF);
+        } else if (selectedPosition == adapterPosition && selectedSubPosition == 0){
+            presenter.setState(viewHolder, FullWidthDetailsOverviewRowPresenter.STATE_FULL);
+        } else {
+            presenter.setState(viewHolder,
+                    FullWidthDetailsOverviewRowPresenter.STATE_SMALL);
+        }
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+
+        setupChildFragmentLayout();
+        mStateMachine.fireEvent(EVT_ONSTART);
+        if (mDetailsParallax != null) {
+            mDetailsParallax.setRecyclerView(mRowsFragment.getVerticalGridView());
+        }
+        if (mPendingFocusOnVideo) {
+            slideOutGridView();
+        } else if (!getView().hasFocus()) {
+            mRowsFragment.getVerticalGridView().requestFocus();
+        }
+    }
+
+    @Override
+    protected Object createEntranceTransition() {
+        return TransitionHelper.loadTransition(FragmentUtil.getContext(DetailsFragment.this),
+                R.transition.lb_details_enter_transition);
+    }
+
+    @Override
+    protected void runEntranceTransition(Object entranceTransition) {
+        TransitionHelper.runTransition(mSceneAfterEntranceTransition, entranceTransition);
+    }
+
+    @Override
+    protected void onEntranceTransitionEnd() {
+        mRowsFragment.onTransitionEnd();
+    }
+
+    @Override
+    protected void onEntranceTransitionPrepare() {
+        mRowsFragment.onTransitionPrepare();
+    }
+
+    @Override
+    protected void onEntranceTransitionStart() {
+        mRowsFragment.onTransitionStart();
+    }
+
+    /**
+     * Returns the {@link DetailsParallax} instance used by
+     * {@link DetailsFragmentBackgroundController} to configure parallax effect of background and
+     * control embedded video playback. App usually does not use this method directly.
+     * App may use this method for other custom parallax tasks.
+     *
+     * @return The DetailsParallax instance attached to the DetailsFragment.
+     */
+    public DetailsParallax getParallax() {
+        if (mDetailsParallax == null) {
+            mDetailsParallax = new DetailsParallax();
+            if (mRowsFragment != null && mRowsFragment.getView() != null) {
+                mDetailsParallax.setRecyclerView(mRowsFragment.getVerticalGridView());
+            }
+        }
+        return mDetailsParallax;
+    }
+
+    /**
+     * Set background drawable shown below foreground rows UI and above
+     * {@link #findOrCreateVideoFragment()}.
+     *
+     * @see DetailsFragmentBackgroundController
+     */
+    void setBackgroundDrawable(Drawable drawable) {
+        if (mBackgroundView != null) {
+            mBackgroundView.setBackground(drawable);
+        }
+        mBackgroundDrawable = drawable;
+    }
+
+    /**
+     * This method does the following
+     * <ul>
+     * <li>sets up focus search handling logic in the root view to enable transitioning between
+     * half screen/full screen/no video mode.</li>
+     *
+     * <li>Sets up the key listener in the root view to intercept events like UP/DOWN and
+     * transition to appropriate mode like half/full screen video.</li>
+     * </ul>
+     */
+    void setupDpadNavigation() {
+        mRootView.setOnChildFocusListener(new BrowseFrameLayout.OnChildFocusListener() {
+
+            @Override
+            public boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
+                return false;
+            }
+
+            @Override
+            public void onRequestChildFocus(View child, View focused) {
+                if (child != mRootView.getFocusedChild()) {
+                    if (child.getId() == R.id.details_fragment_root) {
+                        if (!mPendingFocusOnVideo) {
+                            slideInGridView();
+                            showTitle(true);
+                        }
+                    } else if (child.getId() == R.id.video_surface_container) {
+                        slideOutGridView();
+                        showTitle(false);
+                    } else {
+                        showTitle(true);
+                    }
+                }
+            }
+        });
+        mRootView.setOnFocusSearchListener(new BrowseFrameLayout.OnFocusSearchListener() {
+            @Override
+            public View onFocusSearch(View focused, int direction) {
+                if (mRowsFragment.getVerticalGridView() != null
+                        && mRowsFragment.getVerticalGridView().hasFocus()) {
+                    if (direction == View.FOCUS_UP) {
+                        if (mDetailsBackgroundController != null
+                                && mDetailsBackgroundController.canNavigateToVideoFragment()
+                                && mVideoFragment != null && mVideoFragment.getView() != null) {
+                            return mVideoFragment.getView();
+                        } else if (getTitleView() != null && getTitleView().hasFocusable()) {
+                            return getTitleView();
+                        }
+                    }
+                } else if (getTitleView() != null && getTitleView().hasFocus()) {
+                    if (direction == View.FOCUS_DOWN) {
+                        if (mRowsFragment.getVerticalGridView() != null) {
+                            return mRowsFragment.getVerticalGridView();
+                        }
+                    }
+                }
+                return focused;
+            }
+        });
+
+        // If we press BACK on remote while in full screen video mode, we should
+        // transition back to half screen video playback mode.
+        mRootView.setOnDispatchKeyListener(new View.OnKeyListener() {
+            @Override
+            public boolean onKey(View v, int keyCode, KeyEvent event) {
+                // This is used to check if we are in full screen video mode. This is somewhat
+                // hacky and relies on the behavior of the video helper class to update the
+                // focusability of the video surface view.
+                if (mVideoFragment != null && mVideoFragment.getView() != null
+                        && mVideoFragment.getView().hasFocus()) {
+                    if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE) {
+                        if (getVerticalGridView().getChildCount() > 0) {
+                            getVerticalGridView().requestFocus();
+                            return true;
+                        }
+                    }
+                }
+
+                return false;
+            }
+        });
+    }
+
+    /**
+     * Slides vertical grid view (displaying media item details) out of the screen from below.
+     */
+    void slideOutGridView() {
+        if (getVerticalGridView() != null) {
+            getVerticalGridView().animateOut();
+        }
+    }
+
+    void slideInGridView() {
+        if (getVerticalGridView() != null) {
+            getVerticalGridView().animateIn();
+        }
+    }
+}
diff --git a/leanback/src/android/support/v17/leanback/app/DetailsFragmentBackgroundController.java b/leanback/src/android/support/v17/leanback/app/DetailsFragmentBackgroundController.java
new file mode 100644
index 0000000..25ed723
--- /dev/null
+++ b/leanback/src/android/support/v17/leanback/app/DetailsFragmentBackgroundController.java
@@ -0,0 +1,497 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from {}DetailsSupportFragmentBackgroundController.java.  DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.animation.PropertyValuesHolder;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.ColorInt;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v17.leanback.R;
+import android.support.v17.leanback.graphics.FitWidthBitmapDrawable;
+import android.support.v17.leanback.media.PlaybackGlue;
+import android.support.v17.leanback.media.PlaybackGlueHost;
+import android.support.v17.leanback.widget.DetailsParallaxDrawable;
+import android.support.v17.leanback.widget.ParallaxTarget;
+import android.app.Fragment;
+
+/**
+ * Controller for DetailsFragment parallax background and embedded video play.
+ * <p>
+ * The parallax background drawable is made of two parts: cover drawable (by default
+ * {@link FitWidthBitmapDrawable}) above the details overview row and bottom drawable (by default
+ * {@link ColorDrawable}) below the details overview row. While vertically scrolling rows, the size
+ * of cover drawable and bottom drawable will be updated and the cover drawable will by default
+ * perform a parallax shift using {@link FitWidthBitmapDrawable#PROPERTY_VERTICAL_OFFSET}.
+ * </p>
+ * <pre>
+ *        ***************************
+ *        *      Cover Drawable     *
+ *        * (FitWidthBitmapDrawable)*
+ *        *                         *
+ *        ***************************
+ *        *    DetailsOverviewRow   *
+ *        *                         *
+ *        ***************************
+ *        *     Bottom Drawable     *
+ *        *      (ColorDrawable)    *
+ *        *         Related         *
+ *        *         Content         *
+ *        ***************************
+ * </pre>
+ * Both parallax background drawable and embedded video play are optional. App must call
+ * {@link #enableParallax()} and/or {@link #setupVideoPlayback(PlaybackGlue)} explicitly.
+ * The PlaybackGlue is automatically {@link PlaybackGlue#play()} when fragment starts and
+ * {@link PlaybackGlue#pause()} when fragment stops. When video is ready to play, cover drawable
+ * will be faded out.
+ * Example:
+ * <pre>
+ * DetailsFragmentBackgroundController mController = new DetailsFragmentBackgroundController(this);
+ *
+ * public void onCreate(Bundle savedInstance) {
+ *     super.onCreate(savedInstance);
+ *     MediaPlayerGlue player = new MediaPlayerGlue(..);
+ *     player.setUrl(...);
+ *     mController.enableParallax();
+ *     mController.setupVideoPlayback(player);
+ * }
+ *
+ * static class MyLoadBitmapTask extends ... {
+ *     WeakReference<MyFragment> mFragmentRef;
+ *     MyLoadBitmapTask(MyFragment fragment) {
+ *         mFragmentRef = new WeakReference(fragment);
+ *     }
+ *     protected void onPostExecute(Bitmap bitmap) {
+ *         MyFragment fragment = mFragmentRef.get();
+ *         if (fragment != null) {
+ *             fragment.mController.setCoverBitmap(bitmap);
+ *         }
+ *     }
+ * }
+ *
+ * public void onStart() {
+ *     new MyLoadBitmapTask(this).execute(url);
+ * }
+ *
+ * public void onStop() {
+ *     mController.setCoverBitmap(null);
+ * }
+ * </pre>
+ * <p>
+ * To customize cover drawable and/or bottom drawable, app should call
+ * {@link #enableParallax(Drawable, Drawable, ParallaxTarget.PropertyValuesHolderTarget)}.
+ * If app supplies a custom cover Drawable, it should not call {@link #setCoverBitmap(Bitmap)}.
+ * If app supplies a custom bottom Drawable, it should not call {@link #setSolidColor(int)}.
+ * </p>
+ * <p>
+ * To customize playback fragment, app should override {@link #onCreateVideoFragment()} and
+ * {@link #onCreateGlueHost()}.
+ * </p>
+ *
+ * @deprecated use {@link DetailsSupportFragmentBackgroundController}
+ */
+@Deprecated
+public class DetailsFragmentBackgroundController {
+
+    final DetailsFragment mFragment;
+    DetailsParallaxDrawable mParallaxDrawable;
+    int mParallaxDrawableMaxOffset;
+    PlaybackGlue mPlaybackGlue;
+    DetailsBackgroundVideoHelper mVideoHelper;
+    Bitmap mCoverBitmap;
+    int mSolidColor;
+    boolean mCanUseHost = false;
+    boolean mInitialControlVisible = false;
+
+    private Fragment mLastVideoFragmentForGlueHost;
+
+    /**
+     * Creates a DetailsFragmentBackgroundController for a DetailsFragment. Note that
+     * each DetailsFragment can only associate with one DetailsFragmentBackgroundController.
+     *
+     * @param fragment The DetailsFragment to control background and embedded video playing.
+     * @throws IllegalStateException If fragment was already associated with another controller.
+     */
+    public DetailsFragmentBackgroundController(DetailsFragment fragment) {
+        if (fragment.mDetailsBackgroundController != null) {
+            throw new IllegalStateException("Each DetailsFragment is allowed to initialize "
+                    + "DetailsFragmentBackgroundController once");
+        }
+        fragment.mDetailsBackgroundController = this;
+        mFragment = fragment;
+    }
+
+    /**
+     * Enables default parallax background using a {@link FitWidthBitmapDrawable} as cover drawable
+     * and {@link ColorDrawable} as bottom drawable. A vertical parallax movement will be applied
+     * to the FitWidthBitmapDrawable. App may use {@link #setSolidColor(int)} and
+     * {@link #setCoverBitmap(Bitmap)} to change the content of bottom drawable and cover drawable.
+     * This method must be called before {@link #setupVideoPlayback(PlaybackGlue)}.
+     *
+     * @see #setCoverBitmap(Bitmap)
+     * @see #setSolidColor(int)
+     * @throws IllegalStateException If {@link #setupVideoPlayback(PlaybackGlue)} was called.
+     */
+    public void enableParallax() {
+        int offset = mParallaxDrawableMaxOffset;
+        if (offset == 0) {
+            offset = FragmentUtil.getContext(mFragment).getResources()
+                    .getDimensionPixelSize(R.dimen.lb_details_cover_drawable_parallax_movement);
+        }
+        Drawable coverDrawable = new FitWidthBitmapDrawable();
+        ColorDrawable colorDrawable = new ColorDrawable();
+        enableParallax(coverDrawable, colorDrawable,
+                new ParallaxTarget.PropertyValuesHolderTarget(
+                        coverDrawable,
+                        PropertyValuesHolder.ofInt(FitWidthBitmapDrawable.PROPERTY_VERTICAL_OFFSET,
+                                0, -offset)
+                ));
+    }
+
+    /**
+     * Enables parallax background using a custom cover drawable at top and a custom bottom
+     * drawable. This method must be called before {@link #setupVideoPlayback(PlaybackGlue)}.
+     *
+     * @param coverDrawable Custom cover drawable shown at top. {@link #setCoverBitmap(Bitmap)}
+     *                      will not work if coverDrawable is not {@link FitWidthBitmapDrawable};
+     *                      in that case it's app's responsibility to set content into
+     *                      coverDrawable.
+     * @param bottomDrawable Drawable shown at bottom. {@link #setSolidColor(int)} will not work
+     *                       if bottomDrawable is not {@link ColorDrawable}; in that case it's app's
+     *                       responsibility to set content of bottomDrawable.
+     * @param coverDrawableParallaxTarget Target to perform parallax effect within coverDrawable.
+     *                                    Use null for no parallax movement effect.
+     *                                    Example to move bitmap within FitWidthBitmapDrawable:
+     *                                    new ParallaxTarget.PropertyValuesHolderTarget(
+     *                                        coverDrawable, PropertyValuesHolder.ofInt(
+     *                                            FitWidthBitmapDrawable.PROPERTY_VERTICAL_OFFSET,
+     *                                            0, -120))
+     * @throws IllegalStateException If {@link #setupVideoPlayback(PlaybackGlue)} was called.
+     */
+    public void enableParallax(@NonNull Drawable coverDrawable, @NonNull Drawable bottomDrawable,
+                               @Nullable ParallaxTarget.PropertyValuesHolderTarget
+                                       coverDrawableParallaxTarget) {
+        if (mParallaxDrawable != null) {
+            return;
+        }
+        // if bitmap is set before enableParallax, use it as initial value.
+        if (mCoverBitmap != null && coverDrawable instanceof FitWidthBitmapDrawable) {
+            ((FitWidthBitmapDrawable) coverDrawable).setBitmap(mCoverBitmap);
+        }
+        // if solid color is set before enableParallax, use it as initial value.
+        if (mSolidColor != Color.TRANSPARENT && bottomDrawable instanceof ColorDrawable) {
+            ((ColorDrawable) bottomDrawable).setColor(mSolidColor);
+        }
+        if (mPlaybackGlue != null) {
+            throw new IllegalStateException("enableParallaxDrawable must be called before "
+                    + "enableVideoPlayback");
+        }
+        mParallaxDrawable = new DetailsParallaxDrawable(
+                FragmentUtil.getContext(mFragment),
+                mFragment.getParallax(),
+                coverDrawable,
+                bottomDrawable,
+                coverDrawableParallaxTarget);
+        mFragment.setBackgroundDrawable(mParallaxDrawable);
+        // create a VideoHelper with null PlaybackGlue for changing CoverDrawable visibility
+        // before PlaybackGlue is ready.
+        mVideoHelper = new DetailsBackgroundVideoHelper(null,
+                mFragment.getParallax(), mParallaxDrawable.getCoverDrawable());
+    }
+
+    /**
+     * Enable video playback and set proper {@link PlaybackGlueHost}. This method by default
+     * creates a VideoFragment and VideoFragmentGlueHost to host the PlaybackGlue.
+     * This method must be called after calling details Fragment super.onCreate(). This method
+     * can be called multiple times to replace existing PlaybackGlue or calling
+     * setupVideoPlayback(null) to clear. Note a typical {@link PlaybackGlue} subclass releases
+     * resources in {@link PlaybackGlue#onDetachedFromHost()}, when the {@link PlaybackGlue}
+     * subclass is not doing that, it's app's responsibility to release the resources.
+     *
+     * @param playbackGlue The new PlaybackGlue to set as background or null to clear existing one.
+     * @see #onCreateVideoFragment()
+     * @see #onCreateGlueHost().
+     */
+    @SuppressWarnings("ReferenceEquality")
+    public void setupVideoPlayback(@NonNull PlaybackGlue playbackGlue) {
+        if (mPlaybackGlue == playbackGlue) {
+            return;
+        }
+
+        PlaybackGlueHost playbackGlueHost = null;
+        if (mPlaybackGlue != null) {
+            playbackGlueHost = mPlaybackGlue.getHost();
+            mPlaybackGlue.setHost(null);
+        }
+
+        mPlaybackGlue = playbackGlue;
+        mVideoHelper.setPlaybackGlue(mPlaybackGlue);
+        if (mCanUseHost && mPlaybackGlue != null) {
+            if (playbackGlueHost == null
+                    || mLastVideoFragmentForGlueHost != findOrCreateVideoFragment()) {
+                mPlaybackGlue.setHost(createGlueHost());
+                mLastVideoFragmentForGlueHost = findOrCreateVideoFragment();
+            } else {
+                mPlaybackGlue.setHost(playbackGlueHost);
+            }
+        }
+    }
+
+    /**
+     * Returns current PlaybackGlue or null if not set or cleared.
+     *
+     * @return Current PlaybackGlue or null
+     */
+    public final PlaybackGlue getPlaybackGlue() {
+        return mPlaybackGlue;
+    }
+
+    /**
+     * Precondition allows user navigate to video fragment using DPAD. Default implementation
+     * returns true if PlaybackGlue is not null. Subclass may override, e.g. only allow navigation
+     * when {@link PlaybackGlue#isPrepared()} is true. Note this method does not block
+     * app calls {@link #switchToVideo}.
+     *
+     * @return True allow to navigate to video fragment.
+     */
+    public boolean canNavigateToVideoFragment() {
+        return mPlaybackGlue != null;
+    }
+
+    void switchToVideoBeforeCreate() {
+        mVideoHelper.crossFadeBackgroundToVideo(true, true);
+        mInitialControlVisible = true;
+    }
+
+    /**
+     * Switch to video fragment, note that this method is not affected by result of
+     * {@link #canNavigateToVideoFragment()}. If the method is called in DetailsFragment.onCreate()
+     * it will make video fragment to be initially focused once it is created.
+     * <p>
+     * Calling switchToVideo() in DetailsFragment.onCreate() will clear the activity enter
+     * transition and shared element transition.
+     * </p>
+     * <p>
+     * If switchToVideo() is called after {@link DetailsFragment#prepareEntranceTransition()} and
+     * before {@link DetailsFragment#onEntranceTransitionEnd()}, it will be ignored.
+     * </p>
+     * <p>
+     * If {@link DetailsFragment#prepareEntranceTransition()} is called after switchToVideo(), an
+     * IllegalStateException will be thrown.
+     * </p>
+     */
+    public final void switchToVideo() {
+        mFragment.switchToVideo();
+    }
+
+    /**
+     * Switch to rows fragment.
+     */
+    public final void switchToRows() {
+        mFragment.switchToRows();
+    }
+
+    /**
+     * When fragment is started and no running transition. First set host if not yet set, second
+     * start playing if it was paused before.
+     */
+    void onStart() {
+        if (!mCanUseHost) {
+            mCanUseHost = true;
+            if (mPlaybackGlue != null) {
+                mPlaybackGlue.setHost(createGlueHost());
+                mLastVideoFragmentForGlueHost = findOrCreateVideoFragment();
+            }
+        }
+        if (mPlaybackGlue != null && mPlaybackGlue.isPrepared()) {
+            mPlaybackGlue.play();
+        }
+    }
+
+    void onStop() {
+        if (mPlaybackGlue != null) {
+            mPlaybackGlue.pause();
+        }
+    }
+
+    /**
+     * Disable parallax that would auto-start video playback
+     * @return true if video fragment is visible or false otherwise.
+     */
+    boolean disableVideoParallax() {
+        if (mVideoHelper != null) {
+            mVideoHelper.stopParallax();
+            return mVideoHelper.isVideoVisible();
+        }
+        return false;
+    }
+
+    /**
+     * Returns the cover drawable at top. Returns null if {@link #enableParallax()} is not called.
+     * By default it's a {@link FitWidthBitmapDrawable}.
+     *
+     * @return The cover drawable at top.
+     */
+    public final Drawable getCoverDrawable() {
+        if (mParallaxDrawable == null) {
+            return null;
+        }
+        return mParallaxDrawable.getCoverDrawable();
+    }
+
+    /**
+     * Returns the drawable at bottom. Returns null if {@link #enableParallax()} is not called.
+     * By default it's a {@link ColorDrawable}.
+     *
+     * @return The bottom drawable.
+     */
+    public final Drawable getBottomDrawable() {
+        if (mParallaxDrawable == null) {
+            return null;
+        }
+        return mParallaxDrawable.getBottomDrawable();
+    }
+
+    /**
+     * Creates a Fragment to host {@link PlaybackGlue}. Returns a new {@link VideoFragment} by
+     * default. App may override and return a different fragment and it also must override
+     * {@link #onCreateGlueHost()}.
+     *
+     * @return A new fragment used in {@link #onCreateGlueHost()}.
+     * @see #onCreateGlueHost()
+     * @see #setupVideoPlayback(PlaybackGlue)
+     */
+    public Fragment onCreateVideoFragment() {
+        return new VideoFragment();
+    }
+
+    /**
+     * Creates a PlaybackGlueHost to host PlaybackGlue. App may override this if it overrides
+     * {@link #onCreateVideoFragment()}. This method must be called after calling Fragment
+     * super.onCreate(). When override this method, app may call
+     * {@link #findOrCreateVideoFragment()} to get or create a fragment.
+     *
+     * @return A new PlaybackGlueHost to host PlaybackGlue.
+     * @see #onCreateVideoFragment()
+     * @see #findOrCreateVideoFragment()
+     * @see #setupVideoPlayback(PlaybackGlue)
+     */
+    public PlaybackGlueHost onCreateGlueHost() {
+        return new VideoFragmentGlueHost((VideoFragment) findOrCreateVideoFragment());
+    }
+
+    PlaybackGlueHost createGlueHost() {
+        PlaybackGlueHost host = onCreateGlueHost();
+        if (mInitialControlVisible) {
+            host.showControlsOverlay(false);
+        } else {
+            host.hideControlsOverlay(false);
+        }
+        return host;
+    }
+
+    /**
+     * Adds or gets fragment for rendering video in DetailsFragment. A subclass that
+     * overrides {@link #onCreateGlueHost()} should call this method to get a fragment for creating
+     * a {@link PlaybackGlueHost}.
+     *
+     * @return Fragment the added or restored fragment responsible for rendering video.
+     * @see #onCreateGlueHost()
+     */
+    public final Fragment findOrCreateVideoFragment() {
+        return mFragment.findOrCreateVideoFragment();
+    }
+
+    /**
+     * Convenient method to set Bitmap in cover drawable. If app is not using default
+     * {@link FitWidthBitmapDrawable}, app should not use this method  It's safe to call
+     * setCoverBitmap() before calling {@link #enableParallax()}.
+     *
+     * @param bitmap bitmap to set as cover.
+     */
+    public final void setCoverBitmap(Bitmap bitmap) {
+        mCoverBitmap = bitmap;
+        Drawable drawable = getCoverDrawable();
+        if (drawable instanceof FitWidthBitmapDrawable) {
+            ((FitWidthBitmapDrawable) drawable).setBitmap(mCoverBitmap);
+        }
+    }
+
+    /**
+     * Returns Bitmap set by {@link #setCoverBitmap(Bitmap)}.
+     *
+     * @return Bitmap for cover drawable.
+     */
+    public final Bitmap getCoverBitmap() {
+        return mCoverBitmap;
+    }
+
+    /**
+     * Returns color set by {@link #setSolidColor(int)}.
+     *
+     * @return Solid color used for bottom drawable.
+     */
+    public final @ColorInt int getSolidColor() {
+        return mSolidColor;
+    }
+
+    /**
+     * Convenient method to set color in bottom drawable. If app is not using default
+     * {@link ColorDrawable}, app should not use this method. It's safe to call setSolidColor()
+     * before calling {@link #enableParallax()}.
+     *
+     * @param color color for bottom drawable.
+     */
+    public final void setSolidColor(@ColorInt int color) {
+        mSolidColor = color;
+        Drawable bottomDrawable = getBottomDrawable();
+        if (bottomDrawable instanceof ColorDrawable) {
+            ((ColorDrawable) bottomDrawable).setColor(color);
+        }
+    }
+
+    /**
+     * Sets default parallax offset in pixels for bitmap moving vertically. This method must
+     * be called before {@link #enableParallax()}.
+     *
+     * @param offset Offset in pixels (e.g. 120).
+     * @see #enableParallax()
+     */
+    public final void setParallaxDrawableMaxOffset(int offset) {
+        if (mParallaxDrawable != null) {
+            throw new IllegalStateException("enableParallax already called");
+        }
+        mParallaxDrawableMaxOffset = offset;
+    }
+
+    /**
+     * Returns Default parallax offset in pixels for bitmap moving vertically.
+     * When 0, a default value would be used.
+     *
+     * @return Default parallax offset in pixels for bitmap moving vertically.
+     * @see #enableParallax()
+     */
+    public final int getParallaxDrawableMaxOffset() {
+        return mParallaxDrawableMaxOffset;
+    }
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/DetailsSupportFragment.java b/leanback/src/android/support/v17/leanback/app/DetailsSupportFragment.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/app/DetailsSupportFragment.java
rename to leanback/src/android/support/v17/leanback/app/DetailsSupportFragment.java
diff --git a/v17/leanback/src/android/support/v17/leanback/app/DetailsSupportFragmentBackgroundController.java b/leanback/src/android/support/v17/leanback/app/DetailsSupportFragmentBackgroundController.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/app/DetailsSupportFragmentBackgroundController.java
rename to leanback/src/android/support/v17/leanback/app/DetailsSupportFragmentBackgroundController.java
diff --git a/leanback/src/android/support/v17/leanback/app/ErrorFragment.java b/leanback/src/android/support/v17/leanback/app/ErrorFragment.java
new file mode 100644
index 0000000..eda0de1
--- /dev/null
+++ b/leanback/src/android/support/v17/leanback/app/ErrorFragment.java
@@ -0,0 +1,247 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from ErrorSupportFragment.java.  DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.graphics.Paint;
+import android.graphics.Paint.FontMetricsInt;
+import android.graphics.PixelFormat;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.support.v17.leanback.R;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+/**
+ * A fragment for displaying an error indication.
+ * @deprecated use {@link ErrorSupportFragment}
+ */
+@Deprecated
+public class ErrorFragment extends BrandedFragment {
+
+    private ViewGroup mErrorFrame;
+    private ImageView mImageView;
+    private TextView mTextView;
+    private Button mButton;
+    private Drawable mDrawable;
+    private CharSequence mMessage;
+    private String mButtonText;
+    private View.OnClickListener mButtonClickListener;
+    private Drawable mBackgroundDrawable;
+    private boolean mIsBackgroundTranslucent = true;
+
+    /**
+     * Sets the default background.
+     *
+     * @param translucent True to set a translucent background.
+     */
+    public void setDefaultBackground(boolean translucent) {
+        mBackgroundDrawable = null;
+        mIsBackgroundTranslucent = translucent;
+        updateBackground();
+        updateMessage();
+    }
+
+    /**
+     * Returns true if the background is translucent.
+     */
+    public boolean isBackgroundTranslucent() {
+        return mIsBackgroundTranslucent;
+    }
+
+    /**
+     * Sets a drawable for the fragment background.
+     *
+     * @param drawable The drawable used for the background.
+     */
+    public void setBackgroundDrawable(Drawable drawable) {
+        mBackgroundDrawable = drawable;
+        if (drawable != null) {
+            final int opacity = drawable.getOpacity();
+            mIsBackgroundTranslucent = (opacity == PixelFormat.TRANSLUCENT
+                    || opacity == PixelFormat.TRANSPARENT);
+        }
+        updateBackground();
+        updateMessage();
+    }
+
+    /**
+     * Returns the background drawable.  May be null if a default is used.
+     */
+    public Drawable getBackgroundDrawable() {
+        return mBackgroundDrawable;
+    }
+
+    /**
+     * Sets the drawable to be used for the error image.
+     *
+     * @param drawable The drawable used for the error image.
+     */
+    public void setImageDrawable(Drawable drawable) {
+        mDrawable = drawable;
+        updateImageDrawable();
+    }
+
+    /**
+     * Returns the drawable used for the error image.
+     */
+    public Drawable getImageDrawable() {
+        return mDrawable;
+    }
+
+    /**
+     * Sets the error message.
+     *
+     * @param message The error message.
+     */
+    public void setMessage(CharSequence message) {
+        mMessage = message;
+        updateMessage();
+    }
+
+    /**
+     * Returns the error message.
+     */
+    public CharSequence getMessage() {
+        return mMessage;
+    }
+
+    /**
+     * Sets the button text.
+     *
+     * @param text The button text.
+     */
+    public void setButtonText(String text) {
+        mButtonText = text;
+        updateButton();
+    }
+
+    /**
+     * Returns the button text.
+     */
+    public String getButtonText() {
+        return mButtonText;
+    }
+
+    /**
+     * Set the button click listener.
+     *
+     * @param clickListener The click listener for the button.
+     */
+    public void setButtonClickListener(View.OnClickListener clickListener) {
+        mButtonClickListener = clickListener;
+        updateButton();
+    }
+
+    /**
+     * Returns the button click listener.
+     */
+    public View.OnClickListener getButtonClickListener() {
+        return mButtonClickListener;
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        View root = inflater.inflate(R.layout.lb_error_fragment, container, false);
+
+        mErrorFrame = (ViewGroup) root.findViewById(R.id.error_frame);
+        updateBackground();
+
+        installTitleView(inflater, mErrorFrame, savedInstanceState);
+
+        mImageView = (ImageView) root.findViewById(R.id.image);
+        updateImageDrawable();
+
+        mTextView = (TextView) root.findViewById(R.id.message);
+        updateMessage();
+
+        mButton = (Button) root.findViewById(R.id.button);
+        updateButton();
+
+        FontMetricsInt metrics = getFontMetricsInt(mTextView);
+        int underImageBaselineMargin = container.getResources().getDimensionPixelSize(
+                R.dimen.lb_error_under_image_baseline_margin);
+        setTopMargin(mTextView, underImageBaselineMargin + metrics.ascent);
+
+        int underMessageBaselineMargin = container.getResources().getDimensionPixelSize(
+                R.dimen.lb_error_under_message_baseline_margin);
+        setTopMargin(mButton, underMessageBaselineMargin - metrics.descent);
+
+        return root;
+    }
+
+    private void updateBackground() {
+        if (mErrorFrame != null) {
+            if (mBackgroundDrawable != null) {
+                mErrorFrame.setBackground(mBackgroundDrawable);
+            } else {
+                mErrorFrame.setBackgroundColor(mErrorFrame.getResources().getColor(
+                        mIsBackgroundTranslucent
+                                ? R.color.lb_error_background_color_translucent
+                                : R.color.lb_error_background_color_opaque));
+            }
+        }
+    }
+
+    private void updateMessage() {
+        if (mTextView != null) {
+            mTextView.setText(mMessage);
+            mTextView.setVisibility(TextUtils.isEmpty(mMessage) ? View.GONE : View.VISIBLE);
+        }
+    }
+
+    private void updateImageDrawable() {
+        if (mImageView != null) {
+            mImageView.setImageDrawable(mDrawable);
+            mImageView.setVisibility(mDrawable == null ? View.GONE : View.VISIBLE);
+        }
+    }
+
+    private void updateButton() {
+        if (mButton != null) {
+            mButton.setText(mButtonText);
+            mButton.setOnClickListener(mButtonClickListener);
+            mButton.setVisibility(TextUtils.isEmpty(mButtonText) ? View.GONE : View.VISIBLE);
+            mButton.requestFocus();
+        }
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        mErrorFrame.requestFocus();
+    }
+
+    private static FontMetricsInt getFontMetricsInt(TextView textView) {
+        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
+        paint.setTextSize(textView.getTextSize());
+        paint.setTypeface(textView.getTypeface());
+        return paint.getFontMetricsInt();
+    }
+
+    private static void setTopMargin(TextView textView, int topMargin) {
+        ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) textView.getLayoutParams();
+        lp.topMargin = topMargin;
+        textView.setLayoutParams(lp);
+    }
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/ErrorSupportFragment.java b/leanback/src/android/support/v17/leanback/app/ErrorSupportFragment.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/app/ErrorSupportFragment.java
rename to leanback/src/android/support/v17/leanback/app/ErrorSupportFragment.java
diff --git a/v17/leanback/src/android/support/v17/leanback/app/FragmentUtil.java b/leanback/src/android/support/v17/leanback/app/FragmentUtil.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/app/FragmentUtil.java
rename to leanback/src/android/support/v17/leanback/app/FragmentUtil.java
diff --git a/leanback/src/android/support/v17/leanback/app/GuidedStepFragment.java b/leanback/src/android/support/v17/leanback/app/GuidedStepFragment.java
new file mode 100644
index 0000000..9be350d
--- /dev/null
+++ b/leanback/src/android/support/v17/leanback/app/GuidedStepFragment.java
@@ -0,0 +1,1420 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from GuidedStepSupportFragment.java.  DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.content.Context;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.RestrictTo;
+import android.support.v17.leanback.R;
+import android.support.v17.leanback.transition.TransitionHelper;
+import android.support.v17.leanback.widget.DiffCallback;
+import android.support.v17.leanback.widget.GuidanceStylist;
+import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
+import android.support.v17.leanback.widget.GuidedAction;
+import android.support.v17.leanback.widget.GuidedActionAdapter;
+import android.support.v17.leanback.widget.GuidedActionAdapterGroup;
+import android.support.v17.leanback.widget.GuidedActionsStylist;
+import android.support.v17.leanback.widget.NonOverlappingLinearLayout;
+import android.support.v4.app.ActivityCompat;
+import android.app.Fragment;
+import android.app.Activity;
+import android.app.FragmentManager;
+import android.app.FragmentManager.BackStackEntry;
+import android.app.FragmentTransaction;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.ContextThemeWrapper;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A GuidedStepFragment is used to guide the user through a decision or series of decisions.
+ * It is composed of a guidance view on the left and a view on the right containing a list of
+ * possible actions.
+ * <p>
+ * <h3>Basic Usage</h3>
+ * <p>
+ * Clients of GuidedStepFragment must create a custom subclass to attach to their Activities.
+ * This custom subclass provides the information necessary to construct the user interface and
+ * respond to user actions. At a minimum, subclasses should override:
+ * <ul>
+ * <li>{@link #onCreateGuidance}, to provide instructions to the user</li>
+ * <li>{@link #onCreateActions}, to provide a set of {@link GuidedAction}s the user can take</li>
+ * <li>{@link #onGuidedActionClicked}, to respond to those actions</li>
+ * </ul>
+ * <p>
+ * Clients use following helper functions to add GuidedStepFragment to Activity or FragmentManager:
+ * <ul>
+ * <li>{@link #addAsRoot(Activity, GuidedStepFragment, int)}, to be called during Activity onCreate,
+ * adds GuidedStepFragment as the first Fragment in activity.</li>
+ * <li>{@link #add(FragmentManager, GuidedStepFragment)} or {@link #add(FragmentManager,
+ * GuidedStepFragment, int)}, to add GuidedStepFragment on top of existing Fragments or
+ * replacing existing GuidedStepFragment when moving forward to next step.</li>
+ * <li>{@link #finishGuidedStepFragments()} can either finish the activity or pop all
+ * GuidedStepFragment from stack.
+ * <li>If app chooses not to use the helper function, it is the app's responsibility to call
+ * {@link #setUiStyle(int)} to select fragment transition and remember the stack entry where it
+ * need pops to.
+ * </ul>
+ * <h3>Theming and Stylists</h3>
+ * <p>
+ * GuidedStepFragment delegates its visual styling to classes called stylists. The {@link
+ * GuidanceStylist} is responsible for the left guidance view, while the {@link
+ * GuidedActionsStylist} is responsible for the right actions view. The stylists use theme
+ * attributes to derive values associated with the presentation, such as colors, animations, etc.
+ * Most simple visual aspects of GuidanceStylist and GuidedActionsStylist can be customized
+ * via theming; see their documentation for more information.
+ * <p>
+ * GuidedStepFragments must have access to an appropriate theme in order for the stylists to
+ * function properly.  Specifically, the fragment must receive {@link
+ * android.support.v17.leanback.R.style#Theme_Leanback_GuidedStep}, or a theme whose parent is
+ * is set to that theme. Themes can be provided in one of three ways:
+ * <ul>
+ * <li>The simplest way is to set the theme for the host Activity to the GuidedStep theme or a
+ * theme that derives from it.</li>
+ * <li>If the Activity already has a theme and setting its parent theme is inconvenient, the
+ * existing Activity theme can have an entry added for the attribute {@link
+ * android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedStepTheme}. If present,
+ * this theme will be used by GuidedStepFragment as an overlay to the Activity's theme.</li>
+ * <li>Finally, custom subclasses of GuidedStepFragment may provide a theme through the {@link
+ * #onProvideTheme} method. This can be useful if a subclass is used across multiple
+ * Activities.</li>
+ * </ul>
+ * <p>
+ * If the theme is provided in multiple ways, the onProvideTheme override has priority, followed by
+ * the Activity's theme.  (Themes whose parent theme is already set to the guided step theme do not
+ * need to set the guidedStepTheme attribute; if set, it will be ignored.)
+ * <p>
+ * If themes do not provide enough customizability, the stylists themselves may be subclassed and
+ * provided to the GuidedStepFragment through the {@link #onCreateGuidanceStylist} and {@link
+ * #onCreateActionsStylist} methods.  The stylists have simple hooks so that subclasses
+ * may override layout files; subclasses may also have more complex logic to determine styling.
+ * <p>
+ * <h3>Guided sequences</h3>
+ * <p>
+ * GuidedStepFragments can be grouped together to provide a guided sequence. GuidedStepFragments
+ * grouped as a sequence use custom animations provided by {@link GuidanceStylist} and
+ * {@link GuidedActionsStylist} (or subclasses) during transitions between steps. Clients
+ * should use {@link #add} to place subsequent GuidedFragments onto the fragment stack so that
+ * custom animations are properly configured. (Custom animations are triggered automatically when
+ * the fragment stack is subsequently popped by any normal mechanism.)
+ * <p>
+ * <i>Note: Currently GuidedStepFragments grouped in this way must all be defined programmatically,
+ * rather than in XML. This restriction may be removed in the future.</i>
+ *
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedStepTheme
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedStepBackground
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionContentWidthWeight
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionContentWidthWeightTwoPanels
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionsBackground
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionsBackgroundDark
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionsElevation
+ * @see GuidanceStylist
+ * @see GuidanceStylist.Guidance
+ * @see GuidedAction
+ * @see GuidedActionsStylist
+ * @deprecated use {@link GuidedStepSupportFragment}
+ */
+@Deprecated
+public class GuidedStepFragment extends Fragment implements GuidedActionAdapter.FocusListener {
+
+    private static final String TAG_LEAN_BACK_ACTIONS_FRAGMENT = "leanBackGuidedStepFragment";
+    private static final String EXTRA_ACTION_PREFIX = "action_";
+    private static final String EXTRA_BUTTON_ACTION_PREFIX = "buttonaction_";
+
+    private static final String ENTRY_NAME_REPLACE = "GuidedStepDefault";
+
+    private static final String ENTRY_NAME_ENTRANCE = "GuidedStepEntrance";
+
+    private static final boolean IS_FRAMEWORK_FRAGMENT = true;
+
+    /**
+     * Fragment argument name for UI style.  The argument value is persisted in fragment state and
+     * used to select fragment transition. The value is initially {@link #UI_STYLE_ENTRANCE} and
+     * might be changed in one of the three helper functions:
+     * <ul>
+     * <li>{@link #addAsRoot(Activity, GuidedStepFragment, int)} sets to
+     * {@link #UI_STYLE_ACTIVITY_ROOT}</li>
+     * <li>{@link #add(FragmentManager, GuidedStepFragment)} or {@link #add(FragmentManager,
+     * GuidedStepFragment, int)} sets it to {@link #UI_STYLE_REPLACE} if there is already a
+     * GuidedStepFragment on stack.</li>
+     * <li>{@link #finishGuidedStepFragments()} changes current GuidedStepFragment to
+     * {@link #UI_STYLE_ENTRANCE} for the non activity case.  This is a special case that changes
+     * the transition settings after fragment has been created,  in order to force current
+     * GuidedStepFragment run a return transition of {@link #UI_STYLE_ENTRANCE}</li>
+     * </ul>
+     * <p>
+     * Argument value can be either:
+     * <ul>
+     * <li>{@link #UI_STYLE_REPLACE}</li>
+     * <li>{@link #UI_STYLE_ENTRANCE}</li>
+     * <li>{@link #UI_STYLE_ACTIVITY_ROOT}</li>
+     * </ul>
+     */
+    public static final String EXTRA_UI_STYLE = "uiStyle";
+
+    /**
+     * This is the case that we use GuidedStepFragment to replace another existing
+     * GuidedStepFragment when moving forward to next step. Default behavior of this style is:
+     * <ul>
+     * <li>Enter transition slides in from END(right), exit transition same as
+     * {@link #UI_STYLE_ENTRANCE}.
+     * </li>
+     * </ul>
+     */
+    public static final int UI_STYLE_REPLACE = 0;
+
+    /**
+     * @deprecated Same value as {@link #UI_STYLE_REPLACE}.
+     */
+    @Deprecated
+    public static final int UI_STYLE_DEFAULT = 0;
+
+    /**
+     * Default value for argument {@link #EXTRA_UI_STYLE}. The default value is assigned in
+     * GuidedStepFragment constructor. This is the case that we show GuidedStepFragment on top of
+     * other content. The default behavior of this style:
+     * <ul>
+     * <li>Enter transition slides in from two sides, exit transition slide out to START(left).
+     * Background will be faded in. Note: Changing exit transition by UI style is not working
+     * because fragment transition asks for exit transition before UI style is restored in Fragment
+     * .onCreate().</li>
+     * </ul>
+     * When popping multiple GuidedStepFragment, {@link #finishGuidedStepFragments()} also changes
+     * the top GuidedStepFragment to UI_STYLE_ENTRANCE in order to run the return transition
+     * (reverse of enter transition) of UI_STYLE_ENTRANCE.
+     */
+    public static final int UI_STYLE_ENTRANCE = 1;
+
+    /**
+     * One possible value of argument {@link #EXTRA_UI_STYLE}. This is the case that we show first
+     * GuidedStepFragment in a separate activity. The default behavior of this style:
+     * <ul>
+     * <li>Enter transition is assigned null (will rely on activity transition), exit transition is
+     * same as {@link #UI_STYLE_ENTRANCE}. Note: Changing exit transition by UI style is not working
+     * because fragment transition asks for exit transition before UI style is restored in
+     * Fragment.onCreate().</li>
+     * </ul>
+     */
+    public static final int UI_STYLE_ACTIVITY_ROOT = 2;
+
+    /**
+     * Animation to slide the contents from the side (left/right).
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    public static final int SLIDE_FROM_SIDE = 0;
+
+    /**
+     * Animation to slide the contents from the bottom.
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    public static final int SLIDE_FROM_BOTTOM = 1;
+
+    private static final String TAG = "GuidedStepF";
+    private static final boolean DEBUG = false;
+
+    /**
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    public static class DummyFragment extends Fragment {
+        @Override
+        public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                Bundle savedInstanceState) {
+            final View v = new View(inflater.getContext());
+            v.setVisibility(View.GONE);
+            return v;
+        }
+    }
+
+    private ContextThemeWrapper mThemeWrapper;
+    private GuidanceStylist mGuidanceStylist;
+    GuidedActionsStylist mActionsStylist;
+    private GuidedActionsStylist mButtonActionsStylist;
+    private GuidedActionAdapter mAdapter;
+    private GuidedActionAdapter mSubAdapter;
+    private GuidedActionAdapter mButtonAdapter;
+    private GuidedActionAdapterGroup mAdapterGroup;
+    private List<GuidedAction> mActions = new ArrayList<GuidedAction>();
+    private List<GuidedAction> mButtonActions = new ArrayList<GuidedAction>();
+    private int entranceTransitionType = SLIDE_FROM_SIDE;
+
+    public GuidedStepFragment() {
+        mGuidanceStylist = onCreateGuidanceStylist();
+        mActionsStylist = onCreateActionsStylist();
+        mButtonActionsStylist = onCreateButtonActionsStylist();
+        onProvideFragmentTransitions();
+    }
+
+    /**
+     * Creates the presenter used to style the guidance panel. The default implementation returns
+     * a basic GuidanceStylist.
+     * @return The GuidanceStylist used in this fragment.
+     */
+    public GuidanceStylist onCreateGuidanceStylist() {
+        return new GuidanceStylist();
+    }
+
+    /**
+     * Creates the presenter used to style the guided actions panel. The default implementation
+     * returns a basic GuidedActionsStylist.
+     * @return The GuidedActionsStylist used in this fragment.
+     */
+    public GuidedActionsStylist onCreateActionsStylist() {
+        return new GuidedActionsStylist();
+    }
+
+    /**
+     * Creates the presenter used to style a sided actions panel for button only.
+     * The default implementation returns a basic GuidedActionsStylist.
+     * @return The GuidedActionsStylist used in this fragment.
+     */
+    public GuidedActionsStylist onCreateButtonActionsStylist() {
+        GuidedActionsStylist stylist = new GuidedActionsStylist();
+        stylist.setAsButtonActions();
+        return stylist;
+    }
+
+    /**
+     * Returns the theme used for styling the fragment. The default returns -1, indicating that the
+     * host Activity's theme should be used.
+     * @return The theme resource ID of the theme to use in this fragment, or -1 to use the
+     * host Activity's theme.
+     */
+    public int onProvideTheme() {
+        return -1;
+    }
+
+    /**
+     * Returns the information required to provide guidance to the user. This hook is called during
+     * {@link #onCreateView}.  May be overridden to return a custom subclass of {@link
+     * GuidanceStylist.Guidance} for use in a subclass of {@link GuidanceStylist}. The default
+     * returns a Guidance object with empty fields; subclasses should override.
+     * @param savedInstanceState The saved instance state from onCreateView.
+     * @return The Guidance object representing the information used to guide the user.
+     */
+    public @NonNull Guidance onCreateGuidance(Bundle savedInstanceState) {
+        return new Guidance("", "", "", null);
+    }
+
+    /**
+     * Fills out the set of actions available to the user. This hook is called during {@link
+     * #onCreate}. The default leaves the list of actions empty; subclasses should override.
+     * @param actions A non-null, empty list ready to be populated.
+     * @param savedInstanceState The saved instance state from onCreate.
+     */
+    public void onCreateActions(@NonNull List<GuidedAction> actions, Bundle savedInstanceState) {
+    }
+
+    /**
+     * Fills out the set of actions shown at right available to the user. This hook is called during
+     * {@link #onCreate}. The default leaves the list of actions empty; subclasses may override.
+     * @param actions A non-null, empty list ready to be populated.
+     * @param savedInstanceState The saved instance state from onCreate.
+     */
+    public void onCreateButtonActions(@NonNull List<GuidedAction> actions,
+            Bundle savedInstanceState) {
+    }
+
+    /**
+     * Callback invoked when an action is taken by the user. Subclasses should override in
+     * order to act on the user's decisions.
+     * @param action The chosen action.
+     */
+    public void onGuidedActionClicked(GuidedAction action) {
+    }
+
+    /**
+     * Callback invoked when an action in sub actions is taken by the user. Subclasses should
+     * override in order to act on the user's decisions.  Default return value is true to close
+     * the sub actions list.
+     * @param action The chosen action.
+     * @return true to collapse the sub actions list, false to keep it expanded.
+     */
+    public boolean onSubGuidedActionClicked(GuidedAction action) {
+        return true;
+    }
+
+    /**
+     * @return True if is current expanded including subactions list or
+     * action with {@link GuidedAction#hasEditableActivatorView()} is true.
+     */
+    public boolean isExpanded() {
+        return mActionsStylist.isExpanded();
+    }
+
+    /**
+     * @return True if the sub actions list is expanded, false otherwise.
+     */
+    public boolean isSubActionsExpanded() {
+        return mActionsStylist.isSubActionsExpanded();
+    }
+
+    /**
+     * Expand a given action's sub actions list.
+     * @param action GuidedAction to expand.
+     * @see #expandAction(GuidedAction, boolean)
+     */
+    public void expandSubActions(GuidedAction action) {
+        if (!action.hasSubActions()) {
+            return;
+        }
+        expandAction(action, true);
+    }
+
+    /**
+     * Expand a given action with sub actions list or
+     * {@link GuidedAction#hasEditableActivatorView()} is true. The method must be called after
+     * {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)} creates fragment view.
+     *
+     * @param action GuidedAction to expand.
+     * @param withTransition True to run transition animation, false otherwise.
+     */
+    public void expandAction(GuidedAction action, boolean withTransition) {
+        mActionsStylist.expandAction(action, withTransition);
+    }
+
+    /**
+     * Collapse sub actions list.
+     * @see GuidedAction#getSubActions()
+     */
+    public void collapseSubActions() {
+        collapseAction(true);
+    }
+
+    /**
+     * Collapse action which either has a sub actions list or action with
+     * {@link GuidedAction#hasEditableActivatorView()} is true.
+     *
+     * @param withTransition True to run transition animation, false otherwise.
+     */
+    public void collapseAction(boolean withTransition) {
+        if (mActionsStylist != null && mActionsStylist.getActionsGridView() != null) {
+            mActionsStylist.collapseAction(withTransition);
+        }
+    }
+
+    /**
+     * Callback invoked when an action is focused (made to be the current selection) by the user.
+     */
+    @Override
+    public void onGuidedActionFocused(GuidedAction action) {
+    }
+
+    /**
+     * Callback invoked when an action's title or description has been edited, this happens either
+     * when user clicks confirm button in IME or user closes IME window by BACK key.
+     * @deprecated Override {@link #onGuidedActionEditedAndProceed(GuidedAction)} and/or
+     *             {@link #onGuidedActionEditCanceled(GuidedAction)}.
+     */
+    @Deprecated
+    public void onGuidedActionEdited(GuidedAction action) {
+    }
+
+    /**
+     * Callback invoked when an action has been canceled editing, for example when user closes
+     * IME window by BACK key.  Default implementation calls deprecated method
+     * {@link #onGuidedActionEdited(GuidedAction)}.
+     * @param action The action which has been canceled editing.
+     */
+    public void onGuidedActionEditCanceled(GuidedAction action) {
+        onGuidedActionEdited(action);
+    }
+
+    /**
+     * Callback invoked when an action has been edited, for example when user clicks confirm button
+     * in IME window.  Default implementation calls deprecated method
+     * {@link #onGuidedActionEdited(GuidedAction)} and returns {@link GuidedAction#ACTION_ID_NEXT}.
+     *
+     * @param action The action that has been edited.
+     * @return ID of the action will be focused or {@link GuidedAction#ACTION_ID_NEXT},
+     * {@link GuidedAction#ACTION_ID_CURRENT}.
+     */
+    public long onGuidedActionEditedAndProceed(GuidedAction action) {
+        onGuidedActionEdited(action);
+        return GuidedAction.ACTION_ID_NEXT;
+    }
+
+    /**
+     * Adds the specified GuidedStepFragment to the fragment stack, replacing any existing
+     * GuidedStepFragments in the stack, and configuring the fragment-to-fragment custom
+     * transitions.  A backstack entry is added, so the fragment will be dismissed when BACK key
+     * is pressed.
+     * <li>If current fragment on stack is GuidedStepFragment: assign {@link #UI_STYLE_REPLACE}
+     * <li>If current fragment on stack is not GuidedStepFragment: assign {@link #UI_STYLE_ENTRANCE}
+     * <p>
+     * Note: currently fragments added using this method must be created programmatically rather
+     * than via XML.
+     * @param fragmentManager The FragmentManager to be used in the transaction.
+     * @param fragment The GuidedStepFragment to be inserted into the fragment stack.
+     * @return The ID returned by the call FragmentTransaction.commit.
+     */
+    public static int add(FragmentManager fragmentManager, GuidedStepFragment fragment) {
+        return add(fragmentManager, fragment, android.R.id.content);
+    }
+
+    /**
+     * Adds the specified GuidedStepFragment to the fragment stack, replacing any existing
+     * GuidedStepFragments in the stack, and configuring the fragment-to-fragment custom
+     * transitions.  A backstack entry is added, so the fragment will be dismissed when BACK key
+     * is pressed.
+     * <li>If current fragment on stack is GuidedStepFragment: assign {@link #UI_STYLE_REPLACE} and
+     * {@link #onAddSharedElementTransition(FragmentTransaction, GuidedStepFragment)} will be called
+     * to perform shared element transition between GuidedStepFragments.
+     * <li>If current fragment on stack is not GuidedStepFragment: assign {@link #UI_STYLE_ENTRANCE}
+     * <p>
+     * Note: currently fragments added using this method must be created programmatically rather
+     * than via XML.
+     * @param fragmentManager The FragmentManager to be used in the transaction.
+     * @param fragment The GuidedStepFragment to be inserted into the fragment stack.
+     * @param id The id of container to add GuidedStepFragment, can be android.R.id.content.
+     * @return The ID returned by the call FragmentTransaction.commit.
+     */
+    public static int add(FragmentManager fragmentManager, GuidedStepFragment fragment, int id) {
+        GuidedStepFragment current = getCurrentGuidedStepFragment(fragmentManager);
+        boolean inGuidedStep = current != null;
+        if (IS_FRAMEWORK_FRAGMENT && Build.VERSION.SDK_INT >= 21 && Build.VERSION.SDK_INT < 23
+                && !inGuidedStep) {
+            // workaround b/22631964 for framework fragment
+            fragmentManager.beginTransaction()
+                .replace(id, new DummyFragment(), TAG_LEAN_BACK_ACTIONS_FRAGMENT)
+                .commit();
+        }
+        FragmentTransaction ft = fragmentManager.beginTransaction();
+
+        fragment.setUiStyle(inGuidedStep ? UI_STYLE_REPLACE : UI_STYLE_ENTRANCE);
+        ft.addToBackStack(fragment.generateStackEntryName());
+        if (current != null) {
+            fragment.onAddSharedElementTransition(ft, current);
+        }
+        return ft.replace(id, fragment, TAG_LEAN_BACK_ACTIONS_FRAGMENT).commit();
+    }
+
+    /**
+     * Called when this fragment is added to FragmentTransaction with {@link #UI_STYLE_REPLACE} (aka
+     * when the GuidedStepFragment replacing an existing GuidedStepFragment). Default implementation
+     * establishes connections between action background views to morph action background bounds
+     * change from disappearing GuidedStepFragment into this GuidedStepFragment. The default
+     * implementation heavily relies on {@link GuidedActionsStylist}'s layout, app may override this
+     * method when modifying the default layout of {@link GuidedActionsStylist}.
+     *
+     * @see GuidedActionsStylist
+     * @see #onProvideFragmentTransitions()
+     * @param ft The FragmentTransaction to add shared element.
+     * @param disappearing The disappearing fragment.
+     */
+    protected void onAddSharedElementTransition(FragmentTransaction ft, GuidedStepFragment
+            disappearing) {
+        View fragmentView = disappearing.getView();
+        addNonNullSharedElementTransition(ft, fragmentView.findViewById(
+                R.id.action_fragment_root), "action_fragment_root");
+        addNonNullSharedElementTransition(ft, fragmentView.findViewById(
+                R.id.action_fragment_background), "action_fragment_background");
+        addNonNullSharedElementTransition(ft, fragmentView.findViewById(
+                R.id.action_fragment), "action_fragment");
+        addNonNullSharedElementTransition(ft, fragmentView.findViewById(
+                R.id.guidedactions_root), "guidedactions_root");
+        addNonNullSharedElementTransition(ft, fragmentView.findViewById(
+                R.id.guidedactions_content), "guidedactions_content");
+        addNonNullSharedElementTransition(ft, fragmentView.findViewById(
+                R.id.guidedactions_list_background), "guidedactions_list_background");
+        addNonNullSharedElementTransition(ft, fragmentView.findViewById(
+                R.id.guidedactions_root2), "guidedactions_root2");
+        addNonNullSharedElementTransition(ft, fragmentView.findViewById(
+                R.id.guidedactions_content2), "guidedactions_content2");
+        addNonNullSharedElementTransition(ft, fragmentView.findViewById(
+                R.id.guidedactions_list_background2), "guidedactions_list_background2");
+    }
+
+    private static void addNonNullSharedElementTransition (FragmentTransaction ft, View subView,
+                                                           String transitionName)
+    {
+        if (subView != null)
+            TransitionHelper.addSharedElement(ft, subView, transitionName);
+    }
+
+    /**
+     * Returns BackStackEntry name for the GuidedStepFragment or empty String if no entry is
+     * associated.  Note {@link #UI_STYLE_ACTIVITY_ROOT} will return empty String.  The method
+     * returns undefined value if the fragment is not in FragmentManager.
+     * @return BackStackEntry name for the GuidedStepFragment or empty String if no entry is
+     * associated.
+     */
+    final String generateStackEntryName() {
+        return generateStackEntryName(getUiStyle(), getClass());
+    }
+
+    /**
+     * Generates BackStackEntry name for GuidedStepFragment class or empty String if no entry is
+     * associated.  Note {@link #UI_STYLE_ACTIVITY_ROOT} is not allowed and returns empty String.
+     * @param uiStyle {@link #UI_STYLE_REPLACE} or {@link #UI_STYLE_ENTRANCE}
+     * @return BackStackEntry name for the GuidedStepFragment or empty String if no entry is
+     * associated.
+     */
+    static String generateStackEntryName(int uiStyle, Class guidedStepFragmentClass) {
+        switch (uiStyle) {
+        case UI_STYLE_REPLACE:
+            return ENTRY_NAME_REPLACE + guidedStepFragmentClass.getName();
+        case UI_STYLE_ENTRANCE:
+            return ENTRY_NAME_ENTRANCE + guidedStepFragmentClass.getName();
+        case UI_STYLE_ACTIVITY_ROOT:
+        default:
+            return "";
+        }
+    }
+
+    /**
+     * Returns true if the backstack entry represents GuidedStepFragment with
+     * {@link #UI_STYLE_ENTRANCE}, i.e. this is the first GuidedStepFragment pushed to stack; false
+     * otherwise.
+     * @see #generateStackEntryName(int, Class)
+     * @param backStackEntryName Name of BackStackEntry.
+     * @return True if the backstack represents GuidedStepFragment with {@link #UI_STYLE_ENTRANCE};
+     * false otherwise.
+     */
+    static boolean isStackEntryUiStyleEntrance(String backStackEntryName) {
+        return backStackEntryName != null && backStackEntryName.startsWith(ENTRY_NAME_ENTRANCE);
+    }
+
+    /**
+     * Extract Class name from BackStackEntry name.
+     * @param backStackEntryName Name of BackStackEntry.
+     * @return Class name of GuidedStepFragment.
+     */
+    static String getGuidedStepFragmentClassName(String backStackEntryName) {
+        if (backStackEntryName.startsWith(ENTRY_NAME_REPLACE)) {
+            return backStackEntryName.substring(ENTRY_NAME_REPLACE.length());
+        } else if (backStackEntryName.startsWith(ENTRY_NAME_ENTRANCE)) {
+            return backStackEntryName.substring(ENTRY_NAME_ENTRANCE.length());
+        } else {
+            return "";
+        }
+    }
+
+    /**
+     * Adds the specified GuidedStepFragment as content of Activity; no backstack entry is added so
+     * the activity will be dismissed when BACK key is pressed.  The method is typically called in
+     * Activity.onCreate() when savedInstanceState is null.  When savedInstanceState is not null,
+     * the Activity is being restored,  do not call addAsRoot() to duplicate the Fragment restored
+     * by FragmentManager.
+     * {@link #UI_STYLE_ACTIVITY_ROOT} is assigned.
+     *
+     * Note: currently fragments added using this method must be created programmatically rather
+     * than via XML.
+     * @param activity The Activity to be used to insert GuidedstepFragment.
+     * @param fragment The GuidedStepFragment to be inserted into the fragment stack.
+     * @param id The id of container to add GuidedStepFragment, can be android.R.id.content.
+     * @return The ID returned by the call FragmentTransaction.commit, or -1 there is already
+     *         GuidedStepFragment.
+     */
+    public static int addAsRoot(Activity activity, GuidedStepFragment fragment, int id) {
+        // Workaround b/23764120: call getDecorView() to force requestFeature of ActivityTransition.
+        activity.getWindow().getDecorView();
+        FragmentManager fragmentManager = activity.getFragmentManager();
+        if (fragmentManager.findFragmentByTag(TAG_LEAN_BACK_ACTIONS_FRAGMENT) != null) {
+            Log.w(TAG, "Fragment is already exists, likely calling "
+                    + "addAsRoot() when savedInstanceState is not null in Activity.onCreate().");
+            return -1;
+        }
+        FragmentTransaction ft = fragmentManager.beginTransaction();
+        fragment.setUiStyle(UI_STYLE_ACTIVITY_ROOT);
+        return ft.replace(id, fragment, TAG_LEAN_BACK_ACTIONS_FRAGMENT).commit();
+    }
+
+    /**
+     * Returns the current GuidedStepFragment on the fragment transaction stack.
+     * @return The current GuidedStepFragment, if any, on the fragment transaction stack.
+     */
+    public static GuidedStepFragment getCurrentGuidedStepFragment(FragmentManager fm) {
+        Fragment f = fm.findFragmentByTag(TAG_LEAN_BACK_ACTIONS_FRAGMENT);
+        if (f instanceof GuidedStepFragment) {
+            return (GuidedStepFragment) f;
+        }
+        return null;
+    }
+
+    /**
+     * Returns the GuidanceStylist that displays guidance information for the user.
+     * @return The GuidanceStylist for this fragment.
+     */
+    public GuidanceStylist getGuidanceStylist() {
+        return mGuidanceStylist;
+    }
+
+    /**
+     * Returns the GuidedActionsStylist that displays the actions the user may take.
+     * @return The GuidedActionsStylist for this fragment.
+     */
+    public GuidedActionsStylist getGuidedActionsStylist() {
+        return mActionsStylist;
+    }
+
+    /**
+     * Returns the list of button GuidedActions that the user may take in this fragment.
+     * @return The list of button GuidedActions for this fragment.
+     */
+    public List<GuidedAction> getButtonActions() {
+        return mButtonActions;
+    }
+
+    /**
+     * Find button GuidedAction by Id.
+     * @param id  Id of the button action to search.
+     * @return  GuidedAction object or null if not found.
+     */
+    public GuidedAction findButtonActionById(long id) {
+        int index = findButtonActionPositionById(id);
+        return index >= 0 ? mButtonActions.get(index) : null;
+    }
+
+    /**
+     * Find button GuidedAction position in array by Id.
+     * @param id  Id of the button action to search.
+     * @return  position of GuidedAction object in array or -1 if not found.
+     */
+    public int findButtonActionPositionById(long id) {
+        if (mButtonActions != null) {
+            for (int i = 0; i < mButtonActions.size(); i++) {
+                GuidedAction action = mButtonActions.get(i);
+                if (mButtonActions.get(i).getId() == id) {
+                    return i;
+                }
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * Returns the GuidedActionsStylist that displays the button actions the user may take.
+     * @return The GuidedActionsStylist for this fragment.
+     */
+    public GuidedActionsStylist getGuidedButtonActionsStylist() {
+        return mButtonActionsStylist;
+    }
+
+    /**
+     * Sets the list of button GuidedActions that the user may take in this fragment.
+     * @param actions The list of button GuidedActions for this fragment.
+     */
+    public void setButtonActions(List<GuidedAction> actions) {
+        mButtonActions = actions;
+        if (mButtonAdapter != null) {
+            mButtonAdapter.setActions(mButtonActions);
+        }
+    }
+
+    /**
+     * Notify an button action has changed and update its UI.
+     * @param position Position of the button GuidedAction in array.
+     */
+    public void notifyButtonActionChanged(int position) {
+        if (mButtonAdapter != null) {
+            mButtonAdapter.notifyItemChanged(position);
+        }
+    }
+
+    /**
+     * Returns the view corresponding to the button action at the indicated position in the list of
+     * actions for this fragment.
+     * @param position The integer position of the button action of interest.
+     * @return The View corresponding to the button action at the indicated position, or null if
+     * that action is not currently onscreen.
+     */
+    public View getButtonActionItemView(int position) {
+        final RecyclerView.ViewHolder holder = mButtonActionsStylist.getActionsGridView()
+                    .findViewHolderForPosition(position);
+        return holder == null ? null : holder.itemView;
+    }
+
+    /**
+     * Scrolls the action list to the position indicated, selecting that button action's view.
+     * @param position The integer position of the button action of interest.
+     */
+    public void setSelectedButtonActionPosition(int position) {
+        mButtonActionsStylist.getActionsGridView().setSelectedPosition(position);
+    }
+
+    /**
+     * Returns the position if the currently selected button GuidedAction.
+     * @return position The integer position of the currently selected button action.
+     */
+    public int getSelectedButtonActionPosition() {
+        return mButtonActionsStylist.getActionsGridView().getSelectedPosition();
+    }
+
+    /**
+     * Returns the list of GuidedActions that the user may take in this fragment.
+     * @return The list of GuidedActions for this fragment.
+     */
+    public List<GuidedAction> getActions() {
+        return mActions;
+    }
+
+    /**
+     * Find GuidedAction by Id.
+     * @param id  Id of the action to search.
+     * @return  GuidedAction object or null if not found.
+     */
+    public GuidedAction findActionById(long id) {
+        int index = findActionPositionById(id);
+        return index >= 0 ? mActions.get(index) : null;
+    }
+
+    /**
+     * Find GuidedAction position in array by Id.
+     * @param id  Id of the action to search.
+     * @return  position of GuidedAction object in array or -1 if not found.
+     */
+    public int findActionPositionById(long id) {
+        if (mActions != null) {
+            for (int i = 0; i < mActions.size(); i++) {
+                GuidedAction action = mActions.get(i);
+                if (mActions.get(i).getId() == id) {
+                    return i;
+                }
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * Sets the list of GuidedActions that the user may take in this fragment.
+     * Uses DiffCallback set by {@link #setActionsDiffCallback(DiffCallback)}.
+     *
+     * @param actions The list of GuidedActions for this fragment.
+     */
+    public void setActions(List<GuidedAction> actions) {
+        mActions = actions;
+        if (mAdapter != null) {
+            mAdapter.setActions(mActions);
+        }
+    }
+
+    /**
+     * Sets the RecyclerView DiffCallback used when {@link #setActions(List)} is called. By default
+     * GuidedStepFragment uses
+     * {@link android.support.v17.leanback.widget.GuidedActionDiffCallback}.
+     * Sets it to null if app wants to refresh the whole list.
+     *
+     * @param diffCallback DiffCallback used in {@link #setActions(List)}.
+     */
+    public void setActionsDiffCallback(DiffCallback<GuidedAction> diffCallback) {
+        mAdapter.setDiffCallback(diffCallback);
+    }
+
+    /**
+     * Notify an action has changed and update its UI.
+     * @param position Position of the GuidedAction in array.
+     */
+    public void notifyActionChanged(int position) {
+        if (mAdapter != null) {
+            mAdapter.notifyItemChanged(position);
+        }
+    }
+
+    /**
+     * Returns the view corresponding to the action at the indicated position in the list of
+     * actions for this fragment.
+     * @param position The integer position of the action of interest.
+     * @return The View corresponding to the action at the indicated position, or null if that
+     * action is not currently onscreen.
+     */
+    public View getActionItemView(int position) {
+        final RecyclerView.ViewHolder holder = mActionsStylist.getActionsGridView()
+                    .findViewHolderForPosition(position);
+        return holder == null ? null : holder.itemView;
+    }
+
+    /**
+     * Scrolls the action list to the position indicated, selecting that action's view.
+     * @param position The integer position of the action of interest.
+     */
+    public void setSelectedActionPosition(int position) {
+        mActionsStylist.getActionsGridView().setSelectedPosition(position);
+    }
+
+    /**
+     * Returns the position if the currently selected GuidedAction.
+     * @return position The integer position of the currently selected action.
+     */
+    public int getSelectedActionPosition() {
+        return mActionsStylist.getActionsGridView().getSelectedPosition();
+    }
+
+    /**
+     * Called by Constructor to provide fragment transitions.  The default implementation assigns
+     * transitions based on {@link #getUiStyle()}:
+     * <ul>
+     * <li> {@link #UI_STYLE_REPLACE} Slide from/to end(right) for enter transition, slide from/to
+     * start(left) for exit transition, shared element enter transition is set to ChangeBounds.
+     * <li> {@link #UI_STYLE_ENTRANCE} Enter transition is set to slide from both sides, exit
+     * transition is same as {@link #UI_STYLE_REPLACE}, no shared element enter transition.
+     * <li> {@link #UI_STYLE_ACTIVITY_ROOT} Enter transition is set to null and app should rely on
+     * activity transition, exit transition is same as {@link #UI_STYLE_REPLACE}, no shared element
+     * enter transition.
+     * </ul>
+     * <p>
+     * The default implementation heavily relies on {@link GuidedActionsStylist} and
+     * {@link GuidanceStylist} layout, app may override this method when modifying the default
+     * layout of {@link GuidedActionsStylist} or {@link GuidanceStylist}.
+     * <p>
+     * TIP: because the fragment view is removed during fragment transition, in general app cannot
+     * use two Visibility transition together. Workaround is to create your own Visibility
+     * transition that controls multiple animators (e.g. slide and fade animation in one Transition
+     * class).
+     */
+    protected void onProvideFragmentTransitions() {
+        if (Build.VERSION.SDK_INT >= 21) {
+            final int uiStyle = getUiStyle();
+            if (uiStyle == UI_STYLE_REPLACE) {
+                Object enterTransition = TransitionHelper.createFadeAndShortSlide(Gravity.END);
+                TransitionHelper.exclude(enterTransition, R.id.guidedstep_background, true);
+                TransitionHelper.exclude(enterTransition, R.id.guidedactions_sub_list_background,
+                        true);
+                TransitionHelper.setEnterTransition(this, enterTransition);
+
+                Object fade = TransitionHelper.createFadeTransition(
+                        TransitionHelper.FADE_IN | TransitionHelper.FADE_OUT);
+                TransitionHelper.include(fade, R.id.guidedactions_sub_list_background);
+                Object changeBounds = TransitionHelper.createChangeBounds(false);
+                Object sharedElementTransition = TransitionHelper.createTransitionSet(false);
+                TransitionHelper.addTransition(sharedElementTransition, fade);
+                TransitionHelper.addTransition(sharedElementTransition, changeBounds);
+                TransitionHelper.setSharedElementEnterTransition(this, sharedElementTransition);
+            } else if (uiStyle == UI_STYLE_ENTRANCE) {
+                if (entranceTransitionType == SLIDE_FROM_SIDE) {
+                    Object fade = TransitionHelper.createFadeTransition(
+                            TransitionHelper.FADE_IN | TransitionHelper.FADE_OUT);
+                    TransitionHelper.include(fade, R.id.guidedstep_background);
+                    Object slideFromSide = TransitionHelper.createFadeAndShortSlide(
+                            Gravity.END | Gravity.START);
+                    TransitionHelper.include(slideFromSide, R.id.content_fragment);
+                    TransitionHelper.include(slideFromSide, R.id.action_fragment_root);
+                    Object enterTransition = TransitionHelper.createTransitionSet(false);
+                    TransitionHelper.addTransition(enterTransition, fade);
+                    TransitionHelper.addTransition(enterTransition, slideFromSide);
+                    TransitionHelper.setEnterTransition(this, enterTransition);
+                } else {
+                    Object slideFromBottom = TransitionHelper.createFadeAndShortSlide(
+                            Gravity.BOTTOM);
+                    TransitionHelper.include(slideFromBottom, R.id.guidedstep_background_view_root);
+                    Object enterTransition = TransitionHelper.createTransitionSet(false);
+                    TransitionHelper.addTransition(enterTransition, slideFromBottom);
+                    TransitionHelper.setEnterTransition(this, enterTransition);
+                }
+                // No shared element transition
+                TransitionHelper.setSharedElementEnterTransition(this, null);
+            } else if (uiStyle == UI_STYLE_ACTIVITY_ROOT) {
+                // for Activity root, we don't need enter transition, use activity transition
+                TransitionHelper.setEnterTransition(this, null);
+                // No shared element transition
+                TransitionHelper.setSharedElementEnterTransition(this, null);
+            }
+            // exitTransition is same for all style
+            Object exitTransition = TransitionHelper.createFadeAndShortSlide(Gravity.START);
+            TransitionHelper.exclude(exitTransition, R.id.guidedstep_background, true);
+            TransitionHelper.exclude(exitTransition, R.id.guidedactions_sub_list_background,
+                    true);
+            TransitionHelper.setExitTransition(this, exitTransition);
+        }
+    }
+
+    /**
+     * Called by onCreateView to inflate background view.  Default implementation loads view
+     * from {@link R.layout#lb_guidedstep_background} which holds a reference to
+     * guidedStepBackground.
+     * @param inflater LayoutInflater to load background view.
+     * @param container Parent view of background view.
+     * @param savedInstanceState
+     * @return Created background view or null if no background.
+     */
+    public View onCreateBackgroundView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        return inflater.inflate(R.layout.lb_guidedstep_background, container, false);
+    }
+
+    /**
+     * Set UI style to fragment arguments. Default value is {@link #UI_STYLE_ENTRANCE} when fragment
+     * is first initialized. UI style is used to choose different fragment transition animations and
+     * determine if this is the first GuidedStepFragment on backstack. In most cases app does not
+     * directly call this method, app calls helper function
+     * {@link #add(FragmentManager, GuidedStepFragment, int)}. However if the app creates Fragment
+     * transaction and controls backstack by itself, it would need call setUiStyle() to select the
+     * fragment transition to use.
+     *
+     * @param style {@link #UI_STYLE_ACTIVITY_ROOT} {@link #UI_STYLE_REPLACE} or
+     *        {@link #UI_STYLE_ENTRANCE}.
+     */
+    public void setUiStyle(int style) {
+        int oldStyle = getUiStyle();
+        Bundle arguments = getArguments();
+        boolean isNew = false;
+        if (arguments == null) {
+            arguments = new Bundle();
+            isNew = true;
+        }
+        arguments.putInt(EXTRA_UI_STYLE, style);
+        // call setArgument() will validate if the fragment is already added.
+        if (isNew) {
+            setArguments(arguments);
+        }
+        if (style != oldStyle) {
+            onProvideFragmentTransitions();
+        }
+    }
+
+    /**
+     * Read UI style from fragment arguments.  Default value is {@link #UI_STYLE_ENTRANCE} when
+     * fragment is first initialized.  UI style is used to choose different fragment transition
+     * animations and determine if this is the first GuidedStepFragment on backstack.
+     *
+     * @return {@link #UI_STYLE_ACTIVITY_ROOT} {@link #UI_STYLE_REPLACE} or
+     * {@link #UI_STYLE_ENTRANCE}.
+     * @see #onProvideFragmentTransitions()
+     */
+    public int getUiStyle() {
+        Bundle b = getArguments();
+        if (b == null) return UI_STYLE_ENTRANCE;
+        return b.getInt(EXTRA_UI_STYLE, UI_STYLE_ENTRANCE);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        if (DEBUG) Log.v(TAG, "onCreate");
+        // Set correct transition from saved arguments.
+        onProvideFragmentTransitions();
+
+        ArrayList<GuidedAction> actions = new ArrayList<GuidedAction>();
+        onCreateActions(actions, savedInstanceState);
+        if (savedInstanceState != null) {
+            onRestoreActions(actions, savedInstanceState);
+        }
+        setActions(actions);
+        ArrayList<GuidedAction> buttonActions = new ArrayList<GuidedAction>();
+        onCreateButtonActions(buttonActions, savedInstanceState);
+        if (savedInstanceState != null) {
+            onRestoreButtonActions(buttonActions, savedInstanceState);
+        }
+        setButtonActions(buttonActions);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onDestroyView() {
+        mGuidanceStylist.onDestroyView();
+        mActionsStylist.onDestroyView();
+        mButtonActionsStylist.onDestroyView();
+        mAdapter = null;
+        mSubAdapter =  null;
+        mButtonAdapter = null;
+        mAdapterGroup = null;
+        super.onDestroyView();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        if (DEBUG) Log.v(TAG, "onCreateView");
+
+        resolveTheme();
+        inflater = getThemeInflater(inflater);
+
+        GuidedStepRootLayout root = (GuidedStepRootLayout) inflater.inflate(
+                R.layout.lb_guidedstep_fragment, container, false);
+
+        root.setFocusOutStart(isFocusOutStartAllowed());
+        root.setFocusOutEnd(isFocusOutEndAllowed());
+
+        ViewGroup guidanceContainer = (ViewGroup) root.findViewById(R.id.content_fragment);
+        ViewGroup actionContainer = (ViewGroup) root.findViewById(R.id.action_fragment);
+        ((NonOverlappingLinearLayout) actionContainer).setFocusableViewAvailableFixEnabled(true);
+
+        Guidance guidance = onCreateGuidance(savedInstanceState);
+        View guidanceView = mGuidanceStylist.onCreateView(inflater, guidanceContainer, guidance);
+        guidanceContainer.addView(guidanceView);
+
+        View actionsView = mActionsStylist.onCreateView(inflater, actionContainer);
+        actionContainer.addView(actionsView);
+
+        View buttonActionsView = mButtonActionsStylist.onCreateView(inflater, actionContainer);
+        actionContainer.addView(buttonActionsView);
+
+        GuidedActionAdapter.EditListener editListener = new GuidedActionAdapter.EditListener() {
+
+                @Override
+                public void onImeOpen() {
+                    runImeAnimations(true);
+                }
+
+                @Override
+                public void onImeClose() {
+                    runImeAnimations(false);
+                }
+
+                @Override
+                public long onGuidedActionEditedAndProceed(GuidedAction action) {
+                    return GuidedStepFragment.this.onGuidedActionEditedAndProceed(action);
+                }
+
+                @Override
+                public void onGuidedActionEditCanceled(GuidedAction action) {
+                    GuidedStepFragment.this.onGuidedActionEditCanceled(action);
+                }
+        };
+
+        mAdapter = new GuidedActionAdapter(mActions, new GuidedActionAdapter.ClickListener() {
+            @Override
+            public void onGuidedActionClicked(GuidedAction action) {
+                GuidedStepFragment.this.onGuidedActionClicked(action);
+                if (isExpanded()) {
+                    collapseAction(true);
+                } else if (action.hasSubActions() || action.hasEditableActivatorView()) {
+                    expandAction(action, true);
+                }
+            }
+        }, this, mActionsStylist, false);
+        mButtonAdapter =
+                new GuidedActionAdapter(mButtonActions, new GuidedActionAdapter.ClickListener() {
+                    @Override
+                    public void onGuidedActionClicked(GuidedAction action) {
+                        GuidedStepFragment.this.onGuidedActionClicked(action);
+                    }
+                }, this, mButtonActionsStylist, false);
+        mSubAdapter = new GuidedActionAdapter(null, new GuidedActionAdapter.ClickListener() {
+            @Override
+            public void onGuidedActionClicked(GuidedAction action) {
+                if (mActionsStylist.isInExpandTransition()) {
+                    return;
+                }
+                if (GuidedStepFragment.this.onSubGuidedActionClicked(action)) {
+                    collapseSubActions();
+                }
+            }
+        }, this, mActionsStylist, true);
+        mAdapterGroup = new GuidedActionAdapterGroup();
+        mAdapterGroup.addAdpter(mAdapter, mButtonAdapter);
+        mAdapterGroup.addAdpter(mSubAdapter, null);
+        mAdapterGroup.setEditListener(editListener);
+        mActionsStylist.setEditListener(editListener);
+
+        mActionsStylist.getActionsGridView().setAdapter(mAdapter);
+        if (mActionsStylist.getSubActionsGridView() != null) {
+            mActionsStylist.getSubActionsGridView().setAdapter(mSubAdapter);
+        }
+        mButtonActionsStylist.getActionsGridView().setAdapter(mButtonAdapter);
+        if (mButtonActions.size() == 0) {
+            // when there is no button actions, we don't need show the second panel, but keep
+            // the width zero to run ChangeBounds transition.
+            LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
+                    buttonActionsView.getLayoutParams();
+            lp.weight = 0;
+            buttonActionsView.setLayoutParams(lp);
+        } else {
+            // when there are two actions panel, we need adjust the weight of action to
+            // guidedActionContentWidthWeightTwoPanels.
+            Context ctx = mThemeWrapper != null ? mThemeWrapper : FragmentUtil.getContext(GuidedStepFragment.this);
+            TypedValue typedValue = new TypedValue();
+            if (ctx.getTheme().resolveAttribute(R.attr.guidedActionContentWidthWeightTwoPanels,
+                    typedValue, true)) {
+                View actionsRoot = root.findViewById(R.id.action_fragment_root);
+                float weight = typedValue.getFloat();
+                LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) actionsRoot
+                        .getLayoutParams();
+                lp.weight = weight;
+                actionsRoot.setLayoutParams(lp);
+            }
+        }
+
+        // Add the background view.
+        View backgroundView = onCreateBackgroundView(inflater, root, savedInstanceState);
+        if (backgroundView != null) {
+            FrameLayout backgroundViewRoot = (FrameLayout)root.findViewById(
+                R.id.guidedstep_background_view_root);
+            backgroundViewRoot.addView(backgroundView, 0);
+        }
+
+        return root;
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        getView().findViewById(R.id.action_fragment).requestFocus();
+    }
+
+    /**
+     * Get the key will be used to save GuidedAction with Fragment.
+     * @param action GuidedAction to get key.
+     * @return Key to save the GuidedAction.
+     */
+    final String getAutoRestoreKey(GuidedAction action) {
+        return EXTRA_ACTION_PREFIX + action.getId();
+    }
+
+    /**
+     * Get the key will be used to save GuidedAction with Fragment.
+     * @param action GuidedAction to get key.
+     * @return Key to save the GuidedAction.
+     */
+    final String getButtonAutoRestoreKey(GuidedAction action) {
+        return EXTRA_BUTTON_ACTION_PREFIX + action.getId();
+    }
+
+    final static boolean isSaveEnabled(GuidedAction action) {
+        return action.isAutoSaveRestoreEnabled() && action.getId() != GuidedAction.NO_ID;
+    }
+
+    final void onRestoreActions(List<GuidedAction> actions, Bundle savedInstanceState) {
+        for (int i = 0, size = actions.size(); i < size; i++) {
+            GuidedAction action = actions.get(i);
+            if (isSaveEnabled(action)) {
+                action.onRestoreInstanceState(savedInstanceState, getAutoRestoreKey(action));
+            }
+        }
+    }
+
+    final void onRestoreButtonActions(List<GuidedAction> actions, Bundle savedInstanceState) {
+        for (int i = 0, size = actions.size(); i < size; i++) {
+            GuidedAction action = actions.get(i);
+            if (isSaveEnabled(action)) {
+                action.onRestoreInstanceState(savedInstanceState, getButtonAutoRestoreKey(action));
+            }
+        }
+    }
+
+    final void onSaveActions(List<GuidedAction> actions, Bundle outState) {
+        for (int i = 0, size = actions.size(); i < size; i++) {
+            GuidedAction action = actions.get(i);
+            if (isSaveEnabled(action)) {
+                action.onSaveInstanceState(outState, getAutoRestoreKey(action));
+            }
+        }
+    }
+
+    final void onSaveButtonActions(List<GuidedAction> actions, Bundle outState) {
+        for (int i = 0, size = actions.size(); i < size; i++) {
+            GuidedAction action = actions.get(i);
+            if (isSaveEnabled(action)) {
+                action.onSaveInstanceState(outState, getButtonAutoRestoreKey(action));
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        onSaveActions(mActions, outState);
+        onSaveButtonActions(mButtonActions, outState);
+    }
+
+    private static boolean isGuidedStepTheme(Context context) {
+        int resId = R.attr.guidedStepThemeFlag;
+        TypedValue typedValue = new TypedValue();
+        boolean found = context.getTheme().resolveAttribute(resId, typedValue, true);
+        if (DEBUG) Log.v(TAG, "Found guided step theme flag? " + found);
+        return found && typedValue.type == TypedValue.TYPE_INT_BOOLEAN && typedValue.data != 0;
+    }
+
+    /**
+     * Convenient method to close GuidedStepFragments on top of other content or finish Activity if
+     * GuidedStepFragments were started in a separate activity.  Pops all stack entries including
+     * {@link #UI_STYLE_ENTRANCE}; if {@link #UI_STYLE_ENTRANCE} is not found, finish the activity.
+     * Note that this method must be paired with {@link #add(FragmentManager, GuidedStepFragment,
+     * int)} which sets up the stack entry name for finding which fragment we need to pop back to.
+     */
+    public void finishGuidedStepFragments() {
+        final FragmentManager fragmentManager = getFragmentManager();
+        final int entryCount = fragmentManager.getBackStackEntryCount();
+        if (entryCount > 0) {
+            for (int i = entryCount - 1; i >= 0; i--) {
+                BackStackEntry entry = fragmentManager.getBackStackEntryAt(i);
+                if (isStackEntryUiStyleEntrance(entry.getName())) {
+                    GuidedStepFragment top = getCurrentGuidedStepFragment(fragmentManager);
+                    if (top != null) {
+                        top.setUiStyle(UI_STYLE_ENTRANCE);
+                    }
+                    fragmentManager.popBackStackImmediate(entry.getId(),
+                            FragmentManager.POP_BACK_STACK_INCLUSIVE);
+                    return;
+                }
+            }
+        }
+        ActivityCompat.finishAfterTransition(getActivity());
+    }
+
+    /**
+     * Convenient method to pop to fragment with Given class.
+     * @param  guidedStepFragmentClass  Name of the Class of GuidedStepFragment to pop to.
+     * @param flags Either 0 or {@link FragmentManager#POP_BACK_STACK_INCLUSIVE}.
+     */
+    public void popBackStackToGuidedStepFragment(Class guidedStepFragmentClass, int flags) {
+        if (!GuidedStepFragment.class.isAssignableFrom(guidedStepFragmentClass)) {
+            return;
+        }
+        final FragmentManager fragmentManager = getFragmentManager();
+        final int entryCount = fragmentManager.getBackStackEntryCount();
+        String className = guidedStepFragmentClass.getName();
+        if (entryCount > 0) {
+            for (int i = entryCount - 1; i >= 0; i--) {
+                BackStackEntry entry = fragmentManager.getBackStackEntryAt(i);
+                String entryClassName = getGuidedStepFragmentClassName(entry.getName());
+                if (className.equals(entryClassName)) {
+                    fragmentManager.popBackStackImmediate(entry.getId(), flags);
+                    return;
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns true if allows focus out of start edge of GuidedStepFragment, false otherwise.
+     * Default value is false, the reason is to disable FocusFinder to find focusable views
+     * beneath content of GuidedStepFragment.  Subclass may override.
+     * @return True if allows focus out of start edge of GuidedStepFragment.
+     */
+    public boolean isFocusOutStartAllowed() {
+        return false;
+    }
+
+    /**
+     * Returns true if allows focus out of end edge of GuidedStepFragment, false otherwise.
+     * Default value is false, the reason is to disable FocusFinder to find focusable views
+     * beneath content of GuidedStepFragment.  Subclass may override.
+     * @return True if allows focus out of end edge of GuidedStepFragment.
+     */
+    public boolean isFocusOutEndAllowed() {
+        return false;
+    }
+
+    /**
+     * Sets the transition type to be used for {@link #UI_STYLE_ENTRANCE} animation.
+     * Currently we provide 2 different variations for animation - slide in from
+     * side (default) or bottom.
+     *
+     * Ideally we can retrieve the screen mode settings from the theme attribute
+     * {@code Theme.Leanback.GuidedStep#guidedStepHeightWeight} and use that to
+     * determine the transition. But the fragment context to retrieve the theme
+     * isn't available on platform v23 or earlier.
+     *
+     * For now clients(subclasses) can call this method inside the constructor.
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    public void setEntranceTransitionType(int transitionType) {
+      this.entranceTransitionType = transitionType;
+    }
+
+    /**
+     * Opens the provided action in edit mode and raises ime. This can be
+     * used to programmatically skip the extra click required to go into edit mode. This method
+     * can be invoked in {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)}.
+     */
+    public void openInEditMode(GuidedAction action) {
+        mActionsStylist.openInEditMode(action);
+    }
+
+    private void resolveTheme() {
+        // Look up the guidedStepTheme in the currently specified theme.  If it exists,
+        // replace the theme with its value.
+        Context context = FragmentUtil.getContext(GuidedStepFragment.this);
+        int theme = onProvideTheme();
+        if (theme == -1 && !isGuidedStepTheme(context)) {
+            // Look up the guidedStepTheme in the activity's currently specified theme.  If it
+            // exists, replace the theme with its value.
+            int resId = R.attr.guidedStepTheme;
+            TypedValue typedValue = new TypedValue();
+            boolean found = context.getTheme().resolveAttribute(resId, typedValue, true);
+            if (DEBUG) Log.v(TAG, "Found guided step theme reference? " + found);
+            if (found) {
+                ContextThemeWrapper themeWrapper =
+                        new ContextThemeWrapper(context, typedValue.resourceId);
+                if (isGuidedStepTheme(themeWrapper)) {
+                    mThemeWrapper = themeWrapper;
+                } else {
+                    found = false;
+                    mThemeWrapper = null;
+                }
+            }
+            if (!found) {
+                Log.e(TAG, "GuidedStepFragment does not have an appropriate theme set.");
+            }
+        } else if (theme != -1) {
+            mThemeWrapper = new ContextThemeWrapper(context, theme);
+        }
+    }
+
+    private LayoutInflater getThemeInflater(LayoutInflater inflater) {
+        if (mThemeWrapper == null) {
+            return inflater;
+        } else {
+            return inflater.cloneInContext(mThemeWrapper);
+        }
+    }
+
+    private int getFirstCheckedAction() {
+        for (int i = 0, size = mActions.size(); i < size; i++) {
+            if (mActions.get(i).isChecked()) {
+                return i;
+            }
+        }
+        return 0;
+    }
+
+    void runImeAnimations(boolean entering) {
+        ArrayList<Animator> animators = new ArrayList<Animator>();
+        if (entering) {
+            mGuidanceStylist.onImeAppearing(animators);
+            mActionsStylist.onImeAppearing(animators);
+            mButtonActionsStylist.onImeAppearing(animators);
+        } else {
+            mGuidanceStylist.onImeDisappearing(animators);
+            mActionsStylist.onImeDisappearing(animators);
+            mButtonActionsStylist.onImeDisappearing(animators);
+        }
+        AnimatorSet set = new AnimatorSet();
+        set.playTogether(animators);
+        set.start();
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/GuidedStepRootLayout.java b/leanback/src/android/support/v17/leanback/app/GuidedStepRootLayout.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/app/GuidedStepRootLayout.java
rename to leanback/src/android/support/v17/leanback/app/GuidedStepRootLayout.java
diff --git a/leanback/src/android/support/v17/leanback/app/GuidedStepSupportFragment.java b/leanback/src/android/support/v17/leanback/app/GuidedStepSupportFragment.java
new file mode 100644
index 0000000..e276d07
--- /dev/null
+++ b/leanback/src/android/support/v17/leanback/app/GuidedStepSupportFragment.java
@@ -0,0 +1,1415 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.content.Context;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.RestrictTo;
+import android.support.v17.leanback.R;
+import android.support.v17.leanback.transition.TransitionHelper;
+import android.support.v17.leanback.widget.DiffCallback;
+import android.support.v17.leanback.widget.GuidanceStylist;
+import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
+import android.support.v17.leanback.widget.GuidedAction;
+import android.support.v17.leanback.widget.GuidedActionAdapter;
+import android.support.v17.leanback.widget.GuidedActionAdapterGroup;
+import android.support.v17.leanback.widget.GuidedActionsStylist;
+import android.support.v17.leanback.widget.NonOverlappingLinearLayout;
+import android.support.v4.app.ActivityCompat;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentManager.BackStackEntry;
+import android.support.v4.app.FragmentTransaction;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.ContextThemeWrapper;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A GuidedStepSupportFragment is used to guide the user through a decision or series of decisions.
+ * It is composed of a guidance view on the left and a view on the right containing a list of
+ * possible actions.
+ * <p>
+ * <h3>Basic Usage</h3>
+ * <p>
+ * Clients of GuidedStepSupportFragment must create a custom subclass to attach to their Activities.
+ * This custom subclass provides the information necessary to construct the user interface and
+ * respond to user actions. At a minimum, subclasses should override:
+ * <ul>
+ * <li>{@link #onCreateGuidance}, to provide instructions to the user</li>
+ * <li>{@link #onCreateActions}, to provide a set of {@link GuidedAction}s the user can take</li>
+ * <li>{@link #onGuidedActionClicked}, to respond to those actions</li>
+ * </ul>
+ * <p>
+ * Clients use following helper functions to add GuidedStepSupportFragment to Activity or FragmentManager:
+ * <ul>
+ * <li>{@link #addAsRoot(FragmentActivity, GuidedStepSupportFragment, int)}, to be called during Activity onCreate,
+ * adds GuidedStepSupportFragment as the first Fragment in activity.</li>
+ * <li>{@link #add(FragmentManager, GuidedStepSupportFragment)} or {@link #add(FragmentManager,
+ * GuidedStepSupportFragment, int)}, to add GuidedStepSupportFragment on top of existing Fragments or
+ * replacing existing GuidedStepSupportFragment when moving forward to next step.</li>
+ * <li>{@link #finishGuidedStepSupportFragments()} can either finish the activity or pop all
+ * GuidedStepSupportFragment from stack.
+ * <li>If app chooses not to use the helper function, it is the app's responsibility to call
+ * {@link #setUiStyle(int)} to select fragment transition and remember the stack entry where it
+ * need pops to.
+ * </ul>
+ * <h3>Theming and Stylists</h3>
+ * <p>
+ * GuidedStepSupportFragment delegates its visual styling to classes called stylists. The {@link
+ * GuidanceStylist} is responsible for the left guidance view, while the {@link
+ * GuidedActionsStylist} is responsible for the right actions view. The stylists use theme
+ * attributes to derive values associated with the presentation, such as colors, animations, etc.
+ * Most simple visual aspects of GuidanceStylist and GuidedActionsStylist can be customized
+ * via theming; see their documentation for more information.
+ * <p>
+ * GuidedStepSupportFragments must have access to an appropriate theme in order for the stylists to
+ * function properly.  Specifically, the fragment must receive {@link
+ * android.support.v17.leanback.R.style#Theme_Leanback_GuidedStep}, or a theme whose parent is
+ * is set to that theme. Themes can be provided in one of three ways:
+ * <ul>
+ * <li>The simplest way is to set the theme for the host Activity to the GuidedStep theme or a
+ * theme that derives from it.</li>
+ * <li>If the Activity already has a theme and setting its parent theme is inconvenient, the
+ * existing Activity theme can have an entry added for the attribute {@link
+ * android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedStepTheme}. If present,
+ * this theme will be used by GuidedStepSupportFragment as an overlay to the Activity's theme.</li>
+ * <li>Finally, custom subclasses of GuidedStepSupportFragment may provide a theme through the {@link
+ * #onProvideTheme} method. This can be useful if a subclass is used across multiple
+ * Activities.</li>
+ * </ul>
+ * <p>
+ * If the theme is provided in multiple ways, the onProvideTheme override has priority, followed by
+ * the Activity's theme.  (Themes whose parent theme is already set to the guided step theme do not
+ * need to set the guidedStepTheme attribute; if set, it will be ignored.)
+ * <p>
+ * If themes do not provide enough customizability, the stylists themselves may be subclassed and
+ * provided to the GuidedStepSupportFragment through the {@link #onCreateGuidanceStylist} and {@link
+ * #onCreateActionsStylist} methods.  The stylists have simple hooks so that subclasses
+ * may override layout files; subclasses may also have more complex logic to determine styling.
+ * <p>
+ * <h3>Guided sequences</h3>
+ * <p>
+ * GuidedStepSupportFragments can be grouped together to provide a guided sequence. GuidedStepSupportFragments
+ * grouped as a sequence use custom animations provided by {@link GuidanceStylist} and
+ * {@link GuidedActionsStylist} (or subclasses) during transitions between steps. Clients
+ * should use {@link #add} to place subsequent GuidedFragments onto the fragment stack so that
+ * custom animations are properly configured. (Custom animations are triggered automatically when
+ * the fragment stack is subsequently popped by any normal mechanism.)
+ * <p>
+ * <i>Note: Currently GuidedStepSupportFragments grouped in this way must all be defined programmatically,
+ * rather than in XML. This restriction may be removed in the future.</i>
+ *
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedStepTheme
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedStepBackground
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionContentWidthWeight
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionContentWidthWeightTwoPanels
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionsBackground
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionsBackgroundDark
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionsElevation
+ * @see GuidanceStylist
+ * @see GuidanceStylist.Guidance
+ * @see GuidedAction
+ * @see GuidedActionsStylist
+ */
+public class GuidedStepSupportFragment extends Fragment implements GuidedActionAdapter.FocusListener {
+
+    private static final String TAG_LEAN_BACK_ACTIONS_FRAGMENT = "leanBackGuidedStepSupportFragment";
+    private static final String EXTRA_ACTION_PREFIX = "action_";
+    private static final String EXTRA_BUTTON_ACTION_PREFIX = "buttonaction_";
+
+    private static final String ENTRY_NAME_REPLACE = "GuidedStepDefault";
+
+    private static final String ENTRY_NAME_ENTRANCE = "GuidedStepEntrance";
+
+    private static final boolean IS_FRAMEWORK_FRAGMENT = false;
+
+    /**
+     * Fragment argument name for UI style.  The argument value is persisted in fragment state and
+     * used to select fragment transition. The value is initially {@link #UI_STYLE_ENTRANCE} and
+     * might be changed in one of the three helper functions:
+     * <ul>
+     * <li>{@link #addAsRoot(FragmentActivity, GuidedStepSupportFragment, int)} sets to
+     * {@link #UI_STYLE_ACTIVITY_ROOT}</li>
+     * <li>{@link #add(FragmentManager, GuidedStepSupportFragment)} or {@link #add(FragmentManager,
+     * GuidedStepSupportFragment, int)} sets it to {@link #UI_STYLE_REPLACE} if there is already a
+     * GuidedStepSupportFragment on stack.</li>
+     * <li>{@link #finishGuidedStepSupportFragments()} changes current GuidedStepSupportFragment to
+     * {@link #UI_STYLE_ENTRANCE} for the non activity case.  This is a special case that changes
+     * the transition settings after fragment has been created,  in order to force current
+     * GuidedStepSupportFragment run a return transition of {@link #UI_STYLE_ENTRANCE}</li>
+     * </ul>
+     * <p>
+     * Argument value can be either:
+     * <ul>
+     * <li>{@link #UI_STYLE_REPLACE}</li>
+     * <li>{@link #UI_STYLE_ENTRANCE}</li>
+     * <li>{@link #UI_STYLE_ACTIVITY_ROOT}</li>
+     * </ul>
+     */
+    public static final String EXTRA_UI_STYLE = "uiStyle";
+
+    /**
+     * This is the case that we use GuidedStepSupportFragment to replace another existing
+     * GuidedStepSupportFragment when moving forward to next step. Default behavior of this style is:
+     * <ul>
+     * <li>Enter transition slides in from END(right), exit transition same as
+     * {@link #UI_STYLE_ENTRANCE}.
+     * </li>
+     * </ul>
+     */
+    public static final int UI_STYLE_REPLACE = 0;
+
+    /**
+     * @deprecated Same value as {@link #UI_STYLE_REPLACE}.
+     */
+    @Deprecated
+    public static final int UI_STYLE_DEFAULT = 0;
+
+    /**
+     * Default value for argument {@link #EXTRA_UI_STYLE}. The default value is assigned in
+     * GuidedStepSupportFragment constructor. This is the case that we show GuidedStepSupportFragment on top of
+     * other content. The default behavior of this style:
+     * <ul>
+     * <li>Enter transition slides in from two sides, exit transition slide out to START(left).
+     * Background will be faded in. Note: Changing exit transition by UI style is not working
+     * because fragment transition asks for exit transition before UI style is restored in Fragment
+     * .onCreate().</li>
+     * </ul>
+     * When popping multiple GuidedStepSupportFragment, {@link #finishGuidedStepSupportFragments()} also changes
+     * the top GuidedStepSupportFragment to UI_STYLE_ENTRANCE in order to run the return transition
+     * (reverse of enter transition) of UI_STYLE_ENTRANCE.
+     */
+    public static final int UI_STYLE_ENTRANCE = 1;
+
+    /**
+     * One possible value of argument {@link #EXTRA_UI_STYLE}. This is the case that we show first
+     * GuidedStepSupportFragment in a separate activity. The default behavior of this style:
+     * <ul>
+     * <li>Enter transition is assigned null (will rely on activity transition), exit transition is
+     * same as {@link #UI_STYLE_ENTRANCE}. Note: Changing exit transition by UI style is not working
+     * because fragment transition asks for exit transition before UI style is restored in
+     * Fragment.onCreate().</li>
+     * </ul>
+     */
+    public static final int UI_STYLE_ACTIVITY_ROOT = 2;
+
+    /**
+     * Animation to slide the contents from the side (left/right).
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    public static final int SLIDE_FROM_SIDE = 0;
+
+    /**
+     * Animation to slide the contents from the bottom.
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    public static final int SLIDE_FROM_BOTTOM = 1;
+
+    private static final String TAG = "GuidedStepF";
+    private static final boolean DEBUG = false;
+
+    /**
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    public static class DummyFragment extends Fragment {
+        @Override
+        public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                Bundle savedInstanceState) {
+            final View v = new View(inflater.getContext());
+            v.setVisibility(View.GONE);
+            return v;
+        }
+    }
+
+    private ContextThemeWrapper mThemeWrapper;
+    private GuidanceStylist mGuidanceStylist;
+    GuidedActionsStylist mActionsStylist;
+    private GuidedActionsStylist mButtonActionsStylist;
+    private GuidedActionAdapter mAdapter;
+    private GuidedActionAdapter mSubAdapter;
+    private GuidedActionAdapter mButtonAdapter;
+    private GuidedActionAdapterGroup mAdapterGroup;
+    private List<GuidedAction> mActions = new ArrayList<GuidedAction>();
+    private List<GuidedAction> mButtonActions = new ArrayList<GuidedAction>();
+    private int entranceTransitionType = SLIDE_FROM_SIDE;
+
+    public GuidedStepSupportFragment() {
+        mGuidanceStylist = onCreateGuidanceStylist();
+        mActionsStylist = onCreateActionsStylist();
+        mButtonActionsStylist = onCreateButtonActionsStylist();
+        onProvideFragmentTransitions();
+    }
+
+    /**
+     * Creates the presenter used to style the guidance panel. The default implementation returns
+     * a basic GuidanceStylist.
+     * @return The GuidanceStylist used in this fragment.
+     */
+    public GuidanceStylist onCreateGuidanceStylist() {
+        return new GuidanceStylist();
+    }
+
+    /**
+     * Creates the presenter used to style the guided actions panel. The default implementation
+     * returns a basic GuidedActionsStylist.
+     * @return The GuidedActionsStylist used in this fragment.
+     */
+    public GuidedActionsStylist onCreateActionsStylist() {
+        return new GuidedActionsStylist();
+    }
+
+    /**
+     * Creates the presenter used to style a sided actions panel for button only.
+     * The default implementation returns a basic GuidedActionsStylist.
+     * @return The GuidedActionsStylist used in this fragment.
+     */
+    public GuidedActionsStylist onCreateButtonActionsStylist() {
+        GuidedActionsStylist stylist = new GuidedActionsStylist();
+        stylist.setAsButtonActions();
+        return stylist;
+    }
+
+    /**
+     * Returns the theme used for styling the fragment. The default returns -1, indicating that the
+     * host Activity's theme should be used.
+     * @return The theme resource ID of the theme to use in this fragment, or -1 to use the
+     * host Activity's theme.
+     */
+    public int onProvideTheme() {
+        return -1;
+    }
+
+    /**
+     * Returns the information required to provide guidance to the user. This hook is called during
+     * {@link #onCreateView}.  May be overridden to return a custom subclass of {@link
+     * GuidanceStylist.Guidance} for use in a subclass of {@link GuidanceStylist}. The default
+     * returns a Guidance object with empty fields; subclasses should override.
+     * @param savedInstanceState The saved instance state from onCreateView.
+     * @return The Guidance object representing the information used to guide the user.
+     */
+    public @NonNull Guidance onCreateGuidance(Bundle savedInstanceState) {
+        return new Guidance("", "", "", null);
+    }
+
+    /**
+     * Fills out the set of actions available to the user. This hook is called during {@link
+     * #onCreate}. The default leaves the list of actions empty; subclasses should override.
+     * @param actions A non-null, empty list ready to be populated.
+     * @param savedInstanceState The saved instance state from onCreate.
+     */
+    public void onCreateActions(@NonNull List<GuidedAction> actions, Bundle savedInstanceState) {
+    }
+
+    /**
+     * Fills out the set of actions shown at right available to the user. This hook is called during
+     * {@link #onCreate}. The default leaves the list of actions empty; subclasses may override.
+     * @param actions A non-null, empty list ready to be populated.
+     * @param savedInstanceState The saved instance state from onCreate.
+     */
+    public void onCreateButtonActions(@NonNull List<GuidedAction> actions,
+            Bundle savedInstanceState) {
+    }
+
+    /**
+     * Callback invoked when an action is taken by the user. Subclasses should override in
+     * order to act on the user's decisions.
+     * @param action The chosen action.
+     */
+    public void onGuidedActionClicked(GuidedAction action) {
+    }
+
+    /**
+     * Callback invoked when an action in sub actions is taken by the user. Subclasses should
+     * override in order to act on the user's decisions.  Default return value is true to close
+     * the sub actions list.
+     * @param action The chosen action.
+     * @return true to collapse the sub actions list, false to keep it expanded.
+     */
+    public boolean onSubGuidedActionClicked(GuidedAction action) {
+        return true;
+    }
+
+    /**
+     * @return True if is current expanded including subactions list or
+     * action with {@link GuidedAction#hasEditableActivatorView()} is true.
+     */
+    public boolean isExpanded() {
+        return mActionsStylist.isExpanded();
+    }
+
+    /**
+     * @return True if the sub actions list is expanded, false otherwise.
+     */
+    public boolean isSubActionsExpanded() {
+        return mActionsStylist.isSubActionsExpanded();
+    }
+
+    /**
+     * Expand a given action's sub actions list.
+     * @param action GuidedAction to expand.
+     * @see #expandAction(GuidedAction, boolean)
+     */
+    public void expandSubActions(GuidedAction action) {
+        if (!action.hasSubActions()) {
+            return;
+        }
+        expandAction(action, true);
+    }
+
+    /**
+     * Expand a given action with sub actions list or
+     * {@link GuidedAction#hasEditableActivatorView()} is true. The method must be called after
+     * {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)} creates fragment view.
+     *
+     * @param action GuidedAction to expand.
+     * @param withTransition True to run transition animation, false otherwise.
+     */
+    public void expandAction(GuidedAction action, boolean withTransition) {
+        mActionsStylist.expandAction(action, withTransition);
+    }
+
+    /**
+     * Collapse sub actions list.
+     * @see GuidedAction#getSubActions()
+     */
+    public void collapseSubActions() {
+        collapseAction(true);
+    }
+
+    /**
+     * Collapse action which either has a sub actions list or action with
+     * {@link GuidedAction#hasEditableActivatorView()} is true.
+     *
+     * @param withTransition True to run transition animation, false otherwise.
+     */
+    public void collapseAction(boolean withTransition) {
+        if (mActionsStylist != null && mActionsStylist.getActionsGridView() != null) {
+            mActionsStylist.collapseAction(withTransition);
+        }
+    }
+
+    /**
+     * Callback invoked when an action is focused (made to be the current selection) by the user.
+     */
+    @Override
+    public void onGuidedActionFocused(GuidedAction action) {
+    }
+
+    /**
+     * Callback invoked when an action's title or description has been edited, this happens either
+     * when user clicks confirm button in IME or user closes IME window by BACK key.
+     * @deprecated Override {@link #onGuidedActionEditedAndProceed(GuidedAction)} and/or
+     *             {@link #onGuidedActionEditCanceled(GuidedAction)}.
+     */
+    @Deprecated
+    public void onGuidedActionEdited(GuidedAction action) {
+    }
+
+    /**
+     * Callback invoked when an action has been canceled editing, for example when user closes
+     * IME window by BACK key.  Default implementation calls deprecated method
+     * {@link #onGuidedActionEdited(GuidedAction)}.
+     * @param action The action which has been canceled editing.
+     */
+    public void onGuidedActionEditCanceled(GuidedAction action) {
+        onGuidedActionEdited(action);
+    }
+
+    /**
+     * Callback invoked when an action has been edited, for example when user clicks confirm button
+     * in IME window.  Default implementation calls deprecated method
+     * {@link #onGuidedActionEdited(GuidedAction)} and returns {@link GuidedAction#ACTION_ID_NEXT}.
+     *
+     * @param action The action that has been edited.
+     * @return ID of the action will be focused or {@link GuidedAction#ACTION_ID_NEXT},
+     * {@link GuidedAction#ACTION_ID_CURRENT}.
+     */
+    public long onGuidedActionEditedAndProceed(GuidedAction action) {
+        onGuidedActionEdited(action);
+        return GuidedAction.ACTION_ID_NEXT;
+    }
+
+    /**
+     * Adds the specified GuidedStepSupportFragment to the fragment stack, replacing any existing
+     * GuidedStepSupportFragments in the stack, and configuring the fragment-to-fragment custom
+     * transitions.  A backstack entry is added, so the fragment will be dismissed when BACK key
+     * is pressed.
+     * <li>If current fragment on stack is GuidedStepSupportFragment: assign {@link #UI_STYLE_REPLACE}
+     * <li>If current fragment on stack is not GuidedStepSupportFragment: assign {@link #UI_STYLE_ENTRANCE}
+     * <p>
+     * Note: currently fragments added using this method must be created programmatically rather
+     * than via XML.
+     * @param fragmentManager The FragmentManager to be used in the transaction.
+     * @param fragment The GuidedStepSupportFragment to be inserted into the fragment stack.
+     * @return The ID returned by the call FragmentTransaction.commit.
+     */
+    public static int add(FragmentManager fragmentManager, GuidedStepSupportFragment fragment) {
+        return add(fragmentManager, fragment, android.R.id.content);
+    }
+
+    /**
+     * Adds the specified GuidedStepSupportFragment to the fragment stack, replacing any existing
+     * GuidedStepSupportFragments in the stack, and configuring the fragment-to-fragment custom
+     * transitions.  A backstack entry is added, so the fragment will be dismissed when BACK key
+     * is pressed.
+     * <li>If current fragment on stack is GuidedStepSupportFragment: assign {@link #UI_STYLE_REPLACE} and
+     * {@link #onAddSharedElementTransition(FragmentTransaction, GuidedStepSupportFragment)} will be called
+     * to perform shared element transition between GuidedStepSupportFragments.
+     * <li>If current fragment on stack is not GuidedStepSupportFragment: assign {@link #UI_STYLE_ENTRANCE}
+     * <p>
+     * Note: currently fragments added using this method must be created programmatically rather
+     * than via XML.
+     * @param fragmentManager The FragmentManager to be used in the transaction.
+     * @param fragment The GuidedStepSupportFragment to be inserted into the fragment stack.
+     * @param id The id of container to add GuidedStepSupportFragment, can be android.R.id.content.
+     * @return The ID returned by the call FragmentTransaction.commit.
+     */
+    public static int add(FragmentManager fragmentManager, GuidedStepSupportFragment fragment, int id) {
+        GuidedStepSupportFragment current = getCurrentGuidedStepSupportFragment(fragmentManager);
+        boolean inGuidedStep = current != null;
+        if (IS_FRAMEWORK_FRAGMENT && Build.VERSION.SDK_INT >= 21 && Build.VERSION.SDK_INT < 23
+                && !inGuidedStep) {
+            // workaround b/22631964 for framework fragment
+            fragmentManager.beginTransaction()
+                .replace(id, new DummyFragment(), TAG_LEAN_BACK_ACTIONS_FRAGMENT)
+                .commit();
+        }
+        FragmentTransaction ft = fragmentManager.beginTransaction();
+
+        fragment.setUiStyle(inGuidedStep ? UI_STYLE_REPLACE : UI_STYLE_ENTRANCE);
+        ft.addToBackStack(fragment.generateStackEntryName());
+        if (current != null) {
+            fragment.onAddSharedElementTransition(ft, current);
+        }
+        return ft.replace(id, fragment, TAG_LEAN_BACK_ACTIONS_FRAGMENT).commit();
+    }
+
+    /**
+     * Called when this fragment is added to FragmentTransaction with {@link #UI_STYLE_REPLACE} (aka
+     * when the GuidedStepSupportFragment replacing an existing GuidedStepSupportFragment). Default implementation
+     * establishes connections between action background views to morph action background bounds
+     * change from disappearing GuidedStepSupportFragment into this GuidedStepSupportFragment. The default
+     * implementation heavily relies on {@link GuidedActionsStylist}'s layout, app may override this
+     * method when modifying the default layout of {@link GuidedActionsStylist}.
+     *
+     * @see GuidedActionsStylist
+     * @see #onProvideFragmentTransitions()
+     * @param ft The FragmentTransaction to add shared element.
+     * @param disappearing The disappearing fragment.
+     */
+    protected void onAddSharedElementTransition(FragmentTransaction ft, GuidedStepSupportFragment
+            disappearing) {
+        View fragmentView = disappearing.getView();
+        addNonNullSharedElementTransition(ft, fragmentView.findViewById(
+                R.id.action_fragment_root), "action_fragment_root");
+        addNonNullSharedElementTransition(ft, fragmentView.findViewById(
+                R.id.action_fragment_background), "action_fragment_background");
+        addNonNullSharedElementTransition(ft, fragmentView.findViewById(
+                R.id.action_fragment), "action_fragment");
+        addNonNullSharedElementTransition(ft, fragmentView.findViewById(
+                R.id.guidedactions_root), "guidedactions_root");
+        addNonNullSharedElementTransition(ft, fragmentView.findViewById(
+                R.id.guidedactions_content), "guidedactions_content");
+        addNonNullSharedElementTransition(ft, fragmentView.findViewById(
+                R.id.guidedactions_list_background), "guidedactions_list_background");
+        addNonNullSharedElementTransition(ft, fragmentView.findViewById(
+                R.id.guidedactions_root2), "guidedactions_root2");
+        addNonNullSharedElementTransition(ft, fragmentView.findViewById(
+                R.id.guidedactions_content2), "guidedactions_content2");
+        addNonNullSharedElementTransition(ft, fragmentView.findViewById(
+                R.id.guidedactions_list_background2), "guidedactions_list_background2");
+    }
+
+    private static void addNonNullSharedElementTransition (FragmentTransaction ft, View subView,
+                                                           String transitionName)
+    {
+        if (subView != null)
+            TransitionHelper.addSharedElement(ft, subView, transitionName);
+    }
+
+    /**
+     * Returns BackStackEntry name for the GuidedStepSupportFragment or empty String if no entry is
+     * associated.  Note {@link #UI_STYLE_ACTIVITY_ROOT} will return empty String.  The method
+     * returns undefined value if the fragment is not in FragmentManager.
+     * @return BackStackEntry name for the GuidedStepSupportFragment or empty String if no entry is
+     * associated.
+     */
+    final String generateStackEntryName() {
+        return generateStackEntryName(getUiStyle(), getClass());
+    }
+
+    /**
+     * Generates BackStackEntry name for GuidedStepSupportFragment class or empty String if no entry is
+     * associated.  Note {@link #UI_STYLE_ACTIVITY_ROOT} is not allowed and returns empty String.
+     * @param uiStyle {@link #UI_STYLE_REPLACE} or {@link #UI_STYLE_ENTRANCE}
+     * @return BackStackEntry name for the GuidedStepSupportFragment or empty String if no entry is
+     * associated.
+     */
+    static String generateStackEntryName(int uiStyle, Class guidedStepFragmentClass) {
+        switch (uiStyle) {
+        case UI_STYLE_REPLACE:
+            return ENTRY_NAME_REPLACE + guidedStepFragmentClass.getName();
+        case UI_STYLE_ENTRANCE:
+            return ENTRY_NAME_ENTRANCE + guidedStepFragmentClass.getName();
+        case UI_STYLE_ACTIVITY_ROOT:
+        default:
+            return "";
+        }
+    }
+
+    /**
+     * Returns true if the backstack entry represents GuidedStepSupportFragment with
+     * {@link #UI_STYLE_ENTRANCE}, i.e. this is the first GuidedStepSupportFragment pushed to stack; false
+     * otherwise.
+     * @see #generateStackEntryName(int, Class)
+     * @param backStackEntryName Name of BackStackEntry.
+     * @return True if the backstack represents GuidedStepSupportFragment with {@link #UI_STYLE_ENTRANCE};
+     * false otherwise.
+     */
+    static boolean isStackEntryUiStyleEntrance(String backStackEntryName) {
+        return backStackEntryName != null && backStackEntryName.startsWith(ENTRY_NAME_ENTRANCE);
+    }
+
+    /**
+     * Extract Class name from BackStackEntry name.
+     * @param backStackEntryName Name of BackStackEntry.
+     * @return Class name of GuidedStepSupportFragment.
+     */
+    static String getGuidedStepSupportFragmentClassName(String backStackEntryName) {
+        if (backStackEntryName.startsWith(ENTRY_NAME_REPLACE)) {
+            return backStackEntryName.substring(ENTRY_NAME_REPLACE.length());
+        } else if (backStackEntryName.startsWith(ENTRY_NAME_ENTRANCE)) {
+            return backStackEntryName.substring(ENTRY_NAME_ENTRANCE.length());
+        } else {
+            return "";
+        }
+    }
+
+    /**
+     * Adds the specified GuidedStepSupportFragment as content of Activity; no backstack entry is added so
+     * the activity will be dismissed when BACK key is pressed.  The method is typically called in
+     * Activity.onCreate() when savedInstanceState is null.  When savedInstanceState is not null,
+     * the Activity is being restored,  do not call addAsRoot() to duplicate the Fragment restored
+     * by FragmentManager.
+     * {@link #UI_STYLE_ACTIVITY_ROOT} is assigned.
+     *
+     * Note: currently fragments added using this method must be created programmatically rather
+     * than via XML.
+     * @param activity The Activity to be used to insert GuidedstepFragment.
+     * @param fragment The GuidedStepSupportFragment to be inserted into the fragment stack.
+     * @param id The id of container to add GuidedStepSupportFragment, can be android.R.id.content.
+     * @return The ID returned by the call FragmentTransaction.commit, or -1 there is already
+     *         GuidedStepSupportFragment.
+     */
+    public static int addAsRoot(FragmentActivity activity, GuidedStepSupportFragment fragment, int id) {
+        // Workaround b/23764120: call getDecorView() to force requestFeature of ActivityTransition.
+        activity.getWindow().getDecorView();
+        FragmentManager fragmentManager = activity.getSupportFragmentManager();
+        if (fragmentManager.findFragmentByTag(TAG_LEAN_BACK_ACTIONS_FRAGMENT) != null) {
+            Log.w(TAG, "Fragment is already exists, likely calling "
+                    + "addAsRoot() when savedInstanceState is not null in Activity.onCreate().");
+            return -1;
+        }
+        FragmentTransaction ft = fragmentManager.beginTransaction();
+        fragment.setUiStyle(UI_STYLE_ACTIVITY_ROOT);
+        return ft.replace(id, fragment, TAG_LEAN_BACK_ACTIONS_FRAGMENT).commit();
+    }
+
+    /**
+     * Returns the current GuidedStepSupportFragment on the fragment transaction stack.
+     * @return The current GuidedStepSupportFragment, if any, on the fragment transaction stack.
+     */
+    public static GuidedStepSupportFragment getCurrentGuidedStepSupportFragment(FragmentManager fm) {
+        Fragment f = fm.findFragmentByTag(TAG_LEAN_BACK_ACTIONS_FRAGMENT);
+        if (f instanceof GuidedStepSupportFragment) {
+            return (GuidedStepSupportFragment) f;
+        }
+        return null;
+    }
+
+    /**
+     * Returns the GuidanceStylist that displays guidance information for the user.
+     * @return The GuidanceStylist for this fragment.
+     */
+    public GuidanceStylist getGuidanceStylist() {
+        return mGuidanceStylist;
+    }
+
+    /**
+     * Returns the GuidedActionsStylist that displays the actions the user may take.
+     * @return The GuidedActionsStylist for this fragment.
+     */
+    public GuidedActionsStylist getGuidedActionsStylist() {
+        return mActionsStylist;
+    }
+
+    /**
+     * Returns the list of button GuidedActions that the user may take in this fragment.
+     * @return The list of button GuidedActions for this fragment.
+     */
+    public List<GuidedAction> getButtonActions() {
+        return mButtonActions;
+    }
+
+    /**
+     * Find button GuidedAction by Id.
+     * @param id  Id of the button action to search.
+     * @return  GuidedAction object or null if not found.
+     */
+    public GuidedAction findButtonActionById(long id) {
+        int index = findButtonActionPositionById(id);
+        return index >= 0 ? mButtonActions.get(index) : null;
+    }
+
+    /**
+     * Find button GuidedAction position in array by Id.
+     * @param id  Id of the button action to search.
+     * @return  position of GuidedAction object in array or -1 if not found.
+     */
+    public int findButtonActionPositionById(long id) {
+        if (mButtonActions != null) {
+            for (int i = 0; i < mButtonActions.size(); i++) {
+                GuidedAction action = mButtonActions.get(i);
+                if (mButtonActions.get(i).getId() == id) {
+                    return i;
+                }
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * Returns the GuidedActionsStylist that displays the button actions the user may take.
+     * @return The GuidedActionsStylist for this fragment.
+     */
+    public GuidedActionsStylist getGuidedButtonActionsStylist() {
+        return mButtonActionsStylist;
+    }
+
+    /**
+     * Sets the list of button GuidedActions that the user may take in this fragment.
+     * @param actions The list of button GuidedActions for this fragment.
+     */
+    public void setButtonActions(List<GuidedAction> actions) {
+        mButtonActions = actions;
+        if (mButtonAdapter != null) {
+            mButtonAdapter.setActions(mButtonActions);
+        }
+    }
+
+    /**
+     * Notify an button action has changed and update its UI.
+     * @param position Position of the button GuidedAction in array.
+     */
+    public void notifyButtonActionChanged(int position) {
+        if (mButtonAdapter != null) {
+            mButtonAdapter.notifyItemChanged(position);
+        }
+    }
+
+    /**
+     * Returns the view corresponding to the button action at the indicated position in the list of
+     * actions for this fragment.
+     * @param position The integer position of the button action of interest.
+     * @return The View corresponding to the button action at the indicated position, or null if
+     * that action is not currently onscreen.
+     */
+    public View getButtonActionItemView(int position) {
+        final RecyclerView.ViewHolder holder = mButtonActionsStylist.getActionsGridView()
+                    .findViewHolderForPosition(position);
+        return holder == null ? null : holder.itemView;
+    }
+
+    /**
+     * Scrolls the action list to the position indicated, selecting that button action's view.
+     * @param position The integer position of the button action of interest.
+     */
+    public void setSelectedButtonActionPosition(int position) {
+        mButtonActionsStylist.getActionsGridView().setSelectedPosition(position);
+    }
+
+    /**
+     * Returns the position if the currently selected button GuidedAction.
+     * @return position The integer position of the currently selected button action.
+     */
+    public int getSelectedButtonActionPosition() {
+        return mButtonActionsStylist.getActionsGridView().getSelectedPosition();
+    }
+
+    /**
+     * Returns the list of GuidedActions that the user may take in this fragment.
+     * @return The list of GuidedActions for this fragment.
+     */
+    public List<GuidedAction> getActions() {
+        return mActions;
+    }
+
+    /**
+     * Find GuidedAction by Id.
+     * @param id  Id of the action to search.
+     * @return  GuidedAction object or null if not found.
+     */
+    public GuidedAction findActionById(long id) {
+        int index = findActionPositionById(id);
+        return index >= 0 ? mActions.get(index) : null;
+    }
+
+    /**
+     * Find GuidedAction position in array by Id.
+     * @param id  Id of the action to search.
+     * @return  position of GuidedAction object in array or -1 if not found.
+     */
+    public int findActionPositionById(long id) {
+        if (mActions != null) {
+            for (int i = 0; i < mActions.size(); i++) {
+                GuidedAction action = mActions.get(i);
+                if (mActions.get(i).getId() == id) {
+                    return i;
+                }
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * Sets the list of GuidedActions that the user may take in this fragment.
+     * Uses DiffCallback set by {@link #setActionsDiffCallback(DiffCallback)}.
+     *
+     * @param actions The list of GuidedActions for this fragment.
+     */
+    public void setActions(List<GuidedAction> actions) {
+        mActions = actions;
+        if (mAdapter != null) {
+            mAdapter.setActions(mActions);
+        }
+    }
+
+    /**
+     * Sets the RecyclerView DiffCallback used when {@link #setActions(List)} is called. By default
+     * GuidedStepSupportFragment uses
+     * {@link android.support.v17.leanback.widget.GuidedActionDiffCallback}.
+     * Sets it to null if app wants to refresh the whole list.
+     *
+     * @param diffCallback DiffCallback used in {@link #setActions(List)}.
+     */
+    public void setActionsDiffCallback(DiffCallback<GuidedAction> diffCallback) {
+        mAdapter.setDiffCallback(diffCallback);
+    }
+
+    /**
+     * Notify an action has changed and update its UI.
+     * @param position Position of the GuidedAction in array.
+     */
+    public void notifyActionChanged(int position) {
+        if (mAdapter != null) {
+            mAdapter.notifyItemChanged(position);
+        }
+    }
+
+    /**
+     * Returns the view corresponding to the action at the indicated position in the list of
+     * actions for this fragment.
+     * @param position The integer position of the action of interest.
+     * @return The View corresponding to the action at the indicated position, or null if that
+     * action is not currently onscreen.
+     */
+    public View getActionItemView(int position) {
+        final RecyclerView.ViewHolder holder = mActionsStylist.getActionsGridView()
+                    .findViewHolderForPosition(position);
+        return holder == null ? null : holder.itemView;
+    }
+
+    /**
+     * Scrolls the action list to the position indicated, selecting that action's view.
+     * @param position The integer position of the action of interest.
+     */
+    public void setSelectedActionPosition(int position) {
+        mActionsStylist.getActionsGridView().setSelectedPosition(position);
+    }
+
+    /**
+     * Returns the position if the currently selected GuidedAction.
+     * @return position The integer position of the currently selected action.
+     */
+    public int getSelectedActionPosition() {
+        return mActionsStylist.getActionsGridView().getSelectedPosition();
+    }
+
+    /**
+     * Called by Constructor to provide fragment transitions.  The default implementation assigns
+     * transitions based on {@link #getUiStyle()}:
+     * <ul>
+     * <li> {@link #UI_STYLE_REPLACE} Slide from/to end(right) for enter transition, slide from/to
+     * start(left) for exit transition, shared element enter transition is set to ChangeBounds.
+     * <li> {@link #UI_STYLE_ENTRANCE} Enter transition is set to slide from both sides, exit
+     * transition is same as {@link #UI_STYLE_REPLACE}, no shared element enter transition.
+     * <li> {@link #UI_STYLE_ACTIVITY_ROOT} Enter transition is set to null and app should rely on
+     * activity transition, exit transition is same as {@link #UI_STYLE_REPLACE}, no shared element
+     * enter transition.
+     * </ul>
+     * <p>
+     * The default implementation heavily relies on {@link GuidedActionsStylist} and
+     * {@link GuidanceStylist} layout, app may override this method when modifying the default
+     * layout of {@link GuidedActionsStylist} or {@link GuidanceStylist}.
+     * <p>
+     * TIP: because the fragment view is removed during fragment transition, in general app cannot
+     * use two Visibility transition together. Workaround is to create your own Visibility
+     * transition that controls multiple animators (e.g. slide and fade animation in one Transition
+     * class).
+     */
+    protected void onProvideFragmentTransitions() {
+        if (Build.VERSION.SDK_INT >= 21) {
+            final int uiStyle = getUiStyle();
+            if (uiStyle == UI_STYLE_REPLACE) {
+                Object enterTransition = TransitionHelper.createFadeAndShortSlide(Gravity.END);
+                TransitionHelper.exclude(enterTransition, R.id.guidedstep_background, true);
+                TransitionHelper.exclude(enterTransition, R.id.guidedactions_sub_list_background,
+                        true);
+                TransitionHelper.setEnterTransition(this, enterTransition);
+
+                Object fade = TransitionHelper.createFadeTransition(
+                        TransitionHelper.FADE_IN | TransitionHelper.FADE_OUT);
+                TransitionHelper.include(fade, R.id.guidedactions_sub_list_background);
+                Object changeBounds = TransitionHelper.createChangeBounds(false);
+                Object sharedElementTransition = TransitionHelper.createTransitionSet(false);
+                TransitionHelper.addTransition(sharedElementTransition, fade);
+                TransitionHelper.addTransition(sharedElementTransition, changeBounds);
+                TransitionHelper.setSharedElementEnterTransition(this, sharedElementTransition);
+            } else if (uiStyle == UI_STYLE_ENTRANCE) {
+                if (entranceTransitionType == SLIDE_FROM_SIDE) {
+                    Object fade = TransitionHelper.createFadeTransition(
+                            TransitionHelper.FADE_IN | TransitionHelper.FADE_OUT);
+                    TransitionHelper.include(fade, R.id.guidedstep_background);
+                    Object slideFromSide = TransitionHelper.createFadeAndShortSlide(
+                            Gravity.END | Gravity.START);
+                    TransitionHelper.include(slideFromSide, R.id.content_fragment);
+                    TransitionHelper.include(slideFromSide, R.id.action_fragment_root);
+                    Object enterTransition = TransitionHelper.createTransitionSet(false);
+                    TransitionHelper.addTransition(enterTransition, fade);
+                    TransitionHelper.addTransition(enterTransition, slideFromSide);
+                    TransitionHelper.setEnterTransition(this, enterTransition);
+                } else {
+                    Object slideFromBottom = TransitionHelper.createFadeAndShortSlide(
+                            Gravity.BOTTOM);
+                    TransitionHelper.include(slideFromBottom, R.id.guidedstep_background_view_root);
+                    Object enterTransition = TransitionHelper.createTransitionSet(false);
+                    TransitionHelper.addTransition(enterTransition, slideFromBottom);
+                    TransitionHelper.setEnterTransition(this, enterTransition);
+                }
+                // No shared element transition
+                TransitionHelper.setSharedElementEnterTransition(this, null);
+            } else if (uiStyle == UI_STYLE_ACTIVITY_ROOT) {
+                // for Activity root, we don't need enter transition, use activity transition
+                TransitionHelper.setEnterTransition(this, null);
+                // No shared element transition
+                TransitionHelper.setSharedElementEnterTransition(this, null);
+            }
+            // exitTransition is same for all style
+            Object exitTransition = TransitionHelper.createFadeAndShortSlide(Gravity.START);
+            TransitionHelper.exclude(exitTransition, R.id.guidedstep_background, true);
+            TransitionHelper.exclude(exitTransition, R.id.guidedactions_sub_list_background,
+                    true);
+            TransitionHelper.setExitTransition(this, exitTransition);
+        }
+    }
+
+    /**
+     * Called by onCreateView to inflate background view.  Default implementation loads view
+     * from {@link R.layout#lb_guidedstep_background} which holds a reference to
+     * guidedStepBackground.
+     * @param inflater LayoutInflater to load background view.
+     * @param container Parent view of background view.
+     * @param savedInstanceState
+     * @return Created background view or null if no background.
+     */
+    public View onCreateBackgroundView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        return inflater.inflate(R.layout.lb_guidedstep_background, container, false);
+    }
+
+    /**
+     * Set UI style to fragment arguments. Default value is {@link #UI_STYLE_ENTRANCE} when fragment
+     * is first initialized. UI style is used to choose different fragment transition animations and
+     * determine if this is the first GuidedStepSupportFragment on backstack. In most cases app does not
+     * directly call this method, app calls helper function
+     * {@link #add(FragmentManager, GuidedStepSupportFragment, int)}. However if the app creates Fragment
+     * transaction and controls backstack by itself, it would need call setUiStyle() to select the
+     * fragment transition to use.
+     *
+     * @param style {@link #UI_STYLE_ACTIVITY_ROOT} {@link #UI_STYLE_REPLACE} or
+     *        {@link #UI_STYLE_ENTRANCE}.
+     */
+    public void setUiStyle(int style) {
+        int oldStyle = getUiStyle();
+        Bundle arguments = getArguments();
+        boolean isNew = false;
+        if (arguments == null) {
+            arguments = new Bundle();
+            isNew = true;
+        }
+        arguments.putInt(EXTRA_UI_STYLE, style);
+        // call setArgument() will validate if the fragment is already added.
+        if (isNew) {
+            setArguments(arguments);
+        }
+        if (style != oldStyle) {
+            onProvideFragmentTransitions();
+        }
+    }
+
+    /**
+     * Read UI style from fragment arguments.  Default value is {@link #UI_STYLE_ENTRANCE} when
+     * fragment is first initialized.  UI style is used to choose different fragment transition
+     * animations and determine if this is the first GuidedStepSupportFragment on backstack.
+     *
+     * @return {@link #UI_STYLE_ACTIVITY_ROOT} {@link #UI_STYLE_REPLACE} or
+     * {@link #UI_STYLE_ENTRANCE}.
+     * @see #onProvideFragmentTransitions()
+     */
+    public int getUiStyle() {
+        Bundle b = getArguments();
+        if (b == null) return UI_STYLE_ENTRANCE;
+        return b.getInt(EXTRA_UI_STYLE, UI_STYLE_ENTRANCE);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        if (DEBUG) Log.v(TAG, "onCreate");
+        // Set correct transition from saved arguments.
+        onProvideFragmentTransitions();
+
+        ArrayList<GuidedAction> actions = new ArrayList<GuidedAction>();
+        onCreateActions(actions, savedInstanceState);
+        if (savedInstanceState != null) {
+            onRestoreActions(actions, savedInstanceState);
+        }
+        setActions(actions);
+        ArrayList<GuidedAction> buttonActions = new ArrayList<GuidedAction>();
+        onCreateButtonActions(buttonActions, savedInstanceState);
+        if (savedInstanceState != null) {
+            onRestoreButtonActions(buttonActions, savedInstanceState);
+        }
+        setButtonActions(buttonActions);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onDestroyView() {
+        mGuidanceStylist.onDestroyView();
+        mActionsStylist.onDestroyView();
+        mButtonActionsStylist.onDestroyView();
+        mAdapter = null;
+        mSubAdapter =  null;
+        mButtonAdapter = null;
+        mAdapterGroup = null;
+        super.onDestroyView();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        if (DEBUG) Log.v(TAG, "onCreateView");
+
+        resolveTheme();
+        inflater = getThemeInflater(inflater);
+
+        GuidedStepRootLayout root = (GuidedStepRootLayout) inflater.inflate(
+                R.layout.lb_guidedstep_fragment, container, false);
+
+        root.setFocusOutStart(isFocusOutStartAllowed());
+        root.setFocusOutEnd(isFocusOutEndAllowed());
+
+        ViewGroup guidanceContainer = (ViewGroup) root.findViewById(R.id.content_fragment);
+        ViewGroup actionContainer = (ViewGroup) root.findViewById(R.id.action_fragment);
+        ((NonOverlappingLinearLayout) actionContainer).setFocusableViewAvailableFixEnabled(true);
+
+        Guidance guidance = onCreateGuidance(savedInstanceState);
+        View guidanceView = mGuidanceStylist.onCreateView(inflater, guidanceContainer, guidance);
+        guidanceContainer.addView(guidanceView);
+
+        View actionsView = mActionsStylist.onCreateView(inflater, actionContainer);
+        actionContainer.addView(actionsView);
+
+        View buttonActionsView = mButtonActionsStylist.onCreateView(inflater, actionContainer);
+        actionContainer.addView(buttonActionsView);
+
+        GuidedActionAdapter.EditListener editListener = new GuidedActionAdapter.EditListener() {
+
+                @Override
+                public void onImeOpen() {
+                    runImeAnimations(true);
+                }
+
+                @Override
+                public void onImeClose() {
+                    runImeAnimations(false);
+                }
+
+                @Override
+                public long onGuidedActionEditedAndProceed(GuidedAction action) {
+                    return GuidedStepSupportFragment.this.onGuidedActionEditedAndProceed(action);
+                }
+
+                @Override
+                public void onGuidedActionEditCanceled(GuidedAction action) {
+                    GuidedStepSupportFragment.this.onGuidedActionEditCanceled(action);
+                }
+        };
+
+        mAdapter = new GuidedActionAdapter(mActions, new GuidedActionAdapter.ClickListener() {
+            @Override
+            public void onGuidedActionClicked(GuidedAction action) {
+                GuidedStepSupportFragment.this.onGuidedActionClicked(action);
+                if (isExpanded()) {
+                    collapseAction(true);
+                } else if (action.hasSubActions() || action.hasEditableActivatorView()) {
+                    expandAction(action, true);
+                }
+            }
+        }, this, mActionsStylist, false);
+        mButtonAdapter =
+                new GuidedActionAdapter(mButtonActions, new GuidedActionAdapter.ClickListener() {
+                    @Override
+                    public void onGuidedActionClicked(GuidedAction action) {
+                        GuidedStepSupportFragment.this.onGuidedActionClicked(action);
+                    }
+                }, this, mButtonActionsStylist, false);
+        mSubAdapter = new GuidedActionAdapter(null, new GuidedActionAdapter.ClickListener() {
+            @Override
+            public void onGuidedActionClicked(GuidedAction action) {
+                if (mActionsStylist.isInExpandTransition()) {
+                    return;
+                }
+                if (GuidedStepSupportFragment.this.onSubGuidedActionClicked(action)) {
+                    collapseSubActions();
+                }
+            }
+        }, this, mActionsStylist, true);
+        mAdapterGroup = new GuidedActionAdapterGroup();
+        mAdapterGroup.addAdpter(mAdapter, mButtonAdapter);
+        mAdapterGroup.addAdpter(mSubAdapter, null);
+        mAdapterGroup.setEditListener(editListener);
+        mActionsStylist.setEditListener(editListener);
+
+        mActionsStylist.getActionsGridView().setAdapter(mAdapter);
+        if (mActionsStylist.getSubActionsGridView() != null) {
+            mActionsStylist.getSubActionsGridView().setAdapter(mSubAdapter);
+        }
+        mButtonActionsStylist.getActionsGridView().setAdapter(mButtonAdapter);
+        if (mButtonActions.size() == 0) {
+            // when there is no button actions, we don't need show the second panel, but keep
+            // the width zero to run ChangeBounds transition.
+            LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
+                    buttonActionsView.getLayoutParams();
+            lp.weight = 0;
+            buttonActionsView.setLayoutParams(lp);
+        } else {
+            // when there are two actions panel, we need adjust the weight of action to
+            // guidedActionContentWidthWeightTwoPanels.
+            Context ctx = mThemeWrapper != null ? mThemeWrapper : getContext();
+            TypedValue typedValue = new TypedValue();
+            if (ctx.getTheme().resolveAttribute(R.attr.guidedActionContentWidthWeightTwoPanels,
+                    typedValue, true)) {
+                View actionsRoot = root.findViewById(R.id.action_fragment_root);
+                float weight = typedValue.getFloat();
+                LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) actionsRoot
+                        .getLayoutParams();
+                lp.weight = weight;
+                actionsRoot.setLayoutParams(lp);
+            }
+        }
+
+        // Add the background view.
+        View backgroundView = onCreateBackgroundView(inflater, root, savedInstanceState);
+        if (backgroundView != null) {
+            FrameLayout backgroundViewRoot = (FrameLayout)root.findViewById(
+                R.id.guidedstep_background_view_root);
+            backgroundViewRoot.addView(backgroundView, 0);
+        }
+
+        return root;
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        getView().findViewById(R.id.action_fragment).requestFocus();
+    }
+
+    /**
+     * Get the key will be used to save GuidedAction with Fragment.
+     * @param action GuidedAction to get key.
+     * @return Key to save the GuidedAction.
+     */
+    final String getAutoRestoreKey(GuidedAction action) {
+        return EXTRA_ACTION_PREFIX + action.getId();
+    }
+
+    /**
+     * Get the key will be used to save GuidedAction with Fragment.
+     * @param action GuidedAction to get key.
+     * @return Key to save the GuidedAction.
+     */
+    final String getButtonAutoRestoreKey(GuidedAction action) {
+        return EXTRA_BUTTON_ACTION_PREFIX + action.getId();
+    }
+
+    final static boolean isSaveEnabled(GuidedAction action) {
+        return action.isAutoSaveRestoreEnabled() && action.getId() != GuidedAction.NO_ID;
+    }
+
+    final void onRestoreActions(List<GuidedAction> actions, Bundle savedInstanceState) {
+        for (int i = 0, size = actions.size(); i < size; i++) {
+            GuidedAction action = actions.get(i);
+            if (isSaveEnabled(action)) {
+                action.onRestoreInstanceState(savedInstanceState, getAutoRestoreKey(action));
+            }
+        }
+    }
+
+    final void onRestoreButtonActions(List<GuidedAction> actions, Bundle savedInstanceState) {
+        for (int i = 0, size = actions.size(); i < size; i++) {
+            GuidedAction action = actions.get(i);
+            if (isSaveEnabled(action)) {
+                action.onRestoreInstanceState(savedInstanceState, getButtonAutoRestoreKey(action));
+            }
+        }
+    }
+
+    final void onSaveActions(List<GuidedAction> actions, Bundle outState) {
+        for (int i = 0, size = actions.size(); i < size; i++) {
+            GuidedAction action = actions.get(i);
+            if (isSaveEnabled(action)) {
+                action.onSaveInstanceState(outState, getAutoRestoreKey(action));
+            }
+        }
+    }
+
+    final void onSaveButtonActions(List<GuidedAction> actions, Bundle outState) {
+        for (int i = 0, size = actions.size(); i < size; i++) {
+            GuidedAction action = actions.get(i);
+            if (isSaveEnabled(action)) {
+                action.onSaveInstanceState(outState, getButtonAutoRestoreKey(action));
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        onSaveActions(mActions, outState);
+        onSaveButtonActions(mButtonActions, outState);
+    }
+
+    private static boolean isGuidedStepTheme(Context context) {
+        int resId = R.attr.guidedStepThemeFlag;
+        TypedValue typedValue = new TypedValue();
+        boolean found = context.getTheme().resolveAttribute(resId, typedValue, true);
+        if (DEBUG) Log.v(TAG, "Found guided step theme flag? " + found);
+        return found && typedValue.type == TypedValue.TYPE_INT_BOOLEAN && typedValue.data != 0;
+    }
+
+    /**
+     * Convenient method to close GuidedStepSupportFragments on top of other content or finish Activity if
+     * GuidedStepSupportFragments were started in a separate activity.  Pops all stack entries including
+     * {@link #UI_STYLE_ENTRANCE}; if {@link #UI_STYLE_ENTRANCE} is not found, finish the activity.
+     * Note that this method must be paired with {@link #add(FragmentManager, GuidedStepSupportFragment,
+     * int)} which sets up the stack entry name for finding which fragment we need to pop back to.
+     */
+    public void finishGuidedStepSupportFragments() {
+        final FragmentManager fragmentManager = getFragmentManager();
+        final int entryCount = fragmentManager.getBackStackEntryCount();
+        if (entryCount > 0) {
+            for (int i = entryCount - 1; i >= 0; i--) {
+                BackStackEntry entry = fragmentManager.getBackStackEntryAt(i);
+                if (isStackEntryUiStyleEntrance(entry.getName())) {
+                    GuidedStepSupportFragment top = getCurrentGuidedStepSupportFragment(fragmentManager);
+                    if (top != null) {
+                        top.setUiStyle(UI_STYLE_ENTRANCE);
+                    }
+                    fragmentManager.popBackStackImmediate(entry.getId(),
+                            FragmentManager.POP_BACK_STACK_INCLUSIVE);
+                    return;
+                }
+            }
+        }
+        ActivityCompat.finishAfterTransition(getActivity());
+    }
+
+    /**
+     * Convenient method to pop to fragment with Given class.
+     * @param  guidedStepFragmentClass  Name of the Class of GuidedStepSupportFragment to pop to.
+     * @param flags Either 0 or {@link FragmentManager#POP_BACK_STACK_INCLUSIVE}.
+     */
+    public void popBackStackToGuidedStepSupportFragment(Class guidedStepFragmentClass, int flags) {
+        if (!GuidedStepSupportFragment.class.isAssignableFrom(guidedStepFragmentClass)) {
+            return;
+        }
+        final FragmentManager fragmentManager = getFragmentManager();
+        final int entryCount = fragmentManager.getBackStackEntryCount();
+        String className = guidedStepFragmentClass.getName();
+        if (entryCount > 0) {
+            for (int i = entryCount - 1; i >= 0; i--) {
+                BackStackEntry entry = fragmentManager.getBackStackEntryAt(i);
+                String entryClassName = getGuidedStepSupportFragmentClassName(entry.getName());
+                if (className.equals(entryClassName)) {
+                    fragmentManager.popBackStackImmediate(entry.getId(), flags);
+                    return;
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns true if allows focus out of start edge of GuidedStepSupportFragment, false otherwise.
+     * Default value is false, the reason is to disable FocusFinder to find focusable views
+     * beneath content of GuidedStepSupportFragment.  Subclass may override.
+     * @return True if allows focus out of start edge of GuidedStepSupportFragment.
+     */
+    public boolean isFocusOutStartAllowed() {
+        return false;
+    }
+
+    /**
+     * Returns true if allows focus out of end edge of GuidedStepSupportFragment, false otherwise.
+     * Default value is false, the reason is to disable FocusFinder to find focusable views
+     * beneath content of GuidedStepSupportFragment.  Subclass may override.
+     * @return True if allows focus out of end edge of GuidedStepSupportFragment.
+     */
+    public boolean isFocusOutEndAllowed() {
+        return false;
+    }
+
+    /**
+     * Sets the transition type to be used for {@link #UI_STYLE_ENTRANCE} animation.
+     * Currently we provide 2 different variations for animation - slide in from
+     * side (default) or bottom.
+     *
+     * Ideally we can retrieve the screen mode settings from the theme attribute
+     * {@code Theme.Leanback.GuidedStep#guidedStepHeightWeight} and use that to
+     * determine the transition. But the fragment context to retrieve the theme
+     * isn't available on platform v23 or earlier.
+     *
+     * For now clients(subclasses) can call this method inside the constructor.
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    public void setEntranceTransitionType(int transitionType) {
+      this.entranceTransitionType = transitionType;
+    }
+
+    /**
+     * Opens the provided action in edit mode and raises ime. This can be
+     * used to programmatically skip the extra click required to go into edit mode. This method
+     * can be invoked in {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)}.
+     */
+    public void openInEditMode(GuidedAction action) {
+        mActionsStylist.openInEditMode(action);
+    }
+
+    private void resolveTheme() {
+        // Look up the guidedStepTheme in the currently specified theme.  If it exists,
+        // replace the theme with its value.
+        Context context = getContext();
+        int theme = onProvideTheme();
+        if (theme == -1 && !isGuidedStepTheme(context)) {
+            // Look up the guidedStepTheme in the activity's currently specified theme.  If it
+            // exists, replace the theme with its value.
+            int resId = R.attr.guidedStepTheme;
+            TypedValue typedValue = new TypedValue();
+            boolean found = context.getTheme().resolveAttribute(resId, typedValue, true);
+            if (DEBUG) Log.v(TAG, "Found guided step theme reference? " + found);
+            if (found) {
+                ContextThemeWrapper themeWrapper =
+                        new ContextThemeWrapper(context, typedValue.resourceId);
+                if (isGuidedStepTheme(themeWrapper)) {
+                    mThemeWrapper = themeWrapper;
+                } else {
+                    found = false;
+                    mThemeWrapper = null;
+                }
+            }
+            if (!found) {
+                Log.e(TAG, "GuidedStepSupportFragment does not have an appropriate theme set.");
+            }
+        } else if (theme != -1) {
+            mThemeWrapper = new ContextThemeWrapper(context, theme);
+        }
+    }
+
+    private LayoutInflater getThemeInflater(LayoutInflater inflater) {
+        if (mThemeWrapper == null) {
+            return inflater;
+        } else {
+            return inflater.cloneInContext(mThemeWrapper);
+        }
+    }
+
+    private int getFirstCheckedAction() {
+        for (int i = 0, size = mActions.size(); i < size; i++) {
+            if (mActions.get(i).isChecked()) {
+                return i;
+            }
+        }
+        return 0;
+    }
+
+    void runImeAnimations(boolean entering) {
+        ArrayList<Animator> animators = new ArrayList<Animator>();
+        if (entering) {
+            mGuidanceStylist.onImeAppearing(animators);
+            mActionsStylist.onImeAppearing(animators);
+            mButtonActionsStylist.onImeAppearing(animators);
+        } else {
+            mGuidanceStylist.onImeDisappearing(animators);
+            mActionsStylist.onImeDisappearing(animators);
+            mButtonActionsStylist.onImeDisappearing(animators);
+        }
+        AnimatorSet set = new AnimatorSet();
+        set.playTogether(animators);
+        set.start();
+    }
+}
diff --git a/leanback/src/android/support/v17/leanback/app/HeadersFragment.java b/leanback/src/android/support/v17/leanback/app/HeadersFragment.java
new file mode 100644
index 0000000..08780a5
--- /dev/null
+++ b/leanback/src/android/support/v17/leanback/app/HeadersFragment.java
@@ -0,0 +1,309 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from HeadersSupportFragment.java.  DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package android.support.v17.leanback.app;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v17.leanback.R;
+import android.support.v17.leanback.widget.ClassPresenterSelector;
+import android.support.v17.leanback.widget.DividerPresenter;
+import android.support.v17.leanback.widget.DividerRow;
+import android.support.v17.leanback.widget.FocusHighlightHelper;
+import android.support.v17.leanback.widget.HorizontalGridView;
+import android.support.v17.leanback.widget.ItemBridgeAdapter;
+import android.support.v17.leanback.widget.PresenterSelector;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.RowHeaderPresenter;
+import android.support.v17.leanback.widget.SectionRow;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+import android.view.View.OnLayoutChangeListener;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+/**
+ * An fragment containing a list of row headers. Implementation must support three types of rows:
+ * <ul>
+ *     <li>{@link DividerRow} rendered by {@link DividerPresenter}.</li>
+ *     <li>{@link Row} rendered by {@link RowHeaderPresenter}.</li>
+ *     <li>{@link SectionRow} rendered by {@link RowHeaderPresenter}.</li>
+ * </ul>
+ * Use {@link #setPresenterSelector(PresenterSelector)} in subclass constructor to customize
+ * Presenters. App may override {@link BrowseFragment#onCreateHeadersFragment()}.
+ * @deprecated use {@link HeadersSupportFragment}
+ */
+@Deprecated
+public class HeadersFragment extends BaseRowFragment {
+
+    /**
+     * Interface definition for a callback to be invoked when a header item is clicked.
+     * @deprecated use {@link HeadersSupportFragment}
+     */
+    @Deprecated
+    public interface OnHeaderClickedListener {
+        /**
+         * Called when a header item has been clicked.
+         *
+         * @param viewHolder Row ViewHolder object corresponding to the selected Header.
+         * @param row Row object corresponding to the selected Header.
+         */
+        void onHeaderClicked(RowHeaderPresenter.ViewHolder viewHolder, Row row);
+    }
+
+    /**
+     * Interface definition for a callback to be invoked when a header item is selected.
+     * @deprecated use {@link HeadersSupportFragment}
+     */
+    @Deprecated
+    public interface OnHeaderViewSelectedListener {
+        /**
+         * Called when a header item has been selected.
+         *
+         * @param viewHolder Row ViewHolder object corresponding to the selected Header.
+         * @param row Row object corresponding to the selected Header.
+         */
+        void onHeaderSelected(RowHeaderPresenter.ViewHolder viewHolder, Row row);
+    }
+
+    private OnHeaderViewSelectedListener mOnHeaderViewSelectedListener;
+    OnHeaderClickedListener mOnHeaderClickedListener;
+    private boolean mHeadersEnabled = true;
+    private boolean mHeadersGone = false;
+    private int mBackgroundColor;
+    private boolean mBackgroundColorSet;
+
+    private static final PresenterSelector sHeaderPresenter = new ClassPresenterSelector()
+            .addClassPresenter(DividerRow.class, new DividerPresenter())
+            .addClassPresenter(SectionRow.class,
+                    new RowHeaderPresenter(R.layout.lb_section_header, false))
+            .addClassPresenter(Row.class, new RowHeaderPresenter(R.layout.lb_header));
+
+    public HeadersFragment() {
+        setPresenterSelector(sHeaderPresenter);
+        FocusHighlightHelper.setupHeaderItemFocusHighlight(getBridgeAdapter());
+    }
+
+    public void setOnHeaderClickedListener(OnHeaderClickedListener listener) {
+        mOnHeaderClickedListener = listener;
+    }
+
+    public void setOnHeaderViewSelectedListener(OnHeaderViewSelectedListener listener) {
+        mOnHeaderViewSelectedListener = listener;
+    }
+
+    @Override
+    VerticalGridView findGridViewFromRoot(View view) {
+        return (VerticalGridView) view.findViewById(R.id.browse_headers);
+    }
+
+    @Override
+    void onRowSelected(RecyclerView parent, RecyclerView.ViewHolder viewHolder,
+            int position, int subposition) {
+        if (mOnHeaderViewSelectedListener != null) {
+            if (viewHolder != null && position >= 0) {
+                ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder) viewHolder;
+                mOnHeaderViewSelectedListener.onHeaderSelected(
+                        (RowHeaderPresenter.ViewHolder) vh.getViewHolder(), (Row) vh.getItem());
+            } else {
+                mOnHeaderViewSelectedListener.onHeaderSelected(null, null);
+            }
+        }
+    }
+
+    private final ItemBridgeAdapter.AdapterListener mAdapterListener =
+            new ItemBridgeAdapter.AdapterListener() {
+        @Override
+        public void onCreate(final ItemBridgeAdapter.ViewHolder viewHolder) {
+            View headerView = viewHolder.getViewHolder().view;
+            headerView.setOnClickListener(new View.OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    if (mOnHeaderClickedListener != null) {
+                        mOnHeaderClickedListener.onHeaderClicked(
+                                (RowHeaderPresenter.ViewHolder) viewHolder.getViewHolder(),
+                                (Row) viewHolder.getItem());
+                    }
+                }
+            });
+            if (mWrapper != null) {
+                viewHolder.itemView.addOnLayoutChangeListener(sLayoutChangeListener);
+            } else {
+                headerView.addOnLayoutChangeListener(sLayoutChangeListener);
+            }
+        }
+
+    };
+
+    static OnLayoutChangeListener sLayoutChangeListener = new OnLayoutChangeListener() {
+        @Override
+        public void onLayoutChange(View v, int left, int top, int right, int bottom,
+            int oldLeft, int oldTop, int oldRight, int oldBottom) {
+            v.setPivotX(v.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL ? v.getWidth() : 0);
+            v.setPivotY(v.getMeasuredHeight() / 2);
+        }
+    };
+
+    @Override
+    int getLayoutResourceId() {
+        return R.layout.lb_headers_fragment;
+    }
+
+    @Override
+    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+        final VerticalGridView listView = getVerticalGridView();
+        if (listView == null) {
+            return;
+        }
+        if (mBackgroundColorSet) {
+            listView.setBackgroundColor(mBackgroundColor);
+            updateFadingEdgeToBrandColor(mBackgroundColor);
+        } else {
+            Drawable d = listView.getBackground();
+            if (d instanceof ColorDrawable) {
+                updateFadingEdgeToBrandColor(((ColorDrawable) d).getColor());
+            }
+        }
+        updateListViewVisibility();
+    }
+
+    private void updateListViewVisibility() {
+        final VerticalGridView listView = getVerticalGridView();
+        if (listView != null) {
+            getView().setVisibility(mHeadersGone ? View.GONE : View.VISIBLE);
+            if (!mHeadersGone) {
+                if (mHeadersEnabled) {
+                    listView.setChildrenVisibility(View.VISIBLE);
+                } else {
+                    listView.setChildrenVisibility(View.INVISIBLE);
+                }
+            }
+        }
+    }
+
+    void setHeadersEnabled(boolean enabled) {
+        mHeadersEnabled = enabled;
+        updateListViewVisibility();
+    }
+
+    void setHeadersGone(boolean gone) {
+        mHeadersGone = gone;
+        updateListViewVisibility();
+    }
+
+    static class NoOverlappingFrameLayout extends FrameLayout {
+
+        public NoOverlappingFrameLayout(Context context) {
+            super(context);
+        }
+
+        /**
+         * Avoid creating hardware layer for header dock.
+         */
+        @Override
+        public boolean hasOverlappingRendering() {
+            return false;
+        }
+    }
+
+    // Wrapper needed because of conflict between RecyclerView's use of alpha
+    // for ADD animations, and RowHeaderPresenter's use of alpha for selected level.
+    final ItemBridgeAdapter.Wrapper mWrapper = new ItemBridgeAdapter.Wrapper() {
+        @Override
+        public void wrap(View wrapper, View wrapped) {
+            ((FrameLayout) wrapper).addView(wrapped);
+        }
+
+        @Override
+        public View createWrapper(View root) {
+            return new NoOverlappingFrameLayout(root.getContext());
+        }
+    };
+    @Override
+    void updateAdapter() {
+        super.updateAdapter();
+        ItemBridgeAdapter adapter = getBridgeAdapter();
+        adapter.setAdapterListener(mAdapterListener);
+        adapter.setWrapper(mWrapper);
+    }
+
+    void setBackgroundColor(int color) {
+        mBackgroundColor = color;
+        mBackgroundColorSet = true;
+
+        if (getVerticalGridView() != null) {
+            getVerticalGridView().setBackgroundColor(mBackgroundColor);
+            updateFadingEdgeToBrandColor(mBackgroundColor);
+        }
+    }
+
+    private void updateFadingEdgeToBrandColor(int backgroundColor) {
+        View fadingView = getView().findViewById(R.id.fade_out_edge);
+        Drawable background = fadingView.getBackground();
+        if (background instanceof GradientDrawable) {
+            background.mutate();
+            ((GradientDrawable) background).setColors(
+                    new int[] {Color.TRANSPARENT, backgroundColor});
+        }
+    }
+
+    @Override
+    public void onTransitionStart() {
+        super.onTransitionStart();
+        if (!mHeadersEnabled) {
+            // When enabling headers fragment,  the RowHeaderView gets a focus but
+            // isShown() is still false because its parent is INVISIBLE, accessibility
+            // event is not sent.
+            // Workaround is: prevent focus to a child view during transition and put
+            // focus on it after transition is done.
+            final VerticalGridView listView = getVerticalGridView();
+            if (listView != null) {
+                listView.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
+                if (listView.hasFocus()) {
+                    listView.requestFocus();
+                }
+            }
+        }
+    }
+
+    @Override
+    public void onTransitionEnd() {
+        if (mHeadersEnabled) {
+            final VerticalGridView listView = getVerticalGridView();
+            if (listView != null) {
+                listView.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
+                if (listView.hasFocus()) {
+                    listView.requestFocus();
+                }
+            }
+        }
+        super.onTransitionEnd();
+    }
+
+    public boolean isScrolling() {
+        return getVerticalGridView().getScrollState()
+                != HorizontalGridView.SCROLL_STATE_IDLE;
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/HeadersSupportFragment.java b/leanback/src/android/support/v17/leanback/app/HeadersSupportFragment.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/app/HeadersSupportFragment.java
rename to leanback/src/android/support/v17/leanback/app/HeadersSupportFragment.java
diff --git a/leanback/src/android/support/v17/leanback/app/ListRowDataAdapter.java b/leanback/src/android/support/v17/leanback/app/ListRowDataAdapter.java
new file mode 100644
index 0000000..03d948b
--- /dev/null
+++ b/leanback/src/android/support/v17/leanback/app/ListRowDataAdapter.java
@@ -0,0 +1,174 @@
+package android.support.v17.leanback.app;
+
+import android.support.v17.leanback.widget.ObjectAdapter;
+import android.support.v17.leanback.widget.Row;
+
+/**
+ * Wrapper class for {@link ObjectAdapter} used by {@link BrowseFragment} to initialize
+ * {@link RowsFragment}. We use invisible rows to represent
+ * {@link android.support.v17.leanback.widget.DividerRow},
+ * {@link android.support.v17.leanback.widget.SectionRow} and
+ * {@link android.support.v17.leanback.widget.PageRow} in RowsFragment. In case we have an
+ * invisible row at the end of a RowsFragment, it creates a jumping effect as the layout manager
+ * 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;
+    public static final int ON_ITEM_RANGE_INSERTED = 4;
+    public static final int ON_ITEM_RANGE_REMOVED = 8;
+    public static final int ON_CHANGED = 16;
+
+    private final ObjectAdapter mAdapter;
+    int mLastVisibleRowIndex;
+    final DataObserver mDataObserver;
+
+    public ListRowDataAdapter(ObjectAdapter adapter) {
+        super(adapter.getPresenterSelector());
+        this.mAdapter = adapter;
+        initialize();
+
+        // If an user implements its own ObjectAdapter, notification corresponding to data
+        // updates can be batched e.g. remove, add might be followed by notifyRemove, notifyAdd.
+        // But underlying data would have changed during the notifyRemove call by the previous add
+        // operation. To handle this case, we use QueueBasedDataObserver which forces
+        // recyclerview to do a full data refresh after each update operation.
+        if (adapter.isImmediateNotifySupported()) {
+            mDataObserver = new SimpleDataObserver();
+        } else {
+            mDataObserver = new QueueBasedDataObserver();
+        }
+        attach();
+    }
+
+    void detach() {
+        mAdapter.unregisterObserver(mDataObserver);
+    }
+
+    void attach() {
+        initialize();
+        mAdapter.registerObserver(mDataObserver);
+    }
+
+    void initialize() {
+        mLastVisibleRowIndex = -1;
+        int i = mAdapter.size() - 1;
+        while (i >= 0) {
+            Row item = (Row) mAdapter.get(i);
+            if (item.isRenderedAsRowView()) {
+                mLastVisibleRowIndex = i;
+                break;
+            }
+            i--;
+        }
+    }
+
+    @Override
+    public int size() {
+        return mLastVisibleRowIndex + 1;
+    }
+
+    @Override
+    public Object get(int index) {
+        return mAdapter.get(index);
+    }
+
+    void doNotify(int eventType, int positionStart, int itemCount) {
+        switch (eventType) {
+            case ON_ITEM_RANGE_CHANGED:
+                notifyItemRangeChanged(positionStart, itemCount);
+                break;
+            case ON_ITEM_RANGE_INSERTED:
+                notifyItemRangeInserted(positionStart, itemCount);
+                break;
+            case ON_ITEM_RANGE_REMOVED:
+                notifyItemRangeRemoved(positionStart, itemCount);
+                break;
+            case ON_CHANGED:
+                notifyChanged();
+                break;
+            default:
+                throw new IllegalArgumentException("Invalid event type " + eventType);
+        }
+    }
+
+    private class SimpleDataObserver extends DataObserver {
+
+        SimpleDataObserver() {
+        }
+
+        @Override
+        public void onItemRangeChanged(int positionStart, int itemCount) {
+            if (positionStart <= mLastVisibleRowIndex) {
+                onEventFired(ON_ITEM_RANGE_CHANGED, positionStart,
+                        Math.min(itemCount, mLastVisibleRowIndex - positionStart + 1));
+            }
+        }
+
+        @Override
+        public void onItemRangeInserted(int positionStart, int itemCount) {
+            if (positionStart <= mLastVisibleRowIndex) {
+                mLastVisibleRowIndex += itemCount;
+                onEventFired(ON_ITEM_RANGE_INSERTED, positionStart, itemCount);
+                return;
+            }
+
+            int lastVisibleRowIndex = mLastVisibleRowIndex;
+            initialize();
+            if (mLastVisibleRowIndex > lastVisibleRowIndex) {
+                int totalItems = mLastVisibleRowIndex - lastVisibleRowIndex;
+                onEventFired(ON_ITEM_RANGE_INSERTED, lastVisibleRowIndex + 1, totalItems);
+            }
+        }
+
+        @Override
+        public void onItemRangeRemoved(int positionStart, int itemCount) {
+            if (positionStart + itemCount - 1 < mLastVisibleRowIndex) {
+                mLastVisibleRowIndex -= itemCount;
+                onEventFired(ON_ITEM_RANGE_REMOVED, positionStart, itemCount);
+                return;
+            }
+
+            int lastVisibleRowIndex = mLastVisibleRowIndex;
+            initialize();
+            int totalItems = lastVisibleRowIndex - mLastVisibleRowIndex;
+            if (totalItems > 0) {
+                onEventFired(ON_ITEM_RANGE_REMOVED,
+                        Math.min(mLastVisibleRowIndex + 1, positionStart),
+                        totalItems);
+            }
+        }
+
+        @Override
+        public void onChanged() {
+            initialize();
+            onEventFired(ON_CHANGED, -1, -1);
+        }
+
+        protected void onEventFired(int eventType, int positionStart, int itemCount) {
+            doNotify(eventType, positionStart, itemCount);
+        }
+    }
+
+
+    /**
+     * When using custom {@link ObjectAdapter}, it's possible that the user may make multiple
+     * changes to the underlying data at once. The notifications about those updates may be
+     * batched and the underlying data would have changed to reflect latest updates as opposed
+     * to intermediate changes. In order to force RecyclerView to refresh the view with access
+     * only to the final data, we call notifyChange().
+     */
+    private class QueueBasedDataObserver extends DataObserver {
+
+        QueueBasedDataObserver() {
+        }
+
+        @Override
+        public void onChanged() {
+            initialize();
+            notifyChanged();
+        }
+    }
+}
diff --git a/leanback/src/android/support/v17/leanback/app/OnboardingFragment.java b/leanback/src/android/support/v17/leanback/app/OnboardingFragment.java
new file mode 100644
index 0000000..f352c41
--- /dev/null
+++ b/leanback/src/android/support/v17/leanback/app/OnboardingFragment.java
@@ -0,0 +1,1027 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from OnboardingSupportFragment.java.  DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v17.leanback.app;
+
+import android.animation.Animator;
+import android.animation.AnimatorInflater;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
+import android.content.Context;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.support.annotation.ColorInt;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v17.leanback.R;
+import android.support.v17.leanback.widget.PagingIndicator;
+import android.app.Fragment;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.ContextThemeWrapper;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnKeyListener;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver.OnPreDrawListener;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * An OnboardingFragment provides a common and simple way to build onboarding screen for
+ * applications.
+ * <p>
+ * <h3>Building the screen</h3>
+ * The view structure of onboarding screen is composed of the common parts and custom parts. The
+ * common parts are composed of icon, title, description and page navigator and the custom parts
+ * are composed of background, contents and foreground.
+ * <p>
+ * To build the screen views, the inherited class should override:
+ * <ul>
+ * <li>{@link #onCreateBackgroundView} to provide the background view. Background view has the same
+ * size as the screen and the lowest z-order.</li>
+ * <li>{@link #onCreateContentView} to provide the contents view. The content view is located in
+ * the content area at the center of the screen.</li>
+ * <li>{@link #onCreateForegroundView} to provide the foreground view. Foreground view has the same
+ * size as the screen and the highest z-order</li>
+ * </ul>
+ * <p>
+ * Each of these methods can return {@code null} if the application doesn't want to provide it.
+ * <p>
+ * <h3>Page information</h3>
+ * The onboarding screen may have several pages which explain the functionality of the application.
+ * The inherited class should provide the page information by overriding the methods:
+ * <p>
+ * <ul>
+ * <li>{@link #getPageCount} to provide the number of pages.</li>
+ * <li>{@link #getPageTitle} to provide the title of the page.</li>
+ * <li>{@link #getPageDescription} to provide the description of the page.</li>
+ * </ul>
+ * <p>
+ * Note that the information is used in {@link #onCreateView}, so should be initialized before
+ * calling {@code super.onCreateView}.
+ * <p>
+ * <h3>Animation</h3>
+ * Onboarding screen has three kinds of animations:
+ * <p>
+ * <h4>Logo Splash Animation</a></h4>
+ * When onboarding screen appears, the logo splash animation is played by default. The animation
+ * fades in the logo image, pauses in a few seconds and fades it out.
+ * <p>
+ * In most cases, the logo animation needs to be customized because the logo images of applications
+ * are different from each other, or some applications may want to show their own animations.
+ * <p>
+ * The logo animation can be customized in two ways:
+ * <ul>
+ * <li>The simplest way is to provide the logo image by calling {@link #setLogoResourceId} to show
+ * the default logo animation. This method should be called in {@link Fragment#onCreateView}.</li>
+ * <li>If the logo animation is complex, then override {@link #onCreateLogoAnimation} and return the
+ * {@link Animator} object to run.</li>
+ * </ul>
+ * <p>
+ * If the inherited class provides neither the logo image nor the animation, the logo animation will
+ * be omitted.
+ * <h4>Page enter animation</h4>
+ * After logo animation finishes, page enter animation starts, which causes the header section -
+ * title and description views to fade and slide in. Users can override the default
+ * fade + slide animation by overriding {@link #onCreateTitleAnimator()} &
+ * {@link #onCreateDescriptionAnimator()}. By default we don't animate the custom views but users
+ * can provide animation by overriding {@link #onCreateEnterAnimation}.
+ *
+ * <h4>Page change animation</h4>
+ * When the page changes, the default animations of the title and description are played. The
+ * inherited class can override {@link #onPageChanged} to start the custom animations.
+ * <p>
+ * <h3>Finishing the screen</h3>
+ * <p>
+ * If the user finishes the onboarding screen after navigating all the pages,
+ * {@link #onFinishFragment} is called. The inherited class can override this method to show another
+ * fragment or activity, or just remove this fragment.
+ * <p>
+ * <h3>Theming</h3>
+ * <p>
+ * OnboardingFragment must have access to an appropriate theme. Specifically, the fragment must
+ * receive  {@link R.style#Theme_Leanback_Onboarding}, or a theme whose parent is set to that theme.
+ * Themes can be provided in one of three ways:
+ * <ul>
+ * <li>The simplest way is to set the theme for the host Activity to the Onboarding theme or a theme
+ * that derives from it.</li>
+ * <li>If the Activity already has a theme and setting its parent theme is inconvenient, the
+ * existing Activity theme can have an entry added for the attribute
+ * {@link R.styleable#LeanbackOnboardingTheme_onboardingTheme}. If present, this theme will be used
+ * by OnboardingFragment as an overlay to the Activity's theme.</li>
+ * <li>Finally, custom subclasses of OnboardingFragment may provide a theme through the
+ * {@link #onProvideTheme} method. This can be useful if a subclass is used across multiple
+ * Activities.</li>
+ * </ul>
+ * <p>
+ * If the theme is provided in multiple ways, the onProvideTheme override has priority, followed by
+ * the Activity's theme. (Themes whose parent theme is already set to the onboarding theme do not
+ * need to set the onboardingTheme attribute; if set, it will be ignored.)
+ *
+ * @attr ref R.styleable#LeanbackOnboardingTheme_onboardingTheme
+ * @attr ref R.styleable#LeanbackOnboardingTheme_onboardingHeaderStyle
+ * @attr ref R.styleable#LeanbackOnboardingTheme_onboardingTitleStyle
+ * @attr ref R.styleable#LeanbackOnboardingTheme_onboardingDescriptionStyle
+ * @attr ref R.styleable#LeanbackOnboardingTheme_onboardingNavigatorContainerStyle
+ * @attr ref R.styleable#LeanbackOnboardingTheme_onboardingPageIndicatorStyle
+ * @attr ref R.styleable#LeanbackOnboardingTheme_onboardingStartButtonStyle
+ * @attr ref R.styleable#LeanbackOnboardingTheme_onboardingLogoStyle
+ * @deprecated use {@link OnboardingSupportFragment}
+ */
+@Deprecated
+abstract public class OnboardingFragment extends Fragment {
+    private static final String TAG = "OnboardingF";
+    private static final boolean DEBUG = false;
+
+    private static final long LOGO_SPLASH_PAUSE_DURATION_MS = 1333;
+
+    private static final long HEADER_ANIMATION_DURATION_MS = 417;
+    private static final long DESCRIPTION_START_DELAY_MS = 33;
+    private static final long HEADER_APPEAR_DELAY_MS = 500;
+    private static final int SLIDE_DISTANCE = 60;
+
+    private static int sSlideDistance;
+
+    private static final TimeInterpolator HEADER_APPEAR_INTERPOLATOR = new DecelerateInterpolator();
+    private static final TimeInterpolator HEADER_DISAPPEAR_INTERPOLATOR =
+            new AccelerateInterpolator();
+
+    // Keys used to save and restore the states.
+    private static final String KEY_CURRENT_PAGE_INDEX = "leanback.onboarding.current_page_index";
+    private static final String KEY_LOGO_ANIMATION_FINISHED =
+            "leanback.onboarding.logo_animation_finished";
+    private static final String KEY_ENTER_ANIMATION_FINISHED =
+            "leanback.onboarding.enter_animation_finished";
+
+    private ContextThemeWrapper mThemeWrapper;
+
+    PagingIndicator mPageIndicator;
+    View mStartButton;
+    private ImageView mLogoView;
+    // Optional icon that can be displayed on top of the header section.
+    private ImageView mMainIconView;
+    private int mIconResourceId;
+
+    TextView mTitleView;
+    TextView mDescriptionView;
+
+    boolean mIsLtr;
+
+    // No need to save/restore the logo resource ID, because the logo animation will not appear when
+    // the fragment is restored.
+    private int mLogoResourceId;
+    boolean mLogoAnimationFinished;
+    boolean mEnterAnimationFinished;
+    int mCurrentPageIndex;
+
+    @ColorInt
+    private int mTitleViewTextColor = Color.TRANSPARENT;
+    private boolean mTitleViewTextColorSet;
+
+    @ColorInt
+    private int mDescriptionViewTextColor = Color.TRANSPARENT;
+    private boolean mDescriptionViewTextColorSet;
+
+    @ColorInt
+    private int mDotBackgroundColor = Color.TRANSPARENT;
+    private boolean mDotBackgroundColorSet;
+
+    @ColorInt
+    private int mArrowColor = Color.TRANSPARENT;
+    private boolean mArrowColorSet;
+
+    @ColorInt
+    private int mArrowBackgroundColor = Color.TRANSPARENT;
+    private boolean mArrowBackgroundColorSet;
+
+    private CharSequence mStartButtonText;
+    private boolean mStartButtonTextSet;
+
+
+    private AnimatorSet mAnimator;
+
+    private final OnClickListener mOnClickListener = new OnClickListener() {
+        @Override
+        public void onClick(View view) {
+            if (!mLogoAnimationFinished) {
+                // Do not change page until the enter transition finishes.
+                return;
+            }
+            if (mCurrentPageIndex == getPageCount() - 1) {
+                onFinishFragment();
+            } else {
+                moveToNextPage();
+            }
+        }
+    };
+
+    private final OnKeyListener mOnKeyListener = new OnKeyListener() {
+        @Override
+        public boolean onKey(View v, int keyCode, KeyEvent event) {
+            if (!mLogoAnimationFinished) {
+                // Ignore key event until the enter transition finishes.
+                return keyCode != KeyEvent.KEYCODE_BACK;
+            }
+            if (event.getAction() == KeyEvent.ACTION_DOWN) {
+                return false;
+            }
+            switch (keyCode) {
+                case KeyEvent.KEYCODE_BACK:
+                    if (mCurrentPageIndex == 0) {
+                        return false;
+                    }
+                    moveToPreviousPage();
+                    return true;
+                case KeyEvent.KEYCODE_DPAD_LEFT:
+                    if (mIsLtr) {
+                        moveToPreviousPage();
+                    } else {
+                        moveToNextPage();
+                    }
+                    return true;
+                case KeyEvent.KEYCODE_DPAD_RIGHT:
+                    if (mIsLtr) {
+                        moveToNextPage();
+                    } else {
+                        moveToPreviousPage();
+                    }
+                    return true;
+            }
+            return false;
+        }
+    };
+
+    /**
+     * Navigates to the previous page.
+     */
+    protected void moveToPreviousPage() {
+        if (!mLogoAnimationFinished) {
+            // Ignore if the logo enter transition is in progress.
+            return;
+        }
+        if (mCurrentPageIndex > 0) {
+            --mCurrentPageIndex;
+            onPageChangedInternal(mCurrentPageIndex + 1);
+        }
+    }
+
+    /**
+     * Navigates to the next page.
+     */
+    protected void moveToNextPage() {
+        if (!mLogoAnimationFinished) {
+            // Ignore if the logo enter transition is in progress.
+            return;
+        }
+        if (mCurrentPageIndex < getPageCount() - 1) {
+            ++mCurrentPageIndex;
+            onPageChangedInternal(mCurrentPageIndex - 1);
+        }
+    }
+
+    @Nullable
+    @Override
+    public View onCreateView(LayoutInflater inflater, final ViewGroup container,
+            Bundle savedInstanceState) {
+        resolveTheme();
+        LayoutInflater localInflater = getThemeInflater(inflater);
+        final ViewGroup view = (ViewGroup) localInflater.inflate(R.layout.lb_onboarding_fragment,
+                container, false);
+        mIsLtr = getResources().getConfiguration().getLayoutDirection()
+                == View.LAYOUT_DIRECTION_LTR;
+        mPageIndicator = (PagingIndicator) view.findViewById(R.id.page_indicator);
+        mPageIndicator.setOnClickListener(mOnClickListener);
+        mPageIndicator.setOnKeyListener(mOnKeyListener);
+        mStartButton = view.findViewById(R.id.button_start);
+        mStartButton.setOnClickListener(mOnClickListener);
+        mStartButton.setOnKeyListener(mOnKeyListener);
+        mMainIconView = (ImageView) view.findViewById(R.id.main_icon);
+        mLogoView = (ImageView) view.findViewById(R.id.logo);
+        mTitleView = (TextView) view.findViewById(R.id.title);
+        mDescriptionView = (TextView) view.findViewById(R.id.description);
+
+        if (mTitleViewTextColorSet) {
+            mTitleView.setTextColor(mTitleViewTextColor);
+        }
+        if (mDescriptionViewTextColorSet) {
+            mDescriptionView.setTextColor(mDescriptionViewTextColor);
+        }
+        if (mDotBackgroundColorSet) {
+            mPageIndicator.setDotBackgroundColor(mDotBackgroundColor);
+        }
+        if (mArrowColorSet) {
+            mPageIndicator.setArrowColor(mArrowColor);
+        }
+        if (mArrowBackgroundColorSet) {
+            mPageIndicator.setDotBackgroundColor(mArrowBackgroundColor);
+        }
+        if (mStartButtonTextSet) {
+            ((Button) mStartButton).setText(mStartButtonText);
+        }
+        final Context context = FragmentUtil.getContext(OnboardingFragment.this);
+        if (sSlideDistance == 0) {
+            sSlideDistance = (int) (SLIDE_DISTANCE * context.getResources()
+                    .getDisplayMetrics().scaledDensity);
+        }
+        view.requestFocus();
+        return view;
+    }
+
+    @Override
+    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+        if (savedInstanceState == null) {
+            mCurrentPageIndex = 0;
+            mLogoAnimationFinished = false;
+            mEnterAnimationFinished = false;
+            mPageIndicator.onPageSelected(0, false);
+            view.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
+                @Override
+                public boolean onPreDraw() {
+                    getView().getViewTreeObserver().removeOnPreDrawListener(this);
+                    if (!startLogoAnimation()) {
+                        mLogoAnimationFinished = true;
+                        onLogoAnimationFinished();
+                    }
+                    return true;
+                }
+            });
+        } else {
+            mCurrentPageIndex = savedInstanceState.getInt(KEY_CURRENT_PAGE_INDEX);
+            mLogoAnimationFinished = savedInstanceState.getBoolean(KEY_LOGO_ANIMATION_FINISHED);
+            mEnterAnimationFinished = savedInstanceState.getBoolean(KEY_ENTER_ANIMATION_FINISHED);
+            if (!mLogoAnimationFinished) {
+                // logo animation wasn't started or was interrupted when the activity was destroyed;
+                // restart it againl
+                if (!startLogoAnimation()) {
+                    mLogoAnimationFinished = true;
+                    onLogoAnimationFinished();
+                }
+            } else {
+                onLogoAnimationFinished();
+            }
+        }
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putInt(KEY_CURRENT_PAGE_INDEX, mCurrentPageIndex);
+        outState.putBoolean(KEY_LOGO_ANIMATION_FINISHED, mLogoAnimationFinished);
+        outState.putBoolean(KEY_ENTER_ANIMATION_FINISHED, mEnterAnimationFinished);
+    }
+
+    /**
+     * Sets the text color for TitleView. If not set, the default textColor set in style
+     * referenced by attr {@link R.attr#onboardingTitleStyle} will be used.
+     * @param color the color to use as the text color for TitleView
+     */
+    public void setTitleViewTextColor(@ColorInt int color) {
+        mTitleViewTextColor = color;
+        mTitleViewTextColorSet = true;
+        if (mTitleView != null) {
+            mTitleView.setTextColor(color);
+        }
+    }
+
+    /**
+     * Returns the text color of TitleView if it's set through
+     * {@link #setTitleViewTextColor(int)}. If no color was set, transparent is returned.
+     */
+    @ColorInt
+    public final int getTitleViewTextColor() {
+        return mTitleViewTextColor;
+    }
+
+    /**
+     * Sets the text color for DescriptionView. If not set, the default textColor set in style
+     * referenced by attr {@link R.attr#onboardingDescriptionStyle} will be used.
+     * @param color the color to use as the text color for DescriptionView
+     */
+    public void setDescriptionViewTextColor(@ColorInt int color) {
+        mDescriptionViewTextColor = color;
+        mDescriptionViewTextColorSet = true;
+        if (mDescriptionView != null) {
+            mDescriptionView.setTextColor(color);
+        }
+    }
+
+    /**
+     * Returns the text color of DescriptionView if it's set through
+     * {@link #setDescriptionViewTextColor(int)}. If no color was set, transparent is returned.
+     */
+    @ColorInt
+    public final int getDescriptionViewTextColor() {
+        return mDescriptionViewTextColor;
+    }
+    /**
+     * Sets the background color of the dots. If not set, the default color from attr
+     * {@link R.styleable#PagingIndicator_dotBgColor} in the theme will be used.
+     * @param color the color to use for dot backgrounds
+     */
+    public void setDotBackgroundColor(@ColorInt int color) {
+        mDotBackgroundColor = color;
+        mDotBackgroundColorSet = true;
+        if (mPageIndicator != null) {
+            mPageIndicator.setDotBackgroundColor(color);
+        }
+    }
+
+    /**
+     * Returns the background color of the dot if it's set through
+     * {@link #setDotBackgroundColor(int)}. If no color was set, transparent is returned.
+     */
+    @ColorInt
+    public final int getDotBackgroundColor() {
+        return mDotBackgroundColor;
+    }
+
+    /**
+     * Sets the color of the arrow. This color will supersede the color set in the theme attribute
+     * {@link R.styleable#PagingIndicator_arrowColor} if provided. If none of these two are set, the
+     * arrow will have its original bitmap color.
+     *
+     * @param color the color to use for arrow background
+     */
+    public void setArrowColor(@ColorInt int color) {
+        mArrowColor = color;
+        mArrowColorSet = true;
+        if (mPageIndicator != null) {
+            mPageIndicator.setArrowColor(color);
+        }
+    }
+
+    /**
+     * Returns the color of the arrow if it's set through
+     * {@link #setArrowColor(int)}. If no color was set, transparent is returned.
+     */
+    @ColorInt
+    public final int getArrowColor() {
+        return mArrowColor;
+    }
+
+    /**
+     * Sets the background color of the arrow. If not set, the default color from attr
+     * {@link R.styleable#PagingIndicator_arrowBgColor} in the theme will be used.
+     * @param color the color to use for arrow background
+     */
+    public void setArrowBackgroundColor(@ColorInt int color) {
+        mArrowBackgroundColor = color;
+        mArrowBackgroundColorSet = true;
+        if (mPageIndicator != null) {
+            mPageIndicator.setArrowBackgroundColor(color);
+        }
+    }
+
+    /**
+     * Returns the background color of the arrow if it's set through
+     * {@link #setArrowBackgroundColor(int)}. If no color was set, transparent is returned.
+     */
+    @ColorInt
+    public final int getArrowBackgroundColor() {
+        return mArrowBackgroundColor;
+    }
+
+    /**
+     * Returns the start button text if it's set through
+     * {@link #setStartButtonText(CharSequence)}}. If no string was set, null is returned.
+     */
+    public final CharSequence getStartButtonText() {
+        return mStartButtonText;
+    }
+
+    /**
+     * Sets the text on the start button text. If not set, the default text set in
+     * {@link R.styleable#LeanbackOnboardingTheme_onboardingStartButtonStyle} will be used.
+     *
+     * @param text the start button text
+     */
+    public void setStartButtonText(CharSequence text) {
+        mStartButtonText = text;
+        mStartButtonTextSet = true;
+        if (mStartButton != null) {
+            ((Button) mStartButton).setText(mStartButtonText);
+        }
+    }
+
+    /**
+     * Returns the theme used for styling the fragment. The default returns -1, indicating that the
+     * host Activity's theme should be used.
+     *
+     * @return The theme resource ID of the theme to use in this fragment, or -1 to use the host
+     *         Activity's theme.
+     */
+    public int onProvideTheme() {
+        return -1;
+    }
+
+    private void resolveTheme() {
+        final Context context = FragmentUtil.getContext(OnboardingFragment.this);
+        int theme = onProvideTheme();
+        if (theme == -1) {
+            // Look up the onboardingTheme in the activity's currently specified theme. If it
+            // exists, wrap the theme with its value.
+            int resId = R.attr.onboardingTheme;
+            TypedValue typedValue = new TypedValue();
+            boolean found = context.getTheme().resolveAttribute(resId, typedValue, true);
+            if (DEBUG) Log.v(TAG, "Found onboarding theme reference? " + found);
+            if (found) {
+                mThemeWrapper = new ContextThemeWrapper(context, typedValue.resourceId);
+            }
+        } else {
+            mThemeWrapper = new ContextThemeWrapper(context, theme);
+        }
+    }
+
+    private LayoutInflater getThemeInflater(LayoutInflater inflater) {
+        return mThemeWrapper == null ? inflater : inflater.cloneInContext(mThemeWrapper);
+    }
+
+    /**
+     * Sets the resource ID of the splash logo image. If the logo resource id set, the default logo
+     * splash animation will be played.
+     *
+     * @param id The resource ID of the logo image.
+     */
+    public final void setLogoResourceId(int id) {
+        mLogoResourceId = id;
+    }
+
+    /**
+     * Returns the resource ID of the splash logo image.
+     *
+     * @return The resource ID of the splash logo image.
+     */
+    public final int getLogoResourceId() {
+        return mLogoResourceId;
+    }
+
+    /**
+     * Called to have the inherited class create its own logo animation.
+     * <p>
+     * This is called only if the logo image resource ID is not set by {@link #setLogoResourceId}.
+     * If this returns {@code null}, the logo animation is skipped.
+     *
+     * @return The {@link Animator} object which runs the logo animation.
+     */
+    @Nullable
+    protected Animator onCreateLogoAnimation() {
+        return null;
+    }
+
+    boolean startLogoAnimation() {
+        final Context context = FragmentUtil.getContext(OnboardingFragment.this);
+        if (context == null) {
+            return false;
+        }
+        Animator animator = null;
+        if (mLogoResourceId != 0) {
+            mLogoView.setVisibility(View.VISIBLE);
+            mLogoView.setImageResource(mLogoResourceId);
+            Animator inAnimator = AnimatorInflater.loadAnimator(context,
+                    R.animator.lb_onboarding_logo_enter);
+            Animator outAnimator = AnimatorInflater.loadAnimator(context,
+                    R.animator.lb_onboarding_logo_exit);
+            outAnimator.setStartDelay(LOGO_SPLASH_PAUSE_DURATION_MS);
+            AnimatorSet logoAnimator = new AnimatorSet();
+            logoAnimator.playSequentially(inAnimator, outAnimator);
+            logoAnimator.setTarget(mLogoView);
+            animator = logoAnimator;
+        } else {
+            animator = onCreateLogoAnimation();
+        }
+        if (animator != null) {
+            animator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    if (context != null) {
+                        mLogoAnimationFinished = true;
+                        onLogoAnimationFinished();
+                    }
+                }
+            });
+            animator.start();
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Called to have the inherited class create its enter animation. The start animation runs after
+     * logo animation ends.
+     *
+     * @return The {@link Animator} object which runs the page enter animation.
+     */
+    @Nullable
+    protected Animator onCreateEnterAnimation() {
+        return null;
+    }
+
+
+    /**
+     * Hides the logo view and makes other fragment views visible. Also initializes the texts for
+     * Title and Description views.
+     */
+    void hideLogoView() {
+        mLogoView.setVisibility(View.GONE);
+
+        if (mIconResourceId != 0) {
+            mMainIconView.setImageResource(mIconResourceId);
+            mMainIconView.setVisibility(View.VISIBLE);
+        }
+
+        View container = getView();
+        // Create custom views.
+        LayoutInflater inflater = getThemeInflater(LayoutInflater.from(
+                FragmentUtil.getContext(OnboardingFragment.this)));
+        ViewGroup backgroundContainer = (ViewGroup) container.findViewById(
+                R.id.background_container);
+        View background = onCreateBackgroundView(inflater, backgroundContainer);
+        if (background != null) {
+            backgroundContainer.setVisibility(View.VISIBLE);
+            backgroundContainer.addView(background);
+        }
+        ViewGroup contentContainer = (ViewGroup) container.findViewById(R.id.content_container);
+        View content = onCreateContentView(inflater, contentContainer);
+        if (content != null) {
+            contentContainer.setVisibility(View.VISIBLE);
+            contentContainer.addView(content);
+        }
+        ViewGroup foregroundContainer = (ViewGroup) container.findViewById(
+                R.id.foreground_container);
+        View foreground = onCreateForegroundView(inflater, foregroundContainer);
+        if (foreground != null) {
+            foregroundContainer.setVisibility(View.VISIBLE);
+            foregroundContainer.addView(foreground);
+        }
+        // Make views visible which were invisible while logo animation is running.
+        container.findViewById(R.id.page_container).setVisibility(View.VISIBLE);
+        container.findViewById(R.id.content_container).setVisibility(View.VISIBLE);
+        if (getPageCount() > 1) {
+            mPageIndicator.setPageCount(getPageCount());
+            mPageIndicator.onPageSelected(mCurrentPageIndex, false);
+        }
+        if (mCurrentPageIndex == getPageCount() - 1) {
+            mStartButton.setVisibility(View.VISIBLE);
+        } else {
+            mPageIndicator.setVisibility(View.VISIBLE);
+        }
+        // Header views.
+        mTitleView.setText(getPageTitle(mCurrentPageIndex));
+        mDescriptionView.setText(getPageDescription(mCurrentPageIndex));
+    }
+
+    /**
+     * Called immediately after the logo animation is complete or no logo animation is specified.
+     * This method can also be called when the activity is recreated, i.e. when no logo animation
+     * are performed.
+     * By default, this method will hide the logo view and start the entrance animation for this
+     * fragment.
+     * Overriding subclasses can provide their own data loading logic as to when the entrance
+     * animation should be executed.
+     */
+    protected void onLogoAnimationFinished() {
+        startEnterAnimation(false);
+    }
+
+    /**
+     * Called to start entrance transition. This can be called by subclasses when the logo animation
+     * and data loading is complete. If force flag is set to false, it will only start the animation
+     * if it's not already done yet. Otherwise, it will always start the enter animation. In both
+     * cases, the logo view will hide and the rest of fragment views become visible after this call.
+     *
+     * @param force {@code true} if enter animation has to be performed regardless of whether it's
+     *                          been done in the past, {@code false} otherwise
+     */
+    protected final void startEnterAnimation(boolean force) {
+        final Context context = FragmentUtil.getContext(OnboardingFragment.this);
+        if (context == null) {
+            return;
+        }
+        hideLogoView();
+        if (mEnterAnimationFinished && !force) {
+            return;
+        }
+        List<Animator> animators = new ArrayList<>();
+        Animator animator = AnimatorInflater.loadAnimator(context,
+                R.animator.lb_onboarding_page_indicator_enter);
+        animator.setTarget(getPageCount() <= 1 ? mStartButton : mPageIndicator);
+        animators.add(animator);
+
+        animator = onCreateTitleAnimator();
+        if (animator != null) {
+            // Header title.
+            animator.setTarget(mTitleView);
+            animators.add(animator);
+        }
+
+        animator = onCreateDescriptionAnimator();
+        if (animator != null) {
+            // Header description.
+            animator.setTarget(mDescriptionView);
+            animators.add(animator);
+        }
+
+        // Customized animation by the inherited class.
+        Animator customAnimator = onCreateEnterAnimation();
+        if (customAnimator != null) {
+            animators.add(customAnimator);
+        }
+
+        // Return if we don't have any animations.
+        if (animators.isEmpty()) {
+            return;
+        }
+        mAnimator = new AnimatorSet();
+        mAnimator.playTogether(animators);
+        mAnimator.start();
+        mAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mEnterAnimationFinished = true;
+            }
+        });
+        // Search focus and give the focus to the appropriate child which has become visible.
+        getView().requestFocus();
+    }
+
+    /**
+     * Provides the entry animation for description view. This allows users to override the
+     * default fade and slide animation. Returning null will disable the animation.
+     */
+    protected Animator onCreateDescriptionAnimator() {
+        return AnimatorInflater.loadAnimator(FragmentUtil.getContext(OnboardingFragment.this),
+                R.animator.lb_onboarding_description_enter);
+    }
+
+    /**
+     * Provides the entry animation for title view. This allows users to override the
+     * default fade and slide animation. Returning null will disable the animation.
+     */
+    protected Animator onCreateTitleAnimator() {
+        return AnimatorInflater.loadAnimator(FragmentUtil.getContext(OnboardingFragment.this),
+                R.animator.lb_onboarding_title_enter);
+    }
+
+    /**
+     * Returns whether the logo enter animation is finished.
+     *
+     * @return {@code true} if the logo enter transition is finished, {@code false} otherwise
+     */
+    protected final boolean isLogoAnimationFinished() {
+        return mLogoAnimationFinished;
+    }
+
+    /**
+     * Returns the page count.
+     *
+     * @return The page count.
+     */
+    abstract protected int getPageCount();
+
+    /**
+     * Returns the title of the given page.
+     *
+     * @param pageIndex The page index.
+     *
+     * @return The title of the page.
+     */
+    abstract protected CharSequence getPageTitle(int pageIndex);
+
+    /**
+     * Returns the description of the given page.
+     *
+     * @param pageIndex The page index.
+     *
+     * @return The description of the page.
+     */
+    abstract protected CharSequence getPageDescription(int pageIndex);
+
+    /**
+     * Returns the index of the current page.
+     *
+     * @return The index of the current page.
+     */
+    protected final int getCurrentPageIndex() {
+        return mCurrentPageIndex;
+    }
+
+    /**
+     * Called to have the inherited class create background view. This is optional and the fragment
+     * which doesn't have the background view can return {@code null}. This is called inside
+     * {@link #onCreateView}.
+     *
+     * @param inflater The LayoutInflater object that can be used to inflate the views,
+     * @param container The parent view that the additional views are attached to.The fragment
+     *        should not add the view by itself.
+     *
+     * @return The background view for the onboarding screen, or {@code null}.
+     */
+    @Nullable
+    abstract protected View onCreateBackgroundView(LayoutInflater inflater, ViewGroup container);
+
+    /**
+     * Called to have the inherited class create content view. This is optional and the fragment
+     * which doesn't have the content view can return {@code null}. This is called inside
+     * {@link #onCreateView}.
+     *
+     * <p>The content view would be located at the center of the screen.
+     *
+     * @param inflater The LayoutInflater object that can be used to inflate the views,
+     * @param container The parent view that the additional views are attached to.The fragment
+     *        should not add the view by itself.
+     *
+     * @return The content view for the onboarding screen, or {@code null}.
+     */
+    @Nullable
+    abstract protected View onCreateContentView(LayoutInflater inflater, ViewGroup container);
+
+    /**
+     * Called to have the inherited class create foreground view. This is optional and the fragment
+     * which doesn't need the foreground view can return {@code null}. This is called inside
+     * {@link #onCreateView}.
+     *
+     * <p>This foreground view would have the highest z-order.
+     *
+     * @param inflater The LayoutInflater object that can be used to inflate the views,
+     * @param container The parent view that the additional views are attached to.The fragment
+     *        should not add the view by itself.
+     *
+     * @return The foreground view for the onboarding screen, or {@code null}.
+     */
+    @Nullable
+    abstract protected View onCreateForegroundView(LayoutInflater inflater, ViewGroup container);
+
+    /**
+     * Called when the onboarding flow finishes.
+     */
+    protected void onFinishFragment() { }
+
+    /**
+     * Called when the page changes.
+     */
+    private void onPageChangedInternal(int previousPage) {
+        if (mAnimator != null) {
+            mAnimator.end();
+        }
+        mPageIndicator.onPageSelected(mCurrentPageIndex, true);
+
+        List<Animator> animators = new ArrayList<>();
+        // Header animation
+        Animator fadeAnimator = null;
+        if (previousPage < getCurrentPageIndex()) {
+            // sliding to left
+            animators.add(createAnimator(mTitleView, false, Gravity.START, 0));
+            animators.add(fadeAnimator = createAnimator(mDescriptionView, false, Gravity.START,
+                    DESCRIPTION_START_DELAY_MS));
+            animators.add(createAnimator(mTitleView, true, Gravity.END,
+                    HEADER_APPEAR_DELAY_MS));
+            animators.add(createAnimator(mDescriptionView, true, Gravity.END,
+                    HEADER_APPEAR_DELAY_MS + DESCRIPTION_START_DELAY_MS));
+        } else {
+            // sliding to right
+            animators.add(createAnimator(mTitleView, false, Gravity.END, 0));
+            animators.add(fadeAnimator = createAnimator(mDescriptionView, false, Gravity.END,
+                    DESCRIPTION_START_DELAY_MS));
+            animators.add(createAnimator(mTitleView, true, Gravity.START,
+                    HEADER_APPEAR_DELAY_MS));
+            animators.add(createAnimator(mDescriptionView, true, Gravity.START,
+                    HEADER_APPEAR_DELAY_MS + DESCRIPTION_START_DELAY_MS));
+        }
+        final int currentPageIndex = getCurrentPageIndex();
+        fadeAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mTitleView.setText(getPageTitle(currentPageIndex));
+                mDescriptionView.setText(getPageDescription(currentPageIndex));
+            }
+        });
+
+        final Context context = FragmentUtil.getContext(OnboardingFragment.this);
+        // Animator for switching between page indicator and button.
+        if (getCurrentPageIndex() == getPageCount() - 1) {
+            mStartButton.setVisibility(View.VISIBLE);
+            Animator navigatorFadeOutAnimator = AnimatorInflater.loadAnimator(context,
+                    R.animator.lb_onboarding_page_indicator_fade_out);
+            navigatorFadeOutAnimator.setTarget(mPageIndicator);
+            navigatorFadeOutAnimator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    mPageIndicator.setVisibility(View.GONE);
+                }
+            });
+            animators.add(navigatorFadeOutAnimator);
+            Animator buttonFadeInAnimator = AnimatorInflater.loadAnimator(context,
+                    R.animator.lb_onboarding_start_button_fade_in);
+            buttonFadeInAnimator.setTarget(mStartButton);
+            animators.add(buttonFadeInAnimator);
+        } else if (previousPage == getPageCount() - 1) {
+            mPageIndicator.setVisibility(View.VISIBLE);
+            Animator navigatorFadeInAnimator = AnimatorInflater.loadAnimator(context,
+                    R.animator.lb_onboarding_page_indicator_fade_in);
+            navigatorFadeInAnimator.setTarget(mPageIndicator);
+            animators.add(navigatorFadeInAnimator);
+            Animator buttonFadeOutAnimator = AnimatorInflater.loadAnimator(context,
+                    R.animator.lb_onboarding_start_button_fade_out);
+            buttonFadeOutAnimator.setTarget(mStartButton);
+            buttonFadeOutAnimator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    mStartButton.setVisibility(View.GONE);
+                }
+            });
+            animators.add(buttonFadeOutAnimator);
+        }
+        mAnimator = new AnimatorSet();
+        mAnimator.playTogether(animators);
+        mAnimator.start();
+        onPageChanged(mCurrentPageIndex, previousPage);
+    }
+
+    /**
+     * Called when the page has been changed.
+     *
+     * @param newPage The new page.
+     * @param previousPage The previous page.
+     */
+    protected void onPageChanged(int newPage, int previousPage) { }
+
+    private Animator createAnimator(View view, boolean fadeIn, int slideDirection,
+            long startDelay) {
+        boolean isLtr = getView().getLayoutDirection() == View.LAYOUT_DIRECTION_LTR;
+        boolean slideRight = (isLtr && slideDirection == Gravity.END)
+                || (!isLtr && slideDirection == Gravity.START)
+                || slideDirection == Gravity.RIGHT;
+        Animator fadeAnimator;
+        Animator slideAnimator;
+        if (fadeIn) {
+            fadeAnimator = ObjectAnimator.ofFloat(view, View.ALPHA, 0.0f, 1.0f);
+            slideAnimator = ObjectAnimator.ofFloat(view, View.TRANSLATION_X,
+                    slideRight ? sSlideDistance : -sSlideDistance, 0);
+            fadeAnimator.setInterpolator(HEADER_APPEAR_INTERPOLATOR);
+            slideAnimator.setInterpolator(HEADER_APPEAR_INTERPOLATOR);
+        } else {
+            fadeAnimator = ObjectAnimator.ofFloat(view, View.ALPHA, 1.0f, 0.0f);
+            slideAnimator = ObjectAnimator.ofFloat(view, View.TRANSLATION_X, 0,
+                    slideRight ? sSlideDistance : -sSlideDistance);
+            fadeAnimator.setInterpolator(HEADER_DISAPPEAR_INTERPOLATOR);
+            slideAnimator.setInterpolator(HEADER_DISAPPEAR_INTERPOLATOR);
+        }
+        fadeAnimator.setDuration(HEADER_ANIMATION_DURATION_MS);
+        fadeAnimator.setTarget(view);
+        slideAnimator.setDuration(HEADER_ANIMATION_DURATION_MS);
+        slideAnimator.setTarget(view);
+        AnimatorSet animator = new AnimatorSet();
+        animator.playTogether(fadeAnimator, slideAnimator);
+        if (startDelay > 0) {
+            animator.setStartDelay(startDelay);
+        }
+        return animator;
+    }
+
+    /**
+     * Sets the resource id for the main icon.
+     */
+    public final void setIconResouceId(int resourceId) {
+        this.mIconResourceId = resourceId;
+        if (mMainIconView != null) {
+            mMainIconView.setImageResource(resourceId);
+            mMainIconView.setVisibility(View.VISIBLE);
+        }
+    }
+
+    /**
+     * Returns the resource id of the main icon.
+     */
+    public final int getIconResourceId() {
+        return mIconResourceId;
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/OnboardingSupportFragment.java b/leanback/src/android/support/v17/leanback/app/OnboardingSupportFragment.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/app/OnboardingSupportFragment.java
rename to leanback/src/android/support/v17/leanback/app/OnboardingSupportFragment.java
diff --git a/v17/leanback/src/android/support/v17/leanback/app/PermissionHelper.java b/leanback/src/android/support/v17/leanback/app/PermissionHelper.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/app/PermissionHelper.java
rename to leanback/src/android/support/v17/leanback/app/PermissionHelper.java
diff --git a/leanback/src/android/support/v17/leanback/app/PlaybackFragment.java b/leanback/src/android/support/v17/leanback/app/PlaybackFragment.java
new file mode 100644
index 0000000..e2e6be4
--- /dev/null
+++ b/leanback/src/android/support/v17/leanback/app/PlaybackFragment.java
@@ -0,0 +1,1178 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from PlaybackSupportFragment.java.  DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.animation.Animator;
+import android.animation.AnimatorInflater;
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v17.leanback.R;
+import android.support.v17.leanback.animation.LogAccelerateInterpolator;
+import android.support.v17.leanback.animation.LogDecelerateInterpolator;
+import android.support.v17.leanback.media.PlaybackGlueHost;
+import android.support.v17.leanback.widget.ArrayObjectAdapter;
+import android.support.v17.leanback.widget.BaseOnItemViewClickedListener;
+import android.support.v17.leanback.widget.BaseOnItemViewSelectedListener;
+import android.support.v17.leanback.widget.ClassPresenterSelector;
+import android.support.v17.leanback.widget.ItemAlignmentFacet;
+import android.support.v17.leanback.widget.ItemBridgeAdapter;
+import android.support.v17.leanback.widget.ObjectAdapter;
+import android.support.v17.leanback.widget.PlaybackRowPresenter;
+import android.support.v17.leanback.widget.PlaybackSeekDataProvider;
+import android.support.v17.leanback.widget.PlaybackSeekUi;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.PresenterSelector;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.app.Fragment;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+import android.view.InputEvent;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.AccelerateInterpolator;
+
+/**
+ * A fragment for displaying playback controls and related content.
+ *
+ * <p>
+ * A PlaybackFragment renders the elements of its {@link ObjectAdapter} as a set
+ * of rows in a vertical list.  The Adapter's {@link PresenterSelector} must maintain subclasses
+ * of {@link RowPresenter}.
+ * </p>
+ * <p>
+ * A playback row is a row rendered by {@link PlaybackRowPresenter}.
+ * App can call {@link #setPlaybackRow(Row)} to set playback row for the first element of adapter.
+ * App can call {@link #setPlaybackRowPresenter(PlaybackRowPresenter)} to set presenter for it.
+ * {@link #setPlaybackRow(Row)} and {@link #setPlaybackRowPresenter(PlaybackRowPresenter)} are
+ * optional, app can pass playback row and PlaybackRowPresenter in the adapter using
+ * {@link #setAdapter(ObjectAdapter)}.
+ * </p>
+ * <p>
+ * Auto hide controls upon playing: best practice is calling
+ * {@link #setControlsOverlayAutoHideEnabled(boolean)} upon play/pause. The auto hiding timer will
+ * be cancelled upon {@link #tickle()} triggered by input event.
+ * </p>
+ * @deprecated use {@link PlaybackSupportFragment}
+ */
+@Deprecated
+public class PlaybackFragment extends Fragment {
+    static final String BUNDLE_CONTROL_VISIBLE_ON_CREATEVIEW = "controlvisible_oncreateview";
+
+    /**
+     * No background.
+     */
+    public static final int BG_NONE = 0;
+
+    /**
+     * A dark translucent background.
+     */
+    public static final int BG_DARK = 1;
+    PlaybackGlueHost.HostCallback mHostCallback;
+
+    PlaybackSeekUi.Client mSeekUiClient;
+    boolean mInSeek;
+    ProgressBarManager mProgressBarManager = new ProgressBarManager();
+
+    /**
+     * Resets the focus on the button in the middle of control row.
+     * @hide
+     */
+    public void resetFocus() {
+        ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder) getVerticalGridView()
+                .findViewHolderForAdapterPosition(0);
+        if (vh != null && vh.getPresenter() instanceof PlaybackRowPresenter) {
+            ((PlaybackRowPresenter) vh.getPresenter()).onReappear(
+                    (RowPresenter.ViewHolder) vh.getViewHolder());
+        }
+    }
+
+    private class SetSelectionRunnable implements Runnable {
+        int mPosition;
+        boolean mSmooth = true;
+
+        @Override
+        public void run() {
+            if (mRowsFragment == null) {
+                return;
+            }
+            mRowsFragment.setSelectedPosition(mPosition, mSmooth);
+        }
+    }
+
+    /**
+     * A light translucent background.
+     */
+    public static final int BG_LIGHT = 2;
+    RowsFragment mRowsFragment;
+    ObjectAdapter mAdapter;
+    PlaybackRowPresenter mPresenter;
+    Row mRow;
+    BaseOnItemViewSelectedListener mExternalItemSelectedListener;
+    BaseOnItemViewClickedListener mExternalItemClickedListener;
+    BaseOnItemViewClickedListener mPlaybackItemClickedListener;
+
+    private final BaseOnItemViewClickedListener mOnItemViewClickedListener =
+            new BaseOnItemViewClickedListener() {
+                @Override
+                public void onItemClicked(Presenter.ViewHolder itemViewHolder,
+                                          Object item,
+                                          RowPresenter.ViewHolder rowViewHolder,
+                                          Object row) {
+                    if (mPlaybackItemClickedListener != null
+                            && rowViewHolder instanceof PlaybackRowPresenter.ViewHolder) {
+                        mPlaybackItemClickedListener.onItemClicked(
+                                itemViewHolder, item, rowViewHolder, row);
+                    }
+                    if (mExternalItemClickedListener != null) {
+                        mExternalItemClickedListener.onItemClicked(
+                                itemViewHolder, item, rowViewHolder, row);
+                    }
+                }
+            };
+
+    private final BaseOnItemViewSelectedListener mOnItemViewSelectedListener =
+            new BaseOnItemViewSelectedListener() {
+                @Override
+                public void onItemSelected(Presenter.ViewHolder itemViewHolder,
+                                           Object item,
+                                           RowPresenter.ViewHolder rowViewHolder,
+                                           Object row) {
+                    if (mExternalItemSelectedListener != null) {
+                        mExternalItemSelectedListener.onItemSelected(
+                                itemViewHolder, item, rowViewHolder, row);
+                    }
+                }
+            };
+
+    private final SetSelectionRunnable mSetSelectionRunnable = new SetSelectionRunnable();
+
+    public ObjectAdapter getAdapter() {
+        return mAdapter;
+    }
+
+    /**
+     * Listener allowing the application to receive notification of fade in and/or fade out
+     * completion events.
+     * @hide
+     * @deprecated use {@link PlaybackSupportFragment}
+     */
+    @Deprecated
+    public static class OnFadeCompleteListener {
+        public void onFadeInComplete() {
+        }
+
+        public void onFadeOutComplete() {
+        }
+    }
+
+    private static final String TAG = "PlaybackFragment";
+    private static final boolean DEBUG = false;
+    private static final int ANIMATION_MULTIPLIER = 1;
+
+    private static int START_FADE_OUT = 1;
+
+    // Fading status
+    private static final int IDLE = 0;
+    private static final int ANIMATING = 1;
+
+    int mPaddingBottom;
+    int mOtherRowsCenterToBottom;
+    View mRootView;
+    View mBackgroundView;
+    int mBackgroundType = BG_DARK;
+    int mBgDarkColor;
+    int mBgLightColor;
+    int mShowTimeMs;
+    int mMajorFadeTranslateY, mMinorFadeTranslateY;
+    int mAnimationTranslateY;
+    OnFadeCompleteListener mFadeCompleteListener;
+    View.OnKeyListener mInputEventHandler;
+    boolean mFadingEnabled = true;
+    boolean mControlVisibleBeforeOnCreateView = true;
+    boolean mControlVisible = true;
+    int mBgAlpha;
+    ValueAnimator mBgFadeInAnimator, mBgFadeOutAnimator;
+    ValueAnimator mControlRowFadeInAnimator, mControlRowFadeOutAnimator;
+    ValueAnimator mOtherRowFadeInAnimator, mOtherRowFadeOutAnimator;
+
+    private final Animator.AnimatorListener mFadeListener =
+            new Animator.AnimatorListener() {
+                @Override
+                public void onAnimationStart(Animator animation) {
+                    enableVerticalGridAnimations(false);
+                }
+
+                @Override
+                public void onAnimationRepeat(Animator animation) {
+                }
+
+                @Override
+                public void onAnimationCancel(Animator animation) {
+                }
+
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    if (DEBUG) Log.v(TAG, "onAnimationEnd " + mBgAlpha);
+                    if (mBgAlpha > 0) {
+                        enableVerticalGridAnimations(true);
+                        if (mFadeCompleteListener != null) {
+                            mFadeCompleteListener.onFadeInComplete();
+                        }
+                    } else {
+                        VerticalGridView verticalView = getVerticalGridView();
+                        // reset focus to the primary actions only if the selected row was the controls row
+                        if (verticalView != null && verticalView.getSelectedPosition() == 0) {
+                            ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder)
+                                    verticalView.findViewHolderForAdapterPosition(0);
+                            if (vh != null && vh.getPresenter() instanceof PlaybackRowPresenter) {
+                                ((PlaybackRowPresenter)vh.getPresenter()).onReappear(
+                                        (RowPresenter.ViewHolder) vh.getViewHolder());
+                            }
+                        }
+                        if (mFadeCompleteListener != null) {
+                            mFadeCompleteListener.onFadeOutComplete();
+                        }
+                    }
+                }
+            };
+
+    public PlaybackFragment() {
+        mProgressBarManager.setInitialDelay(500);
+    }
+
+    VerticalGridView getVerticalGridView() {
+        if (mRowsFragment == null) {
+            return null;
+        }
+        return mRowsFragment.getVerticalGridView();
+    }
+
+    private final Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message message) {
+            if (message.what == START_FADE_OUT && mFadingEnabled) {
+                hideControlsOverlay(true);
+            }
+        }
+    };
+
+    private final VerticalGridView.OnTouchInterceptListener mOnTouchInterceptListener =
+            new VerticalGridView.OnTouchInterceptListener() {
+                @Override
+                public boolean onInterceptTouchEvent(MotionEvent event) {
+                    return onInterceptInputEvent(event);
+                }
+            };
+
+    private final VerticalGridView.OnKeyInterceptListener mOnKeyInterceptListener =
+            new VerticalGridView.OnKeyInterceptListener() {
+                @Override
+                public boolean onInterceptKeyEvent(KeyEvent event) {
+                    return onInterceptInputEvent(event);
+                }
+            };
+
+    private void setBgAlpha(int alpha) {
+        mBgAlpha = alpha;
+        if (mBackgroundView != null) {
+            mBackgroundView.getBackground().setAlpha(alpha);
+        }
+    }
+
+    private void enableVerticalGridAnimations(boolean enable) {
+        if (getVerticalGridView() != null) {
+            getVerticalGridView().setAnimateChildLayout(enable);
+        }
+    }
+
+    /**
+     * Enables or disables auto hiding controls overlay after a short delay fragment is resumed.
+     * If enabled and fragment is resumed, the view will fade out after a time period.
+     * {@link #tickle()} will kill the timer, next time fragment is resumed,
+     * the timer will be started again if {@link #isControlsOverlayAutoHideEnabled()} is true.
+     */
+    public void setControlsOverlayAutoHideEnabled(boolean enabled) {
+        if (DEBUG) Log.v(TAG, "setControlsOverlayAutoHideEnabled " + enabled);
+        if (enabled != mFadingEnabled) {
+            mFadingEnabled = enabled;
+            if (isResumed() && getView().hasFocus()) {
+                showControlsOverlay(true);
+                if (enabled) {
+                    // StateGraph 7->2 5->2
+                    startFadeTimer();
+                } else {
+                    // StateGraph 4->5 2->5
+                    stopFadeTimer();
+                }
+            } else {
+                // StateGraph 6->1 1->6
+            }
+        }
+    }
+
+    /**
+     * Returns true if controls will be auto hidden after a delay when fragment is resumed.
+     */
+    public boolean isControlsOverlayAutoHideEnabled() {
+        return mFadingEnabled;
+    }
+
+    /**
+     * @deprecated Uses {@link #setControlsOverlayAutoHideEnabled(boolean)}
+     */
+    @Deprecated
+    public void setFadingEnabled(boolean enabled) {
+        setControlsOverlayAutoHideEnabled(enabled);
+    }
+
+    /**
+     * @deprecated Uses {@link #isControlsOverlayAutoHideEnabled()}
+     */
+    @Deprecated
+    public boolean isFadingEnabled() {
+        return isControlsOverlayAutoHideEnabled();
+    }
+
+    /**
+     * Sets the listener to be called when fade in or out has completed.
+     * @hide
+     */
+    public void setFadeCompleteListener(OnFadeCompleteListener listener) {
+        mFadeCompleteListener = listener;
+    }
+
+    /**
+     * Returns the listener to be called when fade in or out has completed.
+     * @hide
+     */
+    public OnFadeCompleteListener getFadeCompleteListener() {
+        return mFadeCompleteListener;
+    }
+
+    /**
+     * Sets the input event handler.
+     */
+    public final void setOnKeyInterceptListener(View.OnKeyListener handler) {
+        mInputEventHandler = handler;
+    }
+
+    /**
+     * Tickles the playback controls. Fades in the view if it was faded out. {@link #tickle()} will
+     * also kill the timer created by {@link #setControlsOverlayAutoHideEnabled(boolean)}. When
+     * next time fragment is resumed, the timer will be started again if
+     * {@link #isControlsOverlayAutoHideEnabled()} is true. In most cases app does not need call
+     * this method, tickling on input events is handled by the fragment.
+     */
+    public void tickle() {
+        if (DEBUG) Log.v(TAG, "tickle enabled " + mFadingEnabled + " isResumed " + isResumed());
+        //StateGraph 2->4
+        stopFadeTimer();
+        showControlsOverlay(true);
+    }
+
+    private boolean onInterceptInputEvent(InputEvent event) {
+        final boolean controlsHidden = !mControlVisible;
+        if (DEBUG) Log.v(TAG, "onInterceptInputEvent hidden " + controlsHidden + " " + event);
+        boolean consumeEvent = false;
+        int keyCode = KeyEvent.KEYCODE_UNKNOWN;
+        int keyAction = 0;
+
+        if (event instanceof KeyEvent) {
+            keyCode = ((KeyEvent) event).getKeyCode();
+            keyAction = ((KeyEvent) event).getAction();
+            if (mInputEventHandler != null) {
+                consumeEvent = mInputEventHandler.onKey(getView(), keyCode, (KeyEvent) event);
+            }
+        }
+
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_DPAD_CENTER:
+            case KeyEvent.KEYCODE_DPAD_DOWN:
+            case KeyEvent.KEYCODE_DPAD_UP:
+            case KeyEvent.KEYCODE_DPAD_LEFT:
+            case KeyEvent.KEYCODE_DPAD_RIGHT:
+                // Event may be consumed; regardless, if controls are hidden then these keys will
+                // bring up the controls.
+                if (controlsHidden) {
+                    consumeEvent = true;
+                }
+                if (keyAction == KeyEvent.ACTION_DOWN) {
+                    tickle();
+                }
+                break;
+            case KeyEvent.KEYCODE_BACK:
+            case KeyEvent.KEYCODE_ESCAPE:
+                if (mInSeek) {
+                    // when in seek, the SeekUi will handle the BACK.
+                    return false;
+                }
+                // If controls are not hidden, back will be consumed to fade
+                // them out (even if the key was consumed by the handler).
+                if (!controlsHidden) {
+                    consumeEvent = true;
+
+                    if (((KeyEvent) event).getAction() == KeyEvent.ACTION_UP) {
+                        hideControlsOverlay(true);
+                    }
+                }
+                break;
+            default:
+                if (consumeEvent) {
+                    if (keyAction == KeyEvent.ACTION_DOWN) {
+                        tickle();
+                    }
+                }
+        }
+        return consumeEvent;
+    }
+
+    @Override
+    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+        // controls view are initially visible, make it invisible
+        // if app has called hideControlsOverlay() before view created.
+        mControlVisible = true;
+        if (!mControlVisibleBeforeOnCreateView) {
+            showControlsOverlay(false, false);
+            mControlVisibleBeforeOnCreateView = true;
+        }
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+
+        if (mControlVisible) {
+            //StateGraph: 6->5 1->2
+            if (mFadingEnabled) {
+                // StateGraph 1->2
+                startFadeTimer();
+            }
+        } else {
+            //StateGraph: 6->7 1->3
+        }
+        getVerticalGridView().setOnTouchInterceptListener(mOnTouchInterceptListener);
+        getVerticalGridView().setOnKeyInterceptListener(mOnKeyInterceptListener);
+        if (mHostCallback != null) {
+            mHostCallback.onHostResume();
+        }
+    }
+
+    private void stopFadeTimer() {
+        if (mHandler != null) {
+            mHandler.removeMessages(START_FADE_OUT);
+        }
+    }
+
+    private void startFadeTimer() {
+        if (mHandler != null) {
+            mHandler.removeMessages(START_FADE_OUT);
+            mHandler.sendEmptyMessageDelayed(START_FADE_OUT, mShowTimeMs);
+        }
+    }
+
+    private static ValueAnimator loadAnimator(Context context, int resId) {
+        ValueAnimator animator = (ValueAnimator) AnimatorInflater.loadAnimator(context, resId);
+        animator.setDuration(animator.getDuration() * ANIMATION_MULTIPLIER);
+        return animator;
+    }
+
+    private void loadBgAnimator() {
+        AnimatorUpdateListener listener = new AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator arg0) {
+                setBgAlpha((Integer) arg0.getAnimatedValue());
+            }
+        };
+
+        Context context = FragmentUtil.getContext(PlaybackFragment.this);
+        mBgFadeInAnimator = loadAnimator(context, R.animator.lb_playback_bg_fade_in);
+        mBgFadeInAnimator.addUpdateListener(listener);
+        mBgFadeInAnimator.addListener(mFadeListener);
+
+        mBgFadeOutAnimator = loadAnimator(context, R.animator.lb_playback_bg_fade_out);
+        mBgFadeOutAnimator.addUpdateListener(listener);
+        mBgFadeOutAnimator.addListener(mFadeListener);
+    }
+
+    private TimeInterpolator mLogDecelerateInterpolator = new LogDecelerateInterpolator(100, 0);
+    private TimeInterpolator mLogAccelerateInterpolator = new LogAccelerateInterpolator(100, 0);
+
+    private void loadControlRowAnimator() {
+        final AnimatorUpdateListener updateListener = new AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator arg0) {
+                if (getVerticalGridView() == null) {
+                    return;
+                }
+                RecyclerView.ViewHolder vh = getVerticalGridView()
+                        .findViewHolderForAdapterPosition(0);
+                if (vh == null) {
+                    return;
+                }
+                View view = vh.itemView;
+                if (view != null) {
+                    final float fraction = (Float) arg0.getAnimatedValue();
+                    if (DEBUG) Log.v(TAG, "fraction " + fraction);
+                    view.setAlpha(fraction);
+                    view.setTranslationY((float) mAnimationTranslateY * (1f - fraction));
+                }
+            }
+        };
+
+        Context context = FragmentUtil.getContext(PlaybackFragment.this);
+        mControlRowFadeInAnimator = loadAnimator(context, R.animator.lb_playback_controls_fade_in);
+        mControlRowFadeInAnimator.addUpdateListener(updateListener);
+        mControlRowFadeInAnimator.setInterpolator(mLogDecelerateInterpolator);
+
+        mControlRowFadeOutAnimator = loadAnimator(context,
+                R.animator.lb_playback_controls_fade_out);
+        mControlRowFadeOutAnimator.addUpdateListener(updateListener);
+        mControlRowFadeOutAnimator.setInterpolator(mLogAccelerateInterpolator);
+    }
+
+    private void loadOtherRowAnimator() {
+        final AnimatorUpdateListener updateListener = new AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator arg0) {
+                if (getVerticalGridView() == null) {
+                    return;
+                }
+                final float fraction = (Float) arg0.getAnimatedValue();
+                final int count = getVerticalGridView().getChildCount();
+                for (int i = 0; i < count; i++) {
+                    View view = getVerticalGridView().getChildAt(i);
+                    if (getVerticalGridView().getChildAdapterPosition(view) > 0) {
+                        view.setAlpha(fraction);
+                        view.setTranslationY((float) mAnimationTranslateY * (1f - fraction));
+                    }
+                }
+            }
+        };
+
+        Context context = FragmentUtil.getContext(PlaybackFragment.this);
+        mOtherRowFadeInAnimator = loadAnimator(context, R.animator.lb_playback_controls_fade_in);
+        mOtherRowFadeInAnimator.addUpdateListener(updateListener);
+        mOtherRowFadeInAnimator.setInterpolator(mLogDecelerateInterpolator);
+
+        mOtherRowFadeOutAnimator = loadAnimator(context, R.animator.lb_playback_controls_fade_out);
+        mOtherRowFadeOutAnimator.addUpdateListener(updateListener);
+        mOtherRowFadeOutAnimator.setInterpolator(new AccelerateInterpolator());
+    }
+
+    /**
+     * Fades out the playback overlay immediately.
+     * @deprecated Call {@link #hideControlsOverlay(boolean)}
+     */
+    @Deprecated
+    public void fadeOut() {
+        showControlsOverlay(false, false);
+    }
+
+    /**
+     * Show controls overlay.
+     *
+     * @param runAnimation True to run animation, false otherwise.
+     */
+    public void showControlsOverlay(boolean runAnimation) {
+        showControlsOverlay(true, runAnimation);
+    }
+
+    /**
+     * Returns true if controls overlay is visible, false otherwise.
+     *
+     * @return True if controls overlay is visible, false otherwise.
+     * @see #showControlsOverlay(boolean)
+     * @see #hideControlsOverlay(boolean)
+     */
+    public boolean isControlsOverlayVisible() {
+        return mControlVisible;
+    }
+
+    /**
+     * Hide controls overlay.
+     *
+     * @param runAnimation True to run animation, false otherwise.
+     */
+    public void hideControlsOverlay(boolean runAnimation) {
+        showControlsOverlay(false, runAnimation);
+    }
+
+    /**
+     * if first animator is still running, reverse it; otherwise start second animator.
+     */
+    static void reverseFirstOrStartSecond(ValueAnimator first, ValueAnimator second,
+            boolean runAnimation) {
+        if (first.isStarted()) {
+            first.reverse();
+            if (!runAnimation) {
+                first.end();
+            }
+        } else {
+            second.start();
+            if (!runAnimation) {
+                second.end();
+            }
+        }
+    }
+
+    /**
+     * End first or second animator if they are still running.
+     */
+    static void endAll(ValueAnimator first, ValueAnimator second) {
+        if (first.isStarted()) {
+            first.end();
+        } else if (second.isStarted()) {
+            second.end();
+        }
+    }
+
+    /**
+     * Fade in or fade out rows and background.
+     *
+     * @param show True to fade in, false to fade out.
+     * @param animation True to run animation.
+     */
+    void showControlsOverlay(boolean show, boolean animation) {
+        if (DEBUG) Log.v(TAG, "showControlsOverlay " + show);
+        if (getView() == null) {
+            mControlVisibleBeforeOnCreateView = show;
+            return;
+        }
+        // force no animation when fragment is not resumed
+        if (!isResumed()) {
+            animation = false;
+        }
+        if (show == mControlVisible) {
+            if (!animation) {
+                // End animation if needed
+                endAll(mBgFadeInAnimator, mBgFadeOutAnimator);
+                endAll(mControlRowFadeInAnimator, mControlRowFadeOutAnimator);
+                endAll(mOtherRowFadeInAnimator, mOtherRowFadeOutAnimator);
+            }
+            return;
+        }
+        // StateGraph: 7<->5 4<->3 2->3
+        mControlVisible = show;
+        if (!mControlVisible) {
+            // StateGraph 2->3
+            stopFadeTimer();
+        }
+
+        mAnimationTranslateY = (getVerticalGridView() == null
+                || getVerticalGridView().getSelectedPosition() == 0)
+                ? mMajorFadeTranslateY : mMinorFadeTranslateY;
+
+        if (show) {
+            reverseFirstOrStartSecond(mBgFadeOutAnimator, mBgFadeInAnimator, animation);
+            reverseFirstOrStartSecond(mControlRowFadeOutAnimator, mControlRowFadeInAnimator,
+                    animation);
+            reverseFirstOrStartSecond(mOtherRowFadeOutAnimator, mOtherRowFadeInAnimator, animation);
+        } else {
+            reverseFirstOrStartSecond(mBgFadeInAnimator, mBgFadeOutAnimator, animation);
+            reverseFirstOrStartSecond(mControlRowFadeInAnimator, mControlRowFadeOutAnimator,
+                    animation);
+            reverseFirstOrStartSecond(mOtherRowFadeInAnimator, mOtherRowFadeOutAnimator, animation);
+        }
+        if (animation) {
+            getView().announceForAccessibility(getString(show
+                    ? R.string.lb_playback_controls_shown
+                    : R.string.lb_playback_controls_hidden));
+        }
+    }
+
+    /**
+     * Sets the selected row position with smooth animation.
+     */
+    public void setSelectedPosition(int position) {
+        setSelectedPosition(position, true);
+    }
+
+    /**
+     * Sets the selected row position.
+     */
+    public void setSelectedPosition(int position, boolean smooth) {
+        mSetSelectionRunnable.mPosition = position;
+        mSetSelectionRunnable.mSmooth = smooth;
+        if (getView() != null && getView().getHandler() != null) {
+            getView().getHandler().post(mSetSelectionRunnable);
+        }
+    }
+
+    private void setupChildFragmentLayout() {
+        setVerticalGridViewLayout(mRowsFragment.getVerticalGridView());
+    }
+
+    void setVerticalGridViewLayout(VerticalGridView listview) {
+        if (listview == null) {
+            return;
+        }
+
+        // we set the base line of alignment to -paddingBottom
+        listview.setWindowAlignmentOffset(-mPaddingBottom);
+        listview.setWindowAlignmentOffsetPercent(
+                VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
+
+        // align other rows that arent the last to center of screen, since our baseline is
+        // -mPaddingBottom, we need subtract that from mOtherRowsCenterToBottom.
+        listview.setItemAlignmentOffset(mOtherRowsCenterToBottom - mPaddingBottom);
+        listview.setItemAlignmentOffsetPercent(50);
+
+        // Push last row to the bottom padding
+        // Padding affects alignment when last row is focused
+        listview.setPadding(listview.getPaddingLeft(), listview.getPaddingTop(),
+                listview.getPaddingRight(), mPaddingBottom);
+        listview.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE);
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mOtherRowsCenterToBottom = getResources()
+                .getDimensionPixelSize(R.dimen.lb_playback_other_rows_center_to_bottom);
+        mPaddingBottom =
+                getResources().getDimensionPixelSize(R.dimen.lb_playback_controls_padding_bottom);
+        mBgDarkColor =
+                getResources().getColor(R.color.lb_playback_controls_background_dark);
+        mBgLightColor =
+                getResources().getColor(R.color.lb_playback_controls_background_light);
+        mShowTimeMs =
+                getResources().getInteger(R.integer.lb_playback_controls_show_time_ms);
+        mMajorFadeTranslateY =
+                getResources().getDimensionPixelSize(R.dimen.lb_playback_major_fade_translate_y);
+        mMinorFadeTranslateY =
+                getResources().getDimensionPixelSize(R.dimen.lb_playback_minor_fade_translate_y);
+
+        loadBgAnimator();
+        loadControlRowAnimator();
+        loadOtherRowAnimator();
+    }
+
+    /**
+     * Sets the background type.
+     *
+     * @param type One of BG_LIGHT, BG_DARK, or BG_NONE.
+     */
+    public void setBackgroundType(int type) {
+        switch (type) {
+            case BG_LIGHT:
+            case BG_DARK:
+            case BG_NONE:
+                if (type != mBackgroundType) {
+                    mBackgroundType = type;
+                    updateBackground();
+                }
+                break;
+            default:
+                throw new IllegalArgumentException("Invalid background type");
+        }
+    }
+
+    /**
+     * Returns the background type.
+     */
+    public int getBackgroundType() {
+        return mBackgroundType;
+    }
+
+    private void updateBackground() {
+        if (mBackgroundView != null) {
+            int color = mBgDarkColor;
+            switch (mBackgroundType) {
+                case BG_DARK:
+                    break;
+                case BG_LIGHT:
+                    color = mBgLightColor;
+                    break;
+                case BG_NONE:
+                    color = Color.TRANSPARENT;
+                    break;
+            }
+            mBackgroundView.setBackground(new ColorDrawable(color));
+            setBgAlpha(mBgAlpha);
+        }
+    }
+
+    private final ItemBridgeAdapter.AdapterListener mAdapterListener =
+            new ItemBridgeAdapter.AdapterListener() {
+                @Override
+                public void onAttachedToWindow(ItemBridgeAdapter.ViewHolder vh) {
+                    if (DEBUG) Log.v(TAG, "onAttachedToWindow " + vh.getViewHolder().view);
+                    if (!mControlVisible) {
+                        if (DEBUG) Log.v(TAG, "setting alpha to 0");
+                        vh.getViewHolder().view.setAlpha(0);
+                    }
+                }
+
+                @Override
+                public void onCreate(ItemBridgeAdapter.ViewHolder vh) {
+                    Presenter.ViewHolder viewHolder = vh.getViewHolder();
+                    if (viewHolder instanceof PlaybackSeekUi) {
+                        ((PlaybackSeekUi) viewHolder).setPlaybackSeekUiClient(mChainedClient);
+                    }
+                }
+
+                @Override
+                public void onDetachedFromWindow(ItemBridgeAdapter.ViewHolder vh) {
+                    if (DEBUG) Log.v(TAG, "onDetachedFromWindow " + vh.getViewHolder().view);
+                    // Reset animation state
+                    vh.getViewHolder().view.setAlpha(1f);
+                    vh.getViewHolder().view.setTranslationY(0);
+                    vh.getViewHolder().view.setAlpha(1f);
+                }
+
+                @Override
+                public void onBind(ItemBridgeAdapter.ViewHolder vh) {
+                }
+            };
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        mRootView = inflater.inflate(R.layout.lb_playback_fragment, container, false);
+        mBackgroundView = mRootView.findViewById(R.id.playback_fragment_background);
+        mRowsFragment = (RowsFragment) getChildFragmentManager().findFragmentById(
+                R.id.playback_controls_dock);
+        if (mRowsFragment == null) {
+            mRowsFragment = new RowsFragment();
+            getChildFragmentManager().beginTransaction()
+                    .replace(R.id.playback_controls_dock, mRowsFragment)
+                    .commit();
+        }
+        if (mAdapter == null) {
+            setAdapter(new ArrayObjectAdapter(new ClassPresenterSelector()));
+        } else {
+            mRowsFragment.setAdapter(mAdapter);
+        }
+        mRowsFragment.setOnItemViewSelectedListener(mOnItemViewSelectedListener);
+        mRowsFragment.setOnItemViewClickedListener(mOnItemViewClickedListener);
+
+        mBgAlpha = 255;
+        updateBackground();
+        mRowsFragment.setExternalAdapterListener(mAdapterListener);
+        ProgressBarManager progressBarManager = getProgressBarManager();
+        if (progressBarManager != null) {
+            progressBarManager.setRootView((ViewGroup) mRootView);
+        }
+        return mRootView;
+    }
+
+    /**
+     * Sets the {@link PlaybackGlueHost.HostCallback}. Implementor of this interface will
+     * take appropriate actions to take action when the hosting fragment starts/stops processing.
+     */
+    public void setHostCallback(PlaybackGlueHost.HostCallback hostCallback) {
+        this.mHostCallback = hostCallback;
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        setupChildFragmentLayout();
+        mRowsFragment.setAdapter(mAdapter);
+        if (mHostCallback != null) {
+            mHostCallback.onHostStart();
+        }
+    }
+
+    @Override
+    public void onStop() {
+        if (mHostCallback != null) {
+            mHostCallback.onHostStop();
+        }
+        super.onStop();
+    }
+
+    @Override
+    public void onPause() {
+        if (mHostCallback != null) {
+            mHostCallback.onHostPause();
+        }
+        if (mHandler.hasMessages(START_FADE_OUT)) {
+            // StateGraph: 2->1
+            mHandler.removeMessages(START_FADE_OUT);
+        } else {
+            // StateGraph: 5->6, 7->6, 4->1, 3->1
+        }
+        super.onPause();
+    }
+
+    /**
+     * This listener is called every time there is a selection in {@link RowsFragment}. This can
+     * be used by users to take additional actions such as animations.
+     */
+    public void setOnItemViewSelectedListener(final BaseOnItemViewSelectedListener listener) {
+        mExternalItemSelectedListener = listener;
+    }
+
+    /**
+     * This listener is called every time there is a click in {@link RowsFragment}. This can
+     * be used by users to take additional actions such as animations.
+     */
+    public void setOnItemViewClickedListener(final BaseOnItemViewClickedListener listener) {
+        mExternalItemClickedListener = listener;
+    }
+
+    /**
+     * Sets the {@link BaseOnItemViewClickedListener} that would be invoked for clicks
+     * only on {@link android.support.v17.leanback.widget.PlaybackRowPresenter.ViewHolder}.
+     */
+    public void setOnPlaybackItemViewClickedListener(final BaseOnItemViewClickedListener listener) {
+        mPlaybackItemClickedListener = listener;
+    }
+
+    @Override
+    public void onDestroyView() {
+        mRootView = null;
+        mBackgroundView = null;
+        super.onDestroyView();
+    }
+
+    @Override
+    public void onDestroy() {
+        if (mHostCallback != null) {
+            mHostCallback.onHostDestroy();
+        }
+        super.onDestroy();
+    }
+
+    /**
+     * Sets the playback row for the playback controls. The row will be set as first element
+     * of adapter if the adapter is {@link ArrayObjectAdapter} or {@link SparseArrayObjectAdapter}.
+     * @param row The row that represents the playback.
+     */
+    public void setPlaybackRow(Row row) {
+        this.mRow = row;
+        setupRow();
+        setupPresenter();
+    }
+
+    /**
+     * Sets the presenter for rendering the playback row set by {@link #setPlaybackRow(Row)}. If
+     * adapter does not set a {@link PresenterSelector}, {@link #setAdapter(ObjectAdapter)} will
+     * create a {@link ClassPresenterSelector} by default and map from the row object class to this
+     * {@link PlaybackRowPresenter}.
+     *
+     * @param  presenter Presenter used to render {@link #setPlaybackRow(Row)}.
+     */
+    public void setPlaybackRowPresenter(PlaybackRowPresenter presenter) {
+        this.mPresenter = presenter;
+        setupPresenter();
+        setPlaybackRowPresenterAlignment();
+    }
+
+    void setPlaybackRowPresenterAlignment() {
+        if (mAdapter != null && mAdapter.getPresenterSelector() != null) {
+            Presenter[] presenters = mAdapter.getPresenterSelector().getPresenters();
+            if (presenters != null) {
+                for (int i = 0; i < presenters.length; i++) {
+                    if (presenters[i] instanceof PlaybackRowPresenter
+                            && presenters[i].getFacet(ItemAlignmentFacet.class) == null) {
+                        ItemAlignmentFacet itemAlignment = new ItemAlignmentFacet();
+                        ItemAlignmentFacet.ItemAlignmentDef def =
+                                new ItemAlignmentFacet.ItemAlignmentDef();
+                        def.setItemAlignmentOffset(0);
+                        def.setItemAlignmentOffsetPercent(100);
+                        itemAlignment.setAlignmentDefs(new ItemAlignmentFacet.ItemAlignmentDef[]
+                                {def});
+                        presenters[i].setFacet(ItemAlignmentFacet.class, itemAlignment);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Updates the ui when the row data changes.
+     */
+    public void notifyPlaybackRowChanged() {
+        if (mAdapter == null) {
+            return;
+        }
+        mAdapter.notifyItemRangeChanged(0, 1);
+    }
+
+    /**
+     * Sets the list of rows for the fragment. A default {@link ClassPresenterSelector} will be
+     * created if {@link ObjectAdapter#getPresenterSelector()} is null. if user provides
+     * {@link #setPlaybackRow(Row)} and {@link #setPlaybackRowPresenter(PlaybackRowPresenter)},
+     * the row and presenter will be set onto the adapter.
+     *
+     * @param adapter The adapter that contains related rows and optional playback row.
+     */
+    public void setAdapter(ObjectAdapter adapter) {
+        mAdapter = adapter;
+        setupRow();
+        setupPresenter();
+        setPlaybackRowPresenterAlignment();
+
+        if (mRowsFragment != null) {
+            mRowsFragment.setAdapter(adapter);
+        }
+    }
+
+    private void setupRow() {
+        if (mAdapter instanceof ArrayObjectAdapter && mRow != null) {
+            ArrayObjectAdapter adapter = ((ArrayObjectAdapter) mAdapter);
+            if (adapter.size() == 0) {
+                adapter.add(mRow);
+            } else {
+                adapter.replace(0, mRow);
+            }
+        } else if (mAdapter instanceof SparseArrayObjectAdapter && mRow != null) {
+            SparseArrayObjectAdapter adapter = ((SparseArrayObjectAdapter) mAdapter);
+            adapter.set(0, mRow);
+        }
+    }
+
+    private void setupPresenter() {
+        if (mAdapter != null && mRow != null && mPresenter != null) {
+            PresenterSelector selector = mAdapter.getPresenterSelector();
+            if (selector == null) {
+                selector = new ClassPresenterSelector();
+                ((ClassPresenterSelector) selector).addClassPresenter(mRow.getClass(), mPresenter);
+                mAdapter.setPresenterSelector(selector);
+            } else if (selector instanceof ClassPresenterSelector) {
+                ((ClassPresenterSelector) selector).addClassPresenter(mRow.getClass(), mPresenter);
+            }
+        }
+    }
+
+    final PlaybackSeekUi.Client mChainedClient = new PlaybackSeekUi.Client() {
+        @Override
+        public boolean isSeekEnabled() {
+            return mSeekUiClient == null ? false : mSeekUiClient.isSeekEnabled();
+        }
+
+        @Override
+        public void onSeekStarted() {
+            if (mSeekUiClient != null) {
+                mSeekUiClient.onSeekStarted();
+            }
+            setSeekMode(true);
+        }
+
+        @Override
+        public PlaybackSeekDataProvider getPlaybackSeekDataProvider() {
+            return mSeekUiClient == null ? null : mSeekUiClient.getPlaybackSeekDataProvider();
+        }
+
+        @Override
+        public void onSeekPositionChanged(long pos) {
+            if (mSeekUiClient != null) {
+                mSeekUiClient.onSeekPositionChanged(pos);
+            }
+        }
+
+        @Override
+        public void onSeekFinished(boolean cancelled) {
+            if (mSeekUiClient != null) {
+                mSeekUiClient.onSeekFinished(cancelled);
+            }
+            setSeekMode(false);
+        }
+    };
+
+    /**
+     * Interface to be implemented by UI widget to support PlaybackSeekUi.
+     */
+    public void setPlaybackSeekUiClient(PlaybackSeekUi.Client client) {
+        mSeekUiClient = client;
+    }
+
+    /**
+     * Show or hide other rows other than PlaybackRow.
+     * @param inSeek True to make other rows visible, false to make other rows invisible.
+     */
+    void setSeekMode(boolean inSeek) {
+        if (mInSeek == inSeek) {
+            return;
+        }
+        mInSeek = inSeek;
+        getVerticalGridView().setSelectedPosition(0);
+        if (mInSeek) {
+            stopFadeTimer();
+        }
+        // immediately fade in control row.
+        showControlsOverlay(true);
+        final int count = getVerticalGridView().getChildCount();
+        for (int i = 0; i < count; i++) {
+            View view = getVerticalGridView().getChildAt(i);
+            if (getVerticalGridView().getChildAdapterPosition(view) > 0) {
+                view.setVisibility(mInSeek ? View.INVISIBLE : View.VISIBLE);
+            }
+        }
+    }
+
+    /**
+     * Called when size of the video changes. App may override.
+     * @param videoWidth Intrinsic width of video
+     * @param videoHeight Intrinsic height of video
+     */
+    protected void onVideoSizeChanged(int videoWidth, int videoHeight) {
+    }
+
+    /**
+     * Called when media has start or stop buffering. App may override. The default initial state
+     * is not buffering.
+     * @param start True for buffering start, false otherwise.
+     */
+    protected void onBufferingStateChanged(boolean start) {
+        ProgressBarManager progressBarManager = getProgressBarManager();
+        if (progressBarManager != null) {
+            if (start) {
+                progressBarManager.show();
+            } else {
+                progressBarManager.hide();
+            }
+        }
+    }
+
+    /**
+     * Called when media has error. App may override.
+     * @param errorCode Optional error code for specific implementation.
+     * @param errorMessage Optional error message for specific implementation.
+     */
+    protected void onError(int errorCode, CharSequence errorMessage) {
+    }
+
+    /**
+     * Returns the ProgressBarManager that will show or hide progress bar in
+     * {@link #onBufferingStateChanged(boolean)}.
+     * @return The ProgressBarManager that will show or hide progress bar in
+     * {@link #onBufferingStateChanged(boolean)}.
+     */
+    public ProgressBarManager getProgressBarManager() {
+        return mProgressBarManager;
+    }
+}
diff --git a/leanback/src/android/support/v17/leanback/app/PlaybackFragmentGlueHost.java b/leanback/src/android/support/v17/leanback/app/PlaybackFragmentGlueHost.java
new file mode 100644
index 0000000..9e342fd
--- /dev/null
+++ b/leanback/src/android/support/v17/leanback/app/PlaybackFragmentGlueHost.java
@@ -0,0 +1,142 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from {}PlaybackSupportFragmentGlueHost.java.  DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.support.v17.leanback.media.PlaybackGlueHost;
+import android.support.v17.leanback.widget.Action;
+import android.support.v17.leanback.widget.OnActionClickedListener;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.PlaybackRowPresenter;
+import android.support.v17.leanback.widget.PlaybackSeekUi;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.view.View;
+
+/**
+ * {@link PlaybackGlueHost} implementation
+ * the interaction between this class and {@link PlaybackFragment}.
+ * @deprecated use {@link PlaybackSupportFragmentGlueHost}
+ */
+@Deprecated
+public class PlaybackFragmentGlueHost extends PlaybackGlueHost implements PlaybackSeekUi {
+    private final PlaybackFragment mFragment;
+
+    public PlaybackFragmentGlueHost(PlaybackFragment fragment) {
+        this.mFragment = fragment;
+    }
+
+    @Override
+    public void setControlsOverlayAutoHideEnabled(boolean enabled) {
+        mFragment.setControlsOverlayAutoHideEnabled(enabled);
+    }
+
+    @Override
+    public boolean isControlsOverlayAutoHideEnabled() {
+        return mFragment.isControlsOverlayAutoHideEnabled();
+    }
+
+    @Override
+    public void setOnKeyInterceptListener(View.OnKeyListener onKeyListener) {
+        mFragment.setOnKeyInterceptListener(onKeyListener);
+    }
+
+    @Override
+    public void setOnActionClickedListener(final OnActionClickedListener listener) {
+        if (listener == null) {
+            mFragment.setOnPlaybackItemViewClickedListener(null);
+        } else {
+            mFragment.setOnPlaybackItemViewClickedListener(new OnItemViewClickedListener() {
+                @Override
+                public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
+                                          RowPresenter.ViewHolder rowViewHolder, Row row) {
+                    if (item instanceof Action) {
+                        listener.onActionClicked((Action) item);
+                    }
+                }
+            });
+        }
+    }
+
+    @Override
+    public void setHostCallback(HostCallback callback) {
+        mFragment.setHostCallback(callback);
+    }
+
+    @Override
+    public void notifyPlaybackRowChanged() {
+        mFragment.notifyPlaybackRowChanged();
+    }
+
+    @Override
+    public void setPlaybackRowPresenter(PlaybackRowPresenter presenter) {
+        mFragment.setPlaybackRowPresenter(presenter);
+    }
+
+    @Override
+    public void setPlaybackRow(Row row) {
+        mFragment.setPlaybackRow(row);
+    }
+
+    @Override
+    public void fadeOut() {
+        mFragment.fadeOut();
+    }
+
+    @Override
+    public boolean isControlsOverlayVisible() {
+        return mFragment.isControlsOverlayVisible();
+    }
+
+    @Override
+    public void hideControlsOverlay(boolean runAnimation) {
+        mFragment.hideControlsOverlay(runAnimation);
+    }
+
+    @Override
+    public void showControlsOverlay(boolean runAnimation) {
+        mFragment.showControlsOverlay(runAnimation);
+    }
+
+    @Override
+    public void setPlaybackSeekUiClient(Client client) {
+        mFragment.setPlaybackSeekUiClient(client);
+    }
+
+    final PlayerCallback mPlayerCallback =
+            new PlayerCallback() {
+                @Override
+                public void onBufferingStateChanged(boolean start) {
+                    mFragment.onBufferingStateChanged(start);
+                }
+
+                @Override
+                public void onError(int errorCode, CharSequence errorMessage) {
+                    mFragment.onError(errorCode, errorMessage);
+                }
+
+                @Override
+                public void onVideoSizeChanged(int videoWidth, int videoHeight) {
+                    mFragment.onVideoSizeChanged(videoWidth, videoHeight);
+                }
+            };
+
+    @Override
+    public PlayerCallback getPlayerCallback() {
+        return mPlayerCallback;
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/PlaybackSupportFragment.java b/leanback/src/android/support/v17/leanback/app/PlaybackSupportFragment.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/app/PlaybackSupportFragment.java
rename to leanback/src/android/support/v17/leanback/app/PlaybackSupportFragment.java
diff --git a/v17/leanback/src/android/support/v17/leanback/app/PlaybackSupportFragmentGlueHost.java b/leanback/src/android/support/v17/leanback/app/PlaybackSupportFragmentGlueHost.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/app/PlaybackSupportFragmentGlueHost.java
rename to leanback/src/android/support/v17/leanback/app/PlaybackSupportFragmentGlueHost.java
diff --git a/v17/leanback/src/android/support/v17/leanback/app/ProgressBarManager.java b/leanback/src/android/support/v17/leanback/app/ProgressBarManager.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/app/ProgressBarManager.java
rename to leanback/src/android/support/v17/leanback/app/ProgressBarManager.java
diff --git a/leanback/src/android/support/v17/leanback/app/RowsFragment.java b/leanback/src/android/support/v17/leanback/app/RowsFragment.java
new file mode 100644
index 0000000..aa346bd
--- /dev/null
+++ b/leanback/src/android/support/v17/leanback/app/RowsFragment.java
@@ -0,0 +1,689 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from RowsSupportFragment.java.  DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.animation.TimeAnimator;
+import android.animation.TimeAnimator.TimeListener;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v17.leanback.R;
+import android.support.v17.leanback.widget.BaseOnItemViewClickedListener;
+import android.support.v17.leanback.widget.BaseOnItemViewSelectedListener;
+import android.support.v17.leanback.widget.HorizontalGridView;
+import android.support.v17.leanback.widget.ItemBridgeAdapter;
+import android.support.v17.leanback.widget.ListRowPresenter;
+import android.support.v17.leanback.widget.ObjectAdapter;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.OnItemViewSelectedListener;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.PresenterSelector;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.support.v17.leanback.widget.ViewHolderTask;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+
+import java.util.ArrayList;
+
+/**
+ * An ordered set of rows of leanback widgets.
+ * <p>
+ * A RowsFragment renders the elements of its
+ * {@link android.support.v17.leanback.widget.ObjectAdapter} as a set
+ * of rows in a vertical list. The Adapter's {@link PresenterSelector} must maintain subclasses
+ * of {@link RowPresenter}.
+ * </p>
+ * @deprecated use {@link RowsSupportFragment}
+ */
+@Deprecated
+public class RowsFragment extends BaseRowFragment implements
+        BrowseFragment.MainFragmentRowsAdapterProvider,
+        BrowseFragment.MainFragmentAdapterProvider {
+
+    private MainFragmentAdapter mMainFragmentAdapter;
+    private MainFragmentRowsAdapter mMainFragmentRowsAdapter;
+
+    @Override
+    public BrowseFragment.MainFragmentAdapter getMainFragmentAdapter() {
+        if (mMainFragmentAdapter == null) {
+            mMainFragmentAdapter = new MainFragmentAdapter(this);
+        }
+        return mMainFragmentAdapter;
+    }
+
+    @Override
+    public BrowseFragment.MainFragmentRowsAdapter getMainFragmentRowsAdapter() {
+        if (mMainFragmentRowsAdapter == null) {
+            mMainFragmentRowsAdapter = new MainFragmentRowsAdapter(this);
+        }
+        return mMainFragmentRowsAdapter;
+    }
+
+    /**
+     * Internal helper class that manages row select animation and apply a default
+     * dim to each row.
+     */
+    final class RowViewHolderExtra implements TimeListener {
+        final RowPresenter mRowPresenter;
+        final Presenter.ViewHolder mRowViewHolder;
+
+        final TimeAnimator mSelectAnimator = new TimeAnimator();
+
+        int mSelectAnimatorDurationInUse;
+        Interpolator mSelectAnimatorInterpolatorInUse;
+        float mSelectLevelAnimStart;
+        float mSelectLevelAnimDelta;
+
+        RowViewHolderExtra(ItemBridgeAdapter.ViewHolder ibvh) {
+            mRowPresenter = (RowPresenter) ibvh.getPresenter();
+            mRowViewHolder = ibvh.getViewHolder();
+            mSelectAnimator.setTimeListener(this);
+        }
+
+        @Override
+        public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) {
+            if (mSelectAnimator.isRunning()) {
+                updateSelect(totalTime, deltaTime);
+            }
+        }
+
+        void updateSelect(long totalTime, long deltaTime) {
+            float fraction;
+            if (totalTime >= mSelectAnimatorDurationInUse) {
+                fraction = 1;
+                mSelectAnimator.end();
+            } else {
+                fraction = (float) (totalTime / (double) mSelectAnimatorDurationInUse);
+            }
+            if (mSelectAnimatorInterpolatorInUse != null) {
+                fraction = mSelectAnimatorInterpolatorInUse.getInterpolation(fraction);
+            }
+            float level = mSelectLevelAnimStart + fraction * mSelectLevelAnimDelta;
+            mRowPresenter.setSelectLevel(mRowViewHolder, level);
+        }
+
+        void animateSelect(boolean select, boolean immediate) {
+            mSelectAnimator.end();
+            final float end = select ? 1 : 0;
+            if (immediate) {
+                mRowPresenter.setSelectLevel(mRowViewHolder, end);
+            } else if (mRowPresenter.getSelectLevel(mRowViewHolder) != end) {
+                mSelectAnimatorDurationInUse = mSelectAnimatorDuration;
+                mSelectAnimatorInterpolatorInUse = mSelectAnimatorInterpolator;
+                mSelectLevelAnimStart = mRowPresenter.getSelectLevel(mRowViewHolder);
+                mSelectLevelAnimDelta = end - mSelectLevelAnimStart;
+                mSelectAnimator.start();
+            }
+        }
+
+    }
+
+    static final String TAG = "RowsFragment";
+    static final boolean DEBUG = false;
+    static final int ALIGN_TOP_NOT_SET = Integer.MIN_VALUE;
+
+    ItemBridgeAdapter.ViewHolder mSelectedViewHolder;
+    private int mSubPosition;
+    boolean mExpand = true;
+    boolean mViewsCreated;
+    private int mAlignedTop = ALIGN_TOP_NOT_SET;
+    boolean mAfterEntranceTransition = true;
+    boolean mFreezeRows;
+
+    BaseOnItemViewSelectedListener mOnItemViewSelectedListener;
+    BaseOnItemViewClickedListener mOnItemViewClickedListener;
+
+    // Select animation and interpolator are not intended to be
+    // exposed at this moment. They might be synced with vertical scroll
+    // animation later.
+    int mSelectAnimatorDuration;
+    Interpolator mSelectAnimatorInterpolator = new DecelerateInterpolator(2);
+
+    private RecyclerView.RecycledViewPool mRecycledViewPool;
+    private ArrayList<Presenter> mPresenterMapper;
+
+    ItemBridgeAdapter.AdapterListener mExternalAdapterListener;
+
+    @Override
+    protected VerticalGridView findGridViewFromRoot(View view) {
+        return (VerticalGridView) view.findViewById(R.id.container_list);
+    }
+
+    /**
+     * Sets an item clicked listener on the fragment.
+     * OnItemViewClickedListener will override {@link View.OnClickListener} that
+     * item presenter sets during {@link Presenter#onCreateViewHolder(ViewGroup)}.
+     * So in general, developer should choose one of the listeners but not both.
+     */
+    public void setOnItemViewClickedListener(BaseOnItemViewClickedListener listener) {
+        mOnItemViewClickedListener = listener;
+        if (mViewsCreated) {
+            throw new IllegalStateException(
+                    "Item clicked listener must be set before views are created");
+        }
+    }
+
+    /**
+     * Returns the item clicked listener.
+     */
+    public BaseOnItemViewClickedListener getOnItemViewClickedListener() {
+        return mOnItemViewClickedListener;
+    }
+
+    /**
+     * @deprecated use {@link BrowseFragment#enableRowScaling(boolean)} instead.
+     *
+     * @param enable true to enable row scaling
+     */
+    @Deprecated
+    public void enableRowScaling(boolean enable) {
+    }
+
+    /**
+     * Set the visibility of titles/hovercard of browse rows.
+     */
+    public void setExpand(boolean expand) {
+        mExpand = expand;
+        VerticalGridView listView = getVerticalGridView();
+        if (listView != null) {
+            final int count = listView.getChildCount();
+            if (DEBUG) Log.v(TAG, "setExpand " + expand + " count " + count);
+            for (int i = 0; i < count; i++) {
+                View view = listView.getChildAt(i);
+                ItemBridgeAdapter.ViewHolder vh =
+                        (ItemBridgeAdapter.ViewHolder) listView.getChildViewHolder(view);
+                setRowViewExpanded(vh, mExpand);
+            }
+        }
+    }
+
+    /**
+     * Sets an item selection listener.
+     */
+    public void setOnItemViewSelectedListener(BaseOnItemViewSelectedListener listener) {
+        mOnItemViewSelectedListener = listener;
+        VerticalGridView listView = getVerticalGridView();
+        if (listView != null) {
+            final int count = listView.getChildCount();
+            for (int i = 0; i < count; i++) {
+                View view = listView.getChildAt(i);
+                ItemBridgeAdapter.ViewHolder ibvh = (ItemBridgeAdapter.ViewHolder)
+                        listView.getChildViewHolder(view);
+                getRowViewHolder(ibvh).setOnItemViewSelectedListener(mOnItemViewSelectedListener);
+            }
+        }
+    }
+
+    /**
+     * Returns an item selection listener.
+     */
+    public BaseOnItemViewSelectedListener getOnItemViewSelectedListener() {
+        return mOnItemViewSelectedListener;
+    }
+
+    @Override
+    void onRowSelected(RecyclerView parent, RecyclerView.ViewHolder viewHolder,
+            int position, int subposition) {
+        if (mSelectedViewHolder != viewHolder || mSubPosition != subposition) {
+            if (DEBUG) Log.v(TAG, "new row selected position " + position + " subposition "
+                    + subposition + " view " + viewHolder.itemView);
+            mSubPosition = subposition;
+            if (mSelectedViewHolder != null) {
+                setRowViewSelected(mSelectedViewHolder, false, false);
+            }
+            mSelectedViewHolder = (ItemBridgeAdapter.ViewHolder) viewHolder;
+            if (mSelectedViewHolder != null) {
+                setRowViewSelected(mSelectedViewHolder, true, false);
+            }
+        }
+        // When RowsFragment is embedded inside a page fragment, we want to show
+        // the title view only when we're on the first row or there is no data.
+        if (mMainFragmentAdapter != null) {
+            mMainFragmentAdapter.getFragmentHost().showTitleView(position <= 0);
+        }
+    }
+
+    /**
+     * Get row ViewHolder at adapter position.  Returns null if the row object is not in adapter or
+     * the row object has not been bound to a row view.
+     *
+     * @param position Position of row in adapter.
+     * @return Row ViewHolder at a given adapter position.
+     */
+    public RowPresenter.ViewHolder getRowViewHolder(int position) {
+        VerticalGridView verticalView = getVerticalGridView();
+        if (verticalView == null) {
+            return null;
+        }
+        return getRowViewHolder((ItemBridgeAdapter.ViewHolder)
+                verticalView.findViewHolderForAdapterPosition(position));
+    }
+
+    @Override
+    int getLayoutResourceId() {
+        return R.layout.lb_rows_fragment;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mSelectAnimatorDuration = getResources().getInteger(
+                R.integer.lb_browse_rows_anim_duration);
+    }
+
+    @Override
+    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+        if (DEBUG) Log.v(TAG, "onViewCreated");
+        super.onViewCreated(view, savedInstanceState);
+        // Align the top edge of child with id row_content.
+        // Need set this for directly using RowsFragment.
+        getVerticalGridView().setItemAlignmentViewId(R.id.row_content);
+        getVerticalGridView().setSaveChildrenPolicy(VerticalGridView.SAVE_LIMITED_CHILD);
+
+        setAlignment(mAlignedTop);
+
+        mRecycledViewPool = null;
+        mPresenterMapper = null;
+        if (mMainFragmentAdapter != null) {
+            mMainFragmentAdapter.getFragmentHost().notifyViewCreated(mMainFragmentAdapter);
+        }
+
+    }
+
+    @Override
+    public void onDestroyView() {
+        mViewsCreated = false;
+        super.onDestroyView();
+    }
+
+    void setExternalAdapterListener(ItemBridgeAdapter.AdapterListener listener) {
+        mExternalAdapterListener = listener;
+    }
+
+    static void setRowViewExpanded(ItemBridgeAdapter.ViewHolder vh, boolean expanded) {
+        ((RowPresenter) vh.getPresenter()).setRowViewExpanded(vh.getViewHolder(), expanded);
+    }
+
+    static void setRowViewSelected(ItemBridgeAdapter.ViewHolder vh, boolean selected,
+            boolean immediate) {
+        RowViewHolderExtra extra = (RowViewHolderExtra) vh.getExtraObject();
+        extra.animateSelect(selected, immediate);
+        ((RowPresenter) vh.getPresenter()).setRowViewSelected(vh.getViewHolder(), selected);
+    }
+
+    private final ItemBridgeAdapter.AdapterListener mBridgeAdapterListener =
+            new ItemBridgeAdapter.AdapterListener() {
+        @Override
+        public void onAddPresenter(Presenter presenter, int type) {
+            if (mExternalAdapterListener != null) {
+                mExternalAdapterListener.onAddPresenter(presenter, type);
+            }
+        }
+
+        @Override
+        public void onCreate(ItemBridgeAdapter.ViewHolder vh) {
+            VerticalGridView listView = getVerticalGridView();
+            if (listView != null) {
+                // set clip children false for slide animation
+                listView.setClipChildren(false);
+            }
+            setupSharedViewPool(vh);
+            mViewsCreated = true;
+            vh.setExtraObject(new RowViewHolderExtra(vh));
+            // selected state is initialized to false, then driven by grid view onChildSelected
+            // events.  When there is rebind, grid view fires onChildSelected event properly.
+            // So we don't need do anything special later in onBind or onAttachedToWindow.
+            setRowViewSelected(vh, false, true);
+            if (mExternalAdapterListener != null) {
+                mExternalAdapterListener.onCreate(vh);
+            }
+            RowPresenter rowPresenter = (RowPresenter) vh.getPresenter();
+            RowPresenter.ViewHolder rowVh = rowPresenter.getRowViewHolder(vh.getViewHolder());
+            rowVh.setOnItemViewSelectedListener(mOnItemViewSelectedListener);
+            rowVh.setOnItemViewClickedListener(mOnItemViewClickedListener);
+        }
+
+        @Override
+        public void onAttachedToWindow(ItemBridgeAdapter.ViewHolder vh) {
+            if (DEBUG) Log.v(TAG, "onAttachToWindow");
+            // All views share the same mExpand value.  When we attach a view to grid view,
+            // we should make sure it pick up the latest mExpand value we set early on other
+            // attached views.  For no-structure-change update,  the view is rebound to new data,
+            // but again it should use the unchanged mExpand value,  so we don't need do any
+            // thing in onBind.
+            setRowViewExpanded(vh, mExpand);
+            RowPresenter rowPresenter = (RowPresenter) vh.getPresenter();
+            RowPresenter.ViewHolder rowVh = rowPresenter.getRowViewHolder(vh.getViewHolder());
+            rowPresenter.setEntranceTransitionState(rowVh, mAfterEntranceTransition);
+
+            // freeze the rows attached after RowsFragment#freezeRows() is called
+            rowPresenter.freeze(rowVh, mFreezeRows);
+
+            if (mExternalAdapterListener != null) {
+                mExternalAdapterListener.onAttachedToWindow(vh);
+            }
+        }
+
+        @Override
+        public void onDetachedFromWindow(ItemBridgeAdapter.ViewHolder vh) {
+            if (mSelectedViewHolder == vh) {
+                setRowViewSelected(mSelectedViewHolder, false, true);
+                mSelectedViewHolder = null;
+            }
+            if (mExternalAdapterListener != null) {
+                mExternalAdapterListener.onDetachedFromWindow(vh);
+            }
+        }
+
+        @Override
+        public void onBind(ItemBridgeAdapter.ViewHolder vh) {
+            if (mExternalAdapterListener != null) {
+                mExternalAdapterListener.onBind(vh);
+            }
+        }
+
+        @Override
+        public void onUnbind(ItemBridgeAdapter.ViewHolder vh) {
+            setRowViewSelected(vh, false, true);
+            if (mExternalAdapterListener != null) {
+                mExternalAdapterListener.onUnbind(vh);
+            }
+        }
+    };
+
+    void setupSharedViewPool(ItemBridgeAdapter.ViewHolder bridgeVh) {
+        RowPresenter rowPresenter = (RowPresenter) bridgeVh.getPresenter();
+        RowPresenter.ViewHolder rowVh = rowPresenter.getRowViewHolder(bridgeVh.getViewHolder());
+
+        if (rowVh instanceof ListRowPresenter.ViewHolder) {
+            HorizontalGridView view = ((ListRowPresenter.ViewHolder) rowVh).getGridView();
+            // Recycled view pool is shared between all list rows
+            if (mRecycledViewPool == null) {
+                mRecycledViewPool = view.getRecycledViewPool();
+            } else {
+                view.setRecycledViewPool(mRecycledViewPool);
+            }
+
+            ItemBridgeAdapter bridgeAdapter =
+                    ((ListRowPresenter.ViewHolder) rowVh).getBridgeAdapter();
+            if (mPresenterMapper == null) {
+                mPresenterMapper = bridgeAdapter.getPresenterMapper();
+            } else {
+                bridgeAdapter.setPresenterMapper(mPresenterMapper);
+            }
+        }
+    }
+
+    @Override
+    void updateAdapter() {
+        super.updateAdapter();
+        mSelectedViewHolder = null;
+        mViewsCreated = false;
+
+        ItemBridgeAdapter adapter = getBridgeAdapter();
+        if (adapter != null) {
+            adapter.setAdapterListener(mBridgeAdapterListener);
+        }
+    }
+
+    @Override
+    public boolean onTransitionPrepare() {
+        boolean prepared = super.onTransitionPrepare();
+        if (prepared) {
+            freezeRows(true);
+        }
+        return prepared;
+    }
+
+    @Override
+    public void onTransitionEnd() {
+        super.onTransitionEnd();
+        freezeRows(false);
+    }
+
+    private void freezeRows(boolean freeze) {
+        mFreezeRows = freeze;
+        VerticalGridView verticalView = getVerticalGridView();
+        if (verticalView != null) {
+            final int count = verticalView.getChildCount();
+            for (int i = 0; i < count; i++) {
+                ItemBridgeAdapter.ViewHolder ibvh = (ItemBridgeAdapter.ViewHolder)
+                        verticalView.getChildViewHolder(verticalView.getChildAt(i));
+                RowPresenter rowPresenter = (RowPresenter) ibvh.getPresenter();
+                RowPresenter.ViewHolder vh = rowPresenter.getRowViewHolder(ibvh.getViewHolder());
+                rowPresenter.freeze(vh, freeze);
+            }
+        }
+    }
+
+    /**
+     * For rows that willing to participate entrance transition,  this function
+     * hide views if afterTransition is true,  show views if afterTransition is false.
+     */
+    public void setEntranceTransitionState(boolean afterTransition) {
+        mAfterEntranceTransition = afterTransition;
+        VerticalGridView verticalView = getVerticalGridView();
+        if (verticalView != null) {
+            final int count = verticalView.getChildCount();
+            for (int i = 0; i < count; i++) {
+                ItemBridgeAdapter.ViewHolder ibvh = (ItemBridgeAdapter.ViewHolder)
+                        verticalView.getChildViewHolder(verticalView.getChildAt(i));
+                RowPresenter rowPresenter = (RowPresenter) ibvh.getPresenter();
+                RowPresenter.ViewHolder vh = rowPresenter.getRowViewHolder(ibvh.getViewHolder());
+                rowPresenter.setEntranceTransitionState(vh, mAfterEntranceTransition);
+            }
+        }
+    }
+
+    /**
+     * Selects a Row and perform an optional task on the Row. For example
+     * <code>setSelectedPosition(10, true, new ListRowPresenterSelectItemViewHolderTask(5))</code>
+     * Scroll to 11th row and selects 6th item on that row.  The method will be ignored if
+     * RowsFragment has not been created (i.e. before {@link #onCreateView(LayoutInflater,
+     * ViewGroup, Bundle)}).
+     *
+     * @param rowPosition Which row to select.
+     * @param smooth True to scroll to the row, false for no animation.
+     * @param rowHolderTask Task to perform on the Row.
+     */
+    public void setSelectedPosition(int rowPosition, boolean smooth,
+            final Presenter.ViewHolderTask rowHolderTask) {
+        VerticalGridView verticalView = getVerticalGridView();
+        if (verticalView == null) {
+            return;
+        }
+        ViewHolderTask task = null;
+        if (rowHolderTask != null) {
+            // This task will execute once the scroll completes. Once the scrolling finishes,
+            // we will get a success callback to update selected row position. Since the
+            // update to selected row position happens in a post, we want to ensure that this
+            // gets called after that.
+            task = new ViewHolderTask() {
+                @Override
+                public void run(final RecyclerView.ViewHolder rvh) {
+                    rvh.itemView.post(new Runnable() {
+                        @Override
+                        public void run() {
+                            rowHolderTask.run(
+                                    getRowViewHolder((ItemBridgeAdapter.ViewHolder) rvh));
+                        }
+                    });
+                }
+            };
+        }
+
+        if (smooth) {
+            verticalView.setSelectedPositionSmooth(rowPosition, task);
+        } else {
+            verticalView.setSelectedPosition(rowPosition, task);
+        }
+    }
+
+    static RowPresenter.ViewHolder getRowViewHolder(ItemBridgeAdapter.ViewHolder ibvh) {
+        if (ibvh == null) {
+            return null;
+        }
+        RowPresenter rowPresenter = (RowPresenter) ibvh.getPresenter();
+        return rowPresenter.getRowViewHolder(ibvh.getViewHolder());
+    }
+
+    public boolean isScrolling() {
+        if (getVerticalGridView() == null) {
+            return false;
+        }
+        return getVerticalGridView().getScrollState() != HorizontalGridView.SCROLL_STATE_IDLE;
+    }
+
+    @Override
+    public void setAlignment(int windowAlignOffsetFromTop) {
+        if (windowAlignOffsetFromTop == ALIGN_TOP_NOT_SET) {
+            return;
+        }
+        mAlignedTop = windowAlignOffsetFromTop;
+        final VerticalGridView gridView = getVerticalGridView();
+
+        if (gridView != null) {
+            gridView.setItemAlignmentOffset(0);
+            gridView.setItemAlignmentOffsetPercent(
+                    VerticalGridView.ITEM_ALIGN_OFFSET_PERCENT_DISABLED);
+            gridView.setItemAlignmentOffsetWithPadding(true);
+            gridView.setWindowAlignmentOffset(mAlignedTop);
+            // align to a fixed position from top
+            gridView.setWindowAlignmentOffsetPercent(
+                    VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
+            gridView.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE);
+        }
+    }
+
+    /**
+     * Find row ViewHolder by position in adapter.
+     * @param position Position of row.
+     * @return ViewHolder of Row.
+     */
+    public RowPresenter.ViewHolder findRowViewHolderByPosition(int position) {
+        if (mVerticalGridView == null) {
+            return null;
+        }
+        return getRowViewHolder((ItemBridgeAdapter.ViewHolder) mVerticalGridView
+                .findViewHolderForAdapterPosition(position));
+    }
+
+    public static class MainFragmentAdapter extends BrowseFragment.MainFragmentAdapter<RowsFragment> {
+
+        public MainFragmentAdapter(RowsFragment fragment) {
+            super(fragment);
+            setScalingEnabled(true);
+        }
+
+        @Override
+        public boolean isScrolling() {
+            return getFragment().isScrolling();
+        }
+
+        @Override
+        public void setExpand(boolean expand) {
+            getFragment().setExpand(expand);
+        }
+
+        @Override
+        public void setEntranceTransitionState(boolean state) {
+            getFragment().setEntranceTransitionState(state);
+        }
+
+        @Override
+        public void setAlignment(int windowAlignOffsetFromTop) {
+            getFragment().setAlignment(windowAlignOffsetFromTop);
+        }
+
+        @Override
+        public boolean onTransitionPrepare() {
+            return getFragment().onTransitionPrepare();
+        }
+
+        @Override
+        public void onTransitionStart() {
+            getFragment().onTransitionStart();
+        }
+
+        @Override
+        public void onTransitionEnd() {
+            getFragment().onTransitionEnd();
+        }
+
+    }
+
+    /**
+     * The adapter that RowsFragment implements
+     * BrowseFragment.MainFragmentRowsAdapter.
+     * @see #getMainFragmentRowsAdapter().
+     * @deprecated use {@link RowsSupportFragment}
+     */
+    @Deprecated
+    public static class MainFragmentRowsAdapter
+            extends BrowseFragment.MainFragmentRowsAdapter<RowsFragment> {
+
+        public MainFragmentRowsAdapter(RowsFragment fragment) {
+            super(fragment);
+        }
+
+        @Override
+        public void setAdapter(ObjectAdapter adapter) {
+            getFragment().setAdapter(adapter);
+        }
+
+        /**
+         * Sets an item clicked listener on the fragment.
+         */
+        @Override
+        public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
+            getFragment().setOnItemViewClickedListener(listener);
+        }
+
+        @Override
+        public void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
+            getFragment().setOnItemViewSelectedListener(listener);
+        }
+
+        @Override
+        public void setSelectedPosition(int rowPosition,
+                                        boolean smooth,
+                                        final Presenter.ViewHolderTask rowHolderTask) {
+            getFragment().setSelectedPosition(rowPosition, smooth, rowHolderTask);
+        }
+
+        @Override
+        public void setSelectedPosition(int rowPosition, boolean smooth) {
+            getFragment().setSelectedPosition(rowPosition, smooth);
+        }
+
+        @Override
+        public int getSelectedPosition() {
+            return getFragment().getSelectedPosition();
+        }
+
+        @Override
+        public RowPresenter.ViewHolder findRowViewHolderByPosition(int position) {
+            return getFragment().findRowViewHolderByPosition(position);
+        }
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/RowsSupportFragment.java b/leanback/src/android/support/v17/leanback/app/RowsSupportFragment.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/app/RowsSupportFragment.java
rename to leanback/src/android/support/v17/leanback/app/RowsSupportFragment.java
diff --git a/leanback/src/android/support/v17/leanback/app/SearchFragment.java b/leanback/src/android/support/v17/leanback/app/SearchFragment.java
new file mode 100644
index 0000000..00f2cca
--- /dev/null
+++ b/leanback/src/android/support/v17/leanback/app/SearchFragment.java
@@ -0,0 +1,774 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from SearchSupportFragment.java.  DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
+import android.Manifest;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Handler;
+import android.speech.RecognizerIntent;
+import android.speech.SpeechRecognizer;
+import android.support.v17.leanback.R;
+import android.support.v17.leanback.widget.ObjectAdapter;
+import android.support.v17.leanback.widget.ObjectAdapter.DataObserver;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.OnItemViewSelectedListener;
+import android.support.v17.leanback.widget.Presenter.ViewHolder;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.support.v17.leanback.widget.SearchBar;
+import android.support.v17.leanback.widget.SearchOrbView;
+import android.support.v17.leanback.widget.SpeechRecognitionCallback;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.app.Fragment;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.inputmethod.CompletionInfo;
+import android.widget.FrameLayout;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A fragment to handle searches. An application will supply an implementation
+ * of the {@link SearchResultProvider} interface to handle the search and return
+ * an {@link ObjectAdapter} containing the results. The results are rendered
+ * into a {@link RowsFragment}, in the same way that they are in a {@link
+ * BrowseFragment}.
+ *
+ * <p>A SpeechRecognizer object will be created for which your application will need to declare
+ * android.permission.RECORD_AUDIO in AndroidManifest file. If app's target version is >= 23 and
+ * the device version is >= 23, a permission dialog will show first time using speech recognition.
+ * 0 will be used as requestCode in requestPermissions() call.
+ * {@link #setSpeechRecognitionCallback(SpeechRecognitionCallback)} is deprecated.
+ * </p>
+ * <p>
+ * Speech recognition is automatically started when fragment is created, but
+ * not when fragment is restored from an instance state.  Activity may manually
+ * call {@link #startRecognition()}, typically in onNewIntent().
+ * </p>
+ * @deprecated use {@link SearchSupportFragment}
+ */
+@Deprecated
+public class SearchFragment extends Fragment {
+    static final String TAG = SearchFragment.class.getSimpleName();
+    static final boolean DEBUG = false;
+
+    private static final String EXTRA_LEANBACK_BADGE_PRESENT = "LEANBACK_BADGE_PRESENT";
+    private static final String ARG_PREFIX = SearchFragment.class.getCanonicalName();
+    private static final String ARG_QUERY =  ARG_PREFIX + ".query";
+    private static final String ARG_TITLE = ARG_PREFIX  + ".title";
+
+    static final long SPEECH_RECOGNITION_DELAY_MS = 300;
+
+    static final int RESULTS_CHANGED = 0x1;
+    static final int QUERY_COMPLETE = 0x2;
+
+    static final int AUDIO_PERMISSION_REQUEST_CODE = 0;
+
+    /**
+     * Search API to be provided by the application.
+     */
+    public static interface SearchResultProvider {
+        /**
+         * <p>Method invoked some time prior to the first call to onQueryTextChange to retrieve
+         * an ObjectAdapter that will contain the results to future updates of the search query.</p>
+         *
+         * <p>As results are retrieved, the application should use the data set notification methods
+         * on the ObjectAdapter to instruct the SearchFragment to update the results.</p>
+         *
+         * @return ObjectAdapter The result object adapter.
+         */
+        public ObjectAdapter getResultsAdapter();
+
+        /**
+         * <p>Method invoked when the search query is updated.</p>
+         *
+         * <p>This is called as soon as the query changes; it is up to the application to add a
+         * delay before actually executing the queries if needed.
+         *
+         * <p>This method might not always be called before onQueryTextSubmit gets called, in
+         * particular for voice input.
+         *
+         * @param newQuery The current search query.
+         * @return whether the results changed as a result of the new query.
+         */
+        public boolean onQueryTextChange(String newQuery);
+
+        /**
+         * Method invoked when the search query is submitted, either by dismissing the keyboard,
+         * pressing search or next on the keyboard or when voice has detected the end of the query.
+         *
+         * @param query The query entered.
+         * @return whether the results changed as a result of the query.
+         */
+        public boolean onQueryTextSubmit(String query);
+    }
+
+    final DataObserver mAdapterObserver = new DataObserver() {
+        @Override
+        public void onChanged() {
+            // onChanged() may be called multiple times e.g. the provider add
+            // rows to ArrayObjectAdapter one by one.
+            mHandler.removeCallbacks(mResultsChangedCallback);
+            mHandler.post(mResultsChangedCallback);
+        }
+    };
+
+    final Handler mHandler = new Handler();
+
+    final Runnable mResultsChangedCallback = new Runnable() {
+        @Override
+        public void run() {
+            if (DEBUG) Log.v(TAG, "results changed, new size " + mResultAdapter.size());
+            if (mRowsFragment != null
+                    && mRowsFragment.getAdapter() != mResultAdapter) {
+                if (!(mRowsFragment.getAdapter() == null && mResultAdapter.size() == 0)) {
+                    mRowsFragment.setAdapter(mResultAdapter);
+                    mRowsFragment.setSelectedPosition(0);
+                }
+            }
+            updateSearchBarVisibility();
+            mStatus |= RESULTS_CHANGED;
+            if ((mStatus & QUERY_COMPLETE) != 0) {
+                updateFocus();
+            }
+            updateSearchBarNextFocusId();
+        }
+    };
+
+    /**
+     * Runs when a new provider is set AND when the fragment view is created.
+     */
+    private final Runnable mSetSearchResultProvider = new Runnable() {
+        @Override
+        public void run() {
+            if (mRowsFragment == null) {
+                // We'll retry once we have a rows fragment
+                return;
+            }
+            // Retrieve the result adapter
+            ObjectAdapter adapter = mProvider.getResultsAdapter();
+            if (DEBUG) Log.v(TAG, "Got results adapter " + adapter);
+            if (adapter != mResultAdapter) {
+                boolean firstTime = mResultAdapter == null;
+                releaseAdapter();
+                mResultAdapter = adapter;
+                if (mResultAdapter != null) {
+                    mResultAdapter.registerObserver(mAdapterObserver);
+                }
+                if (DEBUG) {
+                    Log.v(TAG, "mResultAdapter " + mResultAdapter + " size "
+                            + (mResultAdapter == null ? 0 : mResultAdapter.size()));
+                }
+                // delay the first time to avoid setting a empty result adapter
+                // until we got first onChange() from the provider
+                if (!(firstTime && (mResultAdapter == null || mResultAdapter.size() == 0))) {
+                    mRowsFragment.setAdapter(mResultAdapter);
+                }
+                executePendingQuery();
+            }
+            updateSearchBarNextFocusId();
+
+            if (DEBUG) {
+                Log.v(TAG, "mAutoStartRecognition " + mAutoStartRecognition
+                        + " mResultAdapter " + mResultAdapter
+                        + " adapter " + mRowsFragment.getAdapter());
+            }
+            if (mAutoStartRecognition) {
+                mHandler.removeCallbacks(mStartRecognitionRunnable);
+                mHandler.postDelayed(mStartRecognitionRunnable, SPEECH_RECOGNITION_DELAY_MS);
+            } else {
+                updateFocus();
+            }
+        }
+    };
+
+    final Runnable mStartRecognitionRunnable = new Runnable() {
+        @Override
+        public void run() {
+            mAutoStartRecognition = false;
+            mSearchBar.startRecognition();
+        }
+    };
+
+    RowsFragment mRowsFragment;
+    SearchBar mSearchBar;
+    SearchResultProvider mProvider;
+    String mPendingQuery = null;
+
+    OnItemViewSelectedListener mOnItemViewSelectedListener;
+    private OnItemViewClickedListener mOnItemViewClickedListener;
+    ObjectAdapter mResultAdapter;
+    private SpeechRecognitionCallback mSpeechRecognitionCallback;
+
+    private String mTitle;
+    private Drawable mBadgeDrawable;
+    private ExternalQuery mExternalQuery;
+
+    private SpeechRecognizer mSpeechRecognizer;
+
+    int mStatus;
+    boolean mAutoStartRecognition = true;
+
+    private boolean mIsPaused;
+    private boolean mPendingStartRecognitionWhenPaused;
+    private SearchBar.SearchBarPermissionListener mPermissionListener =
+            new SearchBar.SearchBarPermissionListener() {
+        @Override
+        public void requestAudioPermission() {
+            PermissionHelper.requestPermissions(SearchFragment.this,
+                    new String[]{Manifest.permission.RECORD_AUDIO}, AUDIO_PERMISSION_REQUEST_CODE);
+        }
+    };
+
+    @Override
+    public void onRequestPermissionsResult(int requestCode, String[] permissions,
+                                           int[] grantResults) {
+        if (requestCode == AUDIO_PERMISSION_REQUEST_CODE && permissions.length > 0) {
+            if (permissions[0].equals(Manifest.permission.RECORD_AUDIO)
+                    && grantResults[0] == PERMISSION_GRANTED) {
+                startRecognition();
+            }
+        }
+    }
+
+    /**
+     * @param args Bundle to use for the arguments, if null a new Bundle will be created.
+     */
+    public static Bundle createArgs(Bundle args, String query) {
+        return createArgs(args, query, null);
+    }
+
+    public static Bundle createArgs(Bundle args, String query, String title)  {
+        if (args == null) {
+            args = new Bundle();
+        }
+        args.putString(ARG_QUERY, query);
+        args.putString(ARG_TITLE, title);
+        return args;
+    }
+
+    /**
+     * Creates a search fragment with a given search query.
+     *
+     * <p>You should only use this if you need to start the search fragment with a
+     * pre-filled query.
+     *
+     * @param query The search query to begin with.
+     * @return A new SearchFragment.
+     */
+    public static SearchFragment newInstance(String query) {
+        SearchFragment fragment = new SearchFragment();
+        Bundle args = createArgs(null, query);
+        fragment.setArguments(args);
+        return fragment;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        if (mAutoStartRecognition) {
+            mAutoStartRecognition = savedInstanceState == null;
+        }
+        super.onCreate(savedInstanceState);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        View root = inflater.inflate(R.layout.lb_search_fragment, container, false);
+
+        FrameLayout searchFrame = (FrameLayout) root.findViewById(R.id.lb_search_frame);
+        mSearchBar = (SearchBar) searchFrame.findViewById(R.id.lb_search_bar);
+        mSearchBar.setSearchBarListener(new SearchBar.SearchBarListener() {
+            @Override
+            public void onSearchQueryChange(String query) {
+                if (DEBUG) Log.v(TAG, String.format("onSearchQueryChange %s %s", query,
+                        null == mProvider ? "(null)" : mProvider));
+                if (null != mProvider) {
+                    retrieveResults(query);
+                } else {
+                    mPendingQuery = query;
+                }
+            }
+
+            @Override
+            public void onSearchQuerySubmit(String query) {
+                if (DEBUG) Log.v(TAG, String.format("onSearchQuerySubmit %s", query));
+                submitQuery(query);
+            }
+
+            @Override
+            public void onKeyboardDismiss(String query) {
+                if (DEBUG) Log.v(TAG, String.format("onKeyboardDismiss %s", query));
+                queryComplete();
+            }
+        });
+        mSearchBar.setSpeechRecognitionCallback(mSpeechRecognitionCallback);
+        mSearchBar.setPermissionListener(mPermissionListener);
+        applyExternalQuery();
+
+        readArguments(getArguments());
+        if (null != mBadgeDrawable) {
+            setBadgeDrawable(mBadgeDrawable);
+        }
+        if (null != mTitle) {
+            setTitle(mTitle);
+        }
+
+        // Inject the RowsFragment in the results container
+        if (getChildFragmentManager().findFragmentById(R.id.lb_results_frame) == null) {
+            mRowsFragment = new RowsFragment();
+            getChildFragmentManager().beginTransaction()
+                    .replace(R.id.lb_results_frame, mRowsFragment).commit();
+        } else {
+            mRowsFragment = (RowsFragment) getChildFragmentManager()
+                    .findFragmentById(R.id.lb_results_frame);
+        }
+        mRowsFragment.setOnItemViewSelectedListener(new OnItemViewSelectedListener() {
+            @Override
+            public void onItemSelected(ViewHolder itemViewHolder, Object item,
+                                       RowPresenter.ViewHolder rowViewHolder, Row row) {
+                if (DEBUG) {
+                    int position = mRowsFragment.getSelectedPosition();
+                    Log.v(TAG, String.format("onItemSelected %d", position));
+                }
+                updateSearchBarVisibility();
+                if (null != mOnItemViewSelectedListener) {
+                    mOnItemViewSelectedListener.onItemSelected(itemViewHolder, item,
+                            rowViewHolder, row);
+                }
+            }
+        });
+        mRowsFragment.setOnItemViewClickedListener(mOnItemViewClickedListener);
+        mRowsFragment.setExpand(true);
+        if (null != mProvider) {
+            onSetSearchResultProvider();
+        }
+        return root;
+    }
+
+    private void resultsAvailable() {
+        if ((mStatus & QUERY_COMPLETE) != 0) {
+            focusOnResults();
+        }
+        updateSearchBarNextFocusId();
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+
+        VerticalGridView list = mRowsFragment.getVerticalGridView();
+        int mContainerListAlignTop =
+                getResources().getDimensionPixelSize(R.dimen.lb_search_browse_rows_align_top);
+        list.setItemAlignmentOffset(0);
+        list.setItemAlignmentOffsetPercent(VerticalGridView.ITEM_ALIGN_OFFSET_PERCENT_DISABLED);
+        list.setWindowAlignmentOffset(mContainerListAlignTop);
+        list.setWindowAlignmentOffsetPercent(VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
+        list.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE);
+        // VerticalGridView should not be focusable (see b/26894680 for details).
+        list.setFocusable(false);
+        list.setFocusableInTouchMode(false);
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        mIsPaused = false;
+        if (mSpeechRecognitionCallback == null && null == mSpeechRecognizer) {
+            mSpeechRecognizer = SpeechRecognizer.createSpeechRecognizer(
+                    FragmentUtil.getContext(SearchFragment.this));
+            mSearchBar.setSpeechRecognizer(mSpeechRecognizer);
+        }
+        if (mPendingStartRecognitionWhenPaused) {
+            mPendingStartRecognitionWhenPaused = false;
+            mSearchBar.startRecognition();
+        } else {
+            // Ensure search bar state consistency when using external recognizer
+            mSearchBar.stopRecognition();
+        }
+    }
+
+    @Override
+    public void onPause() {
+        releaseRecognizer();
+        mIsPaused = true;
+        super.onPause();
+    }
+
+    @Override
+    public void onDestroy() {
+        releaseAdapter();
+        super.onDestroy();
+    }
+
+    /**
+     * Returns RowsFragment that shows result rows. RowsFragment is initialized after
+     * SearchFragment.onCreateView().
+     *
+     * @return RowsFragment that shows result rows.
+     */
+    public RowsFragment getRowsFragment() {
+        return mRowsFragment;
+    }
+
+    private void releaseRecognizer() {
+        if (null != mSpeechRecognizer) {
+            mSearchBar.setSpeechRecognizer(null);
+            mSpeechRecognizer.destroy();
+            mSpeechRecognizer = null;
+        }
+    }
+
+    /**
+     * Starts speech recognition.  Typical use case is that
+     * activity receives onNewIntent() call when user clicks a MIC button.
+     * Note that SearchFragment automatically starts speech recognition
+     * at first time created, there is no need to call startRecognition()
+     * when fragment is created.
+     */
+    public void startRecognition() {
+        if (mIsPaused) {
+            mPendingStartRecognitionWhenPaused = true;
+        } else {
+            mSearchBar.startRecognition();
+        }
+    }
+
+    /**
+     * Sets the search provider that is responsible for returning results for the
+     * search query.
+     */
+    public void setSearchResultProvider(SearchResultProvider searchResultProvider) {
+        if (mProvider != searchResultProvider) {
+            mProvider = searchResultProvider;
+            onSetSearchResultProvider();
+        }
+    }
+
+    /**
+     * Sets an item selection listener for the results.
+     *
+     * @param listener The item selection listener to be invoked when an item in
+     *        the search results is selected.
+     */
+    public void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
+        mOnItemViewSelectedListener = listener;
+    }
+
+    /**
+     * Sets an item clicked listener for the results.
+     *
+     * @param listener The item clicked listener to be invoked when an item in
+     *        the search results is clicked.
+     */
+    public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
+        if (listener != mOnItemViewClickedListener) {
+            mOnItemViewClickedListener = listener;
+            if (mRowsFragment != null) {
+                mRowsFragment.setOnItemViewClickedListener(mOnItemViewClickedListener);
+            }
+        }
+    }
+
+    /**
+     * Sets the title string to be be shown in an empty search bar. The title
+     * may be placed in a call-to-action, such as "Search <i>title</i>" or
+     * "Speak to search <i>title</i>".
+     */
+    public void setTitle(String title) {
+        mTitle = title;
+        if (null != mSearchBar) {
+            mSearchBar.setTitle(title);
+        }
+    }
+
+    /**
+     * Returns the title set in the search bar.
+     */
+    public String getTitle() {
+        if (null != mSearchBar) {
+            return mSearchBar.getTitle();
+        }
+        return null;
+    }
+
+    /**
+     * Sets the badge drawable that will be shown inside the search bar next to
+     * the title.
+     */
+    public void setBadgeDrawable(Drawable drawable) {
+        mBadgeDrawable = drawable;
+        if (null != mSearchBar) {
+            mSearchBar.setBadgeDrawable(drawable);
+        }
+    }
+
+    /**
+     * Returns the badge drawable in the search bar.
+     */
+    public Drawable getBadgeDrawable() {
+        if (null != mSearchBar) {
+            return mSearchBar.getBadgeDrawable();
+        }
+        return null;
+    }
+
+    /**
+     * Sets background color of not-listening state search orb.
+     *
+     * @param colors SearchOrbView.Colors.
+     */
+    public void setSearchAffordanceColors(SearchOrbView.Colors colors) {
+        if (mSearchBar != null) {
+            mSearchBar.setSearchAffordanceColors(colors);
+        }
+    }
+
+    /**
+     * Sets background color of listening state search orb.
+     *
+     * @param colors SearchOrbView.Colors.
+     */
+    public void setSearchAffordanceColorsInListening(SearchOrbView.Colors colors) {
+        if (mSearchBar != null) {
+            mSearchBar.setSearchAffordanceColorsInListening(colors);
+        }
+    }
+
+    /**
+     * Displays the completions shown by the IME. An application may provide
+     * a list of query completions that the system will show in the IME.
+     *
+     * @param completions A list of completions to show in the IME. Setting to
+     *        null or empty will clear the list.
+     */
+    public void displayCompletions(List<String> completions) {
+        mSearchBar.displayCompletions(completions);
+    }
+
+    /**
+     * Displays the completions shown by the IME. An application may provide
+     * a list of query completions that the system will show in the IME.
+     *
+     * @param completions A list of completions to show in the IME. Setting to
+     *        null or empty will clear the list.
+     */
+    public void displayCompletions(CompletionInfo[] completions) {
+        mSearchBar.displayCompletions(completions);
+    }
+
+    /**
+     * Sets this callback to have the fragment pass speech recognition requests
+     * to the activity rather than using a SpeechRecognizer object.
+     * @deprecated Launching voice recognition activity is no longer supported. App should declare
+     *             android.permission.RECORD_AUDIO in AndroidManifest file.
+     */
+    @Deprecated
+    public void setSpeechRecognitionCallback(SpeechRecognitionCallback callback) {
+        mSpeechRecognitionCallback = callback;
+        if (mSearchBar != null) {
+            mSearchBar.setSpeechRecognitionCallback(mSpeechRecognitionCallback);
+        }
+        if (callback != null) {
+            releaseRecognizer();
+        }
+    }
+
+    /**
+     * Sets the text of the search query and optionally submits the query. Either
+     * {@link SearchResultProvider#onQueryTextChange onQueryTextChange} or
+     * {@link SearchResultProvider#onQueryTextSubmit onQueryTextSubmit} will be
+     * called on the provider if it is set.
+     *
+     * @param query The search query to set.
+     * @param submit Whether to submit the query.
+     */
+    public void setSearchQuery(String query, boolean submit) {
+        if (DEBUG) Log.v(TAG, "setSearchQuery " + query + " submit " + submit);
+        if (query == null) {
+            return;
+        }
+        mExternalQuery = new ExternalQuery(query, submit);
+        applyExternalQuery();
+        if (mAutoStartRecognition) {
+            mAutoStartRecognition = false;
+            mHandler.removeCallbacks(mStartRecognitionRunnable);
+        }
+    }
+
+    /**
+     * Sets the text of the search query based on the {@link RecognizerIntent#EXTRA_RESULTS} in
+     * the given intent, and optionally submit the query.  If more than one result is present
+     * in the results list, the first will be used.
+     *
+     * @param intent Intent received from a speech recognition service.
+     * @param submit Whether to submit the query.
+     */
+    public void setSearchQuery(Intent intent, boolean submit) {
+        ArrayList<String> matches = intent.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
+        if (matches != null && matches.size() > 0) {
+            setSearchQuery(matches.get(0), submit);
+        }
+    }
+
+    /**
+     * Returns an intent that can be used to request speech recognition.
+     * Built from the base {@link RecognizerIntent#ACTION_RECOGNIZE_SPEECH} plus
+     * extras:
+     *
+     * <ul>
+     * <li>{@link RecognizerIntent#EXTRA_LANGUAGE_MODEL} set to
+     * {@link RecognizerIntent#LANGUAGE_MODEL_FREE_FORM}</li>
+     * <li>{@link RecognizerIntent#EXTRA_PARTIAL_RESULTS} set to true</li>
+     * <li>{@link RecognizerIntent#EXTRA_PROMPT} set to the search bar hint text</li>
+     * </ul>
+     *
+     * For handling the intent returned from the service, see
+     * {@link #setSearchQuery(Intent, boolean)}.
+     */
+    public Intent getRecognizerIntent() {
+        Intent recognizerIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
+        recognizerIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
+                RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
+        recognizerIntent.putExtra(RecognizerIntent.EXTRA_PARTIAL_RESULTS, true);
+        if (mSearchBar != null && mSearchBar.getHint() != null) {
+            recognizerIntent.putExtra(RecognizerIntent.EXTRA_PROMPT, mSearchBar.getHint());
+        }
+        recognizerIntent.putExtra(EXTRA_LEANBACK_BADGE_PRESENT, mBadgeDrawable != null);
+        return recognizerIntent;
+    }
+
+    void retrieveResults(String searchQuery) {
+        if (DEBUG) Log.v(TAG, "retrieveResults " + searchQuery);
+        if (mProvider.onQueryTextChange(searchQuery)) {
+            mStatus &= ~QUERY_COMPLETE;
+        }
+    }
+
+    void submitQuery(String query) {
+        queryComplete();
+        if (null != mProvider) {
+            mProvider.onQueryTextSubmit(query);
+        }
+    }
+
+    void queryComplete() {
+        if (DEBUG) Log.v(TAG, "queryComplete");
+        mStatus |= QUERY_COMPLETE;
+        focusOnResults();
+    }
+
+    void updateSearchBarVisibility() {
+        int position = mRowsFragment != null ? mRowsFragment.getSelectedPosition() : -1;
+        mSearchBar.setVisibility(position <=0 || mResultAdapter == null
+                || mResultAdapter.size() == 0 ? View.VISIBLE : View.GONE);
+    }
+
+    void updateSearchBarNextFocusId() {
+        if (mSearchBar == null || mResultAdapter == null) {
+            return;
+        }
+        final int viewId = (mResultAdapter.size() == 0 || mRowsFragment == null
+                || mRowsFragment.getVerticalGridView() == null)
+                        ? 0 : mRowsFragment.getVerticalGridView().getId();
+        mSearchBar.setNextFocusDownId(viewId);
+    }
+
+    void updateFocus() {
+        if (mResultAdapter != null && mResultAdapter.size() > 0
+                && mRowsFragment != null && mRowsFragment.getAdapter() == mResultAdapter) {
+            focusOnResults();
+        } else {
+            mSearchBar.requestFocus();
+        }
+    }
+
+    private void focusOnResults() {
+        if (mRowsFragment == null || mRowsFragment.getVerticalGridView() == null
+                || mResultAdapter.size() == 0) {
+            return;
+        }
+        if (mRowsFragment.getVerticalGridView().requestFocus()) {
+            mStatus &= ~RESULTS_CHANGED;
+        }
+    }
+
+    private void onSetSearchResultProvider() {
+        mHandler.removeCallbacks(mSetSearchResultProvider);
+        mHandler.post(mSetSearchResultProvider);
+    }
+
+    void releaseAdapter() {
+        if (mResultAdapter != null) {
+            mResultAdapter.unregisterObserver(mAdapterObserver);
+            mResultAdapter = null;
+        }
+    }
+
+    void executePendingQuery() {
+        if (null != mPendingQuery && null != mResultAdapter) {
+            String query = mPendingQuery;
+            mPendingQuery = null;
+            retrieveResults(query);
+        }
+    }
+
+    private void applyExternalQuery() {
+        if (mExternalQuery == null || mSearchBar == null) {
+            return;
+        }
+        mSearchBar.setSearchQuery(mExternalQuery.mQuery);
+        if (mExternalQuery.mSubmit) {
+            submitQuery(mExternalQuery.mQuery);
+        }
+        mExternalQuery = null;
+    }
+
+    private void readArguments(Bundle args) {
+        if (null == args) {
+            return;
+        }
+        if (args.containsKey(ARG_QUERY)) {
+            setSearchQuery(args.getString(ARG_QUERY));
+        }
+
+        if (args.containsKey(ARG_TITLE)) {
+            setTitle(args.getString(ARG_TITLE));
+        }
+    }
+
+    private void setSearchQuery(String query) {
+        mSearchBar.setSearchQuery(query);
+    }
+
+    static class ExternalQuery {
+        String mQuery;
+        boolean mSubmit;
+
+        ExternalQuery(String query, boolean submit) {
+            mQuery = query;
+            mSubmit = submit;
+        }
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/SearchSupportFragment.java b/leanback/src/android/support/v17/leanback/app/SearchSupportFragment.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/app/SearchSupportFragment.java
rename to leanback/src/android/support/v17/leanback/app/SearchSupportFragment.java
diff --git a/leanback/src/android/support/v17/leanback/app/VerticalGridFragment.java b/leanback/src/android/support/v17/leanback/app/VerticalGridFragment.java
new file mode 100644
index 0000000..bff3dba
--- /dev/null
+++ b/leanback/src/android/support/v17/leanback/app/VerticalGridFragment.java
@@ -0,0 +1,260 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from VerticalGridSupportFragment.java.  DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.os.Bundle;
+import android.support.v17.leanback.R;
+import android.support.v17.leanback.transition.TransitionHelper;
+import android.support.v17.leanback.util.StateMachine.State;
+import android.support.v17.leanback.widget.BrowseFrameLayout;
+import android.support.v17.leanback.widget.ObjectAdapter;
+import android.support.v17.leanback.widget.OnChildLaidOutListener;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.OnItemViewSelectedListener;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.support.v17.leanback.widget.VerticalGridPresenter;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * A fragment for creating leanback vertical grids.
+ *
+ * <p>Renders a vertical grid of objects given a {@link VerticalGridPresenter} and
+ * an {@link ObjectAdapter}.
+ * @deprecated use {@link VerticalGridSupportFragment}
+ */
+@Deprecated
+public class VerticalGridFragment extends BaseFragment {
+    static final String TAG = "VerticalGF";
+    static boolean DEBUG = false;
+
+    private ObjectAdapter mAdapter;
+    private VerticalGridPresenter mGridPresenter;
+    VerticalGridPresenter.ViewHolder mGridViewHolder;
+    OnItemViewSelectedListener mOnItemViewSelectedListener;
+    private OnItemViewClickedListener mOnItemViewClickedListener;
+    private Object mSceneAfterEntranceTransition;
+    private int mSelectedPosition = -1;
+
+    /**
+     * State to setEntranceTransitionState(false)
+     */
+    final State STATE_SET_ENTRANCE_START_STATE = new State("SET_ENTRANCE_START_STATE") {
+        @Override
+        public void run() {
+            setEntranceTransitionState(false);
+        }
+    };
+
+    @Override
+    void createStateMachineStates() {
+        super.createStateMachineStates();
+        mStateMachine.addState(STATE_SET_ENTRANCE_START_STATE);
+    }
+
+    @Override
+    void createStateMachineTransitions() {
+        super.createStateMachineTransitions();
+        mStateMachine.addTransition(STATE_ENTRANCE_ON_PREPARED,
+                STATE_SET_ENTRANCE_START_STATE, EVT_ON_CREATEVIEW);
+    }
+
+    /**
+     * Sets the grid presenter.
+     */
+    public void setGridPresenter(VerticalGridPresenter gridPresenter) {
+        if (gridPresenter == null) {
+            throw new IllegalArgumentException("Grid presenter may not be null");
+        }
+        mGridPresenter = gridPresenter;
+        mGridPresenter.setOnItemViewSelectedListener(mViewSelectedListener);
+        if (mOnItemViewClickedListener != null) {
+            mGridPresenter.setOnItemViewClickedListener(mOnItemViewClickedListener);
+        }
+    }
+
+    /**
+     * Returns the grid presenter.
+     */
+    public VerticalGridPresenter getGridPresenter() {
+        return mGridPresenter;
+    }
+
+    /**
+     * Sets the object adapter for the fragment.
+     */
+    public void setAdapter(ObjectAdapter adapter) {
+        mAdapter = adapter;
+        updateAdapter();
+    }
+
+    /**
+     * Returns the object adapter.
+     */
+    public ObjectAdapter getAdapter() {
+        return mAdapter;
+    }
+
+    final private OnItemViewSelectedListener mViewSelectedListener =
+            new OnItemViewSelectedListener() {
+        @Override
+        public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
+                RowPresenter.ViewHolder rowViewHolder, Row row) {
+            int position = mGridViewHolder.getGridView().getSelectedPosition();
+            if (DEBUG) Log.v(TAG, "grid selected position " + position);
+            gridOnItemSelected(position);
+            if (mOnItemViewSelectedListener != null) {
+                mOnItemViewSelectedListener.onItemSelected(itemViewHolder, item,
+                        rowViewHolder, row);
+            }
+        }
+    };
+
+    final private OnChildLaidOutListener mChildLaidOutListener =
+            new OnChildLaidOutListener() {
+        @Override
+        public void onChildLaidOut(ViewGroup parent, View view, int position, long id) {
+            if (position == 0) {
+                showOrHideTitle();
+            }
+        }
+    };
+
+    /**
+     * Sets an item selection listener.
+     */
+    public void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
+        mOnItemViewSelectedListener = listener;
+    }
+
+    void gridOnItemSelected(int position) {
+        if (position != mSelectedPosition) {
+            mSelectedPosition = position;
+            showOrHideTitle();
+        }
+    }
+
+    void showOrHideTitle() {
+        if (mGridViewHolder.getGridView().findViewHolderForAdapterPosition(mSelectedPosition)
+                == null) {
+            return;
+        }
+        if (!mGridViewHolder.getGridView().hasPreviousViewInSameRow(mSelectedPosition)) {
+            showTitle(true);
+        } else {
+            showTitle(false);
+        }
+    }
+
+    /**
+     * Sets an item clicked listener.
+     */
+    public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
+        mOnItemViewClickedListener = listener;
+        if (mGridPresenter != null) {
+            mGridPresenter.setOnItemViewClickedListener(mOnItemViewClickedListener);
+        }
+    }
+
+    /**
+     * Returns the item clicked listener.
+     */
+    public OnItemViewClickedListener getOnItemViewClickedListener() {
+        return mOnItemViewClickedListener;
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        ViewGroup root = (ViewGroup) inflater.inflate(R.layout.lb_vertical_grid_fragment,
+                container, false);
+        ViewGroup gridFrame = (ViewGroup) root.findViewById(R.id.grid_frame);
+        installTitleView(inflater, gridFrame, savedInstanceState);
+        getProgressBarManager().setRootView(root);
+
+        ViewGroup gridDock = (ViewGroup) root.findViewById(R.id.browse_grid_dock);
+        mGridViewHolder = mGridPresenter.onCreateViewHolder(gridDock);
+        gridDock.addView(mGridViewHolder.view);
+        mGridViewHolder.getGridView().setOnChildLaidOutListener(mChildLaidOutListener);
+
+        mSceneAfterEntranceTransition = TransitionHelper.createScene(gridDock, new Runnable() {
+            @Override
+            public void run() {
+                setEntranceTransitionState(true);
+            }
+        });
+
+        updateAdapter();
+        return root;
+    }
+
+    private void setupFocusSearchListener() {
+        BrowseFrameLayout browseFrameLayout = (BrowseFrameLayout) getView().findViewById(
+                R.id.grid_frame);
+        browseFrameLayout.setOnFocusSearchListener(getTitleHelper().getOnFocusSearchListener());
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        setupFocusSearchListener();
+    }
+
+    @Override
+    public void onDestroyView() {
+        super.onDestroyView();
+        mGridViewHolder = null;
+    }
+
+    /**
+     * Sets the selected item position.
+     */
+    public void setSelectedPosition(int position) {
+        mSelectedPosition = position;
+        if(mGridViewHolder != null && mGridViewHolder.getGridView().getAdapter() != null) {
+            mGridViewHolder.getGridView().setSelectedPositionSmooth(position);
+        }
+    }
+
+    private void updateAdapter() {
+        if (mGridViewHolder != null) {
+            mGridPresenter.onBindViewHolder(mGridViewHolder, mAdapter);
+            if (mSelectedPosition != -1) {
+                mGridViewHolder.getGridView().setSelectedPosition(mSelectedPosition);
+            }
+        }
+    }
+
+    @Override
+    protected Object createEntranceTransition() {
+        return TransitionHelper.loadTransition(FragmentUtil.getContext(VerticalGridFragment.this),
+                R.transition.lb_vertical_grid_entrance_transition);
+    }
+
+    @Override
+    protected void runEntranceTransition(Object entranceTransition) {
+        TransitionHelper.runTransition(mSceneAfterEntranceTransition, entranceTransition);
+    }
+
+    void setEntranceTransitionState(boolean afterTransition) {
+        mGridPresenter.setEntranceTransitionState(mGridViewHolder, afterTransition);
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/VerticalGridSupportFragment.java b/leanback/src/android/support/v17/leanback/app/VerticalGridSupportFragment.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/app/VerticalGridSupportFragment.java
rename to leanback/src/android/support/v17/leanback/app/VerticalGridSupportFragment.java
diff --git a/leanback/src/android/support/v17/leanback/app/VideoFragment.java b/leanback/src/android/support/v17/leanback/app/VideoFragment.java
new file mode 100644
index 0000000..e4d75f3
--- /dev/null
+++ b/leanback/src/android/support/v17/leanback/app/VideoFragment.java
@@ -0,0 +1,122 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from VideoSupportFragment.java.  DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.os.Bundle;
+import android.support.v17.leanback.R;
+import android.view.LayoutInflater;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * Subclass of {@link PlaybackFragment} that is responsible for providing a {@link SurfaceView}
+ * and rendering video.
+ * @deprecated use {@link VideoSupportFragment}
+ */
+@Deprecated
+public class VideoFragment extends PlaybackFragment {
+    static final int SURFACE_NOT_CREATED = 0;
+    static final int SURFACE_CREATED = 1;
+
+    SurfaceView mVideoSurface;
+    SurfaceHolder.Callback mMediaPlaybackCallback;
+
+    int mState = SURFACE_NOT_CREATED;
+
+    @Override
+    public View onCreateView(
+            LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+        ViewGroup root = (ViewGroup) super.onCreateView(inflater, container, savedInstanceState);
+        mVideoSurface = (SurfaceView) LayoutInflater.from(FragmentUtil.getContext(VideoFragment.this)).inflate(
+                R.layout.lb_video_surface, root, false);
+        root.addView(mVideoSurface, 0);
+        mVideoSurface.getHolder().addCallback(new SurfaceHolder.Callback() {
+
+            @Override
+            public void surfaceCreated(SurfaceHolder holder) {
+                if (mMediaPlaybackCallback != null) {
+                    mMediaPlaybackCallback.surfaceCreated(holder);
+                }
+                mState = SURFACE_CREATED;
+            }
+
+            @Override
+            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+                if (mMediaPlaybackCallback != null) {
+                    mMediaPlaybackCallback.surfaceChanged(holder, format, width, height);
+                }
+            }
+
+            @Override
+            public void surfaceDestroyed(SurfaceHolder holder) {
+                if (mMediaPlaybackCallback != null) {
+                    mMediaPlaybackCallback.surfaceDestroyed(holder);
+                }
+                mState = SURFACE_NOT_CREATED;
+            }
+        });
+        setBackgroundType(PlaybackFragment.BG_LIGHT);
+        return root;
+    }
+
+    /**
+     * Adds {@link SurfaceHolder.Callback} to {@link android.view.SurfaceView}.
+     */
+    public void setSurfaceHolderCallback(SurfaceHolder.Callback callback) {
+        mMediaPlaybackCallback = callback;
+
+        if (callback != null) {
+            if (mState == SURFACE_CREATED) {
+                mMediaPlaybackCallback.surfaceCreated(mVideoSurface.getHolder());
+            }
+        }
+    }
+
+    @Override
+    protected void onVideoSizeChanged(int width, int height) {
+        int screenWidth = getView().getWidth();
+        int screenHeight = getView().getHeight();
+
+        ViewGroup.LayoutParams p = mVideoSurface.getLayoutParams();
+        if (screenWidth * height > width * screenHeight) {
+            // fit in screen height
+            p.height = screenHeight;
+            p.width = screenHeight * width / height;
+        } else {
+            // fit in screen width
+            p.width = screenWidth;
+            p.height = screenWidth * height / width;
+        }
+        mVideoSurface.setLayoutParams(p);
+    }
+
+    /**
+     * Returns the surface view.
+     */
+    public SurfaceView getSurfaceView() {
+        return mVideoSurface;
+    }
+
+    @Override
+    public void onDestroyView() {
+        mVideoSurface = null;
+        mState = SURFACE_NOT_CREATED;
+        super.onDestroyView();
+    }
+}
diff --git a/leanback/src/android/support/v17/leanback/app/VideoFragmentGlueHost.java b/leanback/src/android/support/v17/leanback/app/VideoFragmentGlueHost.java
new file mode 100644
index 0000000..546e581
--- /dev/null
+++ b/leanback/src/android/support/v17/leanback/app/VideoFragmentGlueHost.java
@@ -0,0 +1,49 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from VideoSupportFragmentGlueHost.java.  DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.support.v17.leanback.media.PlaybackGlue;
+import android.support.v17.leanback.media.PlaybackGlueHost;
+import android.support.v17.leanback.media.SurfaceHolderGlueHost;
+import android.view.SurfaceHolder;
+
+/**
+ * {@link PlaybackGlueHost} implementation
+ * the interaction between {@link PlaybackGlue} and {@link VideoFragment}.
+ * @deprecated use {@link VideoSupportFragmentGlueHost}
+ */
+@Deprecated
+public class VideoFragmentGlueHost extends PlaybackFragmentGlueHost
+        implements SurfaceHolderGlueHost {
+    private final VideoFragment mFragment;
+
+    public VideoFragmentGlueHost(VideoFragment fragment) {
+        super(fragment);
+        this.mFragment = fragment;
+    }
+
+    /**
+     * Sets the {@link android.view.SurfaceHolder.Callback} on the host.
+     * {@link PlaybackGlueHost} is assumed to either host the {@link SurfaceHolder} or
+     * have a reference to the component hosting it for rendering the video.
+     */
+    @Override
+    public void setSurfaceHolderCallback(SurfaceHolder.Callback callback) {
+        mFragment.setSurfaceHolderCallback(callback);
+    }
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/VideoSupportFragment.java b/leanback/src/android/support/v17/leanback/app/VideoSupportFragment.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/app/VideoSupportFragment.java
rename to leanback/src/android/support/v17/leanback/app/VideoSupportFragment.java
diff --git a/v17/leanback/src/android/support/v17/leanback/app/VideoSupportFragmentGlueHost.java b/leanback/src/android/support/v17/leanback/app/VideoSupportFragmentGlueHost.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/app/VideoSupportFragmentGlueHost.java
rename to leanback/src/android/support/v17/leanback/app/VideoSupportFragmentGlueHost.java
diff --git a/v17/leanback/src/android/support/v17/leanback/app/package-info.java b/leanback/src/android/support/v17/leanback/app/package-info.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/app/package-info.java
rename to leanback/src/android/support/v17/leanback/app/package-info.java
diff --git a/v17/leanback/src/android/support/v17/leanback/database/CursorMapper.java b/leanback/src/android/support/v17/leanback/database/CursorMapper.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/database/CursorMapper.java
rename to leanback/src/android/support/v17/leanback/database/CursorMapper.java
diff --git a/v17/leanback/src/android/support/v17/leanback/graphics/BoundsRule.java b/leanback/src/android/support/v17/leanback/graphics/BoundsRule.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/graphics/BoundsRule.java
rename to leanback/src/android/support/v17/leanback/graphics/BoundsRule.java
diff --git a/v17/leanback/src/android/support/v17/leanback/graphics/ColorFilterCache.java b/leanback/src/android/support/v17/leanback/graphics/ColorFilterCache.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/graphics/ColorFilterCache.java
rename to leanback/src/android/support/v17/leanback/graphics/ColorFilterCache.java
diff --git a/v17/leanback/src/android/support/v17/leanback/graphics/ColorFilterDimmer.java b/leanback/src/android/support/v17/leanback/graphics/ColorFilterDimmer.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/graphics/ColorFilterDimmer.java
rename to leanback/src/android/support/v17/leanback/graphics/ColorFilterDimmer.java
diff --git a/v17/leanback/src/android/support/v17/leanback/graphics/ColorOverlayDimmer.java b/leanback/src/android/support/v17/leanback/graphics/ColorOverlayDimmer.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/graphics/ColorOverlayDimmer.java
rename to leanback/src/android/support/v17/leanback/graphics/ColorOverlayDimmer.java
diff --git a/v17/leanback/src/android/support/v17/leanback/graphics/CompositeDrawable.java b/leanback/src/android/support/v17/leanback/graphics/CompositeDrawable.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/graphics/CompositeDrawable.java
rename to leanback/src/android/support/v17/leanback/graphics/CompositeDrawable.java
diff --git a/v17/leanback/src/android/support/v17/leanback/graphics/FitWidthBitmapDrawable.java b/leanback/src/android/support/v17/leanback/graphics/FitWidthBitmapDrawable.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/graphics/FitWidthBitmapDrawable.java
rename to leanback/src/android/support/v17/leanback/graphics/FitWidthBitmapDrawable.java
diff --git a/v17/leanback/src/android/support/v17/leanback/media/MediaControllerAdapter.java b/leanback/src/android/support/v17/leanback/media/MediaControllerAdapter.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/media/MediaControllerAdapter.java
rename to leanback/src/android/support/v17/leanback/media/MediaControllerAdapter.java
diff --git a/v17/leanback/src/android/support/v17/leanback/media/MediaControllerGlue.java b/leanback/src/android/support/v17/leanback/media/MediaControllerGlue.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/media/MediaControllerGlue.java
rename to leanback/src/android/support/v17/leanback/media/MediaControllerGlue.java
diff --git a/v17/leanback/src/android/support/v17/leanback/media/MediaPlayerAdapter.java b/leanback/src/android/support/v17/leanback/media/MediaPlayerAdapter.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/media/MediaPlayerAdapter.java
rename to leanback/src/android/support/v17/leanback/media/MediaPlayerAdapter.java
diff --git a/v17/leanback/src/android/support/v17/leanback/media/MediaPlayerGlue.java b/leanback/src/android/support/v17/leanback/media/MediaPlayerGlue.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/media/MediaPlayerGlue.java
rename to leanback/src/android/support/v17/leanback/media/MediaPlayerGlue.java
diff --git a/v17/leanback/src/android/support/v17/leanback/media/PlaybackBannerControlGlue.java b/leanback/src/android/support/v17/leanback/media/PlaybackBannerControlGlue.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/media/PlaybackBannerControlGlue.java
rename to leanback/src/android/support/v17/leanback/media/PlaybackBannerControlGlue.java
diff --git a/v17/leanback/src/android/support/v17/leanback/media/PlaybackBaseControlGlue.java b/leanback/src/android/support/v17/leanback/media/PlaybackBaseControlGlue.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/media/PlaybackBaseControlGlue.java
rename to leanback/src/android/support/v17/leanback/media/PlaybackBaseControlGlue.java
diff --git a/v17/leanback/src/android/support/v17/leanback/media/PlaybackControlGlue.java b/leanback/src/android/support/v17/leanback/media/PlaybackControlGlue.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/media/PlaybackControlGlue.java
rename to leanback/src/android/support/v17/leanback/media/PlaybackControlGlue.java
diff --git a/v17/leanback/src/android/support/v17/leanback/media/PlaybackGlue.java b/leanback/src/android/support/v17/leanback/media/PlaybackGlue.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/media/PlaybackGlue.java
rename to leanback/src/android/support/v17/leanback/media/PlaybackGlue.java
diff --git a/v17/leanback/src/android/support/v17/leanback/media/PlaybackGlueHost.java b/leanback/src/android/support/v17/leanback/media/PlaybackGlueHost.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/media/PlaybackGlueHost.java
rename to leanback/src/android/support/v17/leanback/media/PlaybackGlueHost.java
diff --git a/v17/leanback/src/android/support/v17/leanback/media/PlaybackTransportControlGlue.java b/leanback/src/android/support/v17/leanback/media/PlaybackTransportControlGlue.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/media/PlaybackTransportControlGlue.java
rename to leanback/src/android/support/v17/leanback/media/PlaybackTransportControlGlue.java
diff --git a/v17/leanback/src/android/support/v17/leanback/media/PlayerAdapter.java b/leanback/src/android/support/v17/leanback/media/PlayerAdapter.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/media/PlayerAdapter.java
rename to leanback/src/android/support/v17/leanback/media/PlayerAdapter.java
diff --git a/v17/leanback/src/android/support/v17/leanback/media/SurfaceHolderGlueHost.java b/leanback/src/android/support/v17/leanback/media/SurfaceHolderGlueHost.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/media/SurfaceHolderGlueHost.java
rename to leanback/src/android/support/v17/leanback/media/SurfaceHolderGlueHost.java
diff --git a/v17/leanback/src/android/support/v17/leanback/package-info.java b/leanback/src/android/support/v17/leanback/package-info.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/package-info.java
rename to leanback/src/android/support/v17/leanback/package-info.java
diff --git a/v17/leanback/src/android/support/v17/leanback/system/Settings.java b/leanback/src/android/support/v17/leanback/system/Settings.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/system/Settings.java
rename to leanback/src/android/support/v17/leanback/system/Settings.java
diff --git a/v17/leanback/src/android/support/v17/leanback/transition/LeanbackTransitionHelper.java b/leanback/src/android/support/v17/leanback/transition/LeanbackTransitionHelper.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/transition/LeanbackTransitionHelper.java
rename to leanback/src/android/support/v17/leanback/transition/LeanbackTransitionHelper.java
diff --git a/v17/leanback/src/android/support/v17/leanback/transition/ParallaxTransition.java b/leanback/src/android/support/v17/leanback/transition/ParallaxTransition.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/transition/ParallaxTransition.java
rename to leanback/src/android/support/v17/leanback/transition/ParallaxTransition.java
diff --git a/v17/leanback/src/android/support/v17/leanback/transition/TransitionHelper.java b/leanback/src/android/support/v17/leanback/transition/TransitionHelper.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/transition/TransitionHelper.java
rename to leanback/src/android/support/v17/leanback/transition/TransitionHelper.java
diff --git a/v17/leanback/src/android/support/v17/leanback/util/MathUtil.java b/leanback/src/android/support/v17/leanback/util/MathUtil.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/util/MathUtil.java
rename to leanback/src/android/support/v17/leanback/util/MathUtil.java
diff --git a/v17/leanback/src/android/support/v17/leanback/util/StateMachine.java b/leanback/src/android/support/v17/leanback/util/StateMachine.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/util/StateMachine.java
rename to leanback/src/android/support/v17/leanback/util/StateMachine.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/AbstractDetailsDescriptionPresenter.java b/leanback/src/android/support/v17/leanback/widget/AbstractDetailsDescriptionPresenter.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/AbstractDetailsDescriptionPresenter.java
rename to leanback/src/android/support/v17/leanback/widget/AbstractDetailsDescriptionPresenter.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/AbstractMediaItemPresenter.java b/leanback/src/android/support/v17/leanback/widget/AbstractMediaItemPresenter.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/AbstractMediaItemPresenter.java
rename to leanback/src/android/support/v17/leanback/widget/AbstractMediaItemPresenter.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/AbstractMediaListHeaderPresenter.java b/leanback/src/android/support/v17/leanback/widget/AbstractMediaListHeaderPresenter.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/AbstractMediaListHeaderPresenter.java
rename to leanback/src/android/support/v17/leanback/widget/AbstractMediaListHeaderPresenter.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/Action.java b/leanback/src/android/support/v17/leanback/widget/Action.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/Action.java
rename to leanback/src/android/support/v17/leanback/widget/Action.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ActionPresenterSelector.java b/leanback/src/android/support/v17/leanback/widget/ActionPresenterSelector.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/ActionPresenterSelector.java
rename to leanback/src/android/support/v17/leanback/widget/ActionPresenterSelector.java
diff --git a/leanback/src/android/support/v17/leanback/widget/ArrayObjectAdapter.java b/leanback/src/android/support/v17/leanback/widget/ArrayObjectAdapter.java
new file mode 100644
index 0000000..2dcf51f
--- /dev/null
+++ b/leanback/src/android/support/v17/leanback/widget/ArrayObjectAdapter.java
@@ -0,0 +1,324 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.support.annotation.Nullable;
+import android.support.v7.util.DiffUtil;
+import android.support.v7.util.ListUpdateCallback;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * An {@link ObjectAdapter} implemented with an {@link ArrayList}.
+ */
+public class ArrayObjectAdapter extends ObjectAdapter {
+
+    private static final Boolean DEBUG = false;
+    private static final String TAG = "ArrayObjectAdapter";
+
+    private final List mItems = new ArrayList<Object>();
+
+    // To compute the payload correctly, we should use a temporary list to hold all the old items.
+    private final List mOldItems = new ArrayList<Object>();
+
+    // Un modifiable version of mItems;
+    private List mUnmodifiableItems;
+
+    /**
+     * Constructs an adapter with the given {@link PresenterSelector}.
+     */
+    public ArrayObjectAdapter(PresenterSelector presenterSelector) {
+        super(presenterSelector);
+    }
+
+    /**
+     * Constructs an adapter that uses the given {@link Presenter} for all items.
+     */
+    public ArrayObjectAdapter(Presenter presenter) {
+        super(presenter);
+    }
+
+    /**
+     * Constructs an adapter.
+     */
+    public ArrayObjectAdapter() {
+        super();
+    }
+
+    @Override
+    public int size() {
+        return mItems.size();
+    }
+
+    @Override
+    public Object get(int index) {
+        return mItems.get(index);
+    }
+
+    /**
+     * Returns the index for the first occurrence of item in the adapter, or -1 if
+     * not found.
+     *
+     * @param item The item to find in the list.
+     * @return Index of the first occurrence of the item in the adapter, or -1
+     * if not found.
+     */
+    public int indexOf(Object item) {
+        return mItems.indexOf(item);
+    }
+
+    /**
+     * Notify that the content of a range of items changed. Note that this is
+     * not same as items being added or removed.
+     *
+     * @param positionStart The position of first item that has changed.
+     * @param itemCount     The count of how many items have changed.
+     */
+    public void notifyArrayItemRangeChanged(int positionStart, int itemCount) {
+        notifyItemRangeChanged(positionStart, itemCount);
+    }
+
+    /**
+     * Adds an item to the end of the adapter.
+     *
+     * @param item The item to add to the end of the adapter.
+     */
+    public void add(Object item) {
+        add(mItems.size(), item);
+    }
+
+    /**
+     * Inserts an item into this adapter at the specified index.
+     * If the index is > {@link #size} an exception will be thrown.
+     *
+     * @param index The index at which the item should be inserted.
+     * @param item  The item to insert into the adapter.
+     */
+    public void add(int index, Object item) {
+        mItems.add(index, item);
+        notifyItemRangeInserted(index, 1);
+    }
+
+    /**
+     * Adds the objects in the given collection to the adapter, starting at the
+     * given index.  If the index is >= {@link #size} an exception will be thrown.
+     *
+     * @param index The index at which the items should be inserted.
+     * @param items A {@link Collection} of items to insert.
+     */
+    public void addAll(int index, Collection items) {
+        int itemsCount = items.size();
+        if (itemsCount == 0) {
+            return;
+        }
+        mItems.addAll(index, items);
+        notifyItemRangeInserted(index, itemsCount);
+    }
+
+    /**
+     * Removes the first occurrence of the given item from the adapter.
+     *
+     * @param item The item to remove from the adapter.
+     * @return True if the item was found and thus removed from the adapter.
+     */
+    public boolean remove(Object item) {
+        int index = mItems.indexOf(item);
+        if (index >= 0) {
+            mItems.remove(index);
+            notifyItemRangeRemoved(index, 1);
+        }
+        return index >= 0;
+    }
+
+    /**
+     * Moved the item at fromPosition to toPosition.
+     *
+     * @param fromPosition Previous position of the item.
+     * @param toPosition   New position of the item.
+     */
+    public void move(int fromPosition, int toPosition) {
+        if (fromPosition == toPosition) {
+            // no-op
+            return;
+        }
+        Object item = mItems.remove(fromPosition);
+        mItems.add(toPosition, item);
+        notifyItemMoved(fromPosition, toPosition);
+    }
+
+    /**
+     * Replaces item at position with a new item and calls notifyItemRangeChanged()
+     * at the given position.  Note that this method does not compare new item to
+     * existing item.
+     *
+     * @param position The index of item to replace.
+     * @param item     The new item to be placed at given position.
+     */
+    public void replace(int position, Object item) {
+        mItems.set(position, item);
+        notifyItemRangeChanged(position, 1);
+    }
+
+    /**
+     * Removes a range of items from the adapter. The range is specified by giving
+     * the starting position and the number of elements to remove.
+     *
+     * @param position The index of the first item to remove.
+     * @param count    The number of items to remove.
+     * @return The number of items removed.
+     */
+    public int removeItems(int position, int count) {
+        int itemsToRemove = Math.min(count, mItems.size() - position);
+        if (itemsToRemove <= 0) {
+            return 0;
+        }
+
+        for (int i = 0; i < itemsToRemove; i++) {
+            mItems.remove(position);
+        }
+        notifyItemRangeRemoved(position, itemsToRemove);
+        return itemsToRemove;
+    }
+
+    /**
+     * Removes all items from this adapter, leaving it empty.
+     */
+    public void clear() {
+        int itemCount = mItems.size();
+        if (itemCount == 0) {
+            return;
+        }
+        mItems.clear();
+        notifyItemRangeRemoved(0, itemCount);
+    }
+
+    /**
+     * Gets a read-only view of the list of object of this ArrayObjectAdapter.
+     */
+    public <E> List<E> unmodifiableList() {
+
+        // The mUnmodifiableItems will only be created once as long as the content of mItems has not
+        // been changed.
+        if (mUnmodifiableItems == null) {
+            mUnmodifiableItems = Collections.unmodifiableList(mItems);
+        }
+        return mUnmodifiableItems;
+    }
+
+    @Override
+    public boolean isImmediateNotifySupported() {
+        return true;
+    }
+
+    ListUpdateCallback mListUpdateCallback;
+
+    /**
+     * Set a new item list to adapter. The DiffUtil will compute the difference and dispatch it to
+     * specified position.
+     *
+     * @param itemList List of new Items
+     * @param callback Optional DiffCallback Object to compute the difference between the old data
+     *                 set and new data set. When null, {@link #notifyChanged()} will be fired.
+     */
+    public void setItems(final List itemList, final DiffCallback callback) {
+        if (callback == null) {
+            // shortcut when DiffCallback is not provided
+            mItems.clear();
+            mItems.addAll(itemList);
+            notifyChanged();
+            return;
+        }
+        mOldItems.clear();
+        mOldItems.addAll(mItems);
+
+        DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffUtil.Callback() {
+            @Override
+            public int getOldListSize() {
+                return mOldItems.size();
+            }
+
+            @Override
+            public int getNewListSize() {
+                return itemList.size();
+            }
+
+            @Override
+            public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
+                return callback.areItemsTheSame(mOldItems.get(oldItemPosition),
+                        itemList.get(newItemPosition));
+            }
+
+            @Override
+            public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
+                return callback.areContentsTheSame(mOldItems.get(oldItemPosition),
+                        itemList.get(newItemPosition));
+            }
+
+            @Nullable
+            @Override
+            public Object getChangePayload(int oldItemPosition, int newItemPosition) {
+                return callback.getChangePayload(mOldItems.get(oldItemPosition),
+                        itemList.get(newItemPosition));
+            }
+        });
+
+        // update items.
+        mItems.clear();
+        mItems.addAll(itemList);
+
+        // dispatch diff result
+        if (mListUpdateCallback == null) {
+            mListUpdateCallback = new ListUpdateCallback() {
+
+                @Override
+                public void onInserted(int position, int count) {
+                    if (DEBUG) {
+                        Log.d(TAG, "onInserted");
+                    }
+                    notifyItemRangeInserted(position, count);
+                }
+
+                @Override
+                public void onRemoved(int position, int count) {
+                    if (DEBUG) {
+                        Log.d(TAG, "onRemoved");
+                    }
+                    notifyItemRangeRemoved(position, count);
+                }
+
+                @Override
+                public void onMoved(int fromPosition, int toPosition) {
+                    if (DEBUG) {
+                        Log.d(TAG, "onMoved");
+                    }
+                    notifyItemMoved(fromPosition, toPosition);
+                }
+
+                @Override
+                public void onChanged(int position, int count, Object payload) {
+                    if (DEBUG) {
+                        Log.d(TAG, "onChanged");
+                    }
+                    notifyItemRangeChanged(position, count, payload);
+                }
+            };
+        }
+        diffResult.dispatchUpdatesTo(mListUpdateCallback);
+        mOldItems.clear();
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/BackgroundHelper.java b/leanback/src/android/support/v17/leanback/widget/BackgroundHelper.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/BackgroundHelper.java
rename to leanback/src/android/support/v17/leanback/widget/BackgroundHelper.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/BaseCardView.java b/leanback/src/android/support/v17/leanback/widget/BaseCardView.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/BaseCardView.java
rename to leanback/src/android/support/v17/leanback/widget/BaseCardView.java
diff --git a/leanback/src/android/support/v17/leanback/widget/BaseGridView.java b/leanback/src/android/support/v17/leanback/widget/BaseGridView.java
new file mode 100644
index 0000000..2ebec47
--- /dev/null
+++ b/leanback/src/android/support/v17/leanback/widget/BaseGridView.java
@@ -0,0 +1,1202 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Rect;
+import android.support.annotation.RestrictTo;
+import android.support.v17.leanback.R;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.SimpleItemAnimator;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+
+/**
+ * An abstract base class for vertically and horizontally scrolling lists. The items come
+ * from the {@link RecyclerView.Adapter} associated with this view.
+ * Do not directly use this class, use {@link VerticalGridView} and {@link HorizontalGridView}.
+ * The class is not intended to be subclassed other than {@link VerticalGridView} and
+ * {@link HorizontalGridView}.
+ */
+public abstract class BaseGridView extends RecyclerView {
+
+    /**
+     * Always keep focused item at a aligned position.  Developer can use
+     * WINDOW_ALIGN_XXX and ITEM_ALIGN_XXX to define how focused item is aligned.
+     * In this mode, the last focused position will be remembered and restored when focus
+     * is back to the view.
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    public final static int FOCUS_SCROLL_ALIGNED = 0;
+
+    /**
+     * Scroll to make the focused item inside client area.
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    public final static int FOCUS_SCROLL_ITEM = 1;
+
+    /**
+     * Scroll a page of items when focusing to item outside the client area.
+     * The page size matches the client area size of RecyclerView.
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    public final static int FOCUS_SCROLL_PAGE = 2;
+
+    /**
+     * The first item is aligned with the low edge of the viewport. When
+     * navigating away from the first item, the focus item is aligned to a key line location.
+     * <p>
+     * For HorizontalGridView, low edge refers to getPaddingLeft() when RTL is false or
+     * getWidth() - getPaddingRight() when RTL is true.
+     * For VerticalGridView, low edge refers to getPaddingTop().
+     * <p>
+     * The key line location is calculated by "windowAlignOffset" and
+     * "windowAlignOffsetPercent"; if neither of these two is defined, the
+     * default value is 1/2 of the size.
+     * <p>
+     * Note if there are very few items between low edge and key line, use
+     * {@link #setWindowAlignmentPreferKeyLineOverLowEdge(boolean)} to control whether you prefer
+     * to align the items to key line or low edge. Default is preferring low edge.
+     */
+    public final static int WINDOW_ALIGN_LOW_EDGE = 1;
+
+    /**
+     * The last item is aligned with the high edge of the viewport when
+     * navigating to the end of list. When navigating away from the end, the
+     * focus item is aligned to a key line location.
+     * <p>
+     * For HorizontalGridView, high edge refers to getWidth() - getPaddingRight() when RTL is false
+     * or getPaddingLeft() when RTL is true.
+     * For VerticalGridView, high edge refers to getHeight() - getPaddingBottom().
+     * <p>
+     * The key line location is calculated by "windowAlignOffset" and
+     * "windowAlignOffsetPercent"; if neither of these two is defined, the
+     * default value is 1/2 of the size.
+     * <p>
+     * Note if there are very few items between high edge and key line, use
+     * {@link #setWindowAlignmentPreferKeyLineOverHighEdge(boolean)} to control whether you prefer
+     * to align the items to key line or high edge. Default is preferring key line.
+     */
+    public final static int WINDOW_ALIGN_HIGH_EDGE = 1 << 1;
+
+    /**
+     * The first item and last item are aligned with the two edges of the
+     * viewport. When navigating in the middle of list, the focus maintains a
+     * key line location.
+     * <p>
+     * The key line location is calculated by "windowAlignOffset" and
+     * "windowAlignOffsetPercent"; if neither of these two is defined, the
+     * default value is 1/2 of the size.
+     */
+    public final static int WINDOW_ALIGN_BOTH_EDGE =
+            WINDOW_ALIGN_LOW_EDGE | WINDOW_ALIGN_HIGH_EDGE;
+
+    /**
+     * The focused item always stays in a key line location.
+     * <p>
+     * The key line location is calculated by "windowAlignOffset" and
+     * "windowAlignOffsetPercent"; if neither of these two is defined, the
+     * default value is 1/2 of the size.
+     */
+    public final static int WINDOW_ALIGN_NO_EDGE = 0;
+
+    /**
+     * Value indicates that percent is not used.
+     */
+    public final static float WINDOW_ALIGN_OFFSET_PERCENT_DISABLED = -1;
+
+    /**
+     * Value indicates that percent is not used.
+     */
+    public final static float ITEM_ALIGN_OFFSET_PERCENT_DISABLED =
+            ItemAlignmentFacet.ITEM_ALIGN_OFFSET_PERCENT_DISABLED;
+
+    /**
+     * Dont save states of any child views.
+     */
+    public static final int SAVE_NO_CHILD = 0;
+
+    /**
+     * Only save on screen child views, the states are lost when they become off screen.
+     */
+    public static final int SAVE_ON_SCREEN_CHILD = 1;
+
+    /**
+     * Save on screen views plus save off screen child views states up to
+     * {@link #getSaveChildrenLimitNumber()}.
+     */
+    public static final int SAVE_LIMITED_CHILD = 2;
+
+    /**
+     * Save on screen views plus save off screen child views without any limitation.
+     * This might cause out of memory, only use it when you are dealing with limited data.
+     */
+    public static final int SAVE_ALL_CHILD = 3;
+
+    /**
+     * Listener for intercepting touch dispatch events.
+     */
+    public interface OnTouchInterceptListener {
+        /**
+         * Returns true if the touch dispatch event should be consumed.
+         */
+        public boolean onInterceptTouchEvent(MotionEvent event);
+    }
+
+    /**
+     * Listener for intercepting generic motion dispatch events.
+     */
+    public interface OnMotionInterceptListener {
+        /**
+         * Returns true if the touch dispatch event should be consumed.
+         */
+        public boolean onInterceptMotionEvent(MotionEvent event);
+    }
+
+    /**
+     * Listener for intercepting key dispatch events.
+     */
+    public interface OnKeyInterceptListener {
+        /**
+         * Returns true if the key dispatch event should be consumed.
+         */
+        public boolean onInterceptKeyEvent(KeyEvent event);
+    }
+
+    public interface OnUnhandledKeyListener {
+        /**
+         * Returns true if the key event should be consumed.
+         */
+        public boolean onUnhandledKey(KeyEvent event);
+    }
+
+    final GridLayoutManager mLayoutManager;
+
+    /**
+     * Animate layout changes from a child resizing or adding/removing a child.
+     */
+    private boolean mAnimateChildLayout = true;
+
+    private boolean mHasOverlappingRendering = true;
+
+    private RecyclerView.ItemAnimator mSavedItemAnimator;
+
+    private OnTouchInterceptListener mOnTouchInterceptListener;
+    private OnMotionInterceptListener mOnMotionInterceptListener;
+    private OnKeyInterceptListener mOnKeyInterceptListener;
+    RecyclerView.RecyclerListener mChainedRecyclerListener;
+    private OnUnhandledKeyListener mOnUnhandledKeyListener;
+
+    /**
+     * Number of items to prefetch when first coming on screen with new data.
+     */
+    int mInitialPrefetchItemCount = 4;
+
+    BaseGridView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        mLayoutManager = new GridLayoutManager(this);
+        setLayoutManager(mLayoutManager);
+        // leanback LayoutManager already restores focus inside onLayoutChildren().
+        setPreserveFocusAfterLayout(false);
+        setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
+        setHasFixedSize(true);
+        setChildrenDrawingOrderEnabled(true);
+        setWillNotDraw(true);
+        setOverScrollMode(View.OVER_SCROLL_NEVER);
+        // Disable change animation by default on leanback.
+        // Change animation will create a new view and cause undesired
+        // focus animation between the old view and new view.
+        ((SimpleItemAnimator)getItemAnimator()).setSupportsChangeAnimations(false);
+        super.setRecyclerListener(new RecyclerView.RecyclerListener() {
+            @Override
+            public void onViewRecycled(RecyclerView.ViewHolder holder) {
+                mLayoutManager.onChildRecycled(holder);
+                if (mChainedRecyclerListener != null) {
+                    mChainedRecyclerListener.onViewRecycled(holder);
+                }
+            }
+        });
+    }
+
+    void initBaseGridViewAttributes(Context context, AttributeSet attrs) {
+        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.lbBaseGridView);
+        boolean throughFront = a.getBoolean(R.styleable.lbBaseGridView_focusOutFront, false);
+        boolean throughEnd = a.getBoolean(R.styleable.lbBaseGridView_focusOutEnd, false);
+        mLayoutManager.setFocusOutAllowed(throughFront, throughEnd);
+        boolean throughSideStart = a.getBoolean(R.styleable.lbBaseGridView_focusOutSideStart, true);
+        boolean throughSideEnd = a.getBoolean(R.styleable.lbBaseGridView_focusOutSideEnd, true);
+        mLayoutManager.setFocusOutSideAllowed(throughSideStart, throughSideEnd);
+        mLayoutManager.setVerticalSpacing(
+                a.getDimensionPixelSize(R.styleable.lbBaseGridView_android_verticalSpacing,
+                        a.getDimensionPixelSize(R.styleable.lbBaseGridView_verticalMargin, 0)));
+        mLayoutManager.setHorizontalSpacing(
+                a.getDimensionPixelSize(R.styleable.lbBaseGridView_android_horizontalSpacing,
+                        a.getDimensionPixelSize(R.styleable.lbBaseGridView_horizontalMargin, 0)));
+        if (a.hasValue(R.styleable.lbBaseGridView_android_gravity)) {
+            setGravity(a.getInt(R.styleable.lbBaseGridView_android_gravity, Gravity.NO_GRAVITY));
+        }
+        a.recycle();
+    }
+
+    /**
+     * Sets the strategy used to scroll in response to item focus changing:
+     * <ul>
+     * <li>{@link #FOCUS_SCROLL_ALIGNED} (default) </li>
+     * <li>{@link #FOCUS_SCROLL_ITEM}</li>
+     * <li>{@link #FOCUS_SCROLL_PAGE}</li>
+     * </ul>
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    public void setFocusScrollStrategy(int scrollStrategy) {
+        if (scrollStrategy != FOCUS_SCROLL_ALIGNED && scrollStrategy != FOCUS_SCROLL_ITEM
+            && scrollStrategy != FOCUS_SCROLL_PAGE) {
+            throw new IllegalArgumentException("Invalid scrollStrategy");
+        }
+        mLayoutManager.setFocusScrollStrategy(scrollStrategy);
+        requestLayout();
+    }
+
+    /**
+     * Returns the strategy used to scroll in response to item focus changing.
+     * <ul>
+     * <li>{@link #FOCUS_SCROLL_ALIGNED} (default) </li>
+     * <li>{@link #FOCUS_SCROLL_ITEM}</li>
+     * <li>{@link #FOCUS_SCROLL_PAGE}</li>
+     * </ul>
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    public int getFocusScrollStrategy() {
+        return mLayoutManager.getFocusScrollStrategy();
+    }
+
+    /**
+     * Sets the method for focused item alignment in the view.
+     *
+     * @param windowAlignment {@link #WINDOW_ALIGN_BOTH_EDGE},
+     *        {@link #WINDOW_ALIGN_LOW_EDGE}, {@link #WINDOW_ALIGN_HIGH_EDGE} or
+     *        {@link #WINDOW_ALIGN_NO_EDGE}.
+     */
+    public void setWindowAlignment(int windowAlignment) {
+        mLayoutManager.setWindowAlignment(windowAlignment);
+        requestLayout();
+    }
+
+    /**
+     * Returns the method for focused item alignment in the view.
+     *
+     * @return {@link #WINDOW_ALIGN_BOTH_EDGE}, {@link #WINDOW_ALIGN_LOW_EDGE},
+     *         {@link #WINDOW_ALIGN_HIGH_EDGE} or {@link #WINDOW_ALIGN_NO_EDGE}.
+     */
+    public int getWindowAlignment() {
+        return mLayoutManager.getWindowAlignment();
+    }
+
+    /**
+     * Sets whether prefer key line over low edge when {@link #WINDOW_ALIGN_LOW_EDGE} is used.
+     * When true, if there are very few items between low edge and key line, align items to key
+     * line instead of align items to low edge.
+     * Default value is false (aka prefer align to low edge).
+     *
+     * @param preferKeyLineOverLowEdge True to prefer key line over low edge, false otherwise.
+     */
+    public void setWindowAlignmentPreferKeyLineOverLowEdge(boolean preferKeyLineOverLowEdge) {
+        mLayoutManager.mWindowAlignment.mainAxis()
+                .setPreferKeylineOverLowEdge(preferKeyLineOverLowEdge);
+        requestLayout();
+    }
+
+
+    /**
+     * Returns whether prefer key line over high edge when {@link #WINDOW_ALIGN_HIGH_EDGE} is used.
+     * When true, if there are very few items between high edge and key line, align items to key
+     * line instead of align items to high edge.
+     * Default value is true (aka prefer align to key line).
+     *
+     * @param preferKeyLineOverHighEdge True to prefer key line over high edge, false otherwise.
+     */
+    public void setWindowAlignmentPreferKeyLineOverHighEdge(boolean preferKeyLineOverHighEdge) {
+        mLayoutManager.mWindowAlignment.mainAxis()
+                .setPreferKeylineOverHighEdge(preferKeyLineOverHighEdge);
+        requestLayout();
+    }
+
+    /**
+     * Returns whether prefer key line over low edge when {@link #WINDOW_ALIGN_LOW_EDGE} is used.
+     * When true, if there are very few items between low edge and key line, align items to key
+     * line instead of align items to low edge.
+     * Default value is false (aka prefer align to low edge).
+     *
+     * @return True to prefer key line over low edge, false otherwise.
+     */
+    public boolean isWindowAlignmentPreferKeyLineOverLowEdge() {
+        return mLayoutManager.mWindowAlignment.mainAxis().isPreferKeylineOverLowEdge();
+    }
+
+
+    /**
+     * Returns whether prefer key line over high edge when {@link #WINDOW_ALIGN_HIGH_EDGE} is used.
+     * When true, if there are very few items between high edge and key line, align items to key
+     * line instead of align items to high edge.
+     * Default value is true (aka prefer align to key line).
+     *
+     * @return True to prefer key line over high edge, false otherwise.
+     */
+    public boolean isWindowAlignmentPreferKeyLineOverHighEdge() {
+        return mLayoutManager.mWindowAlignment.mainAxis().isPreferKeylineOverHighEdge();
+    }
+
+
+    /**
+     * Sets the offset in pixels for window alignment key line.
+     *
+     * @param offset The number of pixels to offset.  If the offset is positive,
+     *        it is distance from low edge (see {@link #WINDOW_ALIGN_LOW_EDGE});
+     *        if the offset is negative, the absolute value is distance from high
+     *        edge (see {@link #WINDOW_ALIGN_HIGH_EDGE}).
+     *        Default value is 0.
+     */
+    public void setWindowAlignmentOffset(int offset) {
+        mLayoutManager.setWindowAlignmentOffset(offset);
+        requestLayout();
+    }
+
+    /**
+     * Returns the offset in pixels for window alignment key line.
+     *
+     * @return The number of pixels to offset.  If the offset is positive,
+     *        it is distance from low edge (see {@link #WINDOW_ALIGN_LOW_EDGE});
+     *        if the offset is negative, the absolute value is distance from high
+     *        edge (see {@link #WINDOW_ALIGN_HIGH_EDGE}).
+     *        Default value is 0.
+     */
+    public int getWindowAlignmentOffset() {
+        return mLayoutManager.getWindowAlignmentOffset();
+    }
+
+    /**
+     * Sets the offset percent for window alignment key line in addition to {@link
+     * #getWindowAlignmentOffset()}.
+     *
+     * @param offsetPercent Percentage to offset. E.g., 40 means 40% of the
+     *        width from low edge. Use
+     *        {@link #WINDOW_ALIGN_OFFSET_PERCENT_DISABLED} to disable.
+     *         Default value is 50.
+     */
+    public void setWindowAlignmentOffsetPercent(float offsetPercent) {
+        mLayoutManager.setWindowAlignmentOffsetPercent(offsetPercent);
+        requestLayout();
+    }
+
+    /**
+     * Returns the offset percent for window alignment key line in addition to
+     * {@link #getWindowAlignmentOffset()}.
+     *
+     * @return Percentage to offset. E.g., 40 means 40% of the width from the
+     *         low edge, or {@link #WINDOW_ALIGN_OFFSET_PERCENT_DISABLED} if
+     *         disabled. Default value is 50.
+     */
+    public float getWindowAlignmentOffsetPercent() {
+        return mLayoutManager.getWindowAlignmentOffsetPercent();
+    }
+
+    /**
+     * Sets number of pixels to the end of low edge. Supports right to left layout direction.
+     * Item alignment settings are ignored for the child if {@link ItemAlignmentFacet}
+     * is provided by {@link RecyclerView.ViewHolder} or {@link FacetProviderAdapter}.
+     *
+     * @param offset In left to right or vertical case, it's the offset added to left/top edge.
+     *               In right to left case, it's the offset subtracted from right edge.
+     */
+    public void setItemAlignmentOffset(int offset) {
+        mLayoutManager.setItemAlignmentOffset(offset);
+        requestLayout();
+    }
+
+    /**
+     * Returns number of pixels to the end of low edge. Supports right to left layout direction. In
+     * left to right or vertical case, it's the offset added to left/top edge. In right to left
+     * case, it's the offset subtracted from right edge.
+     * Item alignment settings are ignored for the child if {@link ItemAlignmentFacet}
+     * is provided by {@link RecyclerView.ViewHolder} or {@link FacetProviderAdapter}.
+     *
+     * @return The number of pixels to the end of low edge.
+     */
+    public int getItemAlignmentOffset() {
+        return mLayoutManager.getItemAlignmentOffset();
+    }
+
+    /**
+     * Sets whether applies padding to item alignment when {@link #getItemAlignmentOffsetPercent()}
+     * is 0 or 100.
+     * <p>When true:
+     * Applies start/top padding if {@link #getItemAlignmentOffsetPercent()} is 0.
+     * Applies end/bottom padding if {@link #getItemAlignmentOffsetPercent()} is 100.
+     * Does not apply padding if {@link #getItemAlignmentOffsetPercent()} is neither 0 nor 100.
+     * </p>
+     * <p>When false: does not apply padding</p>
+     */
+    public void setItemAlignmentOffsetWithPadding(boolean withPadding) {
+        mLayoutManager.setItemAlignmentOffsetWithPadding(withPadding);
+        requestLayout();
+    }
+
+    /**
+     * Returns true if applies padding to item alignment when
+     * {@link #getItemAlignmentOffsetPercent()} is 0 or 100; returns false otherwise.
+     * <p>When true:
+     * Applies start/top padding when {@link #getItemAlignmentOffsetPercent()} is 0.
+     * Applies end/bottom padding when {@link #getItemAlignmentOffsetPercent()} is 100.
+     * Does not apply padding if {@link #getItemAlignmentOffsetPercent()} is neither 0 nor 100.
+     * </p>
+     * <p>When false: does not apply padding</p>
+     */
+    public boolean isItemAlignmentOffsetWithPadding() {
+        return mLayoutManager.isItemAlignmentOffsetWithPadding();
+    }
+
+    /**
+     * Sets the offset percent for item alignment in addition to {@link
+     * #getItemAlignmentOffset()}.
+     * Item alignment settings are ignored for the child if {@link ItemAlignmentFacet}
+     * is provided by {@link RecyclerView.ViewHolder} or {@link FacetProviderAdapter}.
+     *
+     * @param offsetPercent Percentage to offset. E.g., 40 means 40% of the
+     *        width from the low edge. Use
+     *        {@link #ITEM_ALIGN_OFFSET_PERCENT_DISABLED} to disable.
+     */
+    public void setItemAlignmentOffsetPercent(float offsetPercent) {
+        mLayoutManager.setItemAlignmentOffsetPercent(offsetPercent);
+        requestLayout();
+    }
+
+    /**
+     * Returns the offset percent for item alignment in addition to {@link
+     * #getItemAlignmentOffset()}.
+     *
+     * @return Percentage to offset. E.g., 40 means 40% of the width from the
+     *         low edge, or {@link #ITEM_ALIGN_OFFSET_PERCENT_DISABLED} if
+     *         disabled. Default value is 50.
+     */
+    public float getItemAlignmentOffsetPercent() {
+        return mLayoutManager.getItemAlignmentOffsetPercent();
+    }
+
+    /**
+     * Sets the id of the view to align with. Use {@link android.view.View#NO_ID} (default)
+     * for the root {@link RecyclerView.ViewHolder#itemView}.
+     * Item alignment settings on BaseGridView are if {@link ItemAlignmentFacet}
+     * is provided by {@link RecyclerView.ViewHolder} or {@link FacetProviderAdapter}.
+     */
+    public void setItemAlignmentViewId(int viewId) {
+        mLayoutManager.setItemAlignmentViewId(viewId);
+    }
+
+    /**
+     * Returns the id of the view to align with, or {@link android.view.View#NO_ID} for the root
+     * {@link RecyclerView.ViewHolder#itemView}.
+     * @return The id of the view to align with, or {@link android.view.View#NO_ID} for the root
+     * {@link RecyclerView.ViewHolder#itemView}.
+     */
+    public int getItemAlignmentViewId() {
+        return mLayoutManager.getItemAlignmentViewId();
+    }
+
+    /**
+     * Sets the spacing in pixels between two child items.
+     * @deprecated use {@link #setItemSpacing(int)}
+     */
+    @Deprecated
+    public void setItemMargin(int margin) {
+        setItemSpacing(margin);
+    }
+
+    /**
+     * Sets the vertical and horizontal spacing in pixels between two child items.
+     * @param spacing Vertical and horizontal spacing in pixels between two child items.
+     */
+    public void setItemSpacing(int spacing) {
+        mLayoutManager.setItemSpacing(spacing);
+        requestLayout();
+    }
+
+    /**
+     * Sets the spacing in pixels between two child items vertically.
+     * @deprecated Use {@link #setVerticalSpacing(int)}
+     */
+    @Deprecated
+    public void setVerticalMargin(int margin) {
+        setVerticalSpacing(margin);
+    }
+
+    /**
+     * Returns the spacing in pixels between two child items vertically.
+     * @deprecated Use {@link #getVerticalSpacing()}
+     */
+    @Deprecated
+    public int getVerticalMargin() {
+        return mLayoutManager.getVerticalSpacing();
+    }
+
+    /**
+     * Sets the spacing in pixels between two child items horizontally.
+     * @deprecated Use {@link #setHorizontalSpacing(int)}
+     */
+    @Deprecated
+    public void setHorizontalMargin(int margin) {
+        setHorizontalSpacing(margin);
+    }
+
+    /**
+     * Returns the spacing in pixels between two child items horizontally.
+     * @deprecated Use {@link #getHorizontalSpacing()}
+     */
+    @Deprecated
+    public int getHorizontalMargin() {
+        return mLayoutManager.getHorizontalSpacing();
+    }
+
+    /**
+     * Sets the vertical spacing in pixels between two child items.
+     * @param spacing Vertical spacing between two child items.
+     */
+    public void setVerticalSpacing(int spacing) {
+        mLayoutManager.setVerticalSpacing(spacing);
+        requestLayout();
+    }
+
+    /**
+     * Returns the vertical spacing in pixels between two child items.
+     * @return The vertical spacing in pixels between two child items.
+     */
+    public int getVerticalSpacing() {
+        return mLayoutManager.getVerticalSpacing();
+    }
+
+    /**
+     * Sets the horizontal spacing in pixels between two child items.
+     * @param spacing Horizontal spacing in pixels between two child items.
+     */
+    public void setHorizontalSpacing(int spacing) {
+        mLayoutManager.setHorizontalSpacing(spacing);
+        requestLayout();
+    }
+
+    /**
+     * Returns the horizontal spacing in pixels between two child items.
+     * @return The Horizontal spacing in pixels between two child items.
+     */
+    public int getHorizontalSpacing() {
+        return mLayoutManager.getHorizontalSpacing();
+    }
+
+    /**
+     * Registers a callback to be invoked when an item in BaseGridView has
+     * been laid out.
+     *
+     * @param listener The listener to be invoked.
+     */
+    public void setOnChildLaidOutListener(OnChildLaidOutListener listener) {
+        mLayoutManager.setOnChildLaidOutListener(listener);
+    }
+
+    /**
+     * Registers a callback to be invoked when an item in BaseGridView has
+     * been selected.  Note that the listener may be invoked when there is a
+     * layout pending on the view, affording the listener an opportunity to
+     * adjust the upcoming layout based on the selection state.
+     *
+     * @param listener The listener to be invoked.
+     */
+    public void setOnChildSelectedListener(OnChildSelectedListener listener) {
+        mLayoutManager.setOnChildSelectedListener(listener);
+    }
+
+    /**
+     * Registers a callback to be invoked when an item in BaseGridView has
+     * been selected.  Note that the listener may be invoked when there is a
+     * layout pending on the view, affording the listener an opportunity to
+     * adjust the upcoming layout based on the selection state.
+     * This method will clear all existing listeners added by
+     * {@link #addOnChildViewHolderSelectedListener}.
+     *
+     * @param listener The listener to be invoked.
+     */
+    public void setOnChildViewHolderSelectedListener(OnChildViewHolderSelectedListener listener) {
+        mLayoutManager.setOnChildViewHolderSelectedListener(listener);
+    }
+
+    /**
+     * Registers a callback to be invoked when an item in BaseGridView has
+     * been selected.  Note that the listener may be invoked when there is a
+     * layout pending on the view, affording the listener an opportunity to
+     * adjust the upcoming layout based on the selection state.
+     *
+     * @param listener The listener to be invoked.
+     */
+    public void addOnChildViewHolderSelectedListener(OnChildViewHolderSelectedListener listener) {
+        mLayoutManager.addOnChildViewHolderSelectedListener(listener);
+    }
+
+    /**
+     * Remove the callback invoked when an item in BaseGridView has been selected.
+     *
+     * @param listener The listener to be removed.
+     */
+    public void removeOnChildViewHolderSelectedListener(OnChildViewHolderSelectedListener listener)
+            {
+        mLayoutManager.removeOnChildViewHolderSelectedListener(listener);
+    }
+
+    /**
+     * Changes the selected item immediately without animation.
+     */
+    public void setSelectedPosition(int position) {
+        mLayoutManager.setSelection(position, 0);
+    }
+
+    /**
+     * Changes the selected item and/or subposition immediately without animation.
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    public void setSelectedPositionWithSub(int position, int subposition) {
+        mLayoutManager.setSelectionWithSub(position, subposition, 0);
+    }
+
+    /**
+     * Changes the selected item immediately without animation, scrollExtra is
+     * applied in primary scroll direction.  The scrollExtra will be kept until
+     * another {@link #setSelectedPosition} or {@link #setSelectedPositionSmooth} call.
+     */
+    public void setSelectedPosition(int position, int scrollExtra) {
+        mLayoutManager.setSelection(position, scrollExtra);
+    }
+
+    /**
+     * Changes the selected item and/or subposition immediately without animation, scrollExtra is
+     * applied in primary scroll direction.  The scrollExtra will be kept until
+     * another {@link #setSelectedPosition} or {@link #setSelectedPositionSmooth} call.
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    public void setSelectedPositionWithSub(int position, int subposition, int scrollExtra) {
+        mLayoutManager.setSelectionWithSub(position, subposition, scrollExtra);
+    }
+
+    /**
+     * Changes the selected item and run an animation to scroll to the target
+     * position.
+     * @param position Adapter position of the item to select.
+     */
+    public void setSelectedPositionSmooth(int position) {
+        mLayoutManager.setSelectionSmooth(position);
+    }
+
+    /**
+     * Changes the selected item and/or subposition, runs an animation to scroll to the target
+     * position.
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    public void setSelectedPositionSmoothWithSub(int position, int subposition) {
+        mLayoutManager.setSelectionSmoothWithSub(position, subposition);
+    }
+
+    /**
+     * Perform a task on ViewHolder at given position after smooth scrolling to it.
+     * @param position Position of item in adapter.
+     * @param task Task to executed on the ViewHolder at a given position.
+     */
+    public void setSelectedPositionSmooth(final int position, final ViewHolderTask task) {
+        if (task != null) {
+            RecyclerView.ViewHolder vh = findViewHolderForPosition(position);
+            if (vh == null || hasPendingAdapterUpdates()) {
+                addOnChildViewHolderSelectedListener(new OnChildViewHolderSelectedListener() {
+                    @Override
+                    public void onChildViewHolderSelected(RecyclerView parent,
+                            RecyclerView.ViewHolder child, int selectedPosition, int subposition) {
+                        if (selectedPosition == position) {
+                            removeOnChildViewHolderSelectedListener(this);
+                            task.run(child);
+                        }
+                    }
+                });
+            } else {
+                task.run(vh);
+            }
+        }
+        setSelectedPositionSmooth(position);
+    }
+
+    /**
+     * Perform a task on ViewHolder at given position after scroll to it.
+     * @param position Position of item in adapter.
+     * @param task Task to executed on the ViewHolder at a given position.
+     */
+    public void setSelectedPosition(final int position, final ViewHolderTask task) {
+        if (task != null) {
+            RecyclerView.ViewHolder vh = findViewHolderForPosition(position);
+            if (vh == null || hasPendingAdapterUpdates()) {
+                addOnChildViewHolderSelectedListener(new OnChildViewHolderSelectedListener() {
+                    @Override
+                    public void onChildViewHolderSelectedAndPositioned(RecyclerView parent,
+                            RecyclerView.ViewHolder child, int selectedPosition, int subposition) {
+                        if (selectedPosition == position) {
+                            removeOnChildViewHolderSelectedListener(this);
+                            task.run(child);
+                        }
+                    }
+                });
+            } else {
+                task.run(vh);
+            }
+        }
+        setSelectedPosition(position);
+    }
+
+    /**
+     * Returns the adapter position of selected item.
+     * @return The adapter position of selected item.
+     */
+    public int getSelectedPosition() {
+        return mLayoutManager.getSelection();
+    }
+
+    /**
+     * Returns the sub selected item position started from zero.  An item can have
+     * multiple {@link ItemAlignmentFacet}s provided by {@link RecyclerView.ViewHolder}
+     * or {@link FacetProviderAdapter}.  Zero is returned when no {@link ItemAlignmentFacet}
+     * is defined.
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    public int getSelectedSubPosition() {
+        return mLayoutManager.getSubSelection();
+    }
+
+    /**
+     * Sets whether ItemAnimator should run when a child changes size or when adding
+     * or removing a child.
+     * @param animateChildLayout True to enable ItemAnimator, false to disable.
+     */
+    public void setAnimateChildLayout(boolean animateChildLayout) {
+        if (mAnimateChildLayout != animateChildLayout) {
+            mAnimateChildLayout = animateChildLayout;
+            if (!mAnimateChildLayout) {
+                mSavedItemAnimator = getItemAnimator();
+                super.setItemAnimator(null);
+            } else {
+                super.setItemAnimator(mSavedItemAnimator);
+            }
+        }
+    }
+
+    /**
+     * Returns true if an animation will run when a child changes size or when
+     * adding or removing a child.
+     * @return True if ItemAnimator is enabled, false otherwise.
+     */
+    public boolean isChildLayoutAnimated() {
+        return mAnimateChildLayout;
+    }
+
+    /**
+     * Sets the gravity used for child view positioning. Defaults to
+     * GRAVITY_TOP|GRAVITY_START.
+     *
+     * @param gravity See {@link android.view.Gravity}
+     */
+    public void setGravity(int gravity) {
+        mLayoutManager.setGravity(gravity);
+        requestLayout();
+    }
+
+    @Override
+    public boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
+        return mLayoutManager.gridOnRequestFocusInDescendants(this, direction,
+                previouslyFocusedRect);
+    }
+
+    /**
+     * Returns the x/y offsets to final position from current position if the view
+     * is selected.
+     *
+     * @param view The view to get offsets.
+     * @param offsets offsets[0] holds offset of X, offsets[1] holds offset of Y.
+     */
+    public void getViewSelectedOffsets(View view, int[] offsets) {
+        mLayoutManager.getViewSelectedOffsets(view, offsets);
+    }
+
+    @Override
+    public int getChildDrawingOrder(int childCount, int i) {
+        return mLayoutManager.getChildDrawingOrder(this, childCount, i);
+    }
+
+    final boolean isChildrenDrawingOrderEnabledInternal() {
+        return isChildrenDrawingOrderEnabled();
+    }
+
+    @Override
+    public View focusSearch(int direction) {
+        if (isFocused()) {
+            // focusSearch(int) is called when GridView itself is focused.
+            // Calling focusSearch(view, int) to get next sibling of current selected child.
+            View view = mLayoutManager.findViewByPosition(mLayoutManager.getSelection());
+            if (view != null) {
+                return focusSearch(view, direction);
+            }
+        }
+        // otherwise, go to mParent to perform focusSearch
+        return super.focusSearch(direction);
+    }
+
+    @Override
+    protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
+        super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
+        mLayoutManager.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
+    }
+
+    /**
+     * Disables or enables focus search.
+     * @param disabled True to disable focus search, false to enable.
+     */
+    public final void setFocusSearchDisabled(boolean disabled) {
+        // LayoutManager may detachView and attachView in fastRelayout, it causes RowsFragment
+        // re-gain focus after a BACK key pressed, so block children focus during transition.
+        setDescendantFocusability(disabled ? FOCUS_BLOCK_DESCENDANTS: FOCUS_AFTER_DESCENDANTS);
+        mLayoutManager.setFocusSearchDisabled(disabled);
+    }
+
+    /**
+     * Returns true if focus search is disabled.
+     * @return True if focus search is disabled.
+     */
+    public final boolean isFocusSearchDisabled() {
+        return mLayoutManager.isFocusSearchDisabled();
+    }
+
+    /**
+     * Enables or disables layout.  All children will be removed when layout is
+     * disabled.
+     * @param layoutEnabled True to enable layout, false otherwise.
+     */
+    public void setLayoutEnabled(boolean layoutEnabled) {
+        mLayoutManager.setLayoutEnabled(layoutEnabled);
+    }
+
+    /**
+     * Changes and overrides children's visibility.
+     * @param visibility See {@link View#getVisibility()}.
+     */
+    public void setChildrenVisibility(int visibility) {
+        mLayoutManager.setChildrenVisibility(visibility);
+    }
+
+    /**
+     * Enables or disables pruning of children.  Disable is useful during transition.
+     * @param pruneChild True to prune children out side visible area, false to enable.
+     */
+    public void setPruneChild(boolean pruneChild) {
+        mLayoutManager.setPruneChild(pruneChild);
+    }
+
+    /**
+     * Enables or disables scrolling.  Disable is useful during transition.
+     * @param scrollEnabled True to enable scroll, false to disable.
+     */
+    public void setScrollEnabled(boolean scrollEnabled) {
+        mLayoutManager.setScrollEnabled(scrollEnabled);
+    }
+
+    /**
+     * Returns true if scrolling is enabled, false otherwise.
+     * @return True if scrolling is enabled, false otherwise.
+     */
+    public boolean isScrollEnabled() {
+        return mLayoutManager.isScrollEnabled();
+    }
+
+    /**
+     * Returns true if the view at the given position has a same row sibling
+     * in front of it.  This will return true if first item view is not created.
+     *
+     * @param position Position in adapter.
+     * @return True if the view at the given position has a same row sibling in front of it.
+     */
+    public boolean hasPreviousViewInSameRow(int position) {
+        return mLayoutManager.hasPreviousViewInSameRow(position);
+    }
+
+    /**
+     * Enables or disables the default "focus draw at last" order rule. Default is enabled.
+     * @param enabled True to draw the selected child at last, false otherwise.
+     */
+    public void setFocusDrawingOrderEnabled(boolean enabled) {
+        super.setChildrenDrawingOrderEnabled(enabled);
+    }
+
+    /**
+     * Returns true if draws selected child at last, false otherwise. Default is enabled.
+     * @return True if draws selected child at last, false otherwise.
+     */
+    public boolean isFocusDrawingOrderEnabled() {
+        return super.isChildrenDrawingOrderEnabled();
+    }
+
+    /**
+     * Sets the touch intercept listener.
+     * @param listener The touch intercept listener.
+     */
+    public void setOnTouchInterceptListener(OnTouchInterceptListener listener) {
+        mOnTouchInterceptListener = listener;
+    }
+
+    /**
+     * Sets the generic motion intercept listener.
+     * @param listener The motion intercept listener.
+     */
+    public void setOnMotionInterceptListener(OnMotionInterceptListener listener) {
+        mOnMotionInterceptListener = listener;
+    }
+
+    /**
+     * Sets the key intercept listener.
+     * @param listener The key intercept listener.
+     */
+    public void setOnKeyInterceptListener(OnKeyInterceptListener listener) {
+        mOnKeyInterceptListener = listener;
+    }
+
+    /**
+     * Sets the unhandled key listener.
+     * @param listener The unhandled key intercept listener.
+     */
+    public void setOnUnhandledKeyListener(OnUnhandledKeyListener listener) {
+        mOnUnhandledKeyListener = listener;
+    }
+
+    /**
+     * Returns the unhandled key listener.
+     * @return The unhandled key listener.
+     */
+    public OnUnhandledKeyListener getOnUnhandledKeyListener() {
+        return mOnUnhandledKeyListener;
+    }
+
+    @Override
+    public boolean dispatchKeyEvent(KeyEvent event) {
+        if (mOnKeyInterceptListener != null && mOnKeyInterceptListener.onInterceptKeyEvent(event)) {
+            return true;
+        }
+        if (super.dispatchKeyEvent(event)) {
+            return true;
+        }
+        return mOnUnhandledKeyListener != null && mOnUnhandledKeyListener.onUnhandledKey(event);
+    }
+
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent event) {
+        if (mOnTouchInterceptListener != null) {
+            if (mOnTouchInterceptListener.onInterceptTouchEvent(event)) {
+                return true;
+            }
+        }
+        return super.dispatchTouchEvent(event);
+    }
+
+    @Override
+    protected boolean dispatchGenericFocusedEvent(MotionEvent event) {
+        if (mOnMotionInterceptListener != null) {
+            if (mOnMotionInterceptListener.onInterceptMotionEvent(event)) {
+                return true;
+            }
+        }
+        return super.dispatchGenericFocusedEvent(event);
+    }
+
+    /**
+     * Returns the policy for saving children.
+     *
+     * @return policy, one of {@link #SAVE_NO_CHILD}
+     * {@link #SAVE_ON_SCREEN_CHILD} {@link #SAVE_LIMITED_CHILD} {@link #SAVE_ALL_CHILD}.
+     */
+    public final int getSaveChildrenPolicy() {
+        return mLayoutManager.mChildrenStates.getSavePolicy();
+    }
+
+    /**
+     * Returns the limit used when when {@link #getSaveChildrenPolicy()} is
+     *         {@link #SAVE_LIMITED_CHILD}
+     */
+    public final int getSaveChildrenLimitNumber() {
+        return mLayoutManager.mChildrenStates.getLimitNumber();
+    }
+
+    /**
+     * Sets the policy for saving children.
+     * @param savePolicy One of {@link #SAVE_NO_CHILD} {@link #SAVE_ON_SCREEN_CHILD}
+     * {@link #SAVE_LIMITED_CHILD} {@link #SAVE_ALL_CHILD}.
+     */
+    public final void setSaveChildrenPolicy(int savePolicy) {
+        mLayoutManager.mChildrenStates.setSavePolicy(savePolicy);
+    }
+
+    /**
+     * Sets the limit number when {@link #getSaveChildrenPolicy()} is {@link #SAVE_LIMITED_CHILD}.
+     */
+    public final void setSaveChildrenLimitNumber(int limitNumber) {
+        mLayoutManager.mChildrenStates.setLimitNumber(limitNumber);
+    }
+
+    @Override
+    public boolean hasOverlappingRendering() {
+        return mHasOverlappingRendering;
+    }
+
+    public void setHasOverlappingRendering(boolean hasOverlapping) {
+        mHasOverlappingRendering = hasOverlapping;
+    }
+
+    /**
+     * Notify layout manager that layout directionality has been updated
+     */
+    @Override
+    public void onRtlPropertiesChanged(int layoutDirection) {
+        mLayoutManager.onRtlPropertiesChanged(layoutDirection);
+    }
+
+    @Override
+    public void setRecyclerListener(RecyclerView.RecyclerListener listener) {
+        mChainedRecyclerListener = listener;
+    }
+
+    /**
+     * Sets pixels of extra space for layout child in invisible area.
+     *
+     * @param extraLayoutSpace  Pixels of extra space for layout invisible child.
+     *                          Must be bigger or equals to 0.
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    public void setExtraLayoutSpace(int extraLayoutSpace) {
+        mLayoutManager.setExtraLayoutSpace(extraLayoutSpace);
+    }
+
+    /**
+     * Returns pixels of extra space for layout child in invisible area.
+     *
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    public int getExtraLayoutSpace() {
+        return mLayoutManager.getExtraLayoutSpace();
+    }
+
+    /**
+     * Temporarily slide out child views to bottom (for VerticalGridView) or end
+     * (for HorizontalGridView). Layout and scrolling will be suppressed until
+     * {@link #animateIn()} is called.
+     */
+    public void animateOut() {
+        mLayoutManager.slideOut();
+    }
+
+    /**
+     * Undo animateOut() and slide in child views.
+     */
+    public void animateIn() {
+        mLayoutManager.slideIn();
+    }
+
+    @Override
+    public void scrollToPosition(int position) {
+        // dont abort the animateOut() animation, just record the position
+        if (mLayoutManager.isSlidingChildViews()) {
+            mLayoutManager.setSelectionWithSub(position, 0, 0);
+            return;
+        }
+        super.scrollToPosition(position);
+    }
+
+    @Override
+    public void smoothScrollToPosition(int position) {
+        // dont abort the animateOut() animation, just record the position
+        if (mLayoutManager.isSlidingChildViews()) {
+            mLayoutManager.setSelectionWithSub(position, 0, 0);
+            return;
+        }
+        super.smoothScrollToPosition(position);
+    }
+
+    /**
+     * Sets the number of items to prefetch in
+     * {@link RecyclerView.LayoutManager#collectInitialPrefetchPositions(int, RecyclerView.LayoutManager.LayoutPrefetchRegistry)},
+     * which defines how many inner items should be prefetched when this GridView is nested inside
+     * another RecyclerView.
+     *
+     * <p>Set this value to the number of items this inner GridView will display when it is
+     * first scrolled into the viewport. RecyclerView will attempt to prefetch that number of items
+     * so they are ready, avoiding jank as the inner GridView is scrolled into the viewport.</p>
+     *
+     * <p>For example, take a VerticalGridView of scrolling HorizontalGridViews. The rows always
+     * have 6 items visible in them (or 7 if not aligned). Passing <code>6</code> to this method
+     * for each inner GridView will enable RecyclerView's prefetching feature to do create/bind work
+     * for 6 views within a row early, before it is scrolled on screen, instead of just the default
+     * 4.</p>
+     *
+     * <p>Calling this method does nothing unless the LayoutManager is in a RecyclerView
+     * nested in another RecyclerView.</p>
+     *
+     * <p class="note"><strong>Note:</strong> Setting this value to be larger than the number of
+     * views that will be visible in this view can incur unnecessary bind work, and an increase to
+     * the number of Views created and in active use.</p>
+     *
+     * @param itemCount Number of items to prefetch
+     *
+     * @see #getInitialPrefetchItemCount()
+     * @see RecyclerView.LayoutManager#isItemPrefetchEnabled()
+     * @see RecyclerView.LayoutManager#collectInitialPrefetchPositions(int, RecyclerView.LayoutManager.LayoutPrefetchRegistry)
+     */
+    public void setInitialPrefetchItemCount(int itemCount) {
+        mInitialPrefetchItemCount = itemCount;
+    }
+
+    /**
+     * Gets the number of items to prefetch in
+     * {@link RecyclerView.LayoutManager#collectInitialPrefetchPositions(int, RecyclerView.LayoutManager.LayoutPrefetchRegistry)},
+     * which defines how many inner items should be prefetched when this GridView is nested inside
+     * another RecyclerView.
+     *
+     * @see RecyclerView.LayoutManager#isItemPrefetchEnabled()
+     * @see #setInitialPrefetchItemCount(int)
+     * @see RecyclerView.LayoutManager#collectInitialPrefetchPositions(int, RecyclerView.LayoutManager.LayoutPrefetchRegistry)
+     *
+     * @return number of items to prefetch.
+     */
+    public int getInitialPrefetchItemCount() {
+        return mInitialPrefetchItemCount;
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/BaseOnItemViewClickedListener.java b/leanback/src/android/support/v17/leanback/widget/BaseOnItemViewClickedListener.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/BaseOnItemViewClickedListener.java
rename to leanback/src/android/support/v17/leanback/widget/BaseOnItemViewClickedListener.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/BaseOnItemViewSelectedListener.java b/leanback/src/android/support/v17/leanback/widget/BaseOnItemViewSelectedListener.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/BaseOnItemViewSelectedListener.java
rename to leanback/src/android/support/v17/leanback/widget/BaseOnItemViewSelectedListener.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/BrowseFrameLayout.java b/leanback/src/android/support/v17/leanback/widget/BrowseFrameLayout.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/BrowseFrameLayout.java
rename to leanback/src/android/support/v17/leanback/widget/BrowseFrameLayout.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/BrowseRowsFrameLayout.java b/leanback/src/android/support/v17/leanback/widget/BrowseRowsFrameLayout.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/BrowseRowsFrameLayout.java
rename to leanback/src/android/support/v17/leanback/widget/BrowseRowsFrameLayout.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/CheckableImageView.java b/leanback/src/android/support/v17/leanback/widget/CheckableImageView.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/CheckableImageView.java
rename to leanback/src/android/support/v17/leanback/widget/CheckableImageView.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ClassPresenterSelector.java b/leanback/src/android/support/v17/leanback/widget/ClassPresenterSelector.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/ClassPresenterSelector.java
rename to leanback/src/android/support/v17/leanback/widget/ClassPresenterSelector.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ControlBar.java b/leanback/src/android/support/v17/leanback/widget/ControlBar.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/ControlBar.java
rename to leanback/src/android/support/v17/leanback/widget/ControlBar.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ControlBarPresenter.java b/leanback/src/android/support/v17/leanback/widget/ControlBarPresenter.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/ControlBarPresenter.java
rename to leanback/src/android/support/v17/leanback/widget/ControlBarPresenter.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ControlButtonPresenterSelector.java b/leanback/src/android/support/v17/leanback/widget/ControlButtonPresenterSelector.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/ControlButtonPresenterSelector.java
rename to leanback/src/android/support/v17/leanback/widget/ControlButtonPresenterSelector.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/CursorObjectAdapter.java b/leanback/src/android/support/v17/leanback/widget/CursorObjectAdapter.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/CursorObjectAdapter.java
rename to leanback/src/android/support/v17/leanback/widget/CursorObjectAdapter.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewLogoPresenter.java b/leanback/src/android/support/v17/leanback/widget/DetailsOverviewLogoPresenter.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewLogoPresenter.java
rename to leanback/src/android/support/v17/leanback/widget/DetailsOverviewLogoPresenter.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewRow.java b/leanback/src/android/support/v17/leanback/widget/DetailsOverviewRow.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewRow.java
rename to leanback/src/android/support/v17/leanback/widget/DetailsOverviewRow.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewRowPresenter.java b/leanback/src/android/support/v17/leanback/widget/DetailsOverviewRowPresenter.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewRowPresenter.java
rename to leanback/src/android/support/v17/leanback/widget/DetailsOverviewRowPresenter.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewSharedElementHelper.java b/leanback/src/android/support/v17/leanback/widget/DetailsOverviewSharedElementHelper.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewSharedElementHelper.java
rename to leanback/src/android/support/v17/leanback/widget/DetailsOverviewSharedElementHelper.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/DetailsParallax.java b/leanback/src/android/support/v17/leanback/widget/DetailsParallax.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/DetailsParallax.java
rename to leanback/src/android/support/v17/leanback/widget/DetailsParallax.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/DetailsParallaxDrawable.java b/leanback/src/android/support/v17/leanback/widget/DetailsParallaxDrawable.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/DetailsParallaxDrawable.java
rename to leanback/src/android/support/v17/leanback/widget/DetailsParallaxDrawable.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/DiffCallback.java b/leanback/src/android/support/v17/leanback/widget/DiffCallback.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/DiffCallback.java
rename to leanback/src/android/support/v17/leanback/widget/DiffCallback.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/DividerPresenter.java b/leanback/src/android/support/v17/leanback/widget/DividerPresenter.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/DividerPresenter.java
rename to leanback/src/android/support/v17/leanback/widget/DividerPresenter.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/DividerRow.java b/leanback/src/android/support/v17/leanback/widget/DividerRow.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/DividerRow.java
rename to leanback/src/android/support/v17/leanback/widget/DividerRow.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/FacetProvider.java b/leanback/src/android/support/v17/leanback/widget/FacetProvider.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/FacetProvider.java
rename to leanback/src/android/support/v17/leanback/widget/FacetProvider.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/FacetProviderAdapter.java b/leanback/src/android/support/v17/leanback/widget/FacetProviderAdapter.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/FacetProviderAdapter.java
rename to leanback/src/android/support/v17/leanback/widget/FacetProviderAdapter.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/FocusHighlight.java b/leanback/src/android/support/v17/leanback/widget/FocusHighlight.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/FocusHighlight.java
rename to leanback/src/android/support/v17/leanback/widget/FocusHighlight.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/FocusHighlightHandler.java b/leanback/src/android/support/v17/leanback/widget/FocusHighlightHandler.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/FocusHighlightHandler.java
rename to leanback/src/android/support/v17/leanback/widget/FocusHighlightHandler.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/FocusHighlightHelper.java b/leanback/src/android/support/v17/leanback/widget/FocusHighlightHelper.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/FocusHighlightHelper.java
rename to leanback/src/android/support/v17/leanback/widget/FocusHighlightHelper.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ForegroundHelper.java b/leanback/src/android/support/v17/leanback/widget/ForegroundHelper.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/ForegroundHelper.java
rename to leanback/src/android/support/v17/leanback/widget/ForegroundHelper.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/FragmentAnimationProvider.java b/leanback/src/android/support/v17/leanback/widget/FragmentAnimationProvider.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/FragmentAnimationProvider.java
rename to leanback/src/android/support/v17/leanback/widget/FragmentAnimationProvider.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/FullWidthDetailsOverviewRowPresenter.java b/leanback/src/android/support/v17/leanback/widget/FullWidthDetailsOverviewRowPresenter.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/FullWidthDetailsOverviewRowPresenter.java
rename to leanback/src/android/support/v17/leanback/widget/FullWidthDetailsOverviewRowPresenter.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/FullWidthDetailsOverviewSharedElementHelper.java b/leanback/src/android/support/v17/leanback/widget/FullWidthDetailsOverviewSharedElementHelper.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/FullWidthDetailsOverviewSharedElementHelper.java
rename to leanback/src/android/support/v17/leanback/widget/FullWidthDetailsOverviewSharedElementHelper.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/Grid.java b/leanback/src/android/support/v17/leanback/widget/Grid.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/Grid.java
rename to leanback/src/android/support/v17/leanback/widget/Grid.java
diff --git a/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java b/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java
new file mode 100644
index 0000000..9d159ec
--- /dev/null
+++ b/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java
@@ -0,0 +1,3746 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import static android.support.v7.widget.RecyclerView.HORIZONTAL;
+import static android.support.v7.widget.RecyclerView.NO_ID;
+import static android.support.v7.widget.RecyclerView.NO_POSITION;
+import static android.support.v7.widget.RecyclerView.SCROLL_STATE_IDLE;
+import static android.support.v7.widget.RecyclerView.VERTICAL;
+
+import android.content.Context;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.annotation.VisibleForTesting;
+import android.support.v4.os.TraceCompat;
+import android.support.v4.util.CircularIntArray;
+import android.support.v4.view.ViewCompat;
+import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
+import android.support.v7.widget.LinearSmoothScroller;
+import android.support.v7.widget.OrientationHelper;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerView.Recycler;
+import android.support.v7.widget.RecyclerView.State;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.SparseIntArray;
+import android.view.FocusFinder;
+import android.view.Gravity;
+import android.view.View;
+import android.view.View.MeasureSpec;
+import android.view.ViewGroup;
+import android.view.ViewGroup.MarginLayoutParams;
+import android.view.animation.AccelerateDecelerateInterpolator;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+final class GridLayoutManager extends RecyclerView.LayoutManager {
+
+    /*
+     * LayoutParams for {@link HorizontalGridView} and {@link VerticalGridView}.
+     * The class currently does two internal jobs:
+     * - Saves optical bounds insets.
+     * - Caches focus align view center.
+     */
+    final static class LayoutParams extends RecyclerView.LayoutParams {
+
+        // For placement
+        int mLeftInset;
+        int mTopInset;
+        int mRightInset;
+        int mBottomInset;
+
+        // For alignment
+        private int mAlignX;
+        private int mAlignY;
+        private int[] mAlignMultiple;
+        private ItemAlignmentFacet mAlignmentFacet;
+
+        public LayoutParams(Context c, AttributeSet attrs) {
+            super(c, attrs);
+        }
+
+        public LayoutParams(int width, int height) {
+            super(width, height);
+        }
+
+        public LayoutParams(MarginLayoutParams source) {
+            super(source);
+        }
+
+        public LayoutParams(ViewGroup.LayoutParams source) {
+            super(source);
+        }
+
+        public LayoutParams(RecyclerView.LayoutParams source) {
+            super(source);
+        }
+
+        public LayoutParams(LayoutParams source) {
+            super(source);
+        }
+
+        int getAlignX() {
+            return mAlignX;
+        }
+
+        int getAlignY() {
+            return mAlignY;
+        }
+
+        int getOpticalLeft(View view) {
+            return view.getLeft() + mLeftInset;
+        }
+
+        int getOpticalTop(View view) {
+            return view.getTop() + mTopInset;
+        }
+
+        int getOpticalRight(View view) {
+            return view.getRight() - mRightInset;
+        }
+
+        int getOpticalBottom(View view) {
+            return view.getBottom() - mBottomInset;
+        }
+
+        int getOpticalWidth(View view) {
+            return view.getWidth() - mLeftInset - mRightInset;
+        }
+
+        int getOpticalHeight(View view) {
+            return view.getHeight() - mTopInset - mBottomInset;
+        }
+
+        int getOpticalLeftInset() {
+            return mLeftInset;
+        }
+
+        int getOpticalRightInset() {
+            return mRightInset;
+        }
+
+        int getOpticalTopInset() {
+            return mTopInset;
+        }
+
+        int getOpticalBottomInset() {
+            return mBottomInset;
+        }
+
+        void setAlignX(int alignX) {
+            mAlignX = alignX;
+        }
+
+        void setAlignY(int alignY) {
+            mAlignY = alignY;
+        }
+
+        void setItemAlignmentFacet(ItemAlignmentFacet facet) {
+            mAlignmentFacet = facet;
+        }
+
+        ItemAlignmentFacet getItemAlignmentFacet() {
+            return mAlignmentFacet;
+        }
+
+        void calculateItemAlignments(int orientation, View view) {
+            ItemAlignmentFacet.ItemAlignmentDef[] defs = mAlignmentFacet.getAlignmentDefs();
+            if (mAlignMultiple == null || mAlignMultiple.length != defs.length) {
+                mAlignMultiple = new int[defs.length];
+            }
+            for (int i = 0; i < defs.length; i++) {
+                mAlignMultiple[i] = ItemAlignmentFacetHelper
+                        .getAlignmentPosition(view, defs[i], orientation);
+            }
+            if (orientation == HORIZONTAL) {
+                mAlignX = mAlignMultiple[0];
+            } else {
+                mAlignY = mAlignMultiple[0];
+            }
+        }
+
+        int[] getAlignMultiple() {
+            return mAlignMultiple;
+        }
+
+        void setOpticalInsets(int leftInset, int topInset, int rightInset, int bottomInset) {
+            mLeftInset = leftInset;
+            mTopInset = topInset;
+            mRightInset = rightInset;
+            mBottomInset = bottomInset;
+        }
+
+    }
+
+    /**
+     * Base class which scrolls to selected view in onStop().
+     */
+    abstract class GridLinearSmoothScroller extends LinearSmoothScroller {
+        GridLinearSmoothScroller() {
+            super(mBaseGridView.getContext());
+        }
+
+        @Override
+        protected void onStop() {
+            // onTargetFound() may not be called if we hit the "wall" first or get cancelled.
+            View targetView = findViewByPosition(getTargetPosition());
+            if (targetView == null) {
+                if (getTargetPosition() >= 0) {
+                    // if smooth scroller is stopped without target, immediately jumps
+                    // to the target position.
+                    scrollToSelection(getTargetPosition(), 0, false, 0);
+                }
+                super.onStop();
+                return;
+            }
+            if (mFocusPosition != getTargetPosition()) {
+                // This should not happen since we cropped value in startPositionSmoothScroller()
+                mFocusPosition = getTargetPosition();
+            }
+            if (hasFocus()) {
+                mFlag |= PF_IN_SELECTION;
+                targetView.requestFocus();
+                mFlag &= ~PF_IN_SELECTION;
+            }
+            dispatchChildSelected();
+            dispatchChildSelectedAndPositioned();
+            super.onStop();
+        }
+
+        @Override
+        protected int calculateTimeForScrolling(int dx) {
+            int ms = super.calculateTimeForScrolling(dx);
+            if (mWindowAlignment.mainAxis().getSize() > 0) {
+                float minMs = (float) MIN_MS_SMOOTH_SCROLL_MAIN_SCREEN
+                        / mWindowAlignment.mainAxis().getSize() * dx;
+                if (ms < minMs) {
+                    ms = (int) minMs;
+                }
+            }
+            return ms;
+        }
+
+        @Override
+        protected void onTargetFound(View targetView,
+                RecyclerView.State state, Action action) {
+            if (getScrollPosition(targetView, null, sTwoInts)) {
+                int dx, dy;
+                if (mOrientation == HORIZONTAL) {
+                    dx = sTwoInts[0];
+                    dy = sTwoInts[1];
+                } else {
+                    dx = sTwoInts[1];
+                    dy = sTwoInts[0];
+                }
+                final int distance = (int) Math.sqrt(dx * dx + dy * dy);
+                final int time = calculateTimeForDeceleration(distance);
+                action.update(dx, dy, time, mDecelerateInterpolator);
+            }
+        }
+    }
+
+    /**
+     * The SmoothScroller that remembers pending DPAD keys and consume pending keys
+     * during scroll.
+     */
+    final class PendingMoveSmoothScroller extends GridLinearSmoothScroller {
+        // -2 is a target position that LinearSmoothScroller can never find until
+        // consumePendingMovesXXX() sets real targetPosition.
+        final static int TARGET_UNDEFINED = -2;
+        // whether the grid is staggered.
+        private final boolean mStaggeredGrid;
+        // Number of pending movements on primary direction, negative if PREV_ITEM.
+        private int mPendingMoves;
+
+        PendingMoveSmoothScroller(int initialPendingMoves, boolean staggeredGrid) {
+            mPendingMoves = initialPendingMoves;
+            mStaggeredGrid = staggeredGrid;
+            setTargetPosition(TARGET_UNDEFINED);
+        }
+
+        void increasePendingMoves() {
+            if (mPendingMoves < mMaxPendingMoves) {
+                mPendingMoves++;
+            }
+        }
+
+        void decreasePendingMoves() {
+            if (mPendingMoves > -mMaxPendingMoves) {
+                mPendingMoves--;
+            }
+        }
+
+        /**
+         * Called before laid out an item when non-staggered grid can handle pending movements
+         * by skipping "mNumRows" per movement;  staggered grid will have to wait the item
+         * has been laid out in consumePendingMovesAfterLayout().
+         */
+        void consumePendingMovesBeforeLayout() {
+            if (mStaggeredGrid || mPendingMoves == 0) {
+                return;
+            }
+            View newSelected = null;
+            int startPos = mPendingMoves > 0 ? mFocusPosition + mNumRows :
+                    mFocusPosition - mNumRows;
+            for (int pos = startPos; mPendingMoves != 0;
+                    pos = mPendingMoves > 0 ? pos + mNumRows: pos - mNumRows) {
+                View v = findViewByPosition(pos);
+                if (v == null) {
+                    break;
+                }
+                if (!canScrollTo(v)) {
+                    continue;
+                }
+                newSelected = v;
+                mFocusPosition = pos;
+                mSubFocusPosition = 0;
+                if (mPendingMoves > 0) {
+                    mPendingMoves--;
+                } else {
+                    mPendingMoves++;
+                }
+            }
+            if (newSelected != null && hasFocus()) {
+                mFlag |= PF_IN_SELECTION;
+                newSelected.requestFocus();
+                mFlag &= ~PF_IN_SELECTION;
+            }
+        }
+
+        /**
+         * Called after laid out an item.  Staggered grid should find view on same
+         * Row and consume pending movements.
+         */
+        void consumePendingMovesAfterLayout() {
+            if (mStaggeredGrid && mPendingMoves != 0) {
+                // consume pending moves, focus to item on the same row.
+                mPendingMoves = processSelectionMoves(true, mPendingMoves);
+            }
+            if (mPendingMoves == 0 || (mPendingMoves > 0 && hasCreatedLastItem())
+                    || (mPendingMoves < 0 && hasCreatedFirstItem())) {
+                setTargetPosition(mFocusPosition);
+                stop();
+            }
+        }
+
+        @Override
+        protected void updateActionForInterimTarget(Action action) {
+            if (mPendingMoves == 0) {
+                return;
+            }
+            super.updateActionForInterimTarget(action);
+        }
+
+        @Override
+        public PointF computeScrollVectorForPosition(int targetPosition) {
+            if (mPendingMoves == 0) {
+                return null;
+            }
+            int direction = ((mFlag & PF_REVERSE_FLOW_PRIMARY) != 0
+                    ? mPendingMoves > 0 : mPendingMoves < 0)
+                    ? -1 : 1;
+            if (mOrientation == HORIZONTAL) {
+                return new PointF(direction, 0);
+            } else {
+                return new PointF(0, direction);
+            }
+        }
+
+        @Override
+        protected void onStop() {
+            super.onStop();
+            // if we hit wall,  need clear the remaining pending moves.
+            mPendingMoves = 0;
+            mPendingMoveSmoothScroller = null;
+            View v = findViewByPosition(getTargetPosition());
+            if (v != null) scrollToView(v, true);
+        }
+    };
+
+    private static final String TAG = "GridLayoutManager";
+    static final boolean DEBUG = false;
+    static final boolean TRACE = false;
+
+    // maximum pending movement in one direction.
+    static final int DEFAULT_MAX_PENDING_MOVES = 10;
+    int mMaxPendingMoves = DEFAULT_MAX_PENDING_MOVES;
+    // minimal milliseconds to scroll window size in major direction,  we put a cap to prevent the
+    // effect smooth scrolling too over to bind an item view then drag the item view back.
+    final static int MIN_MS_SMOOTH_SCROLL_MAIN_SCREEN = 30;
+
+    String getTag() {
+        return TAG + ":" + mBaseGridView.getId();
+    }
+
+    final BaseGridView mBaseGridView;
+
+    /**
+     * Note on conventions in the presence of RTL layout directions:
+     * Many properties and method names reference entities related to the
+     * beginnings and ends of things.  In the presence of RTL flows,
+     * it may not be clear whether this is intended to reference a
+     * quantity that changes direction in RTL cases, or a quantity that
+     * does not.  Here are the conventions in use:
+     *
+     * start/end: coordinate quantities - do reverse
+     * (optical) left/right: coordinate quantities - do not reverse
+     * low/high: coordinate quantities - do not reverse
+     * min/max: coordinate quantities - do not reverse
+     * scroll offset - coordinate quantities - do not reverse
+     * first/last: positional indices - do not reverse
+     * front/end: positional indices - do not reverse
+     * prepend/append: related to positional indices - do not reverse
+     *
+     * Note that although quantities do not reverse in RTL flows, their
+     * relationship does.  In LTR flows, the first positional index is
+     * leftmost; in RTL flows, it is rightmost.  Thus, anywhere that
+     * positional quantities are mapped onto coordinate quantities,
+     * the flow must be checked and the logic reversed.
+     */
+
+    /**
+     * The orientation of a "row".
+     */
+    @RecyclerView.Orientation
+    int mOrientation = HORIZONTAL;
+    private OrientationHelper mOrientationHelper = OrientationHelper.createHorizontalHelper(this);
+
+    RecyclerView.State mState;
+    // Suppose currently showing 4, 5, 6, 7; removing 2,3,4 will make the layoutPosition to be
+    // 2(deleted), 3, 4, 5 in prelayout pass. So when we add item in prelayout, we must subtract 2
+    // from index of Grid.createItem.
+    int mPositionDeltaInPreLayout;
+    // Extra layout space needs to fill in prelayout pass. Note we apply the extra space to both
+    // appends and prepends due to the fact leanback is doing mario scrolling: removing items to
+    // the left of focused item might need extra layout on the right.
+    int mExtraLayoutSpaceInPreLayout;
+    // mPositionToRowInPostLayout and mDisappearingPositions are temp variables in post layout.
+    final SparseIntArray mPositionToRowInPostLayout = new SparseIntArray();
+    int[] mDisappearingPositions;
+
+    RecyclerView.Recycler mRecycler;
+
+    private static final Rect sTempRect = new Rect();
+
+    // 2 bits mask is for 3 STAGEs: 0, PF_STAGE_LAYOUT or PF_STAGE_SCROLL.
+    static final int PF_STAGE_MASK = 0x3;
+    static final int PF_STAGE_LAYOUT = 0x1;
+    static final int PF_STAGE_SCROLL = 0x2;
+
+    // Flag for "in fast relayout", determined by layoutInit() result.
+    static final int PF_FAST_RELAYOUT = 1 << 2;
+
+    // Flag for the selected item being updated in fast relayout.
+    static final int PF_FAST_RELAYOUT_UPDATED_SELECTED_POSITION = 1 << 3;
+    /**
+     * During full layout pass, when GridView had focus: onLayoutChildren will
+     * skip non-focusable child and adjust mFocusPosition.
+     */
+    static final int PF_IN_LAYOUT_SEARCH_FOCUS = 1 << 4;
+
+    // flag to prevent reentry if it's already processing selection request.
+    static final int PF_IN_SELECTION = 1 << 5;
+
+    // Represents whether child views are temporarily sliding out
+    static final int PF_SLIDING = 1 << 6;
+    static final int PF_LAYOUT_EATEN_IN_SLIDING = 1 << 7;
+
+    /**
+     * Force a full layout under certain situations.  E.g. Rows change, jump to invisible child.
+     */
+    static final int PF_FORCE_FULL_LAYOUT = 1 << 8;
+
+    /**
+     * True if layout is enabled.
+     */
+    static final int PF_LAYOUT_ENABLED = 1 << 9;
+
+    /**
+     * Flag controlling whether the current/next layout should
+     * be updating the secondary size of rows.
+     */
+    static final int PF_ROW_SECONDARY_SIZE_REFRESH = 1 << 10;
+
+    /**
+     *  Allow DPAD key to navigate out at the front of the View (where position = 0),
+     *  default is false.
+     */
+    static final int PF_FOCUS_OUT_FRONT = 1 << 11;
+
+    /**
+     * Allow DPAD key to navigate out at the end of the view, default is false.
+     */
+    static final int PF_FOCUS_OUT_END = 1 << 12;
+
+    static final int PF_FOCUS_OUT_MASKS = PF_FOCUS_OUT_FRONT | PF_FOCUS_OUT_END;
+
+    /**
+     *  Allow DPAD key to navigate out of second axis.
+     *  default is true.
+     */
+    static final int PF_FOCUS_OUT_SIDE_START = 1 << 13;
+
+    /**
+     * Allow DPAD key to navigate out of second axis.
+     */
+    static final int PF_FOCUS_OUT_SIDE_END = 1 << 14;
+
+    static final int PF_FOCUS_OUT_SIDE_MASKS = PF_FOCUS_OUT_SIDE_START | PF_FOCUS_OUT_SIDE_END;
+
+    /**
+     * True if focus search is disabled.
+     */
+    static final int PF_FOCUS_SEARCH_DISABLED = 1 << 15;
+
+    /**
+     * True if prune child,  might be disabled during transition.
+     */
+    static final int PF_PRUNE_CHILD = 1 << 16;
+
+    /**
+     * True if scroll content,  might be disabled during transition.
+     */
+    static final int PF_SCROLL_ENABLED = 1 << 17;
+
+    /**
+     * Set to true for RTL layout in horizontal orientation
+     */
+    static final int PF_REVERSE_FLOW_PRIMARY = 1 << 18;
+
+    /**
+     * Set to true for RTL layout in vertical orientation
+     */
+    static final int PF_REVERSE_FLOW_SECONDARY = 1 << 19;
+
+    static final int PF_REVERSE_FLOW_MASK = PF_REVERSE_FLOW_PRIMARY | PF_REVERSE_FLOW_SECONDARY;
+
+    int mFlag = PF_LAYOUT_ENABLED
+            | PF_FOCUS_OUT_SIDE_START | PF_FOCUS_OUT_SIDE_END
+            | PF_PRUNE_CHILD | PF_SCROLL_ENABLED;
+
+    private OnChildSelectedListener mChildSelectedListener = null;
+
+    private ArrayList<OnChildViewHolderSelectedListener> mChildViewHolderSelectedListeners = null;
+
+    OnChildLaidOutListener mChildLaidOutListener = null;
+
+    /**
+     * The focused position, it's not the currently visually aligned position
+     * but it is the final position that we intend to focus on. If there are
+     * multiple setSelection() called, mFocusPosition saves last value.
+     */
+    int mFocusPosition = NO_POSITION;
+
+    /**
+     * A view can have multiple alignment position,  this is the index of which
+     * alignment is used,  by default is 0.
+     */
+    int mSubFocusPosition = 0;
+
+    /**
+     * LinearSmoothScroller that consume pending DPAD movements.
+     */
+    PendingMoveSmoothScroller mPendingMoveSmoothScroller;
+
+    /**
+     * The offset to be applied to mFocusPosition, due to adapter change, on the next
+     * layout.  Set to Integer.MIN_VALUE means we should stop adding delta to mFocusPosition
+     * until next layout cycler.
+     * TODO:  This is somewhat duplication of RecyclerView getOldPosition() which is
+     * unfortunately cleared after prelayout.
+     */
+    private int mFocusPositionOffset = 0;
+
+    /**
+     * Extra pixels applied on primary direction.
+     */
+    private int mPrimaryScrollExtra;
+
+    /**
+     * override child visibility
+     */
+    @Visibility
+    int mChildVisibility;
+
+    /**
+     * Pixels that scrolled in secondary forward direction. Negative value means backward.
+     * Note that we treat secondary differently than main. For the main axis, update scroll min/max
+     * based on first/last item's view location. For second axis, we don't use item's view location.
+     * We are using the {@link #getRowSizeSecondary(int)} plus mScrollOffsetSecondary. see
+     * details in {@link #updateSecondaryScrollLimits()}.
+     */
+    int mScrollOffsetSecondary;
+
+    /**
+     * User-specified row height/column width.  Can be WRAP_CONTENT.
+     */
+    private int mRowSizeSecondaryRequested;
+
+    /**
+     * The fixed size of each grid item in the secondary direction. This corresponds to
+     * the row height, equal for all rows. Grid items may have variable length
+     * in the primary direction.
+     */
+    private int mFixedRowSizeSecondary;
+
+    /**
+     * Tracks the secondary size of each row.
+     */
+    private int[] mRowSizeSecondary;
+
+    /**
+     * The maximum measured size of the view.
+     */
+    private int mMaxSizeSecondary;
+
+    /**
+     * Margin between items.
+     */
+    private int mHorizontalSpacing;
+    /**
+     * Margin between items vertically.
+     */
+    private int mVerticalSpacing;
+    /**
+     * Margin in main direction.
+     */
+    private int mSpacingPrimary;
+    /**
+     * Margin in second direction.
+     */
+    private int mSpacingSecondary;
+    /**
+     * How to position child in secondary direction.
+     */
+    private int mGravity = Gravity.START | Gravity.TOP;
+    /**
+     * The number of rows in the grid.
+     */
+    int mNumRows;
+    /**
+     * Number of rows requested, can be 0 to be determined by parent size and
+     * rowHeight.
+     */
+    private int mNumRowsRequested = 1;
+
+    /**
+     * Saves grid information of each view.
+     */
+    Grid mGrid;
+
+    /**
+     * Focus Scroll strategy.
+     */
+    private int mFocusScrollStrategy = BaseGridView.FOCUS_SCROLL_ALIGNED;
+    /**
+     * Defines how item view is aligned in the window.
+     */
+    final WindowAlignment mWindowAlignment = new WindowAlignment();
+
+    /**
+     * Defines how item view is aligned.
+     */
+    private final ItemAlignment mItemAlignment = new ItemAlignment();
+
+    /**
+     * Dimensions of the view, width or height depending on orientation.
+     */
+    private int mSizePrimary;
+
+    /**
+     * Pixels of extra space for layout item (outside the widget)
+     */
+    private int mExtraLayoutSpace;
+
+    /**
+     * Temporary variable: an int array of length=2.
+     */
+    static int[] sTwoInts = new int[2];
+
+    /**
+     * Temporaries used for measuring.
+     */
+    private int[] mMeasuredDimension = new int[2];
+
+    final ViewsStateBundle mChildrenStates = new ViewsStateBundle();
+
+    /**
+     * Optional interface implemented by Adapter.
+     */
+    private FacetProviderAdapter mFacetProviderAdapter;
+
+    public GridLayoutManager(BaseGridView baseGridView) {
+        mBaseGridView = baseGridView;
+        mChildVisibility = -1;
+        // disable prefetch by default, prefetch causes regression on low power chipset
+        setItemPrefetchEnabled(false);
+    }
+
+    public void setOrientation(@RecyclerView.Orientation int orientation) {
+        if (orientation != HORIZONTAL && orientation != VERTICAL) {
+            if (DEBUG) Log.v(getTag(), "invalid orientation: " + orientation);
+            return;
+        }
+
+        mOrientation = orientation;
+        mOrientationHelper = OrientationHelper.createOrientationHelper(this, mOrientation);
+        mWindowAlignment.setOrientation(orientation);
+        mItemAlignment.setOrientation(orientation);
+        mFlag |= PF_FORCE_FULL_LAYOUT;
+    }
+
+    public void onRtlPropertiesChanged(int layoutDirection) {
+        final int flags;
+        if (mOrientation == HORIZONTAL) {
+            flags = layoutDirection == View.LAYOUT_DIRECTION_RTL ? PF_REVERSE_FLOW_PRIMARY : 0;
+        } else {
+            flags = layoutDirection == View.LAYOUT_DIRECTION_RTL ? PF_REVERSE_FLOW_SECONDARY : 0;
+        }
+        if ((mFlag & PF_REVERSE_FLOW_MASK) == flags) {
+            return;
+        }
+        mFlag = (mFlag & ~PF_REVERSE_FLOW_MASK) | flags;
+        mFlag |= PF_FORCE_FULL_LAYOUT;
+        mWindowAlignment.horizontal.setReversedFlow(layoutDirection == View.LAYOUT_DIRECTION_RTL);
+    }
+
+    public int getFocusScrollStrategy() {
+        return mFocusScrollStrategy;
+    }
+
+    public void setFocusScrollStrategy(int focusScrollStrategy) {
+        mFocusScrollStrategy = focusScrollStrategy;
+    }
+
+    public void setWindowAlignment(int windowAlignment) {
+        mWindowAlignment.mainAxis().setWindowAlignment(windowAlignment);
+    }
+
+    public int getWindowAlignment() {
+        return mWindowAlignment.mainAxis().getWindowAlignment();
+    }
+
+    public void setWindowAlignmentOffset(int alignmentOffset) {
+        mWindowAlignment.mainAxis().setWindowAlignmentOffset(alignmentOffset);
+    }
+
+    public int getWindowAlignmentOffset() {
+        return mWindowAlignment.mainAxis().getWindowAlignmentOffset();
+    }
+
+    public void setWindowAlignmentOffsetPercent(float offsetPercent) {
+        mWindowAlignment.mainAxis().setWindowAlignmentOffsetPercent(offsetPercent);
+    }
+
+    public float getWindowAlignmentOffsetPercent() {
+        return mWindowAlignment.mainAxis().getWindowAlignmentOffsetPercent();
+    }
+
+    public void setItemAlignmentOffset(int alignmentOffset) {
+        mItemAlignment.mainAxis().setItemAlignmentOffset(alignmentOffset);
+        updateChildAlignments();
+    }
+
+    public int getItemAlignmentOffset() {
+        return mItemAlignment.mainAxis().getItemAlignmentOffset();
+    }
+
+    public void setItemAlignmentOffsetWithPadding(boolean withPadding) {
+        mItemAlignment.mainAxis().setItemAlignmentOffsetWithPadding(withPadding);
+        updateChildAlignments();
+    }
+
+    public boolean isItemAlignmentOffsetWithPadding() {
+        return mItemAlignment.mainAxis().isItemAlignmentOffsetWithPadding();
+    }
+
+    public void setItemAlignmentOffsetPercent(float offsetPercent) {
+        mItemAlignment.mainAxis().setItemAlignmentOffsetPercent(offsetPercent);
+        updateChildAlignments();
+    }
+
+    public float getItemAlignmentOffsetPercent() {
+        return mItemAlignment.mainAxis().getItemAlignmentOffsetPercent();
+    }
+
+    public void setItemAlignmentViewId(int viewId) {
+        mItemAlignment.mainAxis().setItemAlignmentViewId(viewId);
+        updateChildAlignments();
+    }
+
+    public int getItemAlignmentViewId() {
+        return mItemAlignment.mainAxis().getItemAlignmentViewId();
+    }
+
+    public void setFocusOutAllowed(boolean throughFront, boolean throughEnd) {
+        mFlag = (mFlag & ~PF_FOCUS_OUT_MASKS)
+                | (throughFront ? PF_FOCUS_OUT_FRONT : 0)
+                | (throughEnd ? PF_FOCUS_OUT_END : 0);
+    }
+
+    public void setFocusOutSideAllowed(boolean throughStart, boolean throughEnd) {
+        mFlag = (mFlag & ~PF_FOCUS_OUT_SIDE_MASKS)
+                | (throughStart ? PF_FOCUS_OUT_SIDE_START : 0)
+                | (throughEnd ? PF_FOCUS_OUT_SIDE_END : 0);
+    }
+
+    public void setNumRows(int numRows) {
+        if (numRows < 0) throw new IllegalArgumentException();
+        mNumRowsRequested = numRows;
+    }
+
+    /**
+     * Set the row height. May be WRAP_CONTENT, or a size in pixels.
+     */
+    public void setRowHeight(int height) {
+        if (height >= 0 || height == ViewGroup.LayoutParams.WRAP_CONTENT) {
+            mRowSizeSecondaryRequested = height;
+        } else {
+            throw new IllegalArgumentException("Invalid row height: " + height);
+        }
+    }
+
+    public void setItemSpacing(int space) {
+        mVerticalSpacing = mHorizontalSpacing = space;
+        mSpacingPrimary = mSpacingSecondary = space;
+    }
+
+    public void setVerticalSpacing(int space) {
+        if (mOrientation == VERTICAL) {
+            mSpacingPrimary = mVerticalSpacing = space;
+        } else {
+            mSpacingSecondary = mVerticalSpacing = space;
+        }
+    }
+
+    public void setHorizontalSpacing(int space) {
+        if (mOrientation == HORIZONTAL) {
+            mSpacingPrimary = mHorizontalSpacing = space;
+        } else {
+            mSpacingSecondary = mHorizontalSpacing = space;
+        }
+    }
+
+    public int getVerticalSpacing() {
+        return mVerticalSpacing;
+    }
+
+    public int getHorizontalSpacing() {
+        return mHorizontalSpacing;
+    }
+
+    public void setGravity(int gravity) {
+        mGravity = gravity;
+    }
+
+    protected boolean hasDoneFirstLayout() {
+        return mGrid != null;
+    }
+
+    public void setOnChildSelectedListener(OnChildSelectedListener listener) {
+        mChildSelectedListener = listener;
+    }
+
+    public void setOnChildViewHolderSelectedListener(OnChildViewHolderSelectedListener listener) {
+        if (listener == null) {
+            mChildViewHolderSelectedListeners = null;
+            return;
+        }
+        if (mChildViewHolderSelectedListeners == null) {
+            mChildViewHolderSelectedListeners = new ArrayList<OnChildViewHolderSelectedListener>();
+        } else {
+            mChildViewHolderSelectedListeners.clear();
+        }
+        mChildViewHolderSelectedListeners.add(listener);
+    }
+
+    public void addOnChildViewHolderSelectedListener(OnChildViewHolderSelectedListener listener) {
+        if (mChildViewHolderSelectedListeners == null) {
+            mChildViewHolderSelectedListeners = new ArrayList<OnChildViewHolderSelectedListener>();
+        }
+        mChildViewHolderSelectedListeners.add(listener);
+    }
+
+    public void removeOnChildViewHolderSelectedListener(OnChildViewHolderSelectedListener
+            listener) {
+        if (mChildViewHolderSelectedListeners != null) {
+            mChildViewHolderSelectedListeners.remove(listener);
+        }
+    }
+
+    boolean hasOnChildViewHolderSelectedListener() {
+        return mChildViewHolderSelectedListeners != null
+                && mChildViewHolderSelectedListeners.size() > 0;
+    }
+
+    void fireOnChildViewHolderSelected(RecyclerView parent, RecyclerView.ViewHolder child,
+            int position, int subposition) {
+        if (mChildViewHolderSelectedListeners == null) {
+            return;
+        }
+        for (int i = mChildViewHolderSelectedListeners.size() - 1; i >= 0 ; i--) {
+            mChildViewHolderSelectedListeners.get(i).onChildViewHolderSelected(parent, child,
+                    position, subposition);
+        }
+    }
+
+    void fireOnChildViewHolderSelectedAndPositioned(RecyclerView parent, RecyclerView.ViewHolder
+            child, int position, int subposition) {
+        if (mChildViewHolderSelectedListeners == null) {
+            return;
+        }
+        for (int i = mChildViewHolderSelectedListeners.size() - 1; i >= 0 ; i--) {
+            mChildViewHolderSelectedListeners.get(i).onChildViewHolderSelectedAndPositioned(parent,
+                    child, position, subposition);
+        }
+    }
+
+    void setOnChildLaidOutListener(OnChildLaidOutListener listener) {
+        mChildLaidOutListener = listener;
+    }
+
+    private int getAdapterPositionByView(View view) {
+        if (view == null) {
+            return NO_POSITION;
+        }
+        LayoutParams params = (LayoutParams) view.getLayoutParams();
+        if (params == null || params.isItemRemoved()) {
+            // when item is removed, the position value can be any value.
+            return NO_POSITION;
+        }
+        return params.getViewAdapterPosition();
+    }
+
+    int getSubPositionByView(View view, View childView) {
+        if (view == null || childView == null) {
+            return 0;
+        }
+        final LayoutParams lp = (LayoutParams) view.getLayoutParams();
+        final ItemAlignmentFacet facet = lp.getItemAlignmentFacet();
+        if (facet != null) {
+            final ItemAlignmentFacet.ItemAlignmentDef[] defs = facet.getAlignmentDefs();
+            if (defs.length > 1) {
+                while (childView != view) {
+                    int id = childView.getId();
+                    if (id != View.NO_ID) {
+                        for (int i = 1; i < defs.length; i++) {
+                            if (defs[i].getItemAlignmentFocusViewId() == id) {
+                                return i;
+                            }
+                        }
+                    }
+                    childView = (View) childView.getParent();
+                }
+            }
+        }
+        return 0;
+    }
+
+    private int getAdapterPositionByIndex(int index) {
+        return getAdapterPositionByView(getChildAt(index));
+    }
+
+    void dispatchChildSelected() {
+        if (mChildSelectedListener == null && !hasOnChildViewHolderSelectedListener()) {
+            return;
+        }
+
+        if (TRACE) TraceCompat.beginSection("onChildSelected");
+        View view = mFocusPosition == NO_POSITION ? null : findViewByPosition(mFocusPosition);
+        if (view != null) {
+            RecyclerView.ViewHolder vh = mBaseGridView.getChildViewHolder(view);
+            if (mChildSelectedListener != null) {
+                mChildSelectedListener.onChildSelected(mBaseGridView, view, mFocusPosition,
+                        vh == null? NO_ID: vh.getItemId());
+            }
+            fireOnChildViewHolderSelected(mBaseGridView, vh, mFocusPosition, mSubFocusPosition);
+        } else {
+            if (mChildSelectedListener != null) {
+                mChildSelectedListener.onChildSelected(mBaseGridView, null, NO_POSITION, NO_ID);
+            }
+            fireOnChildViewHolderSelected(mBaseGridView, null, NO_POSITION, 0);
+        }
+        if (TRACE) TraceCompat.endSection();
+
+        // Children may request layout when a child selection event occurs (such as a change of
+        // padding on the current and previously selected rows).
+        // If in layout, a child requesting layout may have been laid out before the selection
+        // callback.
+        // If it was not, the child will be laid out after the selection callback.
+        // If so, the layout request will be honoured though the view system will emit a double-
+        // layout warning.
+        // If not in layout, we may be scrolling in which case the child layout request will be
+        // eaten by recyclerview.  Post a requestLayout.
+        if ((mFlag & PF_STAGE_MASK) != PF_STAGE_LAYOUT && !mBaseGridView.isLayoutRequested()) {
+            int childCount = getChildCount();
+            for (int i = 0; i < childCount; i++) {
+                if (getChildAt(i).isLayoutRequested()) {
+                    forceRequestLayout();
+                    break;
+                }
+            }
+        }
+    }
+
+    private void dispatchChildSelectedAndPositioned() {
+        if (!hasOnChildViewHolderSelectedListener()) {
+            return;
+        }
+
+        if (TRACE) TraceCompat.beginSection("onChildSelectedAndPositioned");
+        View view = mFocusPosition == NO_POSITION ? null : findViewByPosition(mFocusPosition);
+        if (view != null) {
+            RecyclerView.ViewHolder vh = mBaseGridView.getChildViewHolder(view);
+            fireOnChildViewHolderSelectedAndPositioned(mBaseGridView, vh, mFocusPosition,
+                    mSubFocusPosition);
+        } else {
+            if (mChildSelectedListener != null) {
+                mChildSelectedListener.onChildSelected(mBaseGridView, null, NO_POSITION, NO_ID);
+            }
+            fireOnChildViewHolderSelectedAndPositioned(mBaseGridView, null, NO_POSITION, 0);
+        }
+        if (TRACE) TraceCompat.endSection();
+
+    }
+
+    @Override
+    public boolean canScrollHorizontally() {
+        // We can scroll horizontally if we have horizontal orientation, or if
+        // we are vertical and have more than one column.
+        return mOrientation == HORIZONTAL || mNumRows > 1;
+    }
+
+    @Override
+    public boolean canScrollVertically() {
+        // We can scroll vertically if we have vertical orientation, or if we
+        // are horizontal and have more than one row.
+        return mOrientation == VERTICAL || mNumRows > 1;
+    }
+
+    @Override
+    public RecyclerView.LayoutParams generateDefaultLayoutParams() {
+        return new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
+                ViewGroup.LayoutParams.WRAP_CONTENT);
+    }
+
+    @Override
+    public RecyclerView.LayoutParams generateLayoutParams(Context context, AttributeSet attrs) {
+        return new LayoutParams(context, attrs);
+    }
+
+    @Override
+    public RecyclerView.LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
+        if (lp instanceof LayoutParams) {
+            return new LayoutParams((LayoutParams) lp);
+        } else if (lp instanceof RecyclerView.LayoutParams) {
+            return new LayoutParams((RecyclerView.LayoutParams) lp);
+        } else if (lp instanceof MarginLayoutParams) {
+            return new LayoutParams((MarginLayoutParams) lp);
+        } else {
+            return new LayoutParams(lp);
+        }
+    }
+
+    protected View getViewForPosition(int position) {
+        return mRecycler.getViewForPosition(position);
+    }
+
+    final int getOpticalLeft(View v) {
+        return ((LayoutParams) v.getLayoutParams()).getOpticalLeft(v);
+    }
+
+    final int getOpticalRight(View v) {
+        return ((LayoutParams) v.getLayoutParams()).getOpticalRight(v);
+    }
+
+    final int getOpticalTop(View v) {
+        return ((LayoutParams) v.getLayoutParams()).getOpticalTop(v);
+    }
+
+    final int getOpticalBottom(View v) {
+        return ((LayoutParams) v.getLayoutParams()).getOpticalBottom(v);
+    }
+
+    @Override
+    public int getDecoratedLeft(View child) {
+        return super.getDecoratedLeft(child) + ((LayoutParams) child.getLayoutParams()).mLeftInset;
+    }
+
+    @Override
+    public int getDecoratedTop(View child) {
+        return super.getDecoratedTop(child) + ((LayoutParams) child.getLayoutParams()).mTopInset;
+    }
+
+    @Override
+    public int getDecoratedRight(View child) {
+        return super.getDecoratedRight(child)
+                - ((LayoutParams) child.getLayoutParams()).mRightInset;
+    }
+
+    @Override
+    public int getDecoratedBottom(View child) {
+        return super.getDecoratedBottom(child)
+                - ((LayoutParams) child.getLayoutParams()).mBottomInset;
+    }
+
+    @Override
+    public void getDecoratedBoundsWithMargins(View view, Rect outBounds) {
+        super.getDecoratedBoundsWithMargins(view, outBounds);
+        LayoutParams params = ((LayoutParams) view.getLayoutParams());
+        outBounds.left += params.mLeftInset;
+        outBounds.top += params.mTopInset;
+        outBounds.right -= params.mRightInset;
+        outBounds.bottom -= params.mBottomInset;
+    }
+
+    int getViewMin(View v) {
+        return mOrientationHelper.getDecoratedStart(v);
+    }
+
+    int getViewMax(View v) {
+        return mOrientationHelper.getDecoratedEnd(v);
+    }
+
+    int getViewPrimarySize(View view) {
+        getDecoratedBoundsWithMargins(view, sTempRect);
+        return mOrientation == HORIZONTAL ? sTempRect.width() : sTempRect.height();
+    }
+
+    private int getViewCenter(View view) {
+        return (mOrientation == HORIZONTAL) ? getViewCenterX(view) : getViewCenterY(view);
+    }
+
+    private int getAdjustedViewCenter(View view) {
+        if (view.hasFocus()) {
+            View child = view.findFocus();
+            if (child != null && child != view) {
+                return getAdjustedPrimaryAlignedScrollDistance(getViewCenter(view), view, child);
+            }
+        }
+        return getViewCenter(view);
+    }
+
+    private int getViewCenterSecondary(View view) {
+        return (mOrientation == HORIZONTAL) ? getViewCenterY(view) : getViewCenterX(view);
+    }
+
+    private int getViewCenterX(View v) {
+        LayoutParams p = (LayoutParams) v.getLayoutParams();
+        return p.getOpticalLeft(v) + p.getAlignX();
+    }
+
+    private int getViewCenterY(View v) {
+        LayoutParams p = (LayoutParams) v.getLayoutParams();
+        return p.getOpticalTop(v) + p.getAlignY();
+    }
+
+    /**
+     * Save Recycler and State for convenience.  Must be paired with leaveContext().
+     */
+    private void saveContext(Recycler recycler, State state) {
+        if (mRecycler != null || mState != null) {
+            Log.e(TAG, "Recycler information was not released, bug!");
+        }
+        mRecycler = recycler;
+        mState = state;
+        mPositionDeltaInPreLayout = 0;
+        mExtraLayoutSpaceInPreLayout = 0;
+    }
+
+    /**
+     * Discard saved Recycler and State.
+     */
+    private void leaveContext() {
+        mRecycler = null;
+        mState = null;
+        mPositionDeltaInPreLayout = 0;
+        mExtraLayoutSpaceInPreLayout = 0;
+    }
+
+    /**
+     * Re-initialize data structures for a data change or handling invisible
+     * selection. The method tries its best to preserve position information so
+     * that staggered grid looks same before and after re-initialize.
+     * @return true if can fastRelayout()
+     */
+    private boolean layoutInit() {
+        final int newItemCount = mState.getItemCount();
+        if (newItemCount == 0) {
+            mFocusPosition = NO_POSITION;
+            mSubFocusPosition = 0;
+        } else if (mFocusPosition >= newItemCount) {
+            mFocusPosition = newItemCount - 1;
+            mSubFocusPosition = 0;
+        } else if (mFocusPosition == NO_POSITION && newItemCount > 0) {
+            // if focus position is never set before,  initialize it to 0
+            mFocusPosition = 0;
+            mSubFocusPosition = 0;
+        }
+        if (!mState.didStructureChange() && mGrid != null && mGrid.getFirstVisibleIndex() >= 0
+                && (mFlag & PF_FORCE_FULL_LAYOUT) == 0 && mGrid.getNumRows() == mNumRows) {
+            updateScrollController();
+            updateSecondaryScrollLimits();
+            mGrid.setSpacing(mSpacingPrimary);
+            return true;
+        } else {
+            mFlag &= ~PF_FORCE_FULL_LAYOUT;
+
+            if (mGrid == null || mNumRows != mGrid.getNumRows()
+                    || ((mFlag & PF_REVERSE_FLOW_PRIMARY) != 0) != mGrid.isReversedFlow()) {
+                mGrid = Grid.createGrid(mNumRows);
+                mGrid.setProvider(mGridProvider);
+                mGrid.setReversedFlow((mFlag & PF_REVERSE_FLOW_PRIMARY) != 0);
+            }
+            initScrollController();
+            updateSecondaryScrollLimits();
+            mGrid.setSpacing(mSpacingPrimary);
+            detachAndScrapAttachedViews(mRecycler);
+            mGrid.resetVisibleIndex();
+            mWindowAlignment.mainAxis().invalidateScrollMin();
+            mWindowAlignment.mainAxis().invalidateScrollMax();
+            return false;
+        }
+    }
+
+    private int getRowSizeSecondary(int rowIndex) {
+        if (mFixedRowSizeSecondary != 0) {
+            return mFixedRowSizeSecondary;
+        }
+        if (mRowSizeSecondary == null) {
+            return 0;
+        }
+        return mRowSizeSecondary[rowIndex];
+    }
+
+    int getRowStartSecondary(int rowIndex) {
+        int start = 0;
+        // Iterate from left to right, which is a different index traversal
+        // in RTL flow
+        if ((mFlag & PF_REVERSE_FLOW_PRIMARY) != 0) {
+            for (int i = mNumRows-1; i > rowIndex; i--) {
+                start += getRowSizeSecondary(i) + mSpacingSecondary;
+            }
+        } else {
+            for (int i = 0; i < rowIndex; i++) {
+                start += getRowSizeSecondary(i) + mSpacingSecondary;
+            }
+        }
+        return start;
+    }
+
+    private int getSizeSecondary() {
+        int rightmostIndex = (mFlag & PF_REVERSE_FLOW_SECONDARY) != 0 ? 0 : mNumRows - 1;
+        return getRowStartSecondary(rightmostIndex) + getRowSizeSecondary(rightmostIndex);
+    }
+
+    int getDecoratedMeasuredWidthWithMargin(View v) {
+        final LayoutParams lp = (LayoutParams) v.getLayoutParams();
+        return getDecoratedMeasuredWidth(v) + lp.leftMargin + lp.rightMargin;
+    }
+
+    int getDecoratedMeasuredHeightWithMargin(View v) {
+        final LayoutParams lp = (LayoutParams) v.getLayoutParams();
+        return getDecoratedMeasuredHeight(v) + lp.topMargin + lp.bottomMargin;
+    }
+
+    private void measureScrapChild(int position, int widthSpec, int heightSpec,
+            int[] measuredDimension) {
+        View view = mRecycler.getViewForPosition(position);
+        if (view != null) {
+            final LayoutParams p = (LayoutParams) view.getLayoutParams();
+            calculateItemDecorationsForChild(view, sTempRect);
+            int widthUsed = p.leftMargin + p.rightMargin + sTempRect.left + sTempRect.right;
+            int heightUsed = p.topMargin + p.bottomMargin + sTempRect.top + sTempRect.bottom;
+
+            int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,
+                    getPaddingLeft() + getPaddingRight() + widthUsed, p.width);
+            int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
+                    getPaddingTop() + getPaddingBottom() + heightUsed, p.height);
+            view.measure(childWidthSpec, childHeightSpec);
+
+            measuredDimension[0] = getDecoratedMeasuredWidthWithMargin(view);
+            measuredDimension[1] = getDecoratedMeasuredHeightWithMargin(view);
+            mRecycler.recycleView(view);
+        }
+    }
+
+    private boolean processRowSizeSecondary(boolean measure) {
+        if (mFixedRowSizeSecondary != 0 || mRowSizeSecondary == null) {
+            return false;
+        }
+
+        if (TRACE) TraceCompat.beginSection("processRowSizeSecondary");
+        CircularIntArray[] rows = mGrid == null ? null : mGrid.getItemPositionsInRows();
+        boolean changed = false;
+        int scrapeChildSize = -1;
+
+        for (int rowIndex = 0; rowIndex < mNumRows; rowIndex++) {
+            CircularIntArray row = rows == null ? null : rows[rowIndex];
+            final int rowItemsPairCount = row == null ? 0 : row.size();
+            int rowSize = -1;
+            for (int rowItemPairIndex = 0; rowItemPairIndex < rowItemsPairCount;
+                    rowItemPairIndex += 2) {
+                final int rowIndexStart = row.get(rowItemPairIndex);
+                final int rowIndexEnd = row.get(rowItemPairIndex + 1);
+                for (int i = rowIndexStart; i <= rowIndexEnd; i++) {
+                    final View view = findViewByPosition(i - mPositionDeltaInPreLayout);
+                    if (view == null) {
+                        continue;
+                    }
+                    if (measure) {
+                        measureChild(view);
+                    }
+                    final int secondarySize = mOrientation == HORIZONTAL
+                            ? getDecoratedMeasuredHeightWithMargin(view)
+                            : getDecoratedMeasuredWidthWithMargin(view);
+                    if (secondarySize > rowSize) {
+                        rowSize = secondarySize;
+                    }
+                }
+            }
+
+            final int itemCount = mState.getItemCount();
+            if (!mBaseGridView.hasFixedSize() && measure && rowSize < 0 && itemCount > 0) {
+                if (scrapeChildSize < 0) {
+                    // measure a child that is close to mFocusPosition but not currently visible
+                    int position = mFocusPosition;
+                    if (position < 0) {
+                        position = 0;
+                    } else if (position >= itemCount) {
+                        position = itemCount - 1;
+                    }
+                    if (getChildCount() > 0) {
+                        int firstPos = mBaseGridView.getChildViewHolder(
+                                getChildAt(0)).getLayoutPosition();
+                        int lastPos = mBaseGridView.getChildViewHolder(
+                                getChildAt(getChildCount() - 1)).getLayoutPosition();
+                        // if mFocusPosition is between first and last, choose either
+                        // first - 1 or last + 1
+                        if (position >= firstPos && position <= lastPos) {
+                            position = (position - firstPos <= lastPos - position)
+                                    ? (firstPos - 1) : (lastPos + 1);
+                            // try the other value if the position is invalid. if both values are
+                            // invalid, skip measureScrapChild below.
+                            if (position < 0 && lastPos < itemCount - 1) {
+                                position = lastPos + 1;
+                            } else if (position >= itemCount && firstPos > 0) {
+                                position = firstPos - 1;
+                            }
+                        }
+                    }
+                    if (position >= 0 && position < itemCount) {
+                        measureScrapChild(position,
+                                MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
+                                MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
+                                mMeasuredDimension);
+                        scrapeChildSize = mOrientation == HORIZONTAL ? mMeasuredDimension[1] :
+                                mMeasuredDimension[0];
+                        if (DEBUG) {
+                            Log.v(TAG, "measured scrap child: " + mMeasuredDimension[0] + " "
+                                    + mMeasuredDimension[1]);
+                        }
+                    }
+                }
+                if (scrapeChildSize >= 0) {
+                    rowSize = scrapeChildSize;
+                }
+            }
+            if (rowSize < 0) {
+                rowSize = 0;
+            }
+            if (mRowSizeSecondary[rowIndex] != rowSize) {
+                if (DEBUG) {
+                    Log.v(getTag(), "row size secondary changed: " + mRowSizeSecondary[rowIndex]
+                            + ", " + rowSize);
+                }
+                mRowSizeSecondary[rowIndex] = rowSize;
+                changed = true;
+            }
+        }
+
+        if (TRACE) TraceCompat.endSection();
+        return changed;
+    }
+
+    /**
+     * Checks if we need to update row secondary sizes.
+     */
+    private void updateRowSecondarySizeRefresh() {
+        mFlag = (mFlag & ~PF_ROW_SECONDARY_SIZE_REFRESH)
+                | (processRowSizeSecondary(false) ? PF_ROW_SECONDARY_SIZE_REFRESH : 0);
+        if ((mFlag & PF_ROW_SECONDARY_SIZE_REFRESH) != 0) {
+            if (DEBUG) Log.v(getTag(), "mRowSecondarySizeRefresh now set");
+            forceRequestLayout();
+        }
+    }
+
+    private void forceRequestLayout() {
+        if (DEBUG) Log.v(getTag(), "forceRequestLayout");
+        // RecyclerView prevents us from requesting layout in many cases
+        // (during layout, during scroll, etc.)
+        // For secondary row size wrap_content support we currently need a
+        // second layout pass to update the measured size after having measured
+        // and added child views in layoutChildren.
+        // Force the second layout by posting a delayed runnable.
+        // TODO: investigate allowing a second layout pass,
+        // or move child add/measure logic to the measure phase.
+        ViewCompat.postOnAnimation(mBaseGridView, mRequestLayoutRunnable);
+    }
+
+    private final Runnable mRequestLayoutRunnable = new Runnable() {
+        @Override
+        public void run() {
+            if (DEBUG) Log.v(getTag(), "request Layout from runnable");
+            requestLayout();
+        }
+    };
+
+    @Override
+    public void onMeasure(Recycler recycler, State state, int widthSpec, int heightSpec) {
+        saveContext(recycler, state);
+
+        int sizePrimary, sizeSecondary, modeSecondary, paddingSecondary;
+        int measuredSizeSecondary;
+        if (mOrientation == HORIZONTAL) {
+            sizePrimary = MeasureSpec.getSize(widthSpec);
+            sizeSecondary = MeasureSpec.getSize(heightSpec);
+            modeSecondary = MeasureSpec.getMode(heightSpec);
+            paddingSecondary = getPaddingTop() + getPaddingBottom();
+        } else {
+            sizeSecondary = MeasureSpec.getSize(widthSpec);
+            sizePrimary = MeasureSpec.getSize(heightSpec);
+            modeSecondary = MeasureSpec.getMode(widthSpec);
+            paddingSecondary = getPaddingLeft() + getPaddingRight();
+        }
+        if (DEBUG) {
+            Log.v(getTag(), "onMeasure widthSpec " + Integer.toHexString(widthSpec)
+                    + " heightSpec " + Integer.toHexString(heightSpec)
+                    + " modeSecondary " + Integer.toHexString(modeSecondary)
+                    + " sizeSecondary " + sizeSecondary + " " + this);
+        }
+
+        mMaxSizeSecondary = sizeSecondary;
+
+        if (mRowSizeSecondaryRequested == ViewGroup.LayoutParams.WRAP_CONTENT) {
+            mNumRows = mNumRowsRequested == 0 ? 1 : mNumRowsRequested;
+            mFixedRowSizeSecondary = 0;
+
+            if (mRowSizeSecondary == null || mRowSizeSecondary.length != mNumRows) {
+                mRowSizeSecondary = new int[mNumRows];
+            }
+
+            if (mState.isPreLayout()) {
+                updatePositionDeltaInPreLayout();
+            }
+            // Measure all current children and update cached row height or column width
+            processRowSizeSecondary(true);
+
+            switch (modeSecondary) {
+                case MeasureSpec.UNSPECIFIED:
+                    measuredSizeSecondary = getSizeSecondary() + paddingSecondary;
+                    break;
+                case MeasureSpec.AT_MOST:
+                    measuredSizeSecondary = Math.min(getSizeSecondary() + paddingSecondary,
+                            mMaxSizeSecondary);
+                    break;
+                case MeasureSpec.EXACTLY:
+                    measuredSizeSecondary = mMaxSizeSecondary;
+                    break;
+                default:
+                    throw new IllegalStateException("wrong spec");
+            }
+
+        } else {
+            switch (modeSecondary) {
+                case MeasureSpec.UNSPECIFIED:
+                    mFixedRowSizeSecondary = mRowSizeSecondaryRequested == 0
+                            ? sizeSecondary - paddingSecondary : mRowSizeSecondaryRequested;
+                    mNumRows = mNumRowsRequested == 0 ? 1 : mNumRowsRequested;
+                    measuredSizeSecondary = mFixedRowSizeSecondary * mNumRows + mSpacingSecondary
+                            * (mNumRows - 1) + paddingSecondary;
+                    break;
+                case MeasureSpec.AT_MOST:
+                case MeasureSpec.EXACTLY:
+                    if (mNumRowsRequested == 0 && mRowSizeSecondaryRequested == 0) {
+                        mNumRows = 1;
+                        mFixedRowSizeSecondary = sizeSecondary - paddingSecondary;
+                    } else if (mNumRowsRequested == 0) {
+                        mFixedRowSizeSecondary = mRowSizeSecondaryRequested;
+                        mNumRows = (sizeSecondary + mSpacingSecondary)
+                                / (mRowSizeSecondaryRequested + mSpacingSecondary);
+                    } else if (mRowSizeSecondaryRequested == 0) {
+                        mNumRows = mNumRowsRequested;
+                        mFixedRowSizeSecondary = (sizeSecondary - paddingSecondary
+                                - mSpacingSecondary * (mNumRows - 1)) / mNumRows;
+                    } else {
+                        mNumRows = mNumRowsRequested;
+                        mFixedRowSizeSecondary = mRowSizeSecondaryRequested;
+                    }
+                    measuredSizeSecondary = sizeSecondary;
+                    if (modeSecondary == MeasureSpec.AT_MOST) {
+                        int childrenSize = mFixedRowSizeSecondary * mNumRows + mSpacingSecondary
+                                * (mNumRows - 1) + paddingSecondary;
+                        if (childrenSize < measuredSizeSecondary) {
+                            measuredSizeSecondary = childrenSize;
+                        }
+                    }
+                    break;
+                default:
+                    throw new IllegalStateException("wrong spec");
+            }
+        }
+        if (mOrientation == HORIZONTAL) {
+            setMeasuredDimension(sizePrimary, measuredSizeSecondary);
+        } else {
+            setMeasuredDimension(measuredSizeSecondary, sizePrimary);
+        }
+        if (DEBUG) {
+            Log.v(getTag(), "onMeasure sizePrimary " + sizePrimary
+                    + " measuredSizeSecondary " + measuredSizeSecondary
+                    + " mFixedRowSizeSecondary " + mFixedRowSizeSecondary
+                    + " mNumRows " + mNumRows);
+        }
+        leaveContext();
+    }
+
+    void measureChild(View child) {
+        if (TRACE) TraceCompat.beginSection("measureChild");
+        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+        calculateItemDecorationsForChild(child, sTempRect);
+        int widthUsed = lp.leftMargin + lp.rightMargin + sTempRect.left + sTempRect.right;
+        int heightUsed = lp.topMargin + lp.bottomMargin + sTempRect.top + sTempRect.bottom;
+
+        final int secondarySpec =
+                (mRowSizeSecondaryRequested == ViewGroup.LayoutParams.WRAP_CONTENT)
+                        ? MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)
+                        : MeasureSpec.makeMeasureSpec(mFixedRowSizeSecondary, MeasureSpec.EXACTLY);
+        int widthSpec, heightSpec;
+
+        if (mOrientation == HORIZONTAL) {
+            widthSpec = ViewGroup.getChildMeasureSpec(
+                    MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), widthUsed, lp.width);
+            heightSpec = ViewGroup.getChildMeasureSpec(secondarySpec, heightUsed, lp.height);
+        } else {
+            heightSpec = ViewGroup.getChildMeasureSpec(
+                    MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), heightUsed, lp.height);
+            widthSpec = ViewGroup.getChildMeasureSpec(secondarySpec, widthUsed, lp.width);
+        }
+        child.measure(widthSpec, heightSpec);
+        if (DEBUG) {
+            Log.v(getTag(), "measureChild secondarySpec " + Integer.toHexString(secondarySpec)
+                    + " widthSpec " + Integer.toHexString(widthSpec)
+                    + " heightSpec " + Integer.toHexString(heightSpec)
+                    + " measuredWidth " + child.getMeasuredWidth()
+                    + " measuredHeight " + child.getMeasuredHeight());
+        }
+        if (DEBUG) Log.v(getTag(), "child lp width " + lp.width + " height " + lp.height);
+        if (TRACE) TraceCompat.endSection();
+    }
+
+    /**
+     * Get facet from the ViewHolder or the viewType.
+     */
+    <E> E getFacet(RecyclerView.ViewHolder vh, Class<? extends E> facetClass) {
+        E facet = null;
+        if (vh instanceof FacetProvider) {
+            facet = (E) ((FacetProvider) vh).getFacet(facetClass);
+        }
+        if (facet == null && mFacetProviderAdapter != null) {
+            FacetProvider p = mFacetProviderAdapter.getFacetProvider(vh.getItemViewType());
+            if (p != null) {
+                facet = (E) p.getFacet(facetClass);
+            }
+        }
+        return facet;
+    }
+
+    private Grid.Provider mGridProvider = new Grid.Provider() {
+
+        @Override
+        public int getMinIndex() {
+            return mPositionDeltaInPreLayout;
+        }
+
+        @Override
+        public int getCount() {
+            return mState.getItemCount() + mPositionDeltaInPreLayout;
+        }
+
+        @Override
+        public int createItem(int index, boolean append, Object[] item, boolean disappearingItem) {
+            if (TRACE) TraceCompat.beginSection("createItem");
+            if (TRACE) TraceCompat.beginSection("getview");
+            View v = getViewForPosition(index - mPositionDeltaInPreLayout);
+            if (TRACE) TraceCompat.endSection();
+            LayoutParams lp = (LayoutParams) v.getLayoutParams();
+            RecyclerView.ViewHolder vh = mBaseGridView.getChildViewHolder(v);
+            lp.setItemAlignmentFacet((ItemAlignmentFacet)getFacet(vh, ItemAlignmentFacet.class));
+            // See recyclerView docs:  we don't need re-add scraped view if it was removed.
+            if (!lp.isItemRemoved()) {
+                if (TRACE) TraceCompat.beginSection("addView");
+                if (disappearingItem) {
+                    if (append) {
+                        addDisappearingView(v);
+                    } else {
+                        addDisappearingView(v, 0);
+                    }
+                } else {
+                    if (append) {
+                        addView(v);
+                    } else {
+                        addView(v, 0);
+                    }
+                }
+                if (TRACE) TraceCompat.endSection();
+                if (mChildVisibility != -1) {
+                    v.setVisibility(mChildVisibility);
+                }
+
+                if (mPendingMoveSmoothScroller != null) {
+                    mPendingMoveSmoothScroller.consumePendingMovesBeforeLayout();
+                }
+                int subindex = getSubPositionByView(v, v.findFocus());
+                if ((mFlag & PF_STAGE_MASK) != PF_STAGE_LAYOUT) {
+                    // when we are appending item during scroll pass and the item's position
+                    // matches the mFocusPosition,  we should signal a childSelected event.
+                    // However if we are still running PendingMoveSmoothScroller,  we defer and
+                    // signal the event in PendingMoveSmoothScroller.onStop().  This can
+                    // avoid lots of childSelected events during a long smooth scrolling and
+                    // increase performance.
+                    if (index == mFocusPosition && subindex == mSubFocusPosition
+                            && mPendingMoveSmoothScroller == null) {
+                        dispatchChildSelected();
+                    }
+                } else if ((mFlag & PF_FAST_RELAYOUT) == 0) {
+                    // fastRelayout will dispatch event at end of onLayoutChildren().
+                    // For full layout, two situations here:
+                    // 1. mInLayoutSearchFocus is false, dispatchChildSelected() at mFocusPosition.
+                    // 2. mInLayoutSearchFocus is true:  dispatchChildSelected() on first child
+                    //    equal to or after mFocusPosition that can take focus.
+                    if ((mFlag & PF_IN_LAYOUT_SEARCH_FOCUS) == 0 && index == mFocusPosition
+                            && subindex == mSubFocusPosition) {
+                        dispatchChildSelected();
+                    } else if ((mFlag & PF_IN_LAYOUT_SEARCH_FOCUS) != 0 && index >= mFocusPosition
+                            && v.hasFocusable()) {
+                        mFocusPosition = index;
+                        mSubFocusPosition = subindex;
+                        mFlag &= ~PF_IN_LAYOUT_SEARCH_FOCUS;
+                        dispatchChildSelected();
+                    }
+                }
+                measureChild(v);
+            }
+            item[0] = v;
+            return mOrientation == HORIZONTAL ? getDecoratedMeasuredWidthWithMargin(v)
+                    : getDecoratedMeasuredHeightWithMargin(v);
+        }
+
+        @Override
+        public void addItem(Object item, int index, int length, int rowIndex, int edge) {
+            View v = (View) item;
+            int start, end;
+            if (edge == Integer.MIN_VALUE || edge == Integer.MAX_VALUE) {
+                edge = !mGrid.isReversedFlow() ? mWindowAlignment.mainAxis().getPaddingMin()
+                        : mWindowAlignment.mainAxis().getSize()
+                                - mWindowAlignment.mainAxis().getPaddingMax();
+            }
+            boolean edgeIsMin = !mGrid.isReversedFlow();
+            if (edgeIsMin) {
+                start = edge;
+                end = edge + length;
+            } else {
+                start = edge - length;
+                end = edge;
+            }
+            int startSecondary = getRowStartSecondary(rowIndex)
+                    + mWindowAlignment.secondAxis().getPaddingMin() - mScrollOffsetSecondary;
+            mChildrenStates.loadView(v, index);
+            layoutChild(rowIndex, v, start, end, startSecondary);
+            if (DEBUG) {
+                Log.d(getTag(), "addView " + index + " " + v);
+            }
+            if (TRACE) TraceCompat.endSection();
+
+            if (!mState.isPreLayout()) {
+                updateScrollLimits();
+            }
+            if ((mFlag & PF_STAGE_MASK) != PF_STAGE_LAYOUT && mPendingMoveSmoothScroller != null) {
+                mPendingMoveSmoothScroller.consumePendingMovesAfterLayout();
+            }
+            if (mChildLaidOutListener != null) {
+                RecyclerView.ViewHolder vh = mBaseGridView.getChildViewHolder(v);
+                mChildLaidOutListener.onChildLaidOut(mBaseGridView, v, index,
+                        vh == null ? NO_ID : vh.getItemId());
+            }
+        }
+
+        @Override
+        public void removeItem(int index) {
+            if (TRACE) TraceCompat.beginSection("removeItem");
+            View v = findViewByPosition(index - mPositionDeltaInPreLayout);
+            if ((mFlag & PF_STAGE_MASK) == PF_STAGE_LAYOUT) {
+                detachAndScrapView(v, mRecycler);
+            } else {
+                removeAndRecycleView(v, mRecycler);
+            }
+            if (TRACE) TraceCompat.endSection();
+        }
+
+        @Override
+        public int getEdge(int index) {
+            View v = findViewByPosition(index - mPositionDeltaInPreLayout);
+            return (mFlag & PF_REVERSE_FLOW_PRIMARY) != 0 ? getViewMax(v) : getViewMin(v);
+        }
+
+        @Override
+        public int getSize(int index) {
+            return getViewPrimarySize(findViewByPosition(index - mPositionDeltaInPreLayout));
+        }
+    };
+
+    void layoutChild(int rowIndex, View v, int start, int end, int startSecondary) {
+        if (TRACE) TraceCompat.beginSection("layoutChild");
+        int sizeSecondary = mOrientation == HORIZONTAL ? getDecoratedMeasuredHeightWithMargin(v)
+                : getDecoratedMeasuredWidthWithMargin(v);
+        if (mFixedRowSizeSecondary > 0) {
+            sizeSecondary = Math.min(sizeSecondary, mFixedRowSizeSecondary);
+        }
+        final int verticalGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
+        final int horizontalGravity = (mFlag & PF_REVERSE_FLOW_MASK) != 0
+                ? Gravity.getAbsoluteGravity(mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK,
+                View.LAYOUT_DIRECTION_RTL)
+                : mGravity & Gravity.HORIZONTAL_GRAVITY_MASK;
+        if ((mOrientation == HORIZONTAL && verticalGravity == Gravity.TOP)
+                || (mOrientation == VERTICAL && horizontalGravity == Gravity.LEFT)) {
+            // do nothing
+        } else if ((mOrientation == HORIZONTAL && verticalGravity == Gravity.BOTTOM)
+                || (mOrientation == VERTICAL && horizontalGravity == Gravity.RIGHT)) {
+            startSecondary += getRowSizeSecondary(rowIndex) - sizeSecondary;
+        } else if ((mOrientation == HORIZONTAL && verticalGravity == Gravity.CENTER_VERTICAL)
+                || (mOrientation == VERTICAL && horizontalGravity == Gravity.CENTER_HORIZONTAL)) {
+            startSecondary += (getRowSizeSecondary(rowIndex) - sizeSecondary) / 2;
+        }
+        int left, top, right, bottom;
+        if (mOrientation == HORIZONTAL) {
+            left = start;
+            top = startSecondary;
+            right = end;
+            bottom = startSecondary + sizeSecondary;
+        } else {
+            top = start;
+            left = startSecondary;
+            bottom = end;
+            right = startSecondary + sizeSecondary;
+        }
+        LayoutParams params = (LayoutParams) v.getLayoutParams();
+        layoutDecoratedWithMargins(v, left, top, right, bottom);
+        // Now super.getDecoratedBoundsWithMargins() includes the extra space for optical bounds,
+        // subtracting it from value passed in layoutDecoratedWithMargins(), we can get the optical
+        // bounds insets.
+        super.getDecoratedBoundsWithMargins(v, sTempRect);
+        params.setOpticalInsets(left - sTempRect.left, top - sTempRect.top,
+                sTempRect.right - right, sTempRect.bottom - bottom);
+        updateChildAlignments(v);
+        if (TRACE) TraceCompat.endSection();
+    }
+
+    private void updateChildAlignments(View v) {
+        final LayoutParams p = (LayoutParams) v.getLayoutParams();
+        if (p.getItemAlignmentFacet() == null) {
+            // Fallback to global settings on grid view
+            p.setAlignX(mItemAlignment.horizontal.getAlignmentPosition(v));
+            p.setAlignY(mItemAlignment.vertical.getAlignmentPosition(v));
+        } else {
+            // Use ItemAlignmentFacet defined on specific ViewHolder
+            p.calculateItemAlignments(mOrientation, v);
+            if (mOrientation == HORIZONTAL) {
+                p.setAlignY(mItemAlignment.vertical.getAlignmentPosition(v));
+            } else {
+                p.setAlignX(mItemAlignment.horizontal.getAlignmentPosition(v));
+            }
+        }
+    }
+
+    private void updateChildAlignments() {
+        for (int i = 0, c = getChildCount(); i < c; i++) {
+            updateChildAlignments(getChildAt(i));
+        }
+    }
+
+    void setExtraLayoutSpace(int extraLayoutSpace) {
+        if (mExtraLayoutSpace == extraLayoutSpace) {
+            return;
+        } else if (mExtraLayoutSpace < 0) {
+            throw new IllegalArgumentException("ExtraLayoutSpace must >= 0");
+        }
+        mExtraLayoutSpace = extraLayoutSpace;
+        requestLayout();
+    }
+
+    int getExtraLayoutSpace() {
+        return mExtraLayoutSpace;
+    }
+
+    private void removeInvisibleViewsAtEnd() {
+        if ((mFlag & (PF_PRUNE_CHILD | PF_SLIDING)) == PF_PRUNE_CHILD) {
+            mGrid.removeInvisibleItemsAtEnd(mFocusPosition, (mFlag & PF_REVERSE_FLOW_PRIMARY) != 0
+                    ? -mExtraLayoutSpace : mSizePrimary + mExtraLayoutSpace);
+        }
+    }
+
+    private void removeInvisibleViewsAtFront() {
+        if ((mFlag & (PF_PRUNE_CHILD | PF_SLIDING)) == PF_PRUNE_CHILD) {
+            mGrid.removeInvisibleItemsAtFront(mFocusPosition, (mFlag & PF_REVERSE_FLOW_PRIMARY) != 0
+                    ? mSizePrimary + mExtraLayoutSpace : -mExtraLayoutSpace);
+        }
+    }
+
+    private boolean appendOneColumnVisibleItems() {
+        return mGrid.appendOneColumnVisibleItems();
+    }
+
+    void slideIn() {
+        if ((mFlag & PF_SLIDING) != 0) {
+            mFlag &= ~PF_SLIDING;
+            if (mFocusPosition >= 0) {
+                scrollToSelection(mFocusPosition, mSubFocusPosition, true, mPrimaryScrollExtra);
+            } else {
+                mFlag &= ~PF_LAYOUT_EATEN_IN_SLIDING;
+                requestLayout();
+            }
+            if ((mFlag & PF_LAYOUT_EATEN_IN_SLIDING) != 0) {
+                mFlag &= ~PF_LAYOUT_EATEN_IN_SLIDING;
+                if (mBaseGridView.getScrollState() != SCROLL_STATE_IDLE || isSmoothScrolling()) {
+                    mBaseGridView.addOnScrollListener(new RecyclerView.OnScrollListener() {
+                        @Override
+                        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
+                            if (newState == SCROLL_STATE_IDLE) {
+                                mBaseGridView.removeOnScrollListener(this);
+                                requestLayout();
+                            }
+                        }
+                    });
+                } else {
+                    requestLayout();
+                }
+            }
+        }
+    }
+
+    int getSlideOutDistance() {
+        int distance;
+        if (mOrientation == VERTICAL) {
+            distance = -getHeight();
+            if (getChildCount() > 0) {
+                int top = getChildAt(0).getTop();
+                if (top < 0) {
+                    // scroll more if first child is above top edge
+                    distance = distance + top;
+                }
+            }
+        } else {
+            if ((mFlag & PF_REVERSE_FLOW_PRIMARY) != 0) {
+                distance = getWidth();
+                if (getChildCount() > 0) {
+                    int start = getChildAt(0).getRight();
+                    if (start > distance) {
+                        // scroll more if first child is outside right edge
+                        distance = start;
+                    }
+                }
+            } else {
+                distance = -getWidth();
+                if (getChildCount() > 0) {
+                    int start = getChildAt(0).getLeft();
+                    if (start < 0) {
+                        // scroll more if first child is out side left edge
+                        distance = distance + start;
+                    }
+                }
+            }
+        }
+        return distance;
+    }
+
+    boolean isSlidingChildViews() {
+        return (mFlag & PF_SLIDING) != 0;
+    }
+
+    /**
+     * Temporarily slide out child and block layout and scroll requests.
+     */
+    void slideOut() {
+        if ((mFlag & PF_SLIDING) != 0) {
+            return;
+        }
+        mFlag |= PF_SLIDING;
+        if (getChildCount() == 0) {
+            return;
+        }
+        if (mOrientation == VERTICAL) {
+            mBaseGridView.smoothScrollBy(0, getSlideOutDistance(),
+                    new AccelerateDecelerateInterpolator());
+        } else {
+            mBaseGridView.smoothScrollBy(getSlideOutDistance(), 0,
+                    new AccelerateDecelerateInterpolator());
+        }
+    }
+
+    private boolean prependOneColumnVisibleItems() {
+        return mGrid.prependOneColumnVisibleItems();
+    }
+
+    private void appendVisibleItems() {
+        mGrid.appendVisibleItems((mFlag & PF_REVERSE_FLOW_PRIMARY) != 0
+                ? -mExtraLayoutSpace - mExtraLayoutSpaceInPreLayout
+                : mSizePrimary + mExtraLayoutSpace + mExtraLayoutSpaceInPreLayout);
+    }
+
+    private void prependVisibleItems() {
+        mGrid.prependVisibleItems((mFlag & PF_REVERSE_FLOW_PRIMARY) != 0
+                ? mSizePrimary + mExtraLayoutSpace + mExtraLayoutSpaceInPreLayout
+                : -mExtraLayoutSpace - mExtraLayoutSpaceInPreLayout);
+    }
+
+    /**
+     * Fast layout when there is no structure change, adapter change, etc.
+     * It will layout all views was layout requested or updated, until hit a view
+     * with different size,  then it break and detachAndScrap all views after that.
+     */
+    private void fastRelayout() {
+        boolean invalidateAfter = false;
+        final int childCount = getChildCount();
+        int position = mGrid.getFirstVisibleIndex();
+        int index = 0;
+        mFlag &= ~PF_FAST_RELAYOUT_UPDATED_SELECTED_POSITION;
+        for (; index < childCount; index++, position++) {
+            View view = getChildAt(index);
+            // We don't hit fastRelayout() if State.didStructure() is true, but prelayout may add
+            // extra views and invalidate existing Grid position. Also the prelayout calling
+            // getViewForPosotion() may retrieve item from cache with FLAG_INVALID. The adapter
+            // postion will be -1 for this case. Either case, we should invalidate after this item
+            // and call getViewForPosition() again to rebind.
+            if (position != getAdapterPositionByView(view)) {
+                invalidateAfter = true;
+                break;
+            }
+            Grid.Location location = mGrid.getLocation(position);
+            if (location == null) {
+                invalidateAfter = true;
+                break;
+            }
+
+            int startSecondary = getRowStartSecondary(location.row)
+                    + mWindowAlignment.secondAxis().getPaddingMin() - mScrollOffsetSecondary;
+            int primarySize, end;
+            int start = getViewMin(view);
+            int oldPrimarySize = getViewPrimarySize(view);
+
+            LayoutParams lp = (LayoutParams) view.getLayoutParams();
+            if (lp.viewNeedsUpdate()) {
+                mFlag |= PF_FAST_RELAYOUT_UPDATED_SELECTED_POSITION;
+                detachAndScrapView(view, mRecycler);
+                view = getViewForPosition(position);
+                addView(view, index);
+            }
+
+            measureChild(view);
+            if (mOrientation == HORIZONTAL) {
+                primarySize = getDecoratedMeasuredWidthWithMargin(view);
+                end = start + primarySize;
+            } else {
+                primarySize = getDecoratedMeasuredHeightWithMargin(view);
+                end = start + primarySize;
+            }
+            layoutChild(location.row, view, start, end, startSecondary);
+            if (oldPrimarySize != primarySize) {
+                // size changed invalidate remaining Locations
+                if (DEBUG) Log.d(getTag(), "fastRelayout: view size changed at " + position);
+                invalidateAfter = true;
+                break;
+            }
+        }
+        if (invalidateAfter) {
+            final int savedLastPos = mGrid.getLastVisibleIndex();
+            for (int i = childCount - 1; i >= index; i--) {
+                View v = getChildAt(i);
+                detachAndScrapView(v, mRecycler);
+            }
+            mGrid.invalidateItemsAfter(position);
+            if ((mFlag & PF_PRUNE_CHILD) != 0) {
+                // in regular prune child mode, we just append items up to edge limit
+                appendVisibleItems();
+                if (mFocusPosition >= 0 && mFocusPosition <= savedLastPos) {
+                    // make sure add focus view back:  the view might be outside edge limit
+                    // when there is delta in onLayoutChildren().
+                    while (mGrid.getLastVisibleIndex() < mFocusPosition) {
+                        mGrid.appendOneColumnVisibleItems();
+                    }
+                }
+            } else {
+                // prune disabled(e.g. in RowsFragment transition): append all removed items
+                while (mGrid.appendOneColumnVisibleItems()
+                        && mGrid.getLastVisibleIndex() < savedLastPos);
+            }
+        }
+        updateScrollLimits();
+        updateSecondaryScrollLimits();
+    }
+
+    @Override
+    public void removeAndRecycleAllViews(RecyclerView.Recycler recycler) {
+        if (TRACE) TraceCompat.beginSection("removeAndRecycleAllViews");
+        if (DEBUG) Log.v(TAG, "removeAndRecycleAllViews " + getChildCount());
+        for (int i = getChildCount() - 1; i >= 0; i--) {
+            removeAndRecycleViewAt(i, recycler);
+        }
+        if (TRACE) TraceCompat.endSection();
+    }
+
+    // called by onLayoutChildren, either focus to FocusPosition or declare focusViewAvailable
+    // and scroll to the view if framework focus on it.
+    private void focusToViewInLayout(boolean hadFocus, boolean alignToView, int extraDelta,
+            int extraDeltaSecondary) {
+        View focusView = findViewByPosition(mFocusPosition);
+        if (focusView != null && alignToView) {
+            scrollToView(focusView, false, extraDelta, extraDeltaSecondary);
+        }
+        if (focusView != null && hadFocus && !focusView.hasFocus()) {
+            focusView.requestFocus();
+        } else if (!hadFocus && !mBaseGridView.hasFocus()) {
+            if (focusView != null && focusView.hasFocusable()) {
+                mBaseGridView.focusableViewAvailable(focusView);
+            } else {
+                for (int i = 0, count = getChildCount(); i < count; i++) {
+                    focusView = getChildAt(i);
+                    if (focusView != null && focusView.hasFocusable()) {
+                        mBaseGridView.focusableViewAvailable(focusView);
+                        break;
+                    }
+                }
+            }
+            // focusViewAvailable() might focus to the view, scroll to it if that is the case.
+            if (alignToView && focusView != null && focusView.hasFocus()) {
+                scrollToView(focusView, false, extraDelta, extraDeltaSecondary);
+            }
+        }
+    }
+
+    @VisibleForTesting
+    public static class OnLayoutCompleteListener {
+        public void onLayoutCompleted(RecyclerView.State state) {
+        }
+    }
+
+    @VisibleForTesting
+    OnLayoutCompleteListener mLayoutCompleteListener;
+
+    @Override
+    public void onLayoutCompleted(State state) {
+        if (mLayoutCompleteListener != null) {
+            mLayoutCompleteListener.onLayoutCompleted(state);
+        }
+    }
+
+    @Override
+    public boolean supportsPredictiveItemAnimations() {
+        return true;
+    }
+
+    void updatePositionToRowMapInPostLayout() {
+        mPositionToRowInPostLayout.clear();
+        final int childCount = getChildCount();
+        for (int i = 0;  i < childCount; i++) {
+            // Grid still maps to old positions at this point, use old position to get row infor
+            int position = mBaseGridView.getChildViewHolder(getChildAt(i)).getOldPosition();
+            if (position >= 0) {
+                Grid.Location loc = mGrid.getLocation(position);
+                if (loc != null) {
+                    mPositionToRowInPostLayout.put(position, loc.row);
+                }
+            }
+        }
+    }
+
+    void fillScrapViewsInPostLayout() {
+        List<RecyclerView.ViewHolder> scrapList = mRecycler.getScrapList();
+        final int scrapSize = scrapList.size();
+        if (scrapSize == 0) {
+            return;
+        }
+        // initialize the int array or re-allocate the array.
+        if (mDisappearingPositions == null  || scrapSize > mDisappearingPositions.length) {
+            int length = mDisappearingPositions == null ? 16 : mDisappearingPositions.length;
+            while (length < scrapSize) {
+                length = length << 1;
+            }
+            mDisappearingPositions = new int[length];
+        }
+        int totalItems = 0;
+        for (int i = 0; i < scrapSize; i++) {
+            int pos = scrapList.get(i).getAdapterPosition();
+            if (pos >= 0) {
+                mDisappearingPositions[totalItems++] = pos;
+            }
+        }
+        // totalItems now has the length of disappearing items
+        if (totalItems > 0) {
+            Arrays.sort(mDisappearingPositions, 0, totalItems);
+            mGrid.fillDisappearingItems(mDisappearingPositions, totalItems,
+                    mPositionToRowInPostLayout);
+        }
+        mPositionToRowInPostLayout.clear();
+    }
+
+    // in prelayout, first child's getViewPosition can be smaller than old adapter position
+    // if there were items removed before first visible index. For example:
+    // visible items are 3, 4, 5, 6, deleting 1, 2, 3 from adapter; the view position in
+    // prelayout are not 3(deleted), 4, 5, 6. Instead it's 1(deleted), 2, 3, 4.
+    // So there is a delta (2 in this case) between last cached position and prelayout position.
+    void updatePositionDeltaInPreLayout() {
+        if (getChildCount() > 0) {
+            View view = getChildAt(0);
+            LayoutParams lp = (LayoutParams) view.getLayoutParams();
+            mPositionDeltaInPreLayout = mGrid.getFirstVisibleIndex()
+                    - lp.getViewLayoutPosition();
+        } else {
+            mPositionDeltaInPreLayout = 0;
+        }
+    }
+
+    // Lays out items based on the current scroll position
+    @Override
+    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+        if (DEBUG) {
+            Log.v(getTag(), "layoutChildren start numRows " + mNumRows
+                    + " inPreLayout " + state.isPreLayout()
+                    + " didStructureChange " + state.didStructureChange()
+                    + " mForceFullLayout " + ((mFlag & PF_FORCE_FULL_LAYOUT) != 0));
+            Log.v(getTag(), "width " + getWidth() + " height " + getHeight());
+        }
+
+        if (mNumRows == 0) {
+            // haven't done measure yet
+            return;
+        }
+        final int itemCount = state.getItemCount();
+        if (itemCount < 0) {
+            return;
+        }
+
+        if ((mFlag & PF_SLIDING) != 0) {
+            // if there is already children, delay the layout process until slideIn(), if it's
+            // first time layout children: scroll them offscreen at end of onLayoutChildren()
+            if (getChildCount() > 0) {
+                mFlag |= PF_LAYOUT_EATEN_IN_SLIDING;
+                return;
+            }
+        }
+        if ((mFlag & PF_LAYOUT_ENABLED) == 0) {
+            discardLayoutInfo();
+            removeAndRecycleAllViews(recycler);
+            return;
+        }
+        mFlag = (mFlag & ~PF_STAGE_MASK) | PF_STAGE_LAYOUT;
+
+        saveContext(recycler, state);
+        if (state.isPreLayout()) {
+            updatePositionDeltaInPreLayout();
+            int childCount = getChildCount();
+            if (mGrid != null && childCount > 0) {
+                int minChangedEdge = Integer.MAX_VALUE;
+                int maxChangeEdge = Integer.MIN_VALUE;
+                int minOldAdapterPosition = mBaseGridView.getChildViewHolder(
+                        getChildAt(0)).getOldPosition();
+                int maxOldAdapterPosition = mBaseGridView.getChildViewHolder(
+                        getChildAt(childCount - 1)).getOldPosition();
+                for (int i = 0; i < childCount; i++) {
+                    View view = getChildAt(i);
+                    LayoutParams lp = (LayoutParams) view.getLayoutParams();
+                    int newAdapterPosition = mBaseGridView.getChildAdapterPosition(view);
+                    // if either of following happening
+                    // 1. item itself has changed or layout parameter changed
+                    // 2. item is losing focus
+                    // 3. item is gaining focus
+                    // 4. item is moved out of old adapter position range.
+                    if (lp.isItemChanged() || lp.isItemRemoved() || view.isLayoutRequested()
+                            || (!view.hasFocus() && mFocusPosition == lp.getViewAdapterPosition())
+                            || (view.hasFocus() && mFocusPosition != lp.getViewAdapterPosition())
+                            || newAdapterPosition < minOldAdapterPosition
+                            || newAdapterPosition > maxOldAdapterPosition) {
+                        minChangedEdge = Math.min(minChangedEdge, getViewMin(view));
+                        maxChangeEdge = Math.max(maxChangeEdge, getViewMax(view));
+                    }
+                }
+                if (maxChangeEdge > minChangedEdge) {
+                    mExtraLayoutSpaceInPreLayout = maxChangeEdge - minChangedEdge;
+                }
+                // append items for mExtraLayoutSpaceInPreLayout
+                appendVisibleItems();
+                prependVisibleItems();
+            }
+            mFlag &= ~PF_STAGE_MASK;
+            leaveContext();
+            if (DEBUG) Log.v(getTag(), "layoutChildren end");
+            return;
+        }
+
+        // save all view's row information before detach all views
+        if (state.willRunPredictiveAnimations()) {
+            updatePositionToRowMapInPostLayout();
+        }
+        // check if we need align to mFocusPosition, this is usually true unless in smoothScrolling
+        final boolean scrollToFocus = !isSmoothScrolling()
+                && mFocusScrollStrategy == BaseGridView.FOCUS_SCROLL_ALIGNED;
+        if (mFocusPosition != NO_POSITION && mFocusPositionOffset != Integer.MIN_VALUE) {
+            mFocusPosition = mFocusPosition + mFocusPositionOffset;
+            mSubFocusPosition = 0;
+        }
+        mFocusPositionOffset = 0;
+
+        View savedFocusView = findViewByPosition(mFocusPosition);
+        int savedFocusPos = mFocusPosition;
+        int savedSubFocusPos = mSubFocusPosition;
+        boolean hadFocus = mBaseGridView.hasFocus();
+        final int firstVisibleIndex = mGrid != null ? mGrid.getFirstVisibleIndex() : NO_POSITION;
+        final int lastVisibleIndex = mGrid != null ? mGrid.getLastVisibleIndex() : NO_POSITION;
+        final int deltaPrimary;
+        final int deltaSecondary;
+        if (mOrientation == HORIZONTAL) {
+            deltaPrimary = state.getRemainingScrollHorizontal();
+            deltaSecondary = state.getRemainingScrollVertical();
+        } else {
+            deltaSecondary = state.getRemainingScrollHorizontal();
+            deltaPrimary = state.getRemainingScrollVertical();
+        }
+        if (layoutInit()) {
+            mFlag |= PF_FAST_RELAYOUT;
+            // If grid view is empty, we will start from mFocusPosition
+            mGrid.setStart(mFocusPosition);
+            fastRelayout();
+        } else {
+            mFlag &= ~PF_FAST_RELAYOUT;
+            // layoutInit() has detached all views, so start from scratch
+            mFlag = (mFlag & ~PF_IN_LAYOUT_SEARCH_FOCUS)
+                    | (hadFocus ? PF_IN_LAYOUT_SEARCH_FOCUS : 0);
+            int startFromPosition, endPos;
+            if (scrollToFocus && (firstVisibleIndex < 0 || mFocusPosition > lastVisibleIndex
+                    || mFocusPosition < firstVisibleIndex)) {
+                startFromPosition = endPos = mFocusPosition;
+            } else {
+                startFromPosition = firstVisibleIndex;
+                endPos = lastVisibleIndex;
+            }
+            mGrid.setStart(startFromPosition);
+            if (endPos != NO_POSITION) {
+                while (appendOneColumnVisibleItems() && findViewByPosition(endPos) == null) {
+                    // continuously append items until endPos
+                }
+            }
+        }
+        // multiple rounds: scrollToView of first round may drag first/last child into
+        // "visible window" and we update scrollMin/scrollMax then run second scrollToView
+        // we must do this for fastRelayout() for the append item case
+        int oldFirstVisible;
+        int oldLastVisible;
+        do {
+            updateScrollLimits();
+            oldFirstVisible = mGrid.getFirstVisibleIndex();
+            oldLastVisible = mGrid.getLastVisibleIndex();
+            focusToViewInLayout(hadFocus, scrollToFocus, -deltaPrimary, -deltaSecondary);
+            appendVisibleItems();
+            prependVisibleItems();
+            // b/67370222: do not removeInvisibleViewsAtFront/End() in the loop, otherwise
+            // loop may bounce between scroll forward and scroll backward forever. Example:
+            // Assuming there are 19 items, child#18 and child#19 are both in RV, we are
+            // trying to focus to child#18 and there are 200px remaining scroll distance.
+            //   1  focusToViewInLayout() tries scroll forward 50 px to align focused child#18 on
+            //      right edge, but there to compensate remaining scroll 200px, also scroll
+            //      backward 200px, 150px pushes last child#19 out side of right edge.
+            //   2  removeInvisibleViewsAtEnd() remove last child#19, updateScrollLimits()
+            //      invalidates scroll max
+            //   3  In next iteration, when scroll max/min is unknown, focusToViewInLayout() will
+            //      align focused child#18 at center of screen.
+            //   4  Because #18 is aligned at center, appendVisibleItems() will fill child#19 to
+            //      the right.
+            //   5  (back to 1 and loop forever)
+        } while (mGrid.getFirstVisibleIndex() != oldFirstVisible
+                || mGrid.getLastVisibleIndex() != oldLastVisible);
+        removeInvisibleViewsAtFront();
+        removeInvisibleViewsAtEnd();
+
+        if (state.willRunPredictiveAnimations()) {
+            fillScrapViewsInPostLayout();
+        }
+
+        if (DEBUG) {
+            StringWriter sw = new StringWriter();
+            PrintWriter pw = new PrintWriter(sw);
+            mGrid.debugPrint(pw);
+            Log.d(getTag(), sw.toString());
+        }
+
+        if ((mFlag & PF_ROW_SECONDARY_SIZE_REFRESH) != 0) {
+            mFlag &= ~PF_ROW_SECONDARY_SIZE_REFRESH;
+        } else {
+            updateRowSecondarySizeRefresh();
+        }
+
+        // For fastRelayout, only dispatch event when focus position changes or selected item
+        // being updated.
+        if ((mFlag & PF_FAST_RELAYOUT) != 0 && (mFocusPosition != savedFocusPos || mSubFocusPosition
+                != savedSubFocusPos || findViewByPosition(mFocusPosition) != savedFocusView
+                || (mFlag & PF_FAST_RELAYOUT_UPDATED_SELECTED_POSITION) != 0)) {
+            dispatchChildSelected();
+        } else if ((mFlag & (PF_FAST_RELAYOUT | PF_IN_LAYOUT_SEARCH_FOCUS))
+                == PF_IN_LAYOUT_SEARCH_FOCUS) {
+            // For full layout we dispatchChildSelected() in createItem() unless searched all
+            // children and found none is focusable then dispatchChildSelected() here.
+            dispatchChildSelected();
+        }
+        dispatchChildSelectedAndPositioned();
+        if ((mFlag & PF_SLIDING) != 0) {
+            scrollDirectionPrimary(getSlideOutDistance());
+        }
+
+        mFlag &= ~PF_STAGE_MASK;
+        leaveContext();
+        if (DEBUG) Log.v(getTag(), "layoutChildren end");
+    }
+
+    private void offsetChildrenSecondary(int increment) {
+        final int childCount = getChildCount();
+        if (mOrientation == HORIZONTAL) {
+            for (int i = 0; i < childCount; i++) {
+                getChildAt(i).offsetTopAndBottom(increment);
+            }
+        } else {
+            for (int i = 0; i < childCount; i++) {
+                getChildAt(i).offsetLeftAndRight(increment);
+            }
+        }
+    }
+
+    private void offsetChildrenPrimary(int increment) {
+        final int childCount = getChildCount();
+        if (mOrientation == VERTICAL) {
+            for (int i = 0; i < childCount; i++) {
+                getChildAt(i).offsetTopAndBottom(increment);
+            }
+        } else {
+            for (int i = 0; i < childCount; i++) {
+                getChildAt(i).offsetLeftAndRight(increment);
+            }
+        }
+    }
+
+    @Override
+    public int scrollHorizontallyBy(int dx, Recycler recycler, RecyclerView.State state) {
+        if (DEBUG) Log.v(getTag(), "scrollHorizontallyBy " + dx);
+        if ((mFlag & PF_LAYOUT_ENABLED) == 0 || !hasDoneFirstLayout()) {
+            return 0;
+        }
+        saveContext(recycler, state);
+        mFlag = (mFlag & ~PF_STAGE_MASK) | PF_STAGE_SCROLL;
+        int result;
+        if (mOrientation == HORIZONTAL) {
+            result = scrollDirectionPrimary(dx);
+        } else {
+            result = scrollDirectionSecondary(dx);
+        }
+        leaveContext();
+        mFlag &= ~PF_STAGE_MASK;
+        return result;
+    }
+
+    @Override
+    public int scrollVerticallyBy(int dy, Recycler recycler, RecyclerView.State state) {
+        if (DEBUG) Log.v(getTag(), "scrollVerticallyBy " + dy);
+        if ((mFlag & PF_LAYOUT_ENABLED) == 0 || !hasDoneFirstLayout()) {
+            return 0;
+        }
+        mFlag = (mFlag & ~PF_STAGE_MASK) | PF_STAGE_SCROLL;
+        saveContext(recycler, state);
+        int result;
+        if (mOrientation == VERTICAL) {
+            result = scrollDirectionPrimary(dy);
+        } else {
+            result = scrollDirectionSecondary(dy);
+        }
+        leaveContext();
+        mFlag &= ~PF_STAGE_MASK;
+        return result;
+    }
+
+    // scroll in main direction may add/prune views
+    private int scrollDirectionPrimary(int da) {
+        if (TRACE) TraceCompat.beginSection("scrollPrimary");
+        // We apply the cap of maxScroll/minScroll to the delta, except for two cases:
+        // 1. when children are in sliding out mode
+        // 2. During onLayoutChildren(), it may compensate the remaining scroll delta,
+        //    we should honor the request regardless if it goes over minScroll / maxScroll.
+        //    (see b/64931938 testScrollAndRemove and testScrollAndRemoveSample1)
+        if ((mFlag & PF_SLIDING) == 0 && (mFlag & PF_STAGE_MASK) != PF_STAGE_LAYOUT) {
+            if (da > 0) {
+                if (!mWindowAlignment.mainAxis().isMaxUnknown()) {
+                    int maxScroll = mWindowAlignment.mainAxis().getMaxScroll();
+                    if (da > maxScroll) {
+                        da = maxScroll;
+                    }
+                }
+            } else if (da < 0) {
+                if (!mWindowAlignment.mainAxis().isMinUnknown()) {
+                    int minScroll = mWindowAlignment.mainAxis().getMinScroll();
+                    if (da < minScroll) {
+                        da = minScroll;
+                    }
+                }
+            }
+        }
+        if (da == 0) {
+            if (TRACE) TraceCompat.endSection();
+            return 0;
+        }
+        offsetChildrenPrimary(-da);
+        if ((mFlag & PF_STAGE_MASK) == PF_STAGE_LAYOUT) {
+            updateScrollLimits();
+            if (TRACE) TraceCompat.endSection();
+            return da;
+        }
+
+        int childCount = getChildCount();
+        boolean updated;
+
+        if ((mFlag & PF_REVERSE_FLOW_PRIMARY) != 0 ? da > 0 : da < 0) {
+            prependVisibleItems();
+        } else {
+            appendVisibleItems();
+        }
+        updated = getChildCount() > childCount;
+        childCount = getChildCount();
+
+        if (TRACE) TraceCompat.beginSection("remove");
+        if ((mFlag & PF_REVERSE_FLOW_PRIMARY) != 0 ? da > 0 : da < 0) {
+            removeInvisibleViewsAtEnd();
+        } else {
+            removeInvisibleViewsAtFront();
+        }
+        if (TRACE) TraceCompat.endSection();
+        updated |= getChildCount() < childCount;
+        if (updated) {
+            updateRowSecondarySizeRefresh();
+        }
+
+        mBaseGridView.invalidate();
+        updateScrollLimits();
+        if (TRACE) TraceCompat.endSection();
+        return da;
+    }
+
+    // scroll in second direction will not add/prune views
+    private int scrollDirectionSecondary(int dy) {
+        if (dy == 0) {
+            return 0;
+        }
+        offsetChildrenSecondary(-dy);
+        mScrollOffsetSecondary += dy;
+        updateSecondaryScrollLimits();
+        mBaseGridView.invalidate();
+        return dy;
+    }
+
+    @Override
+    public void collectAdjacentPrefetchPositions(int dx, int dy, State state,
+            LayoutPrefetchRegistry layoutPrefetchRegistry) {
+        try {
+            saveContext(null, state);
+            int da = (mOrientation == HORIZONTAL) ? dx : dy;
+            if (getChildCount() == 0 || da == 0) {
+                // can't support this scroll, so don't bother prefetching
+                return;
+            }
+
+            int fromLimit = da < 0
+                    ? -mExtraLayoutSpace
+                    : mSizePrimary + mExtraLayoutSpace;
+            mGrid.collectAdjacentPrefetchPositions(fromLimit, da, layoutPrefetchRegistry);
+        } finally {
+            leaveContext();
+        }
+    }
+
+    @Override
+    public void collectInitialPrefetchPositions(int adapterItemCount,
+            LayoutPrefetchRegistry layoutPrefetchRegistry) {
+        int numToPrefetch = mBaseGridView.mInitialPrefetchItemCount;
+        if (adapterItemCount != 0 && numToPrefetch != 0) {
+            // prefetch items centered around mFocusPosition
+            int initialPos = Math.max(0, Math.min(mFocusPosition - (numToPrefetch - 1)/ 2,
+                    adapterItemCount - numToPrefetch));
+            for (int i = initialPos; i < adapterItemCount && i < initialPos + numToPrefetch; i++) {
+                layoutPrefetchRegistry.addPosition(i, 0);
+            }
+        }
+    }
+
+    void updateScrollLimits() {
+        if (mState.getItemCount() == 0) {
+            return;
+        }
+        int highVisiblePos, lowVisiblePos;
+        int highMaxPos, lowMinPos;
+        if ((mFlag & PF_REVERSE_FLOW_PRIMARY) == 0) {
+            highVisiblePos = mGrid.getLastVisibleIndex();
+            highMaxPos = mState.getItemCount() - 1;
+            lowVisiblePos = mGrid.getFirstVisibleIndex();
+            lowMinPos = 0;
+        } else {
+            highVisiblePos = mGrid.getFirstVisibleIndex();
+            highMaxPos = 0;
+            lowVisiblePos = mGrid.getLastVisibleIndex();
+            lowMinPos = mState.getItemCount() - 1;
+        }
+        if (highVisiblePos < 0 || lowVisiblePos < 0) {
+            return;
+        }
+        final boolean highAvailable = highVisiblePos == highMaxPos;
+        final boolean lowAvailable = lowVisiblePos == lowMinPos;
+        if (!highAvailable && mWindowAlignment.mainAxis().isMaxUnknown()
+                && !lowAvailable && mWindowAlignment.mainAxis().isMinUnknown()) {
+            return;
+        }
+        int maxEdge, maxViewCenter;
+        if (highAvailable) {
+            maxEdge = mGrid.findRowMax(true, sTwoInts);
+            View maxChild = findViewByPosition(sTwoInts[1]);
+            maxViewCenter = getViewCenter(maxChild);
+            final LayoutParams lp = (LayoutParams) maxChild.getLayoutParams();
+            int[] multipleAligns = lp.getAlignMultiple();
+            if (multipleAligns != null && multipleAligns.length > 0) {
+                maxViewCenter += multipleAligns[multipleAligns.length - 1] - multipleAligns[0];
+            }
+        } else {
+            maxEdge = Integer.MAX_VALUE;
+            maxViewCenter = Integer.MAX_VALUE;
+        }
+        int minEdge, minViewCenter;
+        if (lowAvailable) {
+            minEdge = mGrid.findRowMin(false, sTwoInts);
+            View minChild = findViewByPosition(sTwoInts[1]);
+            minViewCenter = getViewCenter(minChild);
+        } else {
+            minEdge = Integer.MIN_VALUE;
+            minViewCenter = Integer.MIN_VALUE;
+        }
+        mWindowAlignment.mainAxis().updateMinMax(minEdge, maxEdge, minViewCenter, maxViewCenter);
+    }
+
+    /**
+     * Update secondary axis's scroll min/max, should be updated in
+     * {@link #scrollDirectionSecondary(int)}.
+     */
+    private void updateSecondaryScrollLimits() {
+        WindowAlignment.Axis secondAxis = mWindowAlignment.secondAxis();
+        int minEdge = secondAxis.getPaddingMin() - mScrollOffsetSecondary;
+        int maxEdge = minEdge + getSizeSecondary();
+        secondAxis.updateMinMax(minEdge, maxEdge, minEdge, maxEdge);
+    }
+
+    private void initScrollController() {
+        mWindowAlignment.reset();
+        mWindowAlignment.horizontal.setSize(getWidth());
+        mWindowAlignment.vertical.setSize(getHeight());
+        mWindowAlignment.horizontal.setPadding(getPaddingLeft(), getPaddingRight());
+        mWindowAlignment.vertical.setPadding(getPaddingTop(), getPaddingBottom());
+        mSizePrimary = mWindowAlignment.mainAxis().getSize();
+        mScrollOffsetSecondary = 0;
+
+        if (DEBUG) {
+            Log.v(getTag(), "initScrollController mSizePrimary " + mSizePrimary
+                    + " mWindowAlignment " + mWindowAlignment);
+        }
+    }
+
+    private void updateScrollController() {
+        mWindowAlignment.horizontal.setSize(getWidth());
+        mWindowAlignment.vertical.setSize(getHeight());
+        mWindowAlignment.horizontal.setPadding(getPaddingLeft(), getPaddingRight());
+        mWindowAlignment.vertical.setPadding(getPaddingTop(), getPaddingBottom());
+        mSizePrimary = mWindowAlignment.mainAxis().getSize();
+
+        if (DEBUG) {
+            Log.v(getTag(), "updateScrollController mSizePrimary " + mSizePrimary
+                    + " mWindowAlignment " + mWindowAlignment);
+        }
+    }
+
+    @Override
+    public void scrollToPosition(int position) {
+        setSelection(position, 0, false, 0);
+    }
+
+    @Override
+    public void smoothScrollToPosition(RecyclerView recyclerView, State state,
+            int position) {
+        setSelection(position, 0, true, 0);
+    }
+
+    public void setSelection(int position,
+            int primaryScrollExtra) {
+        setSelection(position, 0, false, primaryScrollExtra);
+    }
+
+    public void setSelectionSmooth(int position) {
+        setSelection(position, 0, true, 0);
+    }
+
+    public void setSelectionWithSub(int position, int subposition,
+            int primaryScrollExtra) {
+        setSelection(position, subposition, false, primaryScrollExtra);
+    }
+
+    public void setSelectionSmoothWithSub(int position, int subposition) {
+        setSelection(position, subposition, true, 0);
+    }
+
+    public int getSelection() {
+        return mFocusPosition;
+    }
+
+    public int getSubSelection() {
+        return mSubFocusPosition;
+    }
+
+    public void setSelection(int position, int subposition, boolean smooth,
+            int primaryScrollExtra) {
+        if ((mFocusPosition != position && position != NO_POSITION)
+                || subposition != mSubFocusPosition || primaryScrollExtra != mPrimaryScrollExtra) {
+            scrollToSelection(position, subposition, smooth, primaryScrollExtra);
+        }
+    }
+
+    void scrollToSelection(int position, int subposition,
+            boolean smooth, int primaryScrollExtra) {
+        if (TRACE) TraceCompat.beginSection("scrollToSelection");
+        mPrimaryScrollExtra = primaryScrollExtra;
+        View view = findViewByPosition(position);
+        // scrollToView() is based on Adapter position. Only call scrollToView() when item
+        // is still valid.
+        if (view != null && getAdapterPositionByView(view) == position) {
+            mFlag |= PF_IN_SELECTION;
+            scrollToView(view, smooth);
+            mFlag &= ~PF_IN_SELECTION;
+        } else {
+            mFocusPosition = position;
+            mSubFocusPosition = subposition;
+            mFocusPositionOffset = Integer.MIN_VALUE;
+            if ((mFlag & PF_LAYOUT_ENABLED) == 0 || (mFlag & PF_SLIDING) != 0) {
+                return;
+            }
+            if (smooth) {
+                if (!hasDoneFirstLayout()) {
+                    Log.w(getTag(), "setSelectionSmooth should "
+                            + "not be called before first layout pass");
+                    return;
+                }
+                position = startPositionSmoothScroller(position);
+                if (position != mFocusPosition) {
+                    // gets cropped by adapter size
+                    mFocusPosition = position;
+                    mSubFocusPosition = 0;
+                }
+            } else {
+                mFlag |= PF_FORCE_FULL_LAYOUT;
+                requestLayout();
+            }
+        }
+        if (TRACE) TraceCompat.endSection();
+    }
+
+    int startPositionSmoothScroller(int position) {
+        LinearSmoothScroller linearSmoothScroller = new GridLinearSmoothScroller() {
+            @Override
+            public PointF computeScrollVectorForPosition(int targetPosition) {
+                if (getChildCount() == 0) {
+                    return null;
+                }
+                final int firstChildPos = getPosition(getChildAt(0));
+                // TODO We should be able to deduce direction from bounds of current and target
+                // focus, rather than making assumptions about positions and directionality
+                final boolean isStart = (mFlag & PF_REVERSE_FLOW_PRIMARY) != 0
+                        ? targetPosition > firstChildPos
+                        : targetPosition < firstChildPos;
+                final int direction = isStart ? -1 : 1;
+                if (mOrientation == HORIZONTAL) {
+                    return new PointF(direction, 0);
+                } else {
+                    return new PointF(0, direction);
+                }
+            }
+
+        };
+        linearSmoothScroller.setTargetPosition(position);
+        startSmoothScroll(linearSmoothScroller);
+        return linearSmoothScroller.getTargetPosition();
+    }
+
+    private void processPendingMovement(boolean forward) {
+        if (forward ? hasCreatedLastItem() : hasCreatedFirstItem()) {
+            return;
+        }
+        if (mPendingMoveSmoothScroller == null) {
+            // Stop existing scroller and create a new PendingMoveSmoothScroller.
+            mBaseGridView.stopScroll();
+            PendingMoveSmoothScroller linearSmoothScroller = new PendingMoveSmoothScroller(
+                    forward ? 1 : -1, mNumRows > 1);
+            mFocusPositionOffset = 0;
+            startSmoothScroll(linearSmoothScroller);
+            if (linearSmoothScroller.isRunning()) {
+                mPendingMoveSmoothScroller = linearSmoothScroller;
+            }
+        } else {
+            if (forward) {
+                mPendingMoveSmoothScroller.increasePendingMoves();
+            } else {
+                mPendingMoveSmoothScroller.decreasePendingMoves();
+            }
+        }
+    }
+
+    @Override
+    public void onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount) {
+        if (DEBUG) Log.v(getTag(), "onItemsAdded positionStart "
+                + positionStart + " itemCount " + itemCount);
+        if (mFocusPosition != NO_POSITION && mGrid != null && mGrid.getFirstVisibleIndex() >= 0
+                && mFocusPositionOffset != Integer.MIN_VALUE) {
+            int pos = mFocusPosition + mFocusPositionOffset;
+            if (positionStart <= pos) {
+                mFocusPositionOffset += itemCount;
+            }
+        }
+        mChildrenStates.clear();
+    }
+
+    @Override
+    public void onItemsChanged(RecyclerView recyclerView) {
+        if (DEBUG) Log.v(getTag(), "onItemsChanged");
+        mFocusPositionOffset = 0;
+        mChildrenStates.clear();
+    }
+
+    @Override
+    public void onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount) {
+        if (DEBUG) Log.v(getTag(), "onItemsRemoved positionStart "
+                + positionStart + " itemCount " + itemCount);
+        if (mFocusPosition != NO_POSITION  && mGrid != null && mGrid.getFirstVisibleIndex() >= 0
+                && mFocusPositionOffset != Integer.MIN_VALUE) {
+            int pos = mFocusPosition + mFocusPositionOffset;
+            if (positionStart <= pos) {
+                if (positionStart + itemCount > pos) {
+                    // stop updating offset after the focus item was removed
+                    mFocusPositionOffset += positionStart - pos;
+                    mFocusPosition += mFocusPositionOffset;
+                    mFocusPositionOffset = Integer.MIN_VALUE;
+                } else {
+                    mFocusPositionOffset -= itemCount;
+                }
+            }
+        }
+        mChildrenStates.clear();
+    }
+
+    @Override
+    public void onItemsMoved(RecyclerView recyclerView, int fromPosition, int toPosition,
+            int itemCount) {
+        if (DEBUG) Log.v(getTag(), "onItemsMoved fromPosition "
+                + fromPosition + " toPosition " + toPosition);
+        if (mFocusPosition != NO_POSITION && mFocusPositionOffset != Integer.MIN_VALUE) {
+            int pos = mFocusPosition + mFocusPositionOffset;
+            if (fromPosition <= pos && pos < fromPosition + itemCount) {
+                // moved items include focused position
+                mFocusPositionOffset += toPosition - fromPosition;
+            } else if (fromPosition < pos && toPosition > pos - itemCount) {
+                // move items before focus position to after focused position
+                mFocusPositionOffset -= itemCount;
+            } else if (fromPosition > pos && toPosition < pos) {
+                // move items after focus position to before focused position
+                mFocusPositionOffset += itemCount;
+            }
+        }
+        mChildrenStates.clear();
+    }
+
+    @Override
+    public void onItemsUpdated(RecyclerView recyclerView, int positionStart, int itemCount) {
+        if (DEBUG) Log.v(getTag(), "onItemsUpdated positionStart "
+                + positionStart + " itemCount " + itemCount);
+        for (int i = positionStart, end = positionStart + itemCount; i < end; i++) {
+            mChildrenStates.remove(i);
+        }
+    }
+
+    @Override
+    public boolean onRequestChildFocus(RecyclerView parent, View child, View focused) {
+        if ((mFlag & PF_FOCUS_SEARCH_DISABLED) != 0) {
+            return true;
+        }
+        if (getAdapterPositionByView(child) == NO_POSITION) {
+            // This is could be the last view in DISAPPEARING animation.
+            return true;
+        }
+        if ((mFlag & (PF_STAGE_MASK | PF_IN_SELECTION)) == 0) {
+            scrollToView(child, focused, true);
+        }
+        return true;
+    }
+
+    @Override
+    public boolean requestChildRectangleOnScreen(RecyclerView parent, View view, Rect rect,
+            boolean immediate) {
+        if (DEBUG) Log.v(getTag(), "requestChildRectangleOnScreen " + view + " " + rect);
+        return false;
+    }
+
+    public void getViewSelectedOffsets(View view, int[] offsets) {
+        if (mOrientation == HORIZONTAL) {
+            offsets[0] = getPrimaryAlignedScrollDistance(view);
+            offsets[1] = getSecondaryScrollDistance(view);
+        } else {
+            offsets[1] = getPrimaryAlignedScrollDistance(view);
+            offsets[0] = getSecondaryScrollDistance(view);
+        }
+    }
+
+    /**
+     * Return the scroll delta on primary direction to make the view selected. If the return value
+     * is 0, there is no need to scroll.
+     */
+    private int getPrimaryAlignedScrollDistance(View view) {
+        return mWindowAlignment.mainAxis().getScroll(getViewCenter(view));
+    }
+
+    /**
+     * Get adjusted primary position for a given childView (if there is multiple ItemAlignment
+     * defined on the view).
+     */
+    private int getAdjustedPrimaryAlignedScrollDistance(int scrollPrimary, View view,
+            View childView) {
+        int subindex = getSubPositionByView(view, childView);
+        if (subindex != 0) {
+            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
+            scrollPrimary += lp.getAlignMultiple()[subindex] - lp.getAlignMultiple()[0];
+        }
+        return scrollPrimary;
+    }
+
+    private int getSecondaryScrollDistance(View view) {
+        int viewCenterSecondary = getViewCenterSecondary(view);
+        return mWindowAlignment.secondAxis().getScroll(viewCenterSecondary);
+    }
+
+    /**
+     * Scroll to a given child view and change mFocusPosition. Ignored when in slideOut() state.
+     */
+    void scrollToView(View view, boolean smooth) {
+        scrollToView(view, view == null ? null : view.findFocus(), smooth);
+    }
+
+    void scrollToView(View view, boolean smooth, int extraDelta, int extraDeltaSecondary) {
+        scrollToView(view, view == null ? null : view.findFocus(), smooth, extraDelta,
+                extraDeltaSecondary);
+    }
+
+    private void scrollToView(View view, View childView, boolean smooth) {
+        scrollToView(view, childView, smooth, 0, 0);
+    }
+    /**
+     * Scroll to a given child view and change mFocusPosition. Ignored when in slideOut() state.
+     */
+    private void scrollToView(View view, View childView, boolean smooth, int extraDelta,
+            int extraDeltaSecondary) {
+        if ((mFlag & PF_SLIDING) != 0) {
+            return;
+        }
+        int newFocusPosition = getAdapterPositionByView(view);
+        int newSubFocusPosition = getSubPositionByView(view, childView);
+        if (newFocusPosition != mFocusPosition || newSubFocusPosition != mSubFocusPosition) {
+            mFocusPosition = newFocusPosition;
+            mSubFocusPosition = newSubFocusPosition;
+            mFocusPositionOffset = 0;
+            if ((mFlag & PF_STAGE_MASK) != PF_STAGE_LAYOUT) {
+                dispatchChildSelected();
+            }
+            if (mBaseGridView.isChildrenDrawingOrderEnabledInternal()) {
+                mBaseGridView.invalidate();
+            }
+        }
+        if (view == null) {
+            return;
+        }
+        if (!view.hasFocus() && mBaseGridView.hasFocus()) {
+            // transfer focus to the child if it does not have focus yet (e.g. triggered
+            // by setSelection())
+            view.requestFocus();
+        }
+        if ((mFlag & PF_SCROLL_ENABLED) == 0 && smooth) {
+            return;
+        }
+        if (getScrollPosition(view, childView, sTwoInts)
+                || extraDelta != 0 || extraDeltaSecondary != 0) {
+            scrollGrid(sTwoInts[0] + extraDelta, sTwoInts[1] + extraDeltaSecondary, smooth);
+        }
+    }
+
+    boolean getScrollPosition(View view, View childView, int[] deltas) {
+        switch (mFocusScrollStrategy) {
+            case BaseGridView.FOCUS_SCROLL_ALIGNED:
+            default:
+                return getAlignedPosition(view, childView, deltas);
+            case BaseGridView.FOCUS_SCROLL_ITEM:
+            case BaseGridView.FOCUS_SCROLL_PAGE:
+                return getNoneAlignedPosition(view, deltas);
+        }
+    }
+
+    private boolean getNoneAlignedPosition(View view, int[] deltas) {
+        int pos = getAdapterPositionByView(view);
+        int viewMin = getViewMin(view);
+        int viewMax = getViewMax(view);
+        // we either align "firstView" to left/top padding edge
+        // or align "lastView" to right/bottom padding edge
+        View firstView = null;
+        View lastView = null;
+        int paddingMin = mWindowAlignment.mainAxis().getPaddingMin();
+        int clientSize = mWindowAlignment.mainAxis().getClientSize();
+        final int row = mGrid.getRowIndex(pos);
+        if (viewMin < paddingMin) {
+            // view enters low padding area:
+            firstView = view;
+            if (mFocusScrollStrategy == BaseGridView.FOCUS_SCROLL_PAGE) {
+                // scroll one "page" left/top,
+                // align first visible item of the "page" at the low padding edge.
+                while (prependOneColumnVisibleItems()) {
+                    CircularIntArray positions =
+                            mGrid.getItemPositionsInRows(mGrid.getFirstVisibleIndex(), pos)[row];
+                    firstView = findViewByPosition(positions.get(0));
+                    if (viewMax - getViewMin(firstView) > clientSize) {
+                        if (positions.size() > 2) {
+                            firstView = findViewByPosition(positions.get(2));
+                        }
+                        break;
+                    }
+                }
+            }
+        } else if (viewMax > clientSize + paddingMin) {
+            // view enters high padding area:
+            if (mFocusScrollStrategy == BaseGridView.FOCUS_SCROLL_PAGE) {
+                // scroll whole one page right/bottom, align view at the low padding edge.
+                firstView = view;
+                do {
+                    CircularIntArray positions =
+                            mGrid.getItemPositionsInRows(pos, mGrid.getLastVisibleIndex())[row];
+                    lastView = findViewByPosition(positions.get(positions.size() - 1));
+                    if (getViewMax(lastView) - viewMin > clientSize) {
+                        lastView = null;
+                        break;
+                    }
+                } while (appendOneColumnVisibleItems());
+                if (lastView != null) {
+                    // however if we reached end,  we should align last view.
+                    firstView = null;
+                }
+            } else {
+                lastView = view;
+            }
+        }
+        int scrollPrimary = 0;
+        int scrollSecondary = 0;
+        if (firstView != null) {
+            scrollPrimary = getViewMin(firstView) - paddingMin;
+        } else if (lastView != null) {
+            scrollPrimary = getViewMax(lastView) - (paddingMin + clientSize);
+        }
+        View secondaryAlignedView;
+        if (firstView != null) {
+            secondaryAlignedView = firstView;
+        } else if (lastView != null) {
+            secondaryAlignedView = lastView;
+        } else {
+            secondaryAlignedView = view;
+        }
+        scrollSecondary = getSecondaryScrollDistance(secondaryAlignedView);
+        if (scrollPrimary != 0 || scrollSecondary != 0) {
+            deltas[0] = scrollPrimary;
+            deltas[1] = scrollSecondary;
+            return true;
+        }
+        return false;
+    }
+
+    private boolean getAlignedPosition(View view, View childView, int[] deltas) {
+        int scrollPrimary = getPrimaryAlignedScrollDistance(view);
+        if (childView != null) {
+            scrollPrimary = getAdjustedPrimaryAlignedScrollDistance(scrollPrimary, view, childView);
+        }
+        int scrollSecondary = getSecondaryScrollDistance(view);
+        if (DEBUG) {
+            Log.v(getTag(), "getAlignedPosition " + scrollPrimary + " " + scrollSecondary
+                    + " " + mPrimaryScrollExtra + " " + mWindowAlignment);
+        }
+        scrollPrimary += mPrimaryScrollExtra;
+        if (scrollPrimary != 0 || scrollSecondary != 0) {
+            deltas[0] = scrollPrimary;
+            deltas[1] = scrollSecondary;
+            return true;
+        } else {
+            deltas[0] = 0;
+            deltas[1] = 0;
+        }
+        return false;
+    }
+
+    private void scrollGrid(int scrollPrimary, int scrollSecondary, boolean smooth) {
+        if ((mFlag & PF_STAGE_MASK) == PF_STAGE_LAYOUT) {
+            scrollDirectionPrimary(scrollPrimary);
+            scrollDirectionSecondary(scrollSecondary);
+        } else {
+            int scrollX;
+            int scrollY;
+            if (mOrientation == HORIZONTAL) {
+                scrollX = scrollPrimary;
+                scrollY = scrollSecondary;
+            } else {
+                scrollX = scrollSecondary;
+                scrollY = scrollPrimary;
+            }
+            if (smooth) {
+                mBaseGridView.smoothScrollBy(scrollX, scrollY);
+            } else {
+                mBaseGridView.scrollBy(scrollX, scrollY);
+                dispatchChildSelectedAndPositioned();
+            }
+        }
+    }
+
+    public void setPruneChild(boolean pruneChild) {
+        if (((mFlag & PF_PRUNE_CHILD) != 0) != pruneChild) {
+            mFlag = (mFlag & ~PF_PRUNE_CHILD) | (pruneChild ? PF_PRUNE_CHILD : 0);
+            if (pruneChild) {
+                requestLayout();
+            }
+        }
+    }
+
+    public boolean getPruneChild() {
+        return (mFlag & PF_PRUNE_CHILD) != 0;
+    }
+
+    public void setScrollEnabled(boolean scrollEnabled) {
+        if (((mFlag & PF_SCROLL_ENABLED) != 0) != scrollEnabled) {
+            mFlag = (mFlag & ~PF_SCROLL_ENABLED) | (scrollEnabled ? PF_SCROLL_ENABLED : 0);
+            if (((mFlag & PF_SCROLL_ENABLED) != 0)
+                    && mFocusScrollStrategy == BaseGridView.FOCUS_SCROLL_ALIGNED
+                    && mFocusPosition != NO_POSITION) {
+                scrollToSelection(mFocusPosition, mSubFocusPosition,
+                        true, mPrimaryScrollExtra);
+            }
+        }
+    }
+
+    public boolean isScrollEnabled() {
+        return (mFlag & PF_SCROLL_ENABLED) != 0;
+    }
+
+    private int findImmediateChildIndex(View view) {
+        if (mBaseGridView != null && view != mBaseGridView) {
+            view = findContainingItemView(view);
+            if (view != null) {
+                for (int i = 0, count = getChildCount(); i < count; i++) {
+                    if (getChildAt(i) == view) {
+                        return i;
+                    }
+                }
+            }
+        }
+        return NO_POSITION;
+    }
+
+    void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
+        if (gainFocus) {
+            // if gridview.requestFocus() is called, select first focusable child.
+            for (int i = mFocusPosition; ;i++) {
+                View view = findViewByPosition(i);
+                if (view == null) {
+                    break;
+                }
+                if (view.getVisibility() == View.VISIBLE && view.hasFocusable()) {
+                    view.requestFocus();
+                    break;
+                }
+            }
+        }
+    }
+
+    void setFocusSearchDisabled(boolean disabled) {
+        mFlag = (mFlag & ~PF_FOCUS_SEARCH_DISABLED) | (disabled ? PF_FOCUS_SEARCH_DISABLED : 0);
+    }
+
+    boolean isFocusSearchDisabled() {
+        return (mFlag & PF_FOCUS_SEARCH_DISABLED) != 0;
+    }
+
+    @Override
+    public View onInterceptFocusSearch(View focused, int direction) {
+        if ((mFlag & PF_FOCUS_SEARCH_DISABLED) != 0) {
+            return focused;
+        }
+
+        final FocusFinder ff = FocusFinder.getInstance();
+        View result = null;
+        if (direction == View.FOCUS_FORWARD || direction == View.FOCUS_BACKWARD) {
+            // convert direction to absolute direction and see if we have a view there and if not
+            // tell LayoutManager to add if it can.
+            if (canScrollVertically()) {
+                final int absDir =
+                        direction == View.FOCUS_FORWARD ? View.FOCUS_DOWN : View.FOCUS_UP;
+                result = ff.findNextFocus(mBaseGridView, focused, absDir);
+            }
+            if (canScrollHorizontally()) {
+                boolean rtl = getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL;
+                final int absDir = (direction == View.FOCUS_FORWARD) ^ rtl
+                        ? View.FOCUS_RIGHT : View.FOCUS_LEFT;
+                result = ff.findNextFocus(mBaseGridView, focused, absDir);
+            }
+        } else {
+            result = ff.findNextFocus(mBaseGridView, focused, direction);
+        }
+        if (result != null) {
+            return result;
+        }
+
+        if (mBaseGridView.getDescendantFocusability() == ViewGroup.FOCUS_BLOCK_DESCENDANTS) {
+            return mBaseGridView.getParent().focusSearch(focused, direction);
+        }
+
+        if (DEBUG) Log.v(getTag(), "regular focusSearch failed direction " + direction);
+        int movement = getMovement(direction);
+        final boolean isScroll = mBaseGridView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE;
+        if (movement == NEXT_ITEM) {
+            if (isScroll || (mFlag & PF_FOCUS_OUT_END) == 0) {
+                result = focused;
+            }
+            if ((mFlag & PF_SCROLL_ENABLED) != 0 && !hasCreatedLastItem()) {
+                processPendingMovement(true);
+                result = focused;
+            }
+        } else if (movement == PREV_ITEM) {
+            if (isScroll || (mFlag & PF_FOCUS_OUT_FRONT) == 0) {
+                result = focused;
+            }
+            if ((mFlag & PF_SCROLL_ENABLED) != 0 && !hasCreatedFirstItem()) {
+                processPendingMovement(false);
+                result = focused;
+            }
+        } else if (movement == NEXT_ROW) {
+            if (isScroll || (mFlag & PF_FOCUS_OUT_SIDE_END) == 0) {
+                result = focused;
+            }
+        } else if (movement == PREV_ROW) {
+            if (isScroll || (mFlag & PF_FOCUS_OUT_SIDE_START) == 0) {
+                result = focused;
+            }
+        }
+        if (result != null) {
+            return result;
+        }
+
+        if (DEBUG) Log.v(getTag(), "now focusSearch in parent");
+        result = mBaseGridView.getParent().focusSearch(focused, direction);
+        if (result != null) {
+            return result;
+        }
+        return focused != null ? focused : mBaseGridView;
+    }
+
+    boolean hasPreviousViewInSameRow(int pos) {
+        if (mGrid == null || pos == NO_POSITION || mGrid.getFirstVisibleIndex() < 0) {
+            return false;
+        }
+        if (mGrid.getFirstVisibleIndex() > 0) {
+            return true;
+        }
+        final int focusedRow = mGrid.getLocation(pos).row;
+        for (int i = getChildCount() - 1; i >= 0; i--) {
+            int position = getAdapterPositionByIndex(i);
+            Grid.Location loc = mGrid.getLocation(position);
+            if (loc != null && loc.row == focusedRow) {
+                if (position < pos) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean onAddFocusables(RecyclerView recyclerView,
+            ArrayList<View> views, int direction, int focusableMode) {
+        if ((mFlag & PF_FOCUS_SEARCH_DISABLED) != 0) {
+            return true;
+        }
+        // If this viewgroup or one of its children currently has focus then we
+        // consider our children for focus searching in main direction on the same row.
+        // If this viewgroup has no focus and using focus align, we want the system
+        // to ignore our children and pass focus to the viewgroup, which will pass
+        // focus on to its children appropriately.
+        // If this viewgroup has no focus and not using focus align, we want to
+        // consider the child that does not overlap with padding area.
+        if (recyclerView.hasFocus()) {
+            if (mPendingMoveSmoothScroller != null) {
+                // don't find next focusable if has pending movement.
+                return true;
+            }
+            final int movement = getMovement(direction);
+            final View focused = recyclerView.findFocus();
+            final int focusedIndex = findImmediateChildIndex(focused);
+            final int focusedPos = getAdapterPositionByIndex(focusedIndex);
+            // Even if focusedPos != NO_POSITION, findViewByPosition could return null if the view
+            // is ignored or getLayoutPosition does not match the adapter position of focused view.
+            final View immediateFocusedChild = (focusedPos == NO_POSITION) ? null
+                    : findViewByPosition(focusedPos);
+            // Add focusables of focused item.
+            if (immediateFocusedChild != null) {
+                immediateFocusedChild.addFocusables(views,  direction, focusableMode);
+            }
+            if (mGrid == null || getChildCount() == 0) {
+                // no grid information, or no child, bail out.
+                return true;
+            }
+            if ((movement == NEXT_ROW || movement == PREV_ROW) && mGrid.getNumRows() <= 1) {
+                // For single row, cannot navigate to previous/next row.
+                return true;
+            }
+            // Add focusables of neighbor depending on the focus search direction.
+            final int focusedRow = mGrid != null && immediateFocusedChild != null
+                    ? mGrid.getLocation(focusedPos).row : NO_POSITION;
+            final int focusableCount = views.size();
+            int inc = movement == NEXT_ITEM || movement == NEXT_ROW ? 1 : -1;
+            int loop_end = inc > 0 ? getChildCount() - 1 : 0;
+            int loop_start;
+            if (focusedIndex == NO_POSITION) {
+                loop_start = inc > 0 ? 0 : getChildCount() - 1;
+            } else {
+                loop_start = focusedIndex + inc;
+            }
+            for (int i = loop_start; inc > 0 ? i <= loop_end : i >= loop_end; i += inc) {
+                final View child = getChildAt(i);
+                if (child.getVisibility() != View.VISIBLE || !child.hasFocusable()) {
+                    continue;
+                }
+                // if there wasn't any focused item, add the very first focusable
+                // items and stop.
+                if (immediateFocusedChild == null) {
+                    child.addFocusables(views,  direction, focusableMode);
+                    if (views.size() > focusableCount) {
+                        break;
+                    }
+                    continue;
+                }
+                int position = getAdapterPositionByIndex(i);
+                Grid.Location loc = mGrid.getLocation(position);
+                if (loc == null) {
+                    continue;
+                }
+                if (movement == NEXT_ITEM) {
+                    // Add first focusable item on the same row
+                    if (loc.row == focusedRow && position > focusedPos) {
+                        child.addFocusables(views,  direction, focusableMode);
+                        if (views.size() > focusableCount) {
+                            break;
+                        }
+                    }
+                } else if (movement == PREV_ITEM) {
+                    // Add first focusable item on the same row
+                    if (loc.row == focusedRow && position < focusedPos) {
+                        child.addFocusables(views,  direction, focusableMode);
+                        if (views.size() > focusableCount) {
+                            break;
+                        }
+                    }
+                } else if (movement == NEXT_ROW) {
+                    // Add all focusable items after this item whose row index is bigger
+                    if (loc.row == focusedRow) {
+                        continue;
+                    } else if (loc.row < focusedRow) {
+                        break;
+                    }
+                    child.addFocusables(views,  direction, focusableMode);
+                } else if (movement == PREV_ROW) {
+                    // Add all focusable items before this item whose row index is smaller
+                    if (loc.row == focusedRow) {
+                        continue;
+                    } else if (loc.row > focusedRow) {
+                        break;
+                    }
+                    child.addFocusables(views,  direction, focusableMode);
+                }
+            }
+        } else {
+            int focusableCount = views.size();
+            if (mFocusScrollStrategy != BaseGridView.FOCUS_SCROLL_ALIGNED) {
+                // adding views not overlapping padding area to avoid scrolling in gaining focus
+                int left = mWindowAlignment.mainAxis().getPaddingMin();
+                int right = mWindowAlignment.mainAxis().getClientSize() + left;
+                for (int i = 0, count = getChildCount(); i < count; i++) {
+                    View child = getChildAt(i);
+                    if (child.getVisibility() == View.VISIBLE) {
+                        if (getViewMin(child) >= left && getViewMax(child) <= right) {
+                            child.addFocusables(views, direction, focusableMode);
+                        }
+                    }
+                }
+                // if we cannot find any, then just add all children.
+                if (views.size() == focusableCount) {
+                    for (int i = 0, count = getChildCount(); i < count; i++) {
+                        View child = getChildAt(i);
+                        if (child.getVisibility() == View.VISIBLE) {
+                            child.addFocusables(views, direction, focusableMode);
+                        }
+                    }
+                }
+            } else {
+                View view = findViewByPosition(mFocusPosition);
+                if (view != null) {
+                    view.addFocusables(views, direction, focusableMode);
+                }
+            }
+            // if still cannot find any, fall through and add itself
+            if (views.size() != focusableCount) {
+                return true;
+            }
+            if (recyclerView.isFocusable()) {
+                views.add(recyclerView);
+            }
+        }
+        return true;
+    }
+
+    boolean hasCreatedLastItem() {
+        int count = getItemCount();
+        return count == 0 || mBaseGridView.findViewHolderForAdapterPosition(count - 1) != null;
+    }
+
+    boolean hasCreatedFirstItem() {
+        int count = getItemCount();
+        return count == 0 || mBaseGridView.findViewHolderForAdapterPosition(0) != null;
+    }
+
+    boolean isItemFullyVisible(int pos) {
+        RecyclerView.ViewHolder vh = mBaseGridView.findViewHolderForAdapterPosition(pos);
+        if (vh == null) {
+            return false;
+        }
+        return vh.itemView.getLeft() >= 0 && vh.itemView.getRight() < mBaseGridView.getWidth()
+                && vh.itemView.getTop() >= 0 && vh.itemView.getBottom() < mBaseGridView.getHeight();
+    }
+
+    boolean canScrollTo(View view) {
+        return view.getVisibility() == View.VISIBLE && (!hasFocus() || view.hasFocusable());
+    }
+
+    boolean gridOnRequestFocusInDescendants(RecyclerView recyclerView, int direction,
+            Rect previouslyFocusedRect) {
+        switch (mFocusScrollStrategy) {
+            case BaseGridView.FOCUS_SCROLL_ALIGNED:
+            default:
+                return gridOnRequestFocusInDescendantsAligned(recyclerView,
+                        direction, previouslyFocusedRect);
+            case BaseGridView.FOCUS_SCROLL_PAGE:
+            case BaseGridView.FOCUS_SCROLL_ITEM:
+                return gridOnRequestFocusInDescendantsUnaligned(recyclerView,
+                        direction, previouslyFocusedRect);
+        }
+    }
+
+    private boolean gridOnRequestFocusInDescendantsAligned(RecyclerView recyclerView,
+            int direction, Rect previouslyFocusedRect) {
+        View view = findViewByPosition(mFocusPosition);
+        if (view != null) {
+            boolean result = view.requestFocus(direction, previouslyFocusedRect);
+            if (!result && DEBUG) {
+                Log.w(getTag(), "failed to request focus on " + view);
+            }
+            return result;
+        }
+        return false;
+    }
+
+    private boolean gridOnRequestFocusInDescendantsUnaligned(RecyclerView recyclerView,
+            int direction, Rect previouslyFocusedRect) {
+        // focus to view not overlapping padding area to avoid scrolling in gaining focus
+        int index;
+        int increment;
+        int end;
+        int count = getChildCount();
+        if ((direction & View.FOCUS_FORWARD) != 0) {
+            index = 0;
+            increment = 1;
+            end = count;
+        } else {
+            index = count - 1;
+            increment = -1;
+            end = -1;
+        }
+        int left = mWindowAlignment.mainAxis().getPaddingMin();
+        int right = mWindowAlignment.mainAxis().getClientSize() + left;
+        for (int i = index; i != end; i += increment) {
+            View child = getChildAt(i);
+            if (child.getVisibility() == View.VISIBLE) {
+                if (getViewMin(child) >= left && getViewMax(child) <= right) {
+                    if (child.requestFocus(direction, previouslyFocusedRect)) {
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    private final static int PREV_ITEM = 0;
+    private final static int NEXT_ITEM = 1;
+    private final static int PREV_ROW = 2;
+    private final static int NEXT_ROW = 3;
+
+    private int getMovement(int direction) {
+        int movement = View.FOCUS_LEFT;
+
+        if (mOrientation == HORIZONTAL) {
+            switch(direction) {
+                case View.FOCUS_LEFT:
+                    movement = (mFlag & PF_REVERSE_FLOW_PRIMARY) == 0 ? PREV_ITEM : NEXT_ITEM;
+                    break;
+                case View.FOCUS_RIGHT:
+                    movement = (mFlag & PF_REVERSE_FLOW_PRIMARY) == 0 ? NEXT_ITEM : PREV_ITEM;
+                    break;
+                case View.FOCUS_UP:
+                    movement = PREV_ROW;
+                    break;
+                case View.FOCUS_DOWN:
+                    movement = NEXT_ROW;
+                    break;
+            }
+        } else if (mOrientation == VERTICAL) {
+            switch(direction) {
+                case View.FOCUS_LEFT:
+                    movement = (mFlag & PF_REVERSE_FLOW_SECONDARY) == 0 ? PREV_ROW : NEXT_ROW;
+                    break;
+                case View.FOCUS_RIGHT:
+                    movement = (mFlag & PF_REVERSE_FLOW_SECONDARY) == 0 ? NEXT_ROW : PREV_ROW;
+                    break;
+                case View.FOCUS_UP:
+                    movement = PREV_ITEM;
+                    break;
+                case View.FOCUS_DOWN:
+                    movement = NEXT_ITEM;
+                    break;
+            }
+        }
+
+        return movement;
+    }
+
+    int getChildDrawingOrder(RecyclerView recyclerView, int childCount, int i) {
+        View view = findViewByPosition(mFocusPosition);
+        if (view == null) {
+            return i;
+        }
+        int focusIndex = recyclerView.indexOfChild(view);
+        // supposely 0 1 2 3 4 5 6 7 8 9, 4 is the center item
+        // drawing order is 0 1 2 3 9 8 7 6 5 4
+        if (i < focusIndex) {
+            return i;
+        } else if (i < childCount - 1) {
+            return focusIndex + childCount - 1 - i;
+        } else {
+            return focusIndex;
+        }
+    }
+
+    @Override
+    public void onAdapterChanged(RecyclerView.Adapter oldAdapter,
+            RecyclerView.Adapter newAdapter) {
+        if (DEBUG) Log.v(getTag(), "onAdapterChanged to " + newAdapter);
+        if (oldAdapter != null) {
+            discardLayoutInfo();
+            mFocusPosition = NO_POSITION;
+            mFocusPositionOffset = 0;
+            mChildrenStates.clear();
+        }
+        if (newAdapter instanceof FacetProviderAdapter) {
+            mFacetProviderAdapter = (FacetProviderAdapter) newAdapter;
+        } else {
+            mFacetProviderAdapter = null;
+        }
+        super.onAdapterChanged(oldAdapter, newAdapter);
+    }
+
+    private void discardLayoutInfo() {
+        mGrid = null;
+        mRowSizeSecondary = null;
+        mFlag &= ~PF_ROW_SECONDARY_SIZE_REFRESH;
+    }
+
+    public void setLayoutEnabled(boolean layoutEnabled) {
+        if (((mFlag & PF_LAYOUT_ENABLED) != 0) != layoutEnabled) {
+            mFlag = (mFlag & ~PF_LAYOUT_ENABLED) | (layoutEnabled ? PF_LAYOUT_ENABLED : 0);
+            requestLayout();
+        }
+    }
+
+    void setChildrenVisibility(int visibility) {
+        mChildVisibility = visibility;
+        if (mChildVisibility != -1) {
+            int count = getChildCount();
+            for (int i= 0; i < count; i++) {
+                getChildAt(i).setVisibility(mChildVisibility);
+            }
+        }
+    }
+
+    final static class SavedState implements Parcelable {
+
+        int index; // index inside adapter of the current view
+        Bundle childStates = Bundle.EMPTY;
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            out.writeInt(index);
+            out.writeBundle(childStates);
+        }
+
+        @SuppressWarnings("hiding")
+        public static final Parcelable.Creator<SavedState> CREATOR =
+                new Parcelable.Creator<SavedState>() {
+                    @Override
+                    public SavedState createFromParcel(Parcel in) {
+                        return new SavedState(in);
+                    }
+
+                    @Override
+                    public SavedState[] newArray(int size) {
+                        return new SavedState[size];
+                    }
+                };
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        SavedState(Parcel in) {
+            index = in.readInt();
+            childStates = in.readBundle(GridLayoutManager.class.getClassLoader());
+        }
+
+        SavedState() {
+        }
+    }
+
+    @Override
+    public Parcelable onSaveInstanceState() {
+        if (DEBUG) Log.v(getTag(), "onSaveInstanceState getSelection() " + getSelection());
+        SavedState ss = new SavedState();
+        // save selected index
+        ss.index = getSelection();
+        // save offscreen child (state when they are recycled)
+        Bundle bundle = mChildrenStates.saveAsBundle();
+        // save views currently is on screen (TODO save cached views)
+        for (int i = 0, count = getChildCount(); i < count; i++) {
+            View view = getChildAt(i);
+            int position = getAdapterPositionByView(view);
+            if (position != NO_POSITION) {
+                bundle = mChildrenStates.saveOnScreenView(bundle, view, position);
+            }
+        }
+        ss.childStates = bundle;
+        return ss;
+    }
+
+    void onChildRecycled(RecyclerView.ViewHolder holder) {
+        final int position = holder.getAdapterPosition();
+        if (position != NO_POSITION) {
+            mChildrenStates.saveOffscreenView(holder.itemView, position);
+        }
+    }
+
+    @Override
+    public void onRestoreInstanceState(Parcelable state) {
+        if (!(state instanceof SavedState)) {
+            return;
+        }
+        SavedState loadingState = (SavedState)state;
+        mFocusPosition = loadingState.index;
+        mFocusPositionOffset = 0;
+        mChildrenStates.loadFromBundle(loadingState.childStates);
+        mFlag |= PF_FORCE_FULL_LAYOUT;
+        requestLayout();
+        if (DEBUG) Log.v(getTag(), "onRestoreInstanceState mFocusPosition " + mFocusPosition);
+    }
+
+    @Override
+    public int getRowCountForAccessibility(RecyclerView.Recycler recycler,
+            RecyclerView.State state) {
+        if (mOrientation == HORIZONTAL && mGrid != null) {
+            return mGrid.getNumRows();
+        }
+        return super.getRowCountForAccessibility(recycler, state);
+    }
+
+    @Override
+    public int getColumnCountForAccessibility(RecyclerView.Recycler recycler,
+            RecyclerView.State state) {
+        if (mOrientation == VERTICAL && mGrid != null) {
+            return mGrid.getNumRows();
+        }
+        return super.getColumnCountForAccessibility(recycler, state);
+    }
+
+    @Override
+    public void onInitializeAccessibilityNodeInfoForItem(RecyclerView.Recycler recycler,
+            RecyclerView.State state, View host, AccessibilityNodeInfoCompat info) {
+        ViewGroup.LayoutParams lp = host.getLayoutParams();
+        if (mGrid == null || !(lp instanceof LayoutParams)) {
+            return;
+        }
+        LayoutParams glp = (LayoutParams) lp;
+        int position = glp.getViewAdapterPosition();
+        int rowIndex = position >= 0 ? mGrid.getRowIndex(position) : -1;
+        if (rowIndex < 0) {
+            return;
+        }
+        int guessSpanIndex = position / mGrid.getNumRows();
+        if (mOrientation == HORIZONTAL) {
+            info.setCollectionItemInfo(AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain(
+                    rowIndex, 1, guessSpanIndex, 1, false, false));
+        } else {
+            info.setCollectionItemInfo(AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain(
+                    guessSpanIndex, 1, rowIndex, 1, false, false));
+        }
+    }
+
+    /*
+     * Leanback widget is different than the default implementation because the "scroll" is driven
+     * by selection change.
+     */
+    @Override
+    public boolean performAccessibilityAction(Recycler recycler, State state, int action,
+            Bundle args) {
+        saveContext(recycler, state);
+        switch (action) {
+            case AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD:
+                processSelectionMoves(false, -1);
+                break;
+            case AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD:
+                processSelectionMoves(false, 1);
+                break;
+        }
+        leaveContext();
+        return true;
+    }
+
+    /*
+     * Move mFocusPosition multiple steps on the same row in main direction.
+     * Stops when moves are all consumed or reach first/last visible item.
+     * Returning remaining moves.
+     */
+    int processSelectionMoves(boolean preventScroll, int moves) {
+        if (mGrid == null) {
+            return moves;
+        }
+        int focusPosition = mFocusPosition;
+        int focusedRow = focusPosition != NO_POSITION
+                ? mGrid.getRowIndex(focusPosition) : NO_POSITION;
+        View newSelected = null;
+        for (int i = 0, count = getChildCount(); i < count && moves != 0; i++) {
+            int index = moves > 0 ? i : count - 1 - i;
+            final View child = getChildAt(index);
+            if (!canScrollTo(child)) {
+                continue;
+            }
+            int position = getAdapterPositionByIndex(index);
+            int rowIndex = mGrid.getRowIndex(position);
+            if (focusedRow == NO_POSITION) {
+                focusPosition = position;
+                newSelected = child;
+                focusedRow = rowIndex;
+            } else if (rowIndex == focusedRow) {
+                if ((moves > 0 && position > focusPosition)
+                        || (moves < 0 && position < focusPosition)) {
+                    focusPosition = position;
+                    newSelected = child;
+                    if (moves > 0) {
+                        moves--;
+                    } else {
+                        moves++;
+                    }
+                }
+            }
+        }
+        if (newSelected != null) {
+            if (preventScroll) {
+                if (hasFocus()) {
+                    mFlag |= PF_IN_SELECTION;
+                    newSelected.requestFocus();
+                    mFlag &= ~PF_IN_SELECTION;
+                }
+                mFocusPosition = focusPosition;
+                mSubFocusPosition = 0;
+            } else {
+                scrollToView(newSelected, true);
+            }
+        }
+        return moves;
+    }
+
+    @Override
+    public void onInitializeAccessibilityNodeInfo(Recycler recycler, State state,
+            AccessibilityNodeInfoCompat info) {
+        saveContext(recycler, state);
+        int count = state.getItemCount();
+        if ((mFlag & PF_SCROLL_ENABLED) != 0 && count > 1 && !isItemFullyVisible(0)) {
+            info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD);
+            info.setScrollable(true);
+        }
+        if ((mFlag & PF_SCROLL_ENABLED) != 0 && count > 1 && !isItemFullyVisible(count - 1)) {
+            info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD);
+            info.setScrollable(true);
+        }
+        final AccessibilityNodeInfoCompat.CollectionInfoCompat collectionInfo =
+                AccessibilityNodeInfoCompat.CollectionInfoCompat
+                        .obtain(getRowCountForAccessibility(recycler, state),
+                                getColumnCountForAccessibility(recycler, state),
+                                isLayoutHierarchical(recycler, state),
+                                getSelectionModeForAccessibility(recycler, state));
+        info.setCollectionInfo(collectionInfo);
+        leaveContext();
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/GuidanceStylingRelativeLayout.java b/leanback/src/android/support/v17/leanback/widget/GuidanceStylingRelativeLayout.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/GuidanceStylingRelativeLayout.java
rename to leanback/src/android/support/v17/leanback/widget/GuidanceStylingRelativeLayout.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/GuidanceStylist.java b/leanback/src/android/support/v17/leanback/widget/GuidanceStylist.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/GuidanceStylist.java
rename to leanback/src/android/support/v17/leanback/widget/GuidanceStylist.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/GuidedAction.java b/leanback/src/android/support/v17/leanback/widget/GuidedAction.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/GuidedAction.java
rename to leanback/src/android/support/v17/leanback/widget/GuidedAction.java
diff --git a/leanback/src/android/support/v17/leanback/widget/GuidedActionAdapter.java b/leanback/src/android/support/v17/leanback/widget/GuidedActionAdapter.java
new file mode 100644
index 0000000..51b29e2
--- /dev/null
+++ b/leanback/src/android/support/v17/leanback/widget/GuidedActionAdapter.java
@@ -0,0 +1,557 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+import android.support.v7.util.DiffUtil;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerView.ViewHolder;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.view.inputmethod.EditorInfo;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.TextView.OnEditorActionListener;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * GuidedActionAdapter instantiates views for guided actions, and manages their interactions.
+ * Presentation (view creation and state animation) is delegated to a {@link
+ * GuidedActionsStylist}, while clients are notified of interactions via
+ * {@link GuidedActionAdapter.ClickListener} and {@link GuidedActionAdapter.FocusListener}.
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public class GuidedActionAdapter extends RecyclerView.Adapter {
+    static final String TAG = "GuidedActionAdapter";
+    static final boolean DEBUG = false;
+
+    static final String TAG_EDIT = "EditableAction";
+    static final boolean DEBUG_EDIT = false;
+
+    /**
+     * Object listening for click events within a {@link GuidedActionAdapter}.
+     */
+    public interface ClickListener {
+
+        /**
+         * Called when the user clicks on an action.
+         */
+        void onGuidedActionClicked(GuidedAction action);
+
+    }
+
+    /**
+     * Object listening for focus events within a {@link GuidedActionAdapter}.
+     */
+    public interface FocusListener {
+
+        /**
+         * Called when the user focuses on an action.
+         */
+        void onGuidedActionFocused(GuidedAction action);
+    }
+
+    /**
+     * Object listening for edit events within a {@link GuidedActionAdapter}.
+     */
+    public interface EditListener {
+
+        /**
+         * Called when the user exits edit mode on an action.
+         */
+        void onGuidedActionEditCanceled(GuidedAction action);
+
+        /**
+         * Called when the user exits edit mode on an action and process confirm button in IME.
+         */
+        long onGuidedActionEditedAndProceed(GuidedAction action);
+
+        /**
+         * Called when Ime Open
+         */
+        void onImeOpen();
+
+        /**
+         * Called when Ime Close
+         */
+        void onImeClose();
+    }
+
+    private final boolean mIsSubAdapter;
+    private final ActionOnKeyListener mActionOnKeyListener;
+    private final ActionOnFocusListener mActionOnFocusListener;
+    private final ActionEditListener mActionEditListener;
+    private final List<GuidedAction> mActions;
+    private ClickListener mClickListener;
+    final GuidedActionsStylist mStylist;
+    GuidedActionAdapterGroup mGroup;
+    DiffCallback<GuidedAction> mDiffCallback;
+
+    private final View.OnClickListener mOnClickListener = new View.OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            if (v != null && v.getWindowToken() != null && getRecyclerView() != null) {
+                GuidedActionsStylist.ViewHolder avh = (GuidedActionsStylist.ViewHolder)
+                        getRecyclerView().getChildViewHolder(v);
+                GuidedAction action = avh.getAction();
+                if (action.hasTextEditable()) {
+                    if (DEBUG_EDIT) Log.v(TAG_EDIT, "openIme by click");
+                    mGroup.openIme(GuidedActionAdapter.this, avh);
+                } else if (action.hasEditableActivatorView()) {
+                    if (DEBUG_EDIT) Log.v(TAG_EDIT, "toggle editing mode by click");
+                    performOnActionClick(avh);
+                } else {
+                    handleCheckedActions(avh);
+                    if (action.isEnabled() && !action.infoOnly()) {
+                        performOnActionClick(avh);
+                    }
+                }
+            }
+        }
+    };
+
+    /**
+     * Constructs a GuidedActionAdapter with the given list of guided actions, the given click and
+     * focus listeners, and the given presenter.
+     * @param actions The list of guided actions this adapter will manage.
+     * @param focusListener The focus listener for items in this adapter.
+     * @param presenter The presenter that will manage the display of items in this adapter.
+     */
+    public GuidedActionAdapter(List<GuidedAction> actions, ClickListener clickListener,
+            FocusListener focusListener, GuidedActionsStylist presenter, boolean isSubAdapter) {
+        super();
+        mActions = actions == null ? new ArrayList<GuidedAction>() :
+                new ArrayList<GuidedAction>(actions);
+        mClickListener = clickListener;
+        mStylist = presenter;
+        mActionOnKeyListener = new ActionOnKeyListener();
+        mActionOnFocusListener = new ActionOnFocusListener(focusListener);
+        mActionEditListener = new ActionEditListener();
+        mIsSubAdapter = isSubAdapter;
+        if (!isSubAdapter) {
+            mDiffCallback = GuidedActionDiffCallback.getInstance();
+        }
+    }
+
+    /**
+     * Change DiffCallback used in {@link #setActions(List)}. Set to null for firing a
+     * general {@link #notifyDataSetChanged()}.
+     *
+     * @param diffCallback
+     */
+    public void setDiffCallback(DiffCallback<GuidedAction> diffCallback) {
+        mDiffCallback = diffCallback;
+    }
+
+    /**
+     * Sets the list of actions managed by this adapter. Use {@link #setDiffCallback(DiffCallback)}
+     * to change DiffCallback.
+     * @param actions The list of actions to be managed.
+     */
+    public void setActions(final List<GuidedAction> actions) {
+        if (!mIsSubAdapter) {
+            mStylist.collapseAction(false);
+        }
+        mActionOnFocusListener.unFocus();
+        if (mDiffCallback != null) {
+            // temporary variable used for DiffCallback
+            final List<GuidedAction> oldActions = new ArrayList();
+            oldActions.addAll(mActions);
+
+            // update items.
+            mActions.clear();
+            mActions.addAll(actions);
+
+            DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffUtil.Callback() {
+                @Override
+                public int getOldListSize() {
+                    return oldActions.size();
+                }
+
+                @Override
+                public int getNewListSize() {
+                    return mActions.size();
+                }
+
+                @Override
+                public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
+                    return mDiffCallback.areItemsTheSame(oldActions.get(oldItemPosition),
+                            mActions.get(newItemPosition));
+                }
+
+                @Override
+                public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
+                    return mDiffCallback.areContentsTheSame(oldActions.get(oldItemPosition),
+                            mActions.get(newItemPosition));
+                }
+
+                @Nullable
+                @Override
+                public Object getChangePayload(int oldItemPosition, int newItemPosition) {
+                    return mDiffCallback.getChangePayload(oldActions.get(oldItemPosition),
+                            mActions.get(newItemPosition));
+                }
+            });
+
+            // dispatch diff result
+            diffResult.dispatchUpdatesTo(this);
+        } else {
+            mActions.clear();
+            mActions.addAll(actions);
+            notifyDataSetChanged();
+        }
+    }
+
+    /**
+     * Returns the count of actions managed by this adapter.
+     * @return The count of actions managed by this adapter.
+     */
+    public int getCount() {
+        return mActions.size();
+    }
+
+    /**
+     * Returns the GuidedAction at the given position in the managed list.
+     * @param position The position of the desired GuidedAction.
+     * @return The GuidedAction at the given position.
+     */
+    public GuidedAction getItem(int position) {
+        return mActions.get(position);
+    }
+
+    /**
+     * Return index of action in array
+     * @param action Action to search index.
+     * @return Index of Action in array.
+     */
+    public int indexOf(GuidedAction action) {
+        return mActions.indexOf(action);
+    }
+
+    /**
+     * @return GuidedActionsStylist used to build the actions list UI.
+     */
+    public GuidedActionsStylist getGuidedActionsStylist() {
+        return mStylist;
+    }
+
+    /**
+     * Sets the click listener for items managed by this adapter.
+     * @param clickListener The click listener for this adapter.
+     */
+    public void setClickListener(ClickListener clickListener) {
+        mClickListener = clickListener;
+    }
+
+    /**
+     * Sets the focus listener for items managed by this adapter.
+     * @param focusListener The focus listener for this adapter.
+     */
+    public void setFocusListener(FocusListener focusListener) {
+        mActionOnFocusListener.setFocusListener(focusListener);
+    }
+
+    /**
+     * Used for serialization only.
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    public List<GuidedAction> getActions() {
+        return new ArrayList<GuidedAction>(mActions);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int getItemViewType(int position) {
+        return mStylist.getItemViewType(mActions.get(position));
+    }
+
+    RecyclerView getRecyclerView() {
+        return mIsSubAdapter ? mStylist.getSubActionsGridView() : mStylist.getActionsGridView();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        GuidedActionsStylist.ViewHolder vh = mStylist.onCreateViewHolder(parent, viewType);
+        View v = vh.itemView;
+        v.setOnKeyListener(mActionOnKeyListener);
+        v.setOnClickListener(mOnClickListener);
+        v.setOnFocusChangeListener(mActionOnFocusListener);
+
+        setupListeners(vh.getEditableTitleView());
+        setupListeners(vh.getEditableDescriptionView());
+
+        return vh;
+    }
+
+    private void setupListeners(EditText edit) {
+        if (edit != null) {
+            edit.setPrivateImeOptions("EscapeNorth=1;");
+            edit.setOnEditorActionListener(mActionEditListener);
+            if (edit instanceof ImeKeyMonitor) {
+                ImeKeyMonitor monitor = (ImeKeyMonitor)edit;
+                monitor.setImeKeyListener(mActionEditListener);
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onBindViewHolder(ViewHolder holder, int position) {
+        if (position >= mActions.size()) {
+            return;
+        }
+        final GuidedActionsStylist.ViewHolder avh = (GuidedActionsStylist.ViewHolder)holder;
+        GuidedAction action = mActions.get(position);
+        mStylist.onBindViewHolder(avh, action);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int getItemCount() {
+        return mActions.size();
+    }
+
+    private class ActionOnFocusListener implements View.OnFocusChangeListener {
+
+        private FocusListener mFocusListener;
+        private View mSelectedView;
+
+        ActionOnFocusListener(FocusListener focusListener) {
+            mFocusListener = focusListener;
+        }
+
+        public void setFocusListener(FocusListener focusListener) {
+            mFocusListener = focusListener;
+        }
+
+        public void unFocus() {
+            if (mSelectedView != null && getRecyclerView() != null) {
+                ViewHolder vh = getRecyclerView().getChildViewHolder(mSelectedView);
+                if (vh != null) {
+                    GuidedActionsStylist.ViewHolder avh = (GuidedActionsStylist.ViewHolder)vh;
+                    mStylist.onAnimateItemFocused(avh, false);
+                } else {
+                    Log.w(TAG, "RecyclerView returned null view holder",
+                            new Throwable());
+                }
+            }
+        }
+
+        @Override
+        public void onFocusChange(View v, boolean hasFocus) {
+            if (getRecyclerView() == null) {
+                return;
+            }
+            GuidedActionsStylist.ViewHolder avh = (GuidedActionsStylist.ViewHolder)
+                    getRecyclerView().getChildViewHolder(v);
+            if (hasFocus) {
+                mSelectedView = v;
+                if (mFocusListener != null) {
+                    // We still call onGuidedActionFocused so that listeners can clear
+                    // state if they want.
+                    mFocusListener.onGuidedActionFocused(avh.getAction());
+                }
+            } else {
+                if (mSelectedView == v) {
+                    mStylist.onAnimateItemPressedCancelled(avh);
+                    mSelectedView = null;
+                }
+            }
+            mStylist.onAnimateItemFocused(avh, hasFocus);
+        }
+    }
+
+    public GuidedActionsStylist.ViewHolder findSubChildViewHolder(View v) {
+        // Needed because RecyclerView.getChildViewHolder does not traverse the hierarchy
+        if (getRecyclerView() == null) {
+            return null;
+        }
+        GuidedActionsStylist.ViewHolder result = null;
+        ViewParent parent = v.getParent();
+        while (parent != getRecyclerView() && parent != null && v != null) {
+            v = (View)parent;
+            parent = parent.getParent();
+        }
+        if (parent != null && v != null) {
+            result = (GuidedActionsStylist.ViewHolder)getRecyclerView().getChildViewHolder(v);
+        }
+        return result;
+    }
+
+    public void handleCheckedActions(GuidedActionsStylist.ViewHolder avh) {
+        GuidedAction action = avh.getAction();
+        int actionCheckSetId = action.getCheckSetId();
+        if (getRecyclerView() != null && actionCheckSetId != GuidedAction.NO_CHECK_SET) {
+            // Find any actions that are checked and are in the same group
+            // as the selected action. Fade their checkmarks out.
+            if (actionCheckSetId != GuidedAction.CHECKBOX_CHECK_SET_ID) {
+                for (int i = 0, size = mActions.size(); i < size; i++) {
+                    GuidedAction a = mActions.get(i);
+                    if (a != action && a.getCheckSetId() == actionCheckSetId && a.isChecked()) {
+                        a.setChecked(false);
+                        GuidedActionsStylist.ViewHolder vh = (GuidedActionsStylist.ViewHolder)
+                                getRecyclerView().findViewHolderForPosition(i);
+                        if (vh != null) {
+                            mStylist.onAnimateItemChecked(vh, false);
+                        }
+                    }
+                }
+            }
+
+            // If we we'ren't already checked, fade our checkmark in.
+            if (!action.isChecked()) {
+                action.setChecked(true);
+                mStylist.onAnimateItemChecked(avh, true);
+            } else {
+                if (actionCheckSetId == GuidedAction.CHECKBOX_CHECK_SET_ID) {
+                    action.setChecked(false);
+                    mStylist.onAnimateItemChecked(avh, false);
+                }
+            }
+        }
+    }
+
+    public void performOnActionClick(GuidedActionsStylist.ViewHolder avh) {
+        if (mClickListener != null) {
+            mClickListener.onGuidedActionClicked(avh.getAction());
+        }
+    }
+
+    private class ActionOnKeyListener implements View.OnKeyListener {
+
+        private boolean mKeyPressed = false;
+
+        ActionOnKeyListener() {
+        }
+
+        /**
+         * Now only handles KEYCODE_ENTER and KEYCODE_NUMPAD_ENTER key event.
+         */
+        @Override
+        public boolean onKey(View v, int keyCode, KeyEvent event) {
+            if (v == null || event == null || getRecyclerView() == null) {
+                return false;
+            }
+            boolean handled = false;
+            switch (keyCode) {
+                case KeyEvent.KEYCODE_DPAD_CENTER:
+                case KeyEvent.KEYCODE_NUMPAD_ENTER:
+                case KeyEvent.KEYCODE_BUTTON_X:
+                case KeyEvent.KEYCODE_BUTTON_Y:
+                case KeyEvent.KEYCODE_ENTER:
+
+                    GuidedActionsStylist.ViewHolder avh = (GuidedActionsStylist.ViewHolder)
+                            getRecyclerView().getChildViewHolder(v);
+                    GuidedAction action = avh.getAction();
+
+                    if (!action.isEnabled() || action.infoOnly()) {
+                        if (event.getAction() == KeyEvent.ACTION_DOWN) {
+                            // TODO: requires API 19
+                            //playSound(v, AudioManager.FX_KEYPRESS_INVALID);
+                        }
+                        return true;
+                    }
+
+                    switch (event.getAction()) {
+                        case KeyEvent.ACTION_DOWN:
+                            if (DEBUG) {
+                                Log.d(TAG, "Enter Key down");
+                            }
+                            if (!mKeyPressed) {
+                                mKeyPressed = true;
+                                mStylist.onAnimateItemPressed(avh, mKeyPressed);
+                            }
+                            break;
+                        case KeyEvent.ACTION_UP:
+                            if (DEBUG) {
+                                Log.d(TAG, "Enter Key up");
+                            }
+                            // Sometimes we are losing ACTION_DOWN for the first ENTER after pressed
+                            // Escape in IME.
+                            if (mKeyPressed) {
+                                mKeyPressed = false;
+                                mStylist.onAnimateItemPressed(avh, mKeyPressed);
+                            }
+                            break;
+                        default:
+                            break;
+                    }
+                    break;
+                default:
+                    break;
+            }
+            return handled;
+        }
+
+    }
+
+    private class ActionEditListener implements OnEditorActionListener,
+            ImeKeyMonitor.ImeKeyListener {
+
+        ActionEditListener() {
+        }
+
+        @Override
+        public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+            if (DEBUG_EDIT) Log.v(TAG_EDIT, "IME action: " + actionId);
+            boolean handled = false;
+            if (actionId == EditorInfo.IME_ACTION_NEXT
+                    || actionId == EditorInfo.IME_ACTION_DONE) {
+                mGroup.fillAndGoNext(GuidedActionAdapter.this, v);
+                handled = true;
+            } else if (actionId == EditorInfo.IME_ACTION_NONE) {
+                if (DEBUG_EDIT) Log.v(TAG_EDIT, "closeIme escape north");
+                // Escape north handling: stay on current item, but close editor
+                handled = true;
+                mGroup.fillAndStay(GuidedActionAdapter.this, v);
+            }
+            return handled;
+        }
+
+        @Override
+        public boolean onKeyPreIme(EditText editText, int keyCode, KeyEvent event) {
+            if (DEBUG_EDIT) Log.v(TAG_EDIT, "IME key: " + keyCode);
+            if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) {
+                mGroup.fillAndStay(GuidedActionAdapter.this, editText);
+                return true;
+            } else if (keyCode == KeyEvent.KEYCODE_ENTER
+                    && event.getAction() == KeyEvent.ACTION_UP) {
+                mGroup.fillAndGoNext(GuidedActionAdapter.this, editText);
+                return true;
+            }
+            return false;
+        }
+
+    }
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionAdapterGroup.java b/leanback/src/android/support/v17/leanback/widget/GuidedActionAdapterGroup.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/GuidedActionAdapterGroup.java
rename to leanback/src/android/support/v17/leanback/widget/GuidedActionAdapterGroup.java
diff --git a/leanback/src/android/support/v17/leanback/widget/GuidedActionDiffCallback.java b/leanback/src/android/support/v17/leanback/widget/GuidedActionDiffCallback.java
new file mode 100644
index 0000000..d4d4d77
--- /dev/null
+++ b/leanback/src/android/support/v17/leanback/widget/GuidedActionDiffCallback.java
@@ -0,0 +1,65 @@
+/*
+ * 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.v17.leanback.widget;
+
+import android.support.annotation.NonNull;
+import android.text.TextUtils;
+
+/**
+ * DiffCallback used for GuidedActions, see {@link
+ * android.support.v17.leanback.app.GuidedStepSupportFragment#setActionsDiffCallback(DiffCallback)}.
+ */
+public class GuidedActionDiffCallback extends DiffCallback<GuidedAction> {
+
+    static final GuidedActionDiffCallback sInstance = new GuidedActionDiffCallback();
+
+    /**
+     * Returns the singleton GuidedActionDiffCallback.
+     * @return The singleton GuidedActionDiffCallback.
+     */
+    public static final GuidedActionDiffCallback getInstance() {
+        return sInstance;
+    }
+
+    @Override
+    public boolean areItemsTheSame(@NonNull GuidedAction oldItem, @NonNull GuidedAction newItem) {
+        if (oldItem == null) {
+            return newItem == null;
+        } else if (newItem == null) {
+            return false;
+        }
+        return oldItem.getId() == newItem.getId();
+    }
+
+    @Override
+    public boolean areContentsTheSame(@NonNull GuidedAction oldItem,
+            @NonNull GuidedAction newItem) {
+        if (oldItem == null) {
+            return newItem == null;
+        } else if (newItem == null) {
+            return false;
+        }
+        return oldItem.getCheckSetId() == newItem.getCheckSetId()
+                && oldItem.mActionFlags == newItem.mActionFlags
+                && TextUtils.equals(oldItem.getTitle(), newItem.getTitle())
+                && TextUtils.equals(oldItem.getDescription(), newItem.getDescription())
+                && oldItem.getInputType() == newItem.getInputType()
+                && TextUtils.equals(oldItem.getEditTitle(), newItem.getEditTitle())
+                && TextUtils.equals(oldItem.getEditDescription(), newItem.getEditDescription())
+                && oldItem.getEditInputType() == newItem.getEditInputType()
+                && oldItem.getDescriptionEditInputType() == newItem.getDescriptionEditInputType();
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionEditText.java b/leanback/src/android/support/v17/leanback/widget/GuidedActionEditText.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/GuidedActionEditText.java
rename to leanback/src/android/support/v17/leanback/widget/GuidedActionEditText.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionItemContainer.java b/leanback/src/android/support/v17/leanback/widget/GuidedActionItemContainer.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/GuidedActionItemContainer.java
rename to leanback/src/android/support/v17/leanback/widget/GuidedActionItemContainer.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionsRelativeLayout.java b/leanback/src/android/support/v17/leanback/widget/GuidedActionsRelativeLayout.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/GuidedActionsRelativeLayout.java
rename to leanback/src/android/support/v17/leanback/widget/GuidedActionsRelativeLayout.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionsStylist.java b/leanback/src/android/support/v17/leanback/widget/GuidedActionsStylist.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/GuidedActionsStylist.java
rename to leanback/src/android/support/v17/leanback/widget/GuidedActionsStylist.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/GuidedDatePickerAction.java b/leanback/src/android/support/v17/leanback/widget/GuidedDatePickerAction.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/GuidedDatePickerAction.java
rename to leanback/src/android/support/v17/leanback/widget/GuidedDatePickerAction.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/HeaderItem.java b/leanback/src/android/support/v17/leanback/widget/HeaderItem.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/HeaderItem.java
rename to leanback/src/android/support/v17/leanback/widget/HeaderItem.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/HorizontalGridView.java b/leanback/src/android/support/v17/leanback/widget/HorizontalGridView.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/HorizontalGridView.java
rename to leanback/src/android/support/v17/leanback/widget/HorizontalGridView.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/HorizontalHoverCardSwitcher.java b/leanback/src/android/support/v17/leanback/widget/HorizontalHoverCardSwitcher.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/HorizontalHoverCardSwitcher.java
rename to leanback/src/android/support/v17/leanback/widget/HorizontalHoverCardSwitcher.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ImageCardView.java b/leanback/src/android/support/v17/leanback/widget/ImageCardView.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/ImageCardView.java
rename to leanback/src/android/support/v17/leanback/widget/ImageCardView.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ImeKeyMonitor.java b/leanback/src/android/support/v17/leanback/widget/ImeKeyMonitor.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/ImeKeyMonitor.java
rename to leanback/src/android/support/v17/leanback/widget/ImeKeyMonitor.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/InvisibleRowPresenter.java b/leanback/src/android/support/v17/leanback/widget/InvisibleRowPresenter.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/InvisibleRowPresenter.java
rename to leanback/src/android/support/v17/leanback/widget/InvisibleRowPresenter.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ItemAlignment.java b/leanback/src/android/support/v17/leanback/widget/ItemAlignment.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/ItemAlignment.java
rename to leanback/src/android/support/v17/leanback/widget/ItemAlignment.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ItemAlignmentFacet.java b/leanback/src/android/support/v17/leanback/widget/ItemAlignmentFacet.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/ItemAlignmentFacet.java
rename to leanback/src/android/support/v17/leanback/widget/ItemAlignmentFacet.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ItemAlignmentFacetHelper.java b/leanback/src/android/support/v17/leanback/widget/ItemAlignmentFacetHelper.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/ItemAlignmentFacetHelper.java
rename to leanback/src/android/support/v17/leanback/widget/ItemAlignmentFacetHelper.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ItemBridgeAdapter.java b/leanback/src/android/support/v17/leanback/widget/ItemBridgeAdapter.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/ItemBridgeAdapter.java
rename to leanback/src/android/support/v17/leanback/widget/ItemBridgeAdapter.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ItemBridgeAdapterShadowOverlayWrapper.java b/leanback/src/android/support/v17/leanback/widget/ItemBridgeAdapterShadowOverlayWrapper.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/ItemBridgeAdapterShadowOverlayWrapper.java
rename to leanback/src/android/support/v17/leanback/widget/ItemBridgeAdapterShadowOverlayWrapper.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ListRow.java b/leanback/src/android/support/v17/leanback/widget/ListRow.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/ListRow.java
rename to leanback/src/android/support/v17/leanback/widget/ListRow.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ListRowHoverCardView.java b/leanback/src/android/support/v17/leanback/widget/ListRowHoverCardView.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/ListRowHoverCardView.java
rename to leanback/src/android/support/v17/leanback/widget/ListRowHoverCardView.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ListRowPresenter.java b/leanback/src/android/support/v17/leanback/widget/ListRowPresenter.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/ListRowPresenter.java
rename to leanback/src/android/support/v17/leanback/widget/ListRowPresenter.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ListRowView.java b/leanback/src/android/support/v17/leanback/widget/ListRowView.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/ListRowView.java
rename to leanback/src/android/support/v17/leanback/widget/ListRowView.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/MediaItemActionPresenter.java b/leanback/src/android/support/v17/leanback/widget/MediaItemActionPresenter.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/MediaItemActionPresenter.java
rename to leanback/src/android/support/v17/leanback/widget/MediaItemActionPresenter.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/MediaNowPlayingView.java b/leanback/src/android/support/v17/leanback/widget/MediaNowPlayingView.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/MediaNowPlayingView.java
rename to leanback/src/android/support/v17/leanback/widget/MediaNowPlayingView.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/MediaRowFocusView.java b/leanback/src/android/support/v17/leanback/widget/MediaRowFocusView.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/MediaRowFocusView.java
rename to leanback/src/android/support/v17/leanback/widget/MediaRowFocusView.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/MultiActionsProvider.java b/leanback/src/android/support/v17/leanback/widget/MultiActionsProvider.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/MultiActionsProvider.java
rename to leanback/src/android/support/v17/leanback/widget/MultiActionsProvider.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/NonOverlappingFrameLayout.java b/leanback/src/android/support/v17/leanback/widget/NonOverlappingFrameLayout.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/NonOverlappingFrameLayout.java
rename to leanback/src/android/support/v17/leanback/widget/NonOverlappingFrameLayout.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/NonOverlappingLinearLayout.java b/leanback/src/android/support/v17/leanback/widget/NonOverlappingLinearLayout.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/NonOverlappingLinearLayout.java
rename to leanback/src/android/support/v17/leanback/widget/NonOverlappingLinearLayout.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/NonOverlappingLinearLayoutWithForeground.java b/leanback/src/android/support/v17/leanback/widget/NonOverlappingLinearLayoutWithForeground.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/NonOverlappingLinearLayoutWithForeground.java
rename to leanback/src/android/support/v17/leanback/widget/NonOverlappingLinearLayoutWithForeground.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/NonOverlappingRelativeLayout.java b/leanback/src/android/support/v17/leanback/widget/NonOverlappingRelativeLayout.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/NonOverlappingRelativeLayout.java
rename to leanback/src/android/support/v17/leanback/widget/NonOverlappingRelativeLayout.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/NonOverlappingView.java b/leanback/src/android/support/v17/leanback/widget/NonOverlappingView.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/NonOverlappingView.java
rename to leanback/src/android/support/v17/leanback/widget/NonOverlappingView.java
diff --git a/leanback/src/android/support/v17/leanback/widget/ObjectAdapter.java b/leanback/src/android/support/v17/leanback/widget/ObjectAdapter.java
new file mode 100644
index 0000000..d411f9e
--- /dev/null
+++ b/leanback/src/android/support/v17/leanback/widget/ObjectAdapter.java
@@ -0,0 +1,352 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.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
+ * decoupled from the presentation of the items via {@link PresenterSelector}.
+ */
+public abstract class ObjectAdapter {
+
+    /** Indicates that an id has not been set. */
+    public static final int NO_ID = -1;
+
+    /**
+     * A DataObserver can be notified when an ObjectAdapter's underlying data
+     * changes. Separate methods provide notifications about different types of
+     * changes.
+     */
+    public static abstract class DataObserver {
+        /**
+         * Called whenever the ObjectAdapter's data has changed in some manner
+         * outside of the set of changes covered by the other range-based change
+         * notification methods.
+         */
+        public void onChanged() {
+        }
+
+        /**
+         * Called when a range of items in the ObjectAdapter has changed. The
+         * basic ordering and structure of the ObjectAdapter has not changed.
+         *
+         * @param positionStart The position of the first item that changed.
+         * @param itemCount     The number of items changed.
+         */
+        public void onItemRangeChanged(int positionStart, int itemCount) {
+            onChanged();
+        }
+
+        /**
+         * Called when a range of items in the ObjectAdapter has changed. The
+         * basic ordering and structure of the ObjectAdapter has not changed.
+         *
+         * @param positionStart The position of the first item that changed.
+         * @param itemCount     The number of items changed.
+         * @param payload       Optional parameter, use null to identify a "full" update.
+         */
+        public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
+            onChanged();
+        }
+
+        /**
+         * Called when a range of items is inserted into the ObjectAdapter.
+         *
+         * @param positionStart The position of the first inserted item.
+         * @param itemCount     The number of items inserted.
+         */
+        public void onItemRangeInserted(int positionStart, int itemCount) {
+            onChanged();
+        }
+
+        /**
+         * Called when an item is moved from one position to another position
+         *
+         * @param fromPosition Previous position of the item.
+         * @param toPosition   New position of the item.
+         */
+        public void onItemMoved(int fromPosition, int toPosition) {
+            onChanged();
+        }
+
+        /**
+         * Called when a range of items is removed from the ObjectAdapter.
+         *
+         * @param positionStart The position of the first removed item.
+         * @param itemCount     The number of items removed.
+         */
+        public void onItemRangeRemoved(int positionStart, int itemCount) {
+            onChanged();
+        }
+    }
+
+    private static final class DataObservable extends Observable<DataObserver> {
+
+        DataObservable() {
+        }
+
+        public void notifyChanged() {
+            for (int i = mObservers.size() - 1; i >= 0; i--) {
+                mObservers.get(i).onChanged();
+            }
+        }
+
+        public void notifyItemRangeChanged(int positionStart, int itemCount) {
+            for (int i = mObservers.size() - 1; i >= 0; i--) {
+                mObservers.get(i).onItemRangeChanged(positionStart, itemCount);
+            }
+        }
+
+        public void notifyItemRangeChanged(int positionStart, int itemCount, Object payload) {
+            for (int i = mObservers.size() - 1; i >= 0; i--) {
+                mObservers.get(i).onItemRangeChanged(positionStart, itemCount, payload);
+            }
+        }
+
+        public void notifyItemRangeInserted(int positionStart, int itemCount) {
+            for (int i = mObservers.size() - 1; i >= 0; i--) {
+                mObservers.get(i).onItemRangeInserted(positionStart, itemCount);
+            }
+        }
+
+        public void notifyItemRangeRemoved(int positionStart, int itemCount) {
+            for (int i = mObservers.size() - 1; i >= 0; i--) {
+                mObservers.get(i).onItemRangeRemoved(positionStart, itemCount);
+            }
+        }
+
+        public void notifyItemMoved(int positionStart, int toPosition) {
+            for (int i = mObservers.size() - 1; i >= 0; i--) {
+                mObservers.get(i).onItemMoved(positionStart, toPosition);
+            }
+        }
+
+        boolean hasObserver() {
+            return mObservers.size() > 0;
+        }
+    }
+
+    private final DataObservable mObservable = new DataObservable();
+    private boolean mHasStableIds;
+    private PresenterSelector mPresenterSelector;
+
+    /**
+     * Constructs an adapter with the given {@link PresenterSelector}.
+     */
+    public ObjectAdapter(PresenterSelector presenterSelector) {
+        setPresenterSelector(presenterSelector);
+    }
+
+    /**
+     * Constructs an adapter that uses the given {@link Presenter} for all items.
+     */
+    public ObjectAdapter(Presenter presenter) {
+        setPresenterSelector(new SinglePresenterSelector(presenter));
+    }
+
+    /**
+     * Constructs an adapter.
+     */
+    public ObjectAdapter() {
+    }
+
+    /**
+     * Sets the presenter selector.  May not be null.
+     */
+    public final void setPresenterSelector(PresenterSelector presenterSelector) {
+        if (presenterSelector == null) {
+            throw new IllegalArgumentException("Presenter selector must not be null");
+        }
+        final boolean update = (mPresenterSelector != null);
+        final boolean selectorChanged = update && mPresenterSelector != presenterSelector;
+
+        mPresenterSelector = presenterSelector;
+
+        if (selectorChanged) {
+            onPresenterSelectorChanged();
+        }
+        if (update) {
+            notifyChanged();
+        }
+    }
+
+    /**
+     * Called when {@link #setPresenterSelector(PresenterSelector)} is called
+     * and the PresenterSelector differs from the previous one.
+     */
+    protected void onPresenterSelectorChanged() {
+    }
+
+    /**
+     * Returns the presenter selector for this ObjectAdapter.
+     */
+    public final PresenterSelector getPresenterSelector() {
+        return mPresenterSelector;
+    }
+
+    /**
+     * Registers a DataObserver for data change notifications.
+     */
+    public final void registerObserver(DataObserver observer) {
+        mObservable.registerObserver(observer);
+    }
+
+    /**
+     * Unregisters a DataObserver for data change notifications.
+     */
+    public final void unregisterObserver(DataObserver observer) {
+        mObservable.unregisterObserver(observer);
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    public final boolean hasObserver() {
+        return mObservable.hasObserver();
+    }
+
+    /**
+     * Unregisters all DataObservers for this ObjectAdapter.
+     */
+    public final void unregisterAllObservers() {
+        mObservable.unregisterAll();
+    }
+
+    /**
+     * Notifies UI that some items has changed.
+     *
+     * @param positionStart Starting position of the changed items.
+     * @param itemCount     Total number of items that changed.
+     */
+    public final void notifyItemRangeChanged(int positionStart, int itemCount) {
+        mObservable.notifyItemRangeChanged(positionStart, itemCount);
+    }
+
+    /**
+     * Notifies UI that some items has changed.
+     *
+     * @param positionStart Starting position of the changed items.
+     * @param itemCount     Total number of items that changed.
+     * @param payload       Optional parameter, use null to identify a "full" update.
+     */
+    public final void notifyItemRangeChanged(int positionStart, int itemCount, Object payload) {
+        mObservable.notifyItemRangeChanged(positionStart, itemCount, payload);
+    }
+
+    /**
+     * Notifies UI that new items has been inserted.
+     *
+     * @param positionStart Position where new items has been inserted.
+     * @param itemCount     Count of the new items has been inserted.
+     */
+    final protected void notifyItemRangeInserted(int positionStart, int itemCount) {
+        mObservable.notifyItemRangeInserted(positionStart, itemCount);
+    }
+
+    /**
+     * Notifies UI that some items that has been removed.
+     *
+     * @param positionStart Starting position of the removed items.
+     * @param itemCount     Total number of items that has been removed.
+     */
+    final protected void notifyItemRangeRemoved(int positionStart, int itemCount) {
+        mObservable.notifyItemRangeRemoved(positionStart, itemCount);
+    }
+
+    /**
+     * Notifies UI that item at fromPosition has been moved to toPosition.
+     *
+     * @param fromPosition Previous position of the item.
+     * @param toPosition   New position of the item.
+     */
+    protected final void notifyItemMoved(int fromPosition, int toPosition) {
+        mObservable.notifyItemMoved(fromPosition, toPosition);
+    }
+
+    /**
+     * Notifies UI that the underlying data has changed.
+     */
+    final protected void notifyChanged() {
+        mObservable.notifyChanged();
+    }
+
+    /**
+     * Returns true if the item ids are stable across changes to the
+     * underlying data.  When this is true, clients of the ObjectAdapter can use
+     * {@link #getId(int)} to correlate Objects across changes.
+     */
+    public final boolean hasStableIds() {
+        return mHasStableIds;
+    }
+
+    /**
+     * Sets whether the item ids are stable across changes to the underlying
+     * data.
+     */
+    public final void setHasStableIds(boolean hasStableIds) {
+        boolean changed = mHasStableIds != hasStableIds;
+        mHasStableIds = hasStableIds;
+
+        if (changed) {
+            onHasStableIdsChanged();
+        }
+    }
+
+    /**
+     * Called when {@link #setHasStableIds(boolean)} is called and the status
+     * of stable ids has changed.
+     */
+    protected void onHasStableIdsChanged() {
+    }
+
+    /**
+     * Returns the {@link Presenter} for the given item from the adapter.
+     */
+    public final Presenter getPresenter(Object item) {
+        if (mPresenterSelector == null) {
+            throw new IllegalStateException("Presenter selector must not be null");
+        }
+        return mPresenterSelector.getPresenter(item);
+    }
+
+    /**
+     * Returns the number of items in the adapter.
+     */
+    public abstract int size();
+
+    /**
+     * Returns the item for the given position.
+     */
+    public abstract Object get(int position);
+
+    /**
+     * Returns the id for the given position.
+     */
+    public long getId(int position) {
+        return NO_ID;
+    }
+
+    /**
+     * Returns true if the adapter pairs each underlying data change with a call to notify and
+     * false otherwise.
+     */
+    public boolean isImmediateNotifySupported() {
+        return false;
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/OnActionClickedListener.java b/leanback/src/android/support/v17/leanback/widget/OnActionClickedListener.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/OnActionClickedListener.java
rename to leanback/src/android/support/v17/leanback/widget/OnActionClickedListener.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/OnChildLaidOutListener.java b/leanback/src/android/support/v17/leanback/widget/OnChildLaidOutListener.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/OnChildLaidOutListener.java
rename to leanback/src/android/support/v17/leanback/widget/OnChildLaidOutListener.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/OnChildSelectedListener.java b/leanback/src/android/support/v17/leanback/widget/OnChildSelectedListener.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/OnChildSelectedListener.java
rename to leanback/src/android/support/v17/leanback/widget/OnChildSelectedListener.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/OnChildViewHolderSelectedListener.java b/leanback/src/android/support/v17/leanback/widget/OnChildViewHolderSelectedListener.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/OnChildViewHolderSelectedListener.java
rename to leanback/src/android/support/v17/leanback/widget/OnChildViewHolderSelectedListener.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/OnItemViewClickedListener.java b/leanback/src/android/support/v17/leanback/widget/OnItemViewClickedListener.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/OnItemViewClickedListener.java
rename to leanback/src/android/support/v17/leanback/widget/OnItemViewClickedListener.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/OnItemViewSelectedListener.java b/leanback/src/android/support/v17/leanback/widget/OnItemViewSelectedListener.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/OnItemViewSelectedListener.java
rename to leanback/src/android/support/v17/leanback/widget/OnItemViewSelectedListener.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/PageRow.java b/leanback/src/android/support/v17/leanback/widget/PageRow.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/PageRow.java
rename to leanback/src/android/support/v17/leanback/widget/PageRow.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/PagingIndicator.java b/leanback/src/android/support/v17/leanback/widget/PagingIndicator.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/PagingIndicator.java
rename to leanback/src/android/support/v17/leanback/widget/PagingIndicator.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/Parallax.java b/leanback/src/android/support/v17/leanback/widget/Parallax.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/Parallax.java
rename to leanback/src/android/support/v17/leanback/widget/Parallax.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ParallaxEffect.java b/leanback/src/android/support/v17/leanback/widget/ParallaxEffect.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/ParallaxEffect.java
rename to leanback/src/android/support/v17/leanback/widget/ParallaxEffect.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ParallaxTarget.java b/leanback/src/android/support/v17/leanback/widget/ParallaxTarget.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/ParallaxTarget.java
rename to leanback/src/android/support/v17/leanback/widget/ParallaxTarget.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/PersistentFocusWrapper.java b/leanback/src/android/support/v17/leanback/widget/PersistentFocusWrapper.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/PersistentFocusWrapper.java
rename to leanback/src/android/support/v17/leanback/widget/PersistentFocusWrapper.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/PlaybackControlsPresenter.java b/leanback/src/android/support/v17/leanback/widget/PlaybackControlsPresenter.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/PlaybackControlsPresenter.java
rename to leanback/src/android/support/v17/leanback/widget/PlaybackControlsPresenter.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/PlaybackControlsRow.java b/leanback/src/android/support/v17/leanback/widget/PlaybackControlsRow.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/PlaybackControlsRow.java
rename to leanback/src/android/support/v17/leanback/widget/PlaybackControlsRow.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/PlaybackControlsRowPresenter.java b/leanback/src/android/support/v17/leanback/widget/PlaybackControlsRowPresenter.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/PlaybackControlsRowPresenter.java
rename to leanback/src/android/support/v17/leanback/widget/PlaybackControlsRowPresenter.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/PlaybackControlsRowView.java b/leanback/src/android/support/v17/leanback/widget/PlaybackControlsRowView.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/PlaybackControlsRowView.java
rename to leanback/src/android/support/v17/leanback/widget/PlaybackControlsRowView.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/PlaybackRowPresenter.java b/leanback/src/android/support/v17/leanback/widget/PlaybackRowPresenter.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/PlaybackRowPresenter.java
rename to leanback/src/android/support/v17/leanback/widget/PlaybackRowPresenter.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/PlaybackSeekDataProvider.java b/leanback/src/android/support/v17/leanback/widget/PlaybackSeekDataProvider.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/PlaybackSeekDataProvider.java
rename to leanback/src/android/support/v17/leanback/widget/PlaybackSeekDataProvider.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/PlaybackSeekUi.java b/leanback/src/android/support/v17/leanback/widget/PlaybackSeekUi.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/PlaybackSeekUi.java
rename to leanback/src/android/support/v17/leanback/widget/PlaybackSeekUi.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/PlaybackTransportRowPresenter.java b/leanback/src/android/support/v17/leanback/widget/PlaybackTransportRowPresenter.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/PlaybackTransportRowPresenter.java
rename to leanback/src/android/support/v17/leanback/widget/PlaybackTransportRowPresenter.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/PlaybackTransportRowView.java b/leanback/src/android/support/v17/leanback/widget/PlaybackTransportRowView.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/PlaybackTransportRowView.java
rename to leanback/src/android/support/v17/leanback/widget/PlaybackTransportRowView.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/Presenter.java b/leanback/src/android/support/v17/leanback/widget/Presenter.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/Presenter.java
rename to leanback/src/android/support/v17/leanback/widget/Presenter.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/PresenterSelector.java b/leanback/src/android/support/v17/leanback/widget/PresenterSelector.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/PresenterSelector.java
rename to leanback/src/android/support/v17/leanback/widget/PresenterSelector.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/PresenterSwitcher.java b/leanback/src/android/support/v17/leanback/widget/PresenterSwitcher.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/PresenterSwitcher.java
rename to leanback/src/android/support/v17/leanback/widget/PresenterSwitcher.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/RecyclerViewParallax.java b/leanback/src/android/support/v17/leanback/widget/RecyclerViewParallax.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/RecyclerViewParallax.java
rename to leanback/src/android/support/v17/leanback/widget/RecyclerViewParallax.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ResizingTextView.java b/leanback/src/android/support/v17/leanback/widget/ResizingTextView.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/ResizingTextView.java
rename to leanback/src/android/support/v17/leanback/widget/ResizingTextView.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/RoundedRectHelper.java b/leanback/src/android/support/v17/leanback/widget/RoundedRectHelper.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/RoundedRectHelper.java
rename to leanback/src/android/support/v17/leanback/widget/RoundedRectHelper.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/Row.java b/leanback/src/android/support/v17/leanback/widget/Row.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/Row.java
rename to leanback/src/android/support/v17/leanback/widget/Row.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/RowContainerView.java b/leanback/src/android/support/v17/leanback/widget/RowContainerView.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/RowContainerView.java
rename to leanback/src/android/support/v17/leanback/widget/RowContainerView.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/RowHeaderPresenter.java b/leanback/src/android/support/v17/leanback/widget/RowHeaderPresenter.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/RowHeaderPresenter.java
rename to leanback/src/android/support/v17/leanback/widget/RowHeaderPresenter.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/RowHeaderView.java b/leanback/src/android/support/v17/leanback/widget/RowHeaderView.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/RowHeaderView.java
rename to leanback/src/android/support/v17/leanback/widget/RowHeaderView.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/RowPresenter.java b/leanback/src/android/support/v17/leanback/widget/RowPresenter.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/RowPresenter.java
rename to leanback/src/android/support/v17/leanback/widget/RowPresenter.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ScaleFrameLayout.java b/leanback/src/android/support/v17/leanback/widget/ScaleFrameLayout.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/ScaleFrameLayout.java
rename to leanback/src/android/support/v17/leanback/widget/ScaleFrameLayout.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/SearchBar.java b/leanback/src/android/support/v17/leanback/widget/SearchBar.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/SearchBar.java
rename to leanback/src/android/support/v17/leanback/widget/SearchBar.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/SearchEditText.java b/leanback/src/android/support/v17/leanback/widget/SearchEditText.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/SearchEditText.java
rename to leanback/src/android/support/v17/leanback/widget/SearchEditText.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/SearchOrbView.java b/leanback/src/android/support/v17/leanback/widget/SearchOrbView.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/SearchOrbView.java
rename to leanback/src/android/support/v17/leanback/widget/SearchOrbView.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/SectionRow.java b/leanback/src/android/support/v17/leanback/widget/SectionRow.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/SectionRow.java
rename to leanback/src/android/support/v17/leanback/widget/SectionRow.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/SeekBar.java b/leanback/src/android/support/v17/leanback/widget/SeekBar.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/SeekBar.java
rename to leanback/src/android/support/v17/leanback/widget/SeekBar.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ShadowHelper.java b/leanback/src/android/support/v17/leanback/widget/ShadowHelper.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/ShadowHelper.java
rename to leanback/src/android/support/v17/leanback/widget/ShadowHelper.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ShadowOverlayContainer.java b/leanback/src/android/support/v17/leanback/widget/ShadowOverlayContainer.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/ShadowOverlayContainer.java
rename to leanback/src/android/support/v17/leanback/widget/ShadowOverlayContainer.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ShadowOverlayHelper.java b/leanback/src/android/support/v17/leanback/widget/ShadowOverlayHelper.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/ShadowOverlayHelper.java
rename to leanback/src/android/support/v17/leanback/widget/ShadowOverlayHelper.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/SinglePresenterSelector.java b/leanback/src/android/support/v17/leanback/widget/SinglePresenterSelector.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/SinglePresenterSelector.java
rename to leanback/src/android/support/v17/leanback/widget/SinglePresenterSelector.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/SingleRow.java b/leanback/src/android/support/v17/leanback/widget/SingleRow.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/SingleRow.java
rename to leanback/src/android/support/v17/leanback/widget/SingleRow.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/SparseArrayObjectAdapter.java b/leanback/src/android/support/v17/leanback/widget/SparseArrayObjectAdapter.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/SparseArrayObjectAdapter.java
rename to leanback/src/android/support/v17/leanback/widget/SparseArrayObjectAdapter.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/SpeechOrbView.java b/leanback/src/android/support/v17/leanback/widget/SpeechOrbView.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/SpeechOrbView.java
rename to leanback/src/android/support/v17/leanback/widget/SpeechOrbView.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/SpeechRecognitionCallback.java b/leanback/src/android/support/v17/leanback/widget/SpeechRecognitionCallback.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/SpeechRecognitionCallback.java
rename to leanback/src/android/support/v17/leanback/widget/SpeechRecognitionCallback.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/StaggeredGrid.java b/leanback/src/android/support/v17/leanback/widget/StaggeredGrid.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/StaggeredGrid.java
rename to leanback/src/android/support/v17/leanback/widget/StaggeredGrid.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/StaggeredGridDefault.java b/leanback/src/android/support/v17/leanback/widget/StaggeredGridDefault.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/StaggeredGridDefault.java
rename to leanback/src/android/support/v17/leanback/widget/StaggeredGridDefault.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/StaticShadowHelper.java b/leanback/src/android/support/v17/leanback/widget/StaticShadowHelper.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/StaticShadowHelper.java
rename to leanback/src/android/support/v17/leanback/widget/StaticShadowHelper.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/StreamingTextView.java b/leanback/src/android/support/v17/leanback/widget/StreamingTextView.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/StreamingTextView.java
rename to leanback/src/android/support/v17/leanback/widget/StreamingTextView.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ThumbsBar.java b/leanback/src/android/support/v17/leanback/widget/ThumbsBar.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/ThumbsBar.java
rename to leanback/src/android/support/v17/leanback/widget/ThumbsBar.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/TitleHelper.java b/leanback/src/android/support/v17/leanback/widget/TitleHelper.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/TitleHelper.java
rename to leanback/src/android/support/v17/leanback/widget/TitleHelper.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/TitleView.java b/leanback/src/android/support/v17/leanback/widget/TitleView.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/TitleView.java
rename to leanback/src/android/support/v17/leanback/widget/TitleView.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/TitleViewAdapter.java b/leanback/src/android/support/v17/leanback/widget/TitleViewAdapter.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/TitleViewAdapter.java
rename to leanback/src/android/support/v17/leanback/widget/TitleViewAdapter.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/Util.java b/leanback/src/android/support/v17/leanback/widget/Util.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/Util.java
rename to leanback/src/android/support/v17/leanback/widget/Util.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/VerticalGridPresenter.java b/leanback/src/android/support/v17/leanback/widget/VerticalGridPresenter.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/VerticalGridPresenter.java
rename to leanback/src/android/support/v17/leanback/widget/VerticalGridPresenter.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/VerticalGridView.java b/leanback/src/android/support/v17/leanback/widget/VerticalGridView.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/VerticalGridView.java
rename to leanback/src/android/support/v17/leanback/widget/VerticalGridView.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/VideoSurfaceView.java b/leanback/src/android/support/v17/leanback/widget/VideoSurfaceView.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/VideoSurfaceView.java
rename to leanback/src/android/support/v17/leanback/widget/VideoSurfaceView.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ViewHolderTask.java b/leanback/src/android/support/v17/leanback/widget/ViewHolderTask.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/ViewHolderTask.java
rename to leanback/src/android/support/v17/leanback/widget/ViewHolderTask.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ViewsStateBundle.java b/leanback/src/android/support/v17/leanback/widget/ViewsStateBundle.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/ViewsStateBundle.java
rename to leanback/src/android/support/v17/leanback/widget/ViewsStateBundle.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/Visibility.java b/leanback/src/android/support/v17/leanback/widget/Visibility.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/Visibility.java
rename to leanback/src/android/support/v17/leanback/widget/Visibility.java
diff --git a/leanback/src/android/support/v17/leanback/widget/WindowAlignment.java b/leanback/src/android/support/v17/leanback/widget/WindowAlignment.java
new file mode 100644
index 0000000..c6589d4
--- /dev/null
+++ b/leanback/src/android/support/v17/leanback/widget/WindowAlignment.java
@@ -0,0 +1,396 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package android.support.v17.leanback.widget;
+
+import static android.support.v17.leanback.widget.BaseGridView.WINDOW_ALIGN_BOTH_EDGE;
+import static android.support.v17.leanback.widget.BaseGridView.WINDOW_ALIGN_HIGH_EDGE;
+import static android.support.v17.leanback.widget.BaseGridView.WINDOW_ALIGN_LOW_EDGE;
+import static android.support.v17.leanback.widget.BaseGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED;
+import static android.support.v7.widget.RecyclerView.HORIZONTAL;
+
+/**
+ * Maintains Window Alignment information of two axis.
+ */
+class WindowAlignment {
+
+    /**
+     * Maintains alignment information in one direction.
+     */
+    public static class Axis {
+        /**
+         * Right or bottom edge of last child.
+         */
+        private int mMaxEdge;
+        /**
+         * Left or top edge of first child
+         */
+        private int mMinEdge;
+        /**
+         * Scroll distance to align last child, it defines limit of scroll.
+         */
+        private int mMaxScroll;
+        /**
+         * Scroll distance to align first child, it defines limit of scroll.
+         */
+        private int mMinScroll;
+
+        static final int PF_KEYLINE_OVER_LOW_EDGE = 1;
+        static final int PF_KEYLINE_OVER_HIGH_EDGE = 1 << 1;
+
+        /**
+         * By default we prefer low edge over keyline, prefer keyline over high edge.
+         */
+        private int mPreferredKeyLine = PF_KEYLINE_OVER_HIGH_EDGE;
+
+        private int mWindowAlignment = WINDOW_ALIGN_BOTH_EDGE;
+
+        private int mWindowAlignmentOffset = 0;
+
+        private float mWindowAlignmentOffsetPercent = 50f;
+
+        private int mSize;
+
+        /**
+         * Padding at the min edge, it is the left or top padding.
+         */
+        private int mPaddingMin;
+
+        /**
+         * Padding at the max edge, it is the right or bottom padding.
+         */
+        private int mPaddingMax;
+
+        private boolean mReversedFlow;
+
+        private String mName; // for debugging
+
+        public Axis(String name) {
+            reset();
+            mName = name;
+        }
+
+        public final int getWindowAlignment() {
+            return mWindowAlignment;
+        }
+
+        public final void setWindowAlignment(int windowAlignment) {
+            mWindowAlignment = windowAlignment;
+        }
+
+        final void setPreferKeylineOverLowEdge(boolean keylineOverLowEdge) {
+            mPreferredKeyLine = keylineOverLowEdge
+                    ? mPreferredKeyLine | PF_KEYLINE_OVER_LOW_EDGE
+                    : mPreferredKeyLine & ~PF_KEYLINE_OVER_LOW_EDGE;
+        }
+
+        final void setPreferKeylineOverHighEdge(boolean keylineOverHighEdge) {
+            mPreferredKeyLine = keylineOverHighEdge
+                    ? mPreferredKeyLine | PF_KEYLINE_OVER_HIGH_EDGE
+                    : mPreferredKeyLine & ~PF_KEYLINE_OVER_HIGH_EDGE;
+        }
+
+        final boolean isPreferKeylineOverHighEdge() {
+            return (mPreferredKeyLine & PF_KEYLINE_OVER_HIGH_EDGE) != 0;
+        }
+
+        final boolean isPreferKeylineOverLowEdge() {
+            return (mPreferredKeyLine & PF_KEYLINE_OVER_LOW_EDGE) != 0;
+        }
+
+        public final int getWindowAlignmentOffset() {
+            return mWindowAlignmentOffset;
+        }
+
+        public final void setWindowAlignmentOffset(int offset) {
+            mWindowAlignmentOffset = offset;
+        }
+
+        public final void setWindowAlignmentOffsetPercent(float percent) {
+            if ((percent < 0 || percent > 100)
+                    && percent != WINDOW_ALIGN_OFFSET_PERCENT_DISABLED) {
+                throw new IllegalArgumentException();
+            }
+            mWindowAlignmentOffsetPercent = percent;
+        }
+
+        public final float getWindowAlignmentOffsetPercent() {
+            return mWindowAlignmentOffsetPercent;
+        }
+
+        /**
+         * Returns scroll distance to align min child.
+         */
+        public final int getMinScroll() {
+            return mMinScroll;
+        }
+
+        public final void invalidateScrollMin() {
+            mMinEdge = Integer.MIN_VALUE;
+            mMinScroll = Integer.MIN_VALUE;
+        }
+
+        /**
+         * Returns scroll distance to align max child.
+         */
+        public final int getMaxScroll() {
+            return mMaxScroll;
+        }
+
+        public final void invalidateScrollMax() {
+            mMaxEdge = Integer.MAX_VALUE;
+            mMaxScroll = Integer.MAX_VALUE;
+        }
+
+        void reset() {
+            mMinEdge = Integer.MIN_VALUE;
+            mMaxEdge = Integer.MAX_VALUE;
+        }
+
+        public final boolean isMinUnknown() {
+            return mMinEdge == Integer.MIN_VALUE;
+        }
+
+        public final boolean isMaxUnknown() {
+            return mMaxEdge == Integer.MAX_VALUE;
+        }
+
+        public final void setSize(int size) {
+            mSize = size;
+        }
+
+        public final int getSize() {
+            return mSize;
+        }
+
+        public final void setPadding(int paddingMin, int paddingMax) {
+            mPaddingMin = paddingMin;
+            mPaddingMax = paddingMax;
+        }
+
+        public final int getPaddingMin() {
+            return mPaddingMin;
+        }
+
+        public final int getPaddingMax() {
+            return mPaddingMax;
+        }
+
+        public final int getClientSize() {
+            return mSize - mPaddingMin - mPaddingMax;
+        }
+
+        final int calculateKeyline() {
+            int keyLine;
+            if (!mReversedFlow) {
+                if (mWindowAlignmentOffset >= 0) {
+                    keyLine = mWindowAlignmentOffset;
+                } else {
+                    keyLine = mSize + mWindowAlignmentOffset;
+                }
+                if (mWindowAlignmentOffsetPercent != WINDOW_ALIGN_OFFSET_PERCENT_DISABLED) {
+                    keyLine += (int) (mSize * mWindowAlignmentOffsetPercent / 100);
+                }
+            } else {
+                if (mWindowAlignmentOffset >= 0) {
+                    keyLine = mSize - mWindowAlignmentOffset;
+                } else {
+                    keyLine = -mWindowAlignmentOffset;
+                }
+                if (mWindowAlignmentOffsetPercent != WINDOW_ALIGN_OFFSET_PERCENT_DISABLED) {
+                    keyLine -= (int) (mSize * mWindowAlignmentOffsetPercent / 100);
+                }
+            }
+            return keyLine;
+        }
+
+        /**
+         * Returns scroll distance to move viewCenterPosition to keyLine.
+         */
+        final int calculateScrollToKeyLine(int viewCenterPosition, int keyLine) {
+            return viewCenterPosition - keyLine;
+        }
+
+        /**
+         * Update {@link #getMinScroll()} and {@link #getMaxScroll()}
+         */
+        public final void updateMinMax(int minEdge, int maxEdge,
+                int minChildViewCenter, int maxChildViewCenter) {
+            mMinEdge = minEdge;
+            mMaxEdge = maxEdge;
+            final int clientSize = getClientSize();
+            final int keyLine = calculateKeyline();
+            final boolean isMinUnknown = isMinUnknown();
+            final boolean isMaxUnknown = isMaxUnknown();
+            if (!isMinUnknown) {
+                if (!mReversedFlow ? (mWindowAlignment & WINDOW_ALIGN_LOW_EDGE) != 0
+                        : (mWindowAlignment & WINDOW_ALIGN_HIGH_EDGE) != 0) {
+                    // calculate scroll distance to move current mMinEdge to padding at min edge
+                    mMinScroll = mMinEdge - mPaddingMin;
+                } else  {
+                    // calculate scroll distance to move min child center to key line
+                    mMinScroll = calculateScrollToKeyLine(minChildViewCenter, keyLine);
+                }
+            }
+            if (!isMaxUnknown) {
+                if (!mReversedFlow ? (mWindowAlignment & WINDOW_ALIGN_HIGH_EDGE) != 0
+                        : (mWindowAlignment & WINDOW_ALIGN_LOW_EDGE) != 0) {
+                    // calculate scroll distance to move current mMaxEdge to padding at max edge
+                    mMaxScroll = mMaxEdge - mPaddingMin - clientSize;
+                } else  {
+                    // calculate scroll distance to move max child center to key line
+                    mMaxScroll = calculateScrollToKeyLine(maxChildViewCenter, keyLine);
+                }
+            }
+            if (!isMaxUnknown && !isMinUnknown) {
+                if (!mReversedFlow) {
+                    if ((mWindowAlignment & WINDOW_ALIGN_LOW_EDGE) != 0) {
+                        if (isPreferKeylineOverLowEdge()) {
+                            // if we prefer key line, might align max child to key line for
+                            // minScroll
+                            mMinScroll = Math.min(mMinScroll,
+                                    calculateScrollToKeyLine(maxChildViewCenter, keyLine));
+                        }
+                        // don't over scroll max
+                        mMaxScroll = Math.max(mMinScroll, mMaxScroll);
+                    } else if ((mWindowAlignment & WINDOW_ALIGN_HIGH_EDGE) != 0) {
+                        if (isPreferKeylineOverHighEdge()) {
+                            // if we prefer key line, might align min child to key line for
+                            // maxScroll
+                            mMaxScroll = Math.max(mMaxScroll,
+                                    calculateScrollToKeyLine(minChildViewCenter, keyLine));
+                        }
+                        // don't over scroll min
+                        mMinScroll = Math.min(mMinScroll, mMaxScroll);
+                    }
+                } else {
+                    if ((mWindowAlignment & WINDOW_ALIGN_LOW_EDGE) != 0) {
+                        if (isPreferKeylineOverLowEdge()) {
+                            // if we prefer key line, might align min child to key line for
+                            // maxScroll
+                            mMaxScroll = Math.max(mMaxScroll,
+                                    calculateScrollToKeyLine(minChildViewCenter, keyLine));
+                        }
+                        // don't over scroll min
+                        mMinScroll = Math.min(mMinScroll, mMaxScroll);
+                    } else if ((mWindowAlignment & WINDOW_ALIGN_HIGH_EDGE) != 0) {
+                        if (isPreferKeylineOverHighEdge()) {
+                            // if we prefer key line, might align max child to key line for
+                            // minScroll
+                            mMinScroll = Math.min(mMinScroll,
+                                    calculateScrollToKeyLine(maxChildViewCenter, keyLine));
+                        }
+                        // don't over scroll max
+                        mMaxScroll = Math.max(mMinScroll, mMaxScroll);
+                    }
+                }
+            }
+        }
+
+        /**
+         * Get scroll distance of align an item (depends on ALIGN_LOW_EDGE, ALIGN_HIGH_EDGE or the
+         * item should be aligned to key line). The scroll distance will be capped by
+         * {@link #getMinScroll()} and {@link #getMaxScroll()}.
+         */
+        public final int getScroll(int viewCenter) {
+            final int size = getSize();
+            final int keyLine = calculateKeyline();
+            final boolean isMinUnknown = isMinUnknown();
+            final boolean isMaxUnknown = isMaxUnknown();
+            if (!isMinUnknown) {
+                final int keyLineToMinEdge = keyLine - mPaddingMin;
+                if ((!mReversedFlow ? (mWindowAlignment & WINDOW_ALIGN_LOW_EDGE) != 0
+                     : (mWindowAlignment & WINDOW_ALIGN_HIGH_EDGE) != 0)
+                        && (viewCenter - mMinEdge <= keyLineToMinEdge)) {
+                    // view center is before key line: align the min edge (first child) to padding.
+                    int alignToMin = mMinEdge - mPaddingMin;
+                    // Also we need make sure don't over scroll
+                    if (!isMaxUnknown && alignToMin > mMaxScroll) {
+                        alignToMin = mMaxScroll;
+                    }
+                    return alignToMin;
+                }
+            }
+            if (!isMaxUnknown) {
+                final int keyLineToMaxEdge = size - keyLine - mPaddingMax;
+                if ((!mReversedFlow ? (mWindowAlignment & WINDOW_ALIGN_HIGH_EDGE) != 0
+                        : (mWindowAlignment & WINDOW_ALIGN_LOW_EDGE) != 0)
+                        && (mMaxEdge - viewCenter <= keyLineToMaxEdge)) {
+                    // view center is after key line: align the max edge (last child) to padding.
+                    int alignToMax = mMaxEdge - (size - mPaddingMax);
+                    // Also we need make sure don't over scroll
+                    if (!isMinUnknown && alignToMax < mMinScroll) {
+                        alignToMax = mMinScroll;
+                    }
+                    return alignToMax;
+                }
+            }
+            // else put view center at key line.
+            return calculateScrollToKeyLine(viewCenter, keyLine);
+        }
+
+        public final void setReversedFlow(boolean reversedFlow) {
+            mReversedFlow = reversedFlow;
+        }
+
+        @Override
+        public String toString() {
+            return " min:" + mMinEdge + " " + mMinScroll + " max:" + mMaxEdge + " " + mMaxScroll;
+        }
+
+    }
+
+    private int mOrientation = HORIZONTAL;
+
+    public final Axis vertical = new Axis("vertical");
+
+    public final Axis horizontal = new Axis("horizontal");
+
+    private Axis mMainAxis = horizontal;
+
+    private Axis mSecondAxis = vertical;
+
+    public final Axis mainAxis() {
+        return mMainAxis;
+    }
+
+    public final Axis secondAxis() {
+        return mSecondAxis;
+    }
+
+    public final void setOrientation(int orientation) {
+        mOrientation = orientation;
+        if (mOrientation == HORIZONTAL) {
+            mMainAxis = horizontal;
+            mSecondAxis = vertical;
+        } else {
+            mMainAxis = vertical;
+            mSecondAxis = horizontal;
+        }
+    }
+
+    public final int getOrientation() {
+        return mOrientation;
+    }
+
+    public final void reset() {
+        mainAxis().reset();
+    }
+
+    @Override
+    public String toString() {
+        return "horizontal=" + horizontal + "; vertical=" + vertical;
+    }
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/package-info.java b/leanback/src/android/support/v17/leanback/widget/package-info.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/package-info.java
rename to leanback/src/android/support/v17/leanback/widget/package-info.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/picker/DatePicker.java b/leanback/src/android/support/v17/leanback/widget/picker/DatePicker.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/picker/DatePicker.java
rename to leanback/src/android/support/v17/leanback/widget/picker/DatePicker.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/picker/Picker.java b/leanback/src/android/support/v17/leanback/widget/picker/Picker.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/picker/Picker.java
rename to leanback/src/android/support/v17/leanback/widget/picker/Picker.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/picker/PickerColumn.java b/leanback/src/android/support/v17/leanback/widget/picker/PickerColumn.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/picker/PickerColumn.java
rename to leanback/src/android/support/v17/leanback/widget/picker/PickerColumn.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/picker/PickerUtility.java b/leanback/src/android/support/v17/leanback/widget/picker/PickerUtility.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/picker/PickerUtility.java
rename to leanback/src/android/support/v17/leanback/widget/picker/PickerUtility.java
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/picker/TimePicker.java b/leanback/src/android/support/v17/leanback/widget/picker/TimePicker.java
similarity index 100%
rename from v17/leanback/src/android/support/v17/leanback/widget/picker/TimePicker.java
rename to leanback/src/android/support/v17/leanback/widget/picker/TimePicker.java
diff --git a/v17/leanback/tests/AndroidManifest.xml b/leanback/tests/AndroidManifest.xml
similarity index 100%
rename from v17/leanback/tests/AndroidManifest.xml
rename to leanback/tests/AndroidManifest.xml
diff --git a/v17/leanback/tests/NO_DOCS b/leanback/tests/NO_DOCS
similarity index 100%
rename from v17/leanback/tests/NO_DOCS
rename to leanback/tests/NO_DOCS
diff --git a/leanback/tests/generatev4.py b/leanback/tests/generatev4.py
new file mode 100755
index 0000000..d7d14a8
--- /dev/null
+++ b/leanback/tests/generatev4.py
@@ -0,0 +1,168 @@
+#!/usr/bin/python
+
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os
+import sys
+
+print "Generate v4 fragment related code for leanback"
+
+####### generate XXXTestFragment classes #######
+
+files = ['BrowseTest', 'GuidedStepTest', 'PlaybackTest', 'DetailsTest']
+
+cls = ['BrowseTest', 'Background', 'Base', 'BaseRow', 'Browse', 'Details', 'Error', 'Headers',
+      'PlaybackOverlay', 'Rows', 'Search', 'VerticalGrid', 'Branded',
+      'GuidedStepTest', 'GuidedStep', 'RowsTest', 'PlaybackTest', 'Playback', 'Video',
+      'DetailsTest']
+
+for w in files:
+    print "copy {}SupportFragment to {}Fragment".format(w, w)
+
+    file = open('java/android/support/v17/leanback/app/{}SupportFragment.java'.format(w), 'r')
+    outfile = open('java/android/support/v17/leanback/app/{}Fragment.java'.format(w), 'w')
+
+    outfile.write("// CHECKSTYLE:OFF Generated code\n")
+    outfile.write("/* This file is auto-generated from {}SupportFragment.java.  DO NOT MODIFY. */\n\n".format(w))
+
+    for line in file:
+        for w in cls:
+            line = line.replace('{}SupportFragment'.format(w), '{}Fragment'.format(w))
+        line = line.replace('android.support.v4.app.FragmentActivity', 'android.app.Activity')
+        line = line.replace('android.support.v4.app.Fragment', 'android.app.Fragment')
+        line = line.replace('FragmentActivity getActivity()', 'Activity getActivity()')
+        outfile.write(line)
+    file.close()
+    outfile.close()
+
+####### generate XXXFragmentTestBase classes #######
+
+testcls = ['GuidedStep', 'Single']
+
+for w in testcls:
+    print "copy {}SupportFrgamentTestBase to {}FragmentTestBase".format(w, w)
+
+    file = open('java/android/support/v17/leanback/app/{}SupportFragmentTestBase.java'.format(w), 'r')
+    outfile = open('java/android/support/v17/leanback/app/{}FragmentTestBase.java'.format(w), 'w')
+
+    outfile.write("// CHECKSTYLE:OFF Generated code\n")
+    outfile.write("/* This file is auto-generated from {}SupportFrgamentTestBase.java.  DO NOT MODIFY. */\n\n".format(w))
+
+    for line in file:
+        for w in cls:
+            line = line.replace('{}SupportFragment'.format(w), '{}Fragment'.format(w))
+        for w in testcls:
+            line = line.replace('{}SupportFragmentTestBase'.format(w), '{}FragmentTestBase'.format(w))
+            line = line.replace('{}SupportFragmentTestActivity'.format(w), '{}FragmentTestActivity'.format(w))
+            line = line.replace('{}TestSupportFragment'.format(w), '{}TestFragment'.format(w))
+        line = line.replace('android.support.v4.app.FragmentActivity', 'android.app.Activity')
+        line = line.replace('android.support.v4.app.Fragment', 'android.app.Fragment')
+        outfile.write(line)
+    file.close()
+    outfile.close()
+
+####### generate XXXFragmentTest classes #######
+
+testcls = ['Browse', 'GuidedStep', 'VerticalGrid', 'Playback', 'Video', 'Details', 'Rows', 'Headers']
+
+for w in testcls:
+    print "copy {}SupporFrgamentTest to {}tFragmentTest".format(w, w)
+
+    file = open('java/android/support/v17/leanback/app/{}SupportFragmentTest.java'.format(w), 'r')
+    outfile = open('java/android/support/v17/leanback/app/{}FragmentTest.java'.format(w), 'w')
+
+    outfile.write("// CHECKSTYLE:OFF Generated code\n")
+    outfile.write("/* This file is auto-generated from {}SupportFragmentTest.java.  DO NOT MODIFY. */\n\n".format(w))
+
+    for line in file:
+        for w in cls:
+            line = line.replace('{}SupportFragment'.format(w), '{}Fragment'.format(w))
+        for w in testcls:
+            line = line.replace('SingleSupportFragmentTestBase', 'SingleFragmentTestBase')
+            line = line.replace('SingleSupportFragmentTestActivity', 'SingleFragmentTestActivity')
+            line = line.replace('{}SupportFragmentTestBase'.format(w), '{}FragmentTestBase'.format(w))
+            line = line.replace('{}SupportFragmentTest'.format(w), '{}FragmentTest'.format(w))
+            line = line.replace('{}SupportFragmentTestActivity'.format(w), '{}FragmentTestActivity'.format(w))
+            line = line.replace('{}TestSupportFragment'.format(w), '{}TestFragment'.format(w))
+        line = line.replace('android.support.v4.app.FragmentActivity', 'android.app.Activity')
+        line = line.replace('android.support.v4.app.Fragment', 'android.app.Fragment')
+	line = line.replace('extends FragmentActivity', 'extends Activity')
+	line = line.replace('Activity.this.getSupportFragmentManager', 'Activity.this.getFragmentManager')
+	line = line.replace('tivity.getSupportFragmentManager', 'tivity.getFragmentManager')
+        outfile.write(line)
+    file.close()
+    outfile.close()
+
+
+####### generate XXXTestActivity classes #######
+testcls = ['Browse', 'GuidedStep', 'Single']
+
+for w in testcls:
+    print "copy {}SupportFragmentTestActivity to {}FragmentTestActivity".format(w, w)
+    file = open('java/android/support/v17/leanback/app/{}SupportFragmentTestActivity.java'.format(w), 'r')
+    outfile = open('java/android/support/v17/leanback/app/{}FragmentTestActivity.java'.format(w), 'w')
+    outfile.write("// CHECKSTYLE:OFF Generated code\n")
+    outfile.write("/* This file is auto-generated from {}SupportFragmentTestActivity.java.  DO NOT MODIFY. */\n\n".format(w))
+    for line in file:
+        line = line.replace('{}TestSupportFragment'.format(w), '{}TestFragment'.format(w))
+        line = line.replace('{}SupportFragmentTestActivity'.format(w), '{}FragmentTestActivity'.format(w))
+        line = line.replace('android.support.v4.app.FragmentActivity', 'android.app.Activity')
+        line = line.replace('android.support.v4.app.Fragment', 'android.app.Fragment')
+        line = line.replace('extends FragmentActivity', 'extends Activity')
+        line = line.replace('getSupportFragmentManager', 'getFragmentManager')
+        outfile.write(line)
+    file.close()
+    outfile.close()
+
+####### generate Float parallax test #######
+
+print "copy ParallaxIntEffectTest to ParallaxFloatEffectTest"
+file = open('java/android/support/v17/leanback/widget/ParallaxIntEffectTest.java', 'r')
+outfile = open('java/android/support/v17/leanback/widget/ParallaxFloatEffectTest.java', 'w')
+outfile.write("// CHECKSTYLE:OFF Generated code\n")
+outfile.write("/* This file is auto-generated from ParallaxIntEffectTest.java.  DO NOT MODIFY. */\n\n")
+for line in file:
+    line = line.replace('IntEffect', 'FloatEffect')
+    line = line.replace('IntParallax', 'FloatParallax')
+    line = line.replace('IntProperty', 'FloatProperty')
+    line = line.replace('intValue()', 'floatValue()')
+    line = line.replace('int screenMax', 'float screenMax')
+    line = line.replace('assertEquals((int)', 'assertFloatEquals((float)')
+    line = line.replace('(int)', '(float)')
+    line = line.replace('int[', 'float[')
+    line = line.replace('Integer', 'Float');
+    outfile.write(line)
+file.close()
+outfile.close()
+
+
+print "copy ParallaxIntTest to ParallaxFloatTest"
+file = open('java/android/support/v17/leanback/widget/ParallaxIntTest.java', 'r')
+outfile = open('java/android/support/v17/leanback/widget/ParallaxFloatTest.java', 'w')
+outfile.write("// CHECKSTYLE:OFF Generated code\n")
+outfile.write("/* This file is auto-generated from ParallaxIntTest.java.  DO NOT MODIFY. */\n\n")
+for line in file:
+    line = line.replace('ParallaxIntTest', 'ParallaxFloatTest')
+    line = line.replace('IntParallax', 'FloatParallax')
+    line = line.replace('IntProperty', 'FloatProperty')
+    line = line.replace('verifyIntProperties', 'verifyFloatProperties')
+    line = line.replace('intValue()', 'floatValue()')
+    line = line.replace('int screenMax', 'float screenMax')
+    line = line.replace('assertEquals((int)', 'assertFloatEquals((float)')
+    line = line.replace('(int)', '(float)')
+    outfile.write(line)
+file.close()
+outfile.close()
+
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/BackgroundManagerTest.java b/leanback/tests/java/android/support/v17/leanback/app/BackgroundManagerTest.java
similarity index 100%
rename from v17/leanback/tests/java/android/support/v17/leanback/app/BackgroundManagerTest.java
rename to leanback/tests/java/android/support/v17/leanback/app/BackgroundManagerTest.java
diff --git a/leanback/tests/java/android/support/v17/leanback/app/BrowseFragmentTest.java b/leanback/tests/java/android/support/v17/leanback/app/BrowseFragmentTest.java
new file mode 100644
index 0000000..654bbe7
--- /dev/null
+++ b/leanback/tests/java/android/support/v17/leanback/app/BrowseFragmentTest.java
@@ -0,0 +1,257 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from BrowseSupportFragmentTest.java.  DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v17.leanback.app;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+import android.content.Intent;
+import android.os.Build;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v17.leanback.testutils.PollingCheck;
+import android.support.v17.leanback.widget.ItemBridgeAdapter;
+import android.support.v17.leanback.widget.ListRowPresenter;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v7.widget.RecyclerView;
+import android.view.KeyEvent;
+import android.view.View;
+
+import org.junit.After;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class BrowseFragmentTest {
+
+    static final String TAG = "BrowseFragmentTest";
+    static final long WAIT_TRANSIITON_TIMEOUT = 10000;
+
+    @Rule
+    public ActivityTestRule<BrowseFragmentTestActivity> activityTestRule =
+            new ActivityTestRule<>(BrowseFragmentTestActivity.class, false, false);
+    private BrowseFragmentTestActivity mActivity;
+
+    @After
+    public void afterTest() throws Throwable {
+        activityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                if (mActivity != null) {
+                    mActivity.finish();
+                    mActivity = null;
+                }
+            }
+        });
+    }
+
+    void waitForEntranceTransitionFinished() {
+        PollingCheck.waitFor(WAIT_TRANSIITON_TIMEOUT, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                if (Build.VERSION.SDK_INT >= 21) {
+                    return mActivity.getBrowseTestFragment() != null
+                            && mActivity.getBrowseTestFragment().mEntranceTransitionEnded;
+                } else {
+                    // when entrance transition not supported, wait main fragment loaded.
+                    return mActivity.getBrowseTestFragment() != null
+                            && mActivity.getBrowseTestFragment().getMainFragment() != null;
+                }
+            }
+        });
+    }
+
+    void waitForHeaderTransitionFinished() {
+        View row = mActivity.getBrowseTestFragment().getRowsFragment().getRowViewHolder(
+                mActivity.getBrowseTestFragment().getSelectedPosition()).view;
+        PollingCheck.waitFor(WAIT_TRANSIITON_TIMEOUT, new PollingCheck.ViewStableOnScreen(row));
+    }
+
+    @Test
+    public void testTwoBackKeysWithBackStack() throws Throwable {
+        final long dataLoadingDelay = 1000;
+        Intent intent = new Intent();
+        intent.putExtra(BrowseFragmentTestActivity.EXTRA_LOAD_DATA_DELAY, dataLoadingDelay);
+        intent.putExtra(BrowseFragmentTestActivity.EXTRA_ADD_TO_BACKSTACK , true);
+        mActivity = activityTestRule.launchActivity(intent);
+
+        waitForEntranceTransitionFinished();
+
+        assertNotNull(mActivity.getBrowseTestFragment().getMainFragment());
+        sendKeys(KeyEvent.KEYCODE_DPAD_RIGHT);
+        waitForHeaderTransitionFinished();
+        sendKeys(KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_BACK);
+    }
+
+    @Test
+    public void testTwoBackKeysWithoutBackStack() throws Throwable {
+        final long dataLoadingDelay = 1000;
+        Intent intent = new Intent();
+        intent.putExtra(BrowseFragmentTestActivity.EXTRA_LOAD_DATA_DELAY, dataLoadingDelay);
+        intent.putExtra(BrowseFragmentTestActivity.EXTRA_ADD_TO_BACKSTACK , false);
+        mActivity = activityTestRule.launchActivity(intent);
+
+        waitForEntranceTransitionFinished();
+
+        assertNotNull(mActivity.getBrowseTestFragment().getMainFragment());
+        sendKeys(KeyEvent.KEYCODE_DPAD_RIGHT);
+        waitForHeaderTransitionFinished();
+        sendKeys(KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_BACK);
+    }
+
+    @Test
+    public void testPressRightBeforeMainFragmentCreated() throws Throwable {
+        final long dataLoadingDelay = 1000;
+        Intent intent = new Intent();
+        intent.putExtra(BrowseFragmentTestActivity.EXTRA_LOAD_DATA_DELAY, dataLoadingDelay);
+        intent.putExtra(BrowseFragmentTestActivity.EXTRA_ADD_TO_BACKSTACK , false);
+        mActivity = activityTestRule.launchActivity(intent);
+
+        assertNull(mActivity.getBrowseTestFragment().getMainFragment());
+        sendKeys(KeyEvent.KEYCODE_DPAD_RIGHT);
+    }
+
+    @Test
+    public void testSelectCardOnARow() throws Throwable {
+        final int selectRow = 10;
+        final int selectItem = 20;
+        Intent intent = new Intent();
+        final long dataLoadingDelay = 1000;
+        intent.putExtra(BrowseFragmentTestActivity.EXTRA_LOAD_DATA_DELAY, dataLoadingDelay);
+        intent.putExtra(BrowseFragmentTestActivity.EXTRA_ADD_TO_BACKSTACK , true);
+        mActivity = activityTestRule.launchActivity(intent);
+
+        waitForEntranceTransitionFinished();
+
+        Presenter.ViewHolderTask itemTask = Mockito.spy(
+                new ItemSelectionTask(mActivity, selectRow));
+
+        final ListRowPresenter.SelectItemViewHolderTask task =
+                new ListRowPresenter.SelectItemViewHolderTask(selectItem);
+        task.setItemTask(itemTask);
+
+        mActivity.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mActivity.getBrowseTestFragment().setSelectedPosition(selectRow, true, task);
+            }
+        });
+
+        verify(itemTask, timeout(5000).times(1)).run(any(Presenter.ViewHolder.class));
+
+        activityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                ListRowPresenter.ViewHolder row = (ListRowPresenter.ViewHolder) mActivity
+                        .getBrowseTestFragment().getRowsFragment().getRowViewHolder(selectRow);
+                assertNotNull(dumpRecyclerView(mActivity.getBrowseTestFragment().getGridView()), row);
+                assertNotNull(row.getGridView());
+                assertEquals(selectItem, row.getGridView().getSelectedPosition());
+            }
+        });
+    }
+
+    @Test
+    public void activityRecreate_notCrash() throws Throwable {
+        final long dataLoadingDelay = 1000;
+        Intent intent = new Intent();
+        intent.putExtra(BrowseFragmentTestActivity.EXTRA_LOAD_DATA_DELAY, dataLoadingDelay);
+        intent.putExtra(BrowseFragmentTestActivity.EXTRA_ADD_TO_BACKSTACK , false);
+        intent.putExtra(BrowseFragmentTestActivity.EXTRA_SET_ADAPTER_AFTER_DATA_LOAD, true);
+        mActivity = activityTestRule.launchActivity(intent);
+
+        waitForEntranceTransitionFinished();
+
+        InstrumentationRegistry.getInstrumentation().callActivityOnRestart(mActivity);
+        activityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mActivity.recreate();
+            }
+        });
+    }
+
+
+    @Test
+    public void lateLoadingHeaderDisabled() throws Throwable {
+        final long dataLoadingDelay = 1000;
+        Intent intent = new Intent();
+        intent.putExtra(BrowseFragmentTestActivity.EXTRA_LOAD_DATA_DELAY, dataLoadingDelay);
+        intent.putExtra(BrowseFragmentTestActivity.EXTRA_HEADERS_STATE,
+                BrowseFragment.HEADERS_DISABLED);
+        mActivity = activityTestRule.launchActivity(intent);
+        waitForEntranceTransitionFinished();
+        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return mActivity.getBrowseTestFragment().getGridView() != null
+                        && mActivity.getBrowseTestFragment().getGridView().getChildCount() > 0;
+            }
+        });
+    }
+
+    private void sendKeys(int ...keys) {
+        for (int i = 0; i < keys.length; i++) {
+            InstrumentationRegistry.getInstrumentation().sendKeyDownUpSync(keys[i]);
+        }
+    }
+
+    public static class ItemSelectionTask extends Presenter.ViewHolderTask {
+
+        private final BrowseFragmentTestActivity activity;
+        private final int expectedRow;
+
+        public ItemSelectionTask(BrowseFragmentTestActivity activity, int expectedRow) {
+            this.activity = activity;
+            this.expectedRow = expectedRow;
+        }
+
+        @Override
+        public void run(Presenter.ViewHolder holder) {
+            android.util.Log.d(TAG, dumpRecyclerView(activity.getBrowseTestFragment()
+                    .getGridView()));
+            android.util.Log.d(TAG, "Row " + expectedRow + " " + activity.getBrowseTestFragment()
+                    .getRowsFragment().getRowViewHolder(expectedRow), new Exception());
+        }
+    }
+
+    static String dumpRecyclerView(RecyclerView recyclerView) {
+        StringBuffer b = new StringBuffer();
+        for (int i = 0; i < recyclerView.getChildCount(); i++) {
+            View child = recyclerView.getChildAt(i);
+            ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder)
+                    recyclerView.getChildViewHolder(child);
+            b.append("child").append(i).append(":").append(vh);
+            if (vh != null) {
+                b.append(",").append(vh.getViewHolder());
+            }
+            b.append(";");
+        }
+        return b.toString();
+    }
+}
diff --git a/leanback/tests/java/android/support/v17/leanback/app/BrowseFragmentTestActivity.java b/leanback/tests/java/android/support/v17/leanback/app/BrowseFragmentTestActivity.java
new file mode 100644
index 0000000..3ce025d
--- /dev/null
+++ b/leanback/tests/java/android/support/v17/leanback/app/BrowseFragmentTestActivity.java
@@ -0,0 +1,60 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from BrowseSupportFragmentTestActivity.java.  DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v17.leanback.test.R;
+import android.app.Activity;
+import android.app.FragmentTransaction;
+
+public class BrowseFragmentTestActivity extends Activity {
+
+    public static final String EXTRA_ADD_TO_BACKSTACK = "addToBackStack";
+    public static final String EXTRA_NUM_ROWS = "numRows";
+    public static final String EXTRA_REPEAT_PER_ROW = "repeatPerRow";
+    public static final String EXTRA_LOAD_DATA_DELAY = "loadDataDelay";
+    public static final String EXTRA_TEST_ENTRANCE_TRANSITION = "testEntranceTransition";
+    public static final String EXTRA_SET_ADAPTER_AFTER_DATA_LOAD = "set_adapter_after_data_load";
+    public static final String EXTRA_HEADERS_STATE = "headers_state";
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        Intent intent = getIntent();
+
+        setContentView(R.layout.browse);
+        if (savedInstanceState == null) {
+            Bundle arguments = new Bundle();
+            arguments.putAll(intent.getExtras());
+            BrowseTestFragment fragment = new BrowseTestFragment();
+            fragment.setArguments(arguments);
+            FragmentTransaction ft = getFragmentManager().beginTransaction();
+            ft.replace(R.id.main_frame, fragment);
+            if (intent.getBooleanExtra(EXTRA_ADD_TO_BACKSTACK, false)) {
+                ft.addToBackStack(null);
+            }
+            ft.commit();
+        }
+    }
+
+    public BrowseTestFragment getBrowseTestFragment() {
+        return (BrowseTestFragment) getFragmentManager().findFragmentById(R.id.main_frame);
+    }
+}
diff --git a/leanback/tests/java/android/support/v17/leanback/app/BrowseSupportFragmentTest.java b/leanback/tests/java/android/support/v17/leanback/app/BrowseSupportFragmentTest.java
new file mode 100644
index 0000000..51151ae
--- /dev/null
+++ b/leanback/tests/java/android/support/v17/leanback/app/BrowseSupportFragmentTest.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v17.leanback.app;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+import android.content.Intent;
+import android.os.Build;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v17.leanback.testutils.PollingCheck;
+import android.support.v17.leanback.widget.ItemBridgeAdapter;
+import android.support.v17.leanback.widget.ListRowPresenter;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v7.widget.RecyclerView;
+import android.view.KeyEvent;
+import android.view.View;
+
+import org.junit.After;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class BrowseSupportFragmentTest {
+
+    static final String TAG = "BrowseSupportFragmentTest";
+    static final long WAIT_TRANSIITON_TIMEOUT = 10000;
+
+    @Rule
+    public ActivityTestRule<BrowseSupportFragmentTestActivity> activityTestRule =
+            new ActivityTestRule<>(BrowseSupportFragmentTestActivity.class, false, false);
+    private BrowseSupportFragmentTestActivity mActivity;
+
+    @After
+    public void afterTest() throws Throwable {
+        activityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                if (mActivity != null) {
+                    mActivity.finish();
+                    mActivity = null;
+                }
+            }
+        });
+    }
+
+    void waitForEntranceTransitionFinished() {
+        PollingCheck.waitFor(WAIT_TRANSIITON_TIMEOUT, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                if (Build.VERSION.SDK_INT >= 21) {
+                    return mActivity.getBrowseTestSupportFragment() != null
+                            && mActivity.getBrowseTestSupportFragment().mEntranceTransitionEnded;
+                } else {
+                    // when entrance transition not supported, wait main fragment loaded.
+                    return mActivity.getBrowseTestSupportFragment() != null
+                            && mActivity.getBrowseTestSupportFragment().getMainFragment() != null;
+                }
+            }
+        });
+    }
+
+    void waitForHeaderTransitionFinished() {
+        View row = mActivity.getBrowseTestSupportFragment().getRowsSupportFragment().getRowViewHolder(
+                mActivity.getBrowseTestSupportFragment().getSelectedPosition()).view;
+        PollingCheck.waitFor(WAIT_TRANSIITON_TIMEOUT, new PollingCheck.ViewStableOnScreen(row));
+    }
+
+    @Test
+    public void testTwoBackKeysWithBackStack() throws Throwable {
+        final long dataLoadingDelay = 1000;
+        Intent intent = new Intent();
+        intent.putExtra(BrowseSupportFragmentTestActivity.EXTRA_LOAD_DATA_DELAY, dataLoadingDelay);
+        intent.putExtra(BrowseSupportFragmentTestActivity.EXTRA_ADD_TO_BACKSTACK , true);
+        mActivity = activityTestRule.launchActivity(intent);
+
+        waitForEntranceTransitionFinished();
+
+        assertNotNull(mActivity.getBrowseTestSupportFragment().getMainFragment());
+        sendKeys(KeyEvent.KEYCODE_DPAD_RIGHT);
+        waitForHeaderTransitionFinished();
+        sendKeys(KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_BACK);
+    }
+
+    @Test
+    public void testTwoBackKeysWithoutBackStack() throws Throwable {
+        final long dataLoadingDelay = 1000;
+        Intent intent = new Intent();
+        intent.putExtra(BrowseSupportFragmentTestActivity.EXTRA_LOAD_DATA_DELAY, dataLoadingDelay);
+        intent.putExtra(BrowseSupportFragmentTestActivity.EXTRA_ADD_TO_BACKSTACK , false);
+        mActivity = activityTestRule.launchActivity(intent);
+
+        waitForEntranceTransitionFinished();
+
+        assertNotNull(mActivity.getBrowseTestSupportFragment().getMainFragment());
+        sendKeys(KeyEvent.KEYCODE_DPAD_RIGHT);
+        waitForHeaderTransitionFinished();
+        sendKeys(KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_BACK);
+    }
+
+    @Test
+    public void testPressRightBeforeMainFragmentCreated() throws Throwable {
+        final long dataLoadingDelay = 1000;
+        Intent intent = new Intent();
+        intent.putExtra(BrowseSupportFragmentTestActivity.EXTRA_LOAD_DATA_DELAY, dataLoadingDelay);
+        intent.putExtra(BrowseSupportFragmentTestActivity.EXTRA_ADD_TO_BACKSTACK , false);
+        mActivity = activityTestRule.launchActivity(intent);
+
+        assertNull(mActivity.getBrowseTestSupportFragment().getMainFragment());
+        sendKeys(KeyEvent.KEYCODE_DPAD_RIGHT);
+    }
+
+    @Test
+    public void testSelectCardOnARow() throws Throwable {
+        final int selectRow = 10;
+        final int selectItem = 20;
+        Intent intent = new Intent();
+        final long dataLoadingDelay = 1000;
+        intent.putExtra(BrowseSupportFragmentTestActivity.EXTRA_LOAD_DATA_DELAY, dataLoadingDelay);
+        intent.putExtra(BrowseSupportFragmentTestActivity.EXTRA_ADD_TO_BACKSTACK , true);
+        mActivity = activityTestRule.launchActivity(intent);
+
+        waitForEntranceTransitionFinished();
+
+        Presenter.ViewHolderTask itemTask = Mockito.spy(
+                new ItemSelectionTask(mActivity, selectRow));
+
+        final ListRowPresenter.SelectItemViewHolderTask task =
+                new ListRowPresenter.SelectItemViewHolderTask(selectItem);
+        task.setItemTask(itemTask);
+
+        mActivity.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mActivity.getBrowseTestSupportFragment().setSelectedPosition(selectRow, true, task);
+            }
+        });
+
+        verify(itemTask, timeout(5000).times(1)).run(any(Presenter.ViewHolder.class));
+
+        activityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                ListRowPresenter.ViewHolder row = (ListRowPresenter.ViewHolder) mActivity
+                        .getBrowseTestSupportFragment().getRowsSupportFragment().getRowViewHolder(selectRow);
+                assertNotNull(dumpRecyclerView(mActivity.getBrowseTestSupportFragment().getGridView()), row);
+                assertNotNull(row.getGridView());
+                assertEquals(selectItem, row.getGridView().getSelectedPosition());
+            }
+        });
+    }
+
+    @Test
+    public void activityRecreate_notCrash() throws Throwable {
+        final long dataLoadingDelay = 1000;
+        Intent intent = new Intent();
+        intent.putExtra(BrowseSupportFragmentTestActivity.EXTRA_LOAD_DATA_DELAY, dataLoadingDelay);
+        intent.putExtra(BrowseSupportFragmentTestActivity.EXTRA_ADD_TO_BACKSTACK , false);
+        intent.putExtra(BrowseSupportFragmentTestActivity.EXTRA_SET_ADAPTER_AFTER_DATA_LOAD, true);
+        mActivity = activityTestRule.launchActivity(intent);
+
+        waitForEntranceTransitionFinished();
+
+        InstrumentationRegistry.getInstrumentation().callActivityOnRestart(mActivity);
+        activityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mActivity.recreate();
+            }
+        });
+    }
+
+
+    @Test
+    public void lateLoadingHeaderDisabled() throws Throwable {
+        final long dataLoadingDelay = 1000;
+        Intent intent = new Intent();
+        intent.putExtra(BrowseSupportFragmentTestActivity.EXTRA_LOAD_DATA_DELAY, dataLoadingDelay);
+        intent.putExtra(BrowseSupportFragmentTestActivity.EXTRA_HEADERS_STATE,
+                BrowseSupportFragment.HEADERS_DISABLED);
+        mActivity = activityTestRule.launchActivity(intent);
+        waitForEntranceTransitionFinished();
+        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return mActivity.getBrowseTestSupportFragment().getGridView() != null
+                        && mActivity.getBrowseTestSupportFragment().getGridView().getChildCount() > 0;
+            }
+        });
+    }
+
+    private void sendKeys(int ...keys) {
+        for (int i = 0; i < keys.length; i++) {
+            InstrumentationRegistry.getInstrumentation().sendKeyDownUpSync(keys[i]);
+        }
+    }
+
+    public static class ItemSelectionTask extends Presenter.ViewHolderTask {
+
+        private final BrowseSupportFragmentTestActivity activity;
+        private final int expectedRow;
+
+        public ItemSelectionTask(BrowseSupportFragmentTestActivity activity, int expectedRow) {
+            this.activity = activity;
+            this.expectedRow = expectedRow;
+        }
+
+        @Override
+        public void run(Presenter.ViewHolder holder) {
+            android.util.Log.d(TAG, dumpRecyclerView(activity.getBrowseTestSupportFragment()
+                    .getGridView()));
+            android.util.Log.d(TAG, "Row " + expectedRow + " " + activity.getBrowseTestSupportFragment()
+                    .getRowsSupportFragment().getRowViewHolder(expectedRow), new Exception());
+        }
+    }
+
+    static String dumpRecyclerView(RecyclerView recyclerView) {
+        StringBuffer b = new StringBuffer();
+        for (int i = 0; i < recyclerView.getChildCount(); i++) {
+            View child = recyclerView.getChildAt(i);
+            ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder)
+                    recyclerView.getChildViewHolder(child);
+            b.append("child").append(i).append(":").append(vh);
+            if (vh != null) {
+                b.append(",").append(vh.getViewHolder());
+            }
+            b.append(";");
+        }
+        return b.toString();
+    }
+}
diff --git a/leanback/tests/java/android/support/v17/leanback/app/BrowseSupportFragmentTestActivity.java b/leanback/tests/java/android/support/v17/leanback/app/BrowseSupportFragmentTestActivity.java
new file mode 100644
index 0000000..313dc86
--- /dev/null
+++ b/leanback/tests/java/android/support/v17/leanback/app/BrowseSupportFragmentTestActivity.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v17.leanback.test.R;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.FragmentTransaction;
+
+public class BrowseSupportFragmentTestActivity extends FragmentActivity {
+
+    public static final String EXTRA_ADD_TO_BACKSTACK = "addToBackStack";
+    public static final String EXTRA_NUM_ROWS = "numRows";
+    public static final String EXTRA_REPEAT_PER_ROW = "repeatPerRow";
+    public static final String EXTRA_LOAD_DATA_DELAY = "loadDataDelay";
+    public static final String EXTRA_TEST_ENTRANCE_TRANSITION = "testEntranceTransition";
+    public static final String EXTRA_SET_ADAPTER_AFTER_DATA_LOAD = "set_adapter_after_data_load";
+    public static final String EXTRA_HEADERS_STATE = "headers_state";
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        Intent intent = getIntent();
+
+        setContentView(R.layout.browse);
+        if (savedInstanceState == null) {
+            Bundle arguments = new Bundle();
+            arguments.putAll(intent.getExtras());
+            BrowseTestSupportFragment fragment = new BrowseTestSupportFragment();
+            fragment.setArguments(arguments);
+            FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
+            ft.replace(R.id.main_frame, fragment);
+            if (intent.getBooleanExtra(EXTRA_ADD_TO_BACKSTACK, false)) {
+                ft.addToBackStack(null);
+            }
+            ft.commit();
+        }
+    }
+
+    public BrowseTestSupportFragment getBrowseTestSupportFragment() {
+        return (BrowseTestSupportFragment) getSupportFragmentManager().findFragmentById(R.id.main_frame);
+    }
+}
diff --git a/leanback/tests/java/android/support/v17/leanback/app/BrowseTestFragment.java b/leanback/tests/java/android/support/v17/leanback/app/BrowseTestFragment.java
new file mode 100644
index 0000000..6a34c53
--- /dev/null
+++ b/leanback/tests/java/android/support/v17/leanback/app/BrowseTestFragment.java
@@ -0,0 +1,175 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from BrowseTestSupportFragment.java.  DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import static android.support.v17.leanback.app.BrowseFragmentTestActivity.EXTRA_HEADERS_STATE;
+import static android.support.v17.leanback.app.BrowseFragmentTestActivity.EXTRA_LOAD_DATA_DELAY;
+import static android.support.v17.leanback.app.BrowseFragmentTestActivity.EXTRA_NUM_ROWS;
+import static android.support.v17.leanback.app.BrowseFragmentTestActivity.EXTRA_REPEAT_PER_ROW;
+import static android.support.v17.leanback.app.BrowseFragmentTestActivity.EXTRA_SET_ADAPTER_AFTER_DATA_LOAD;
+import static android.support.v17.leanback.app.BrowseFragmentTestActivity.EXTRA_TEST_ENTRANCE_TRANSITION;
+
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.v17.leanback.widget.ArrayObjectAdapter;
+import android.support.v17.leanback.widget.HeaderItem;
+import android.support.v17.leanback.widget.ListRow;
+import android.support.v17.leanback.widget.ListRowPresenter;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.OnItemViewSelectedListener;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.util.Log;
+import android.view.View;
+
+public class BrowseTestFragment extends BrowseFragment {
+    private static final String TAG = "BrowseTestFragment";
+
+    final static int DEFAULT_NUM_ROWS = 100;
+    final static int DEFAULT_REPEAT_PER_ROW = 20;
+    final static long DEFAULT_LOAD_DATA_DELAY = 2000;
+    final static boolean DEFAULT_TEST_ENTRANCE_TRANSITION = true;
+    final static boolean DEFAULT_SET_ADAPTER_AFTER_DATA_LOAD = false;
+
+    private ArrayObjectAdapter mRowsAdapter;
+
+    // For good performance, it's important to use a single instance of
+    // a card presenter for all rows using that presenter.
+    final static StringPresenter sCardPresenter = new StringPresenter();
+
+    int NUM_ROWS;
+    int REPEAT_PER_ROW;
+    boolean mEntranceTransitionStarted;
+    boolean mEntranceTransitionEnded;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        Log.i(TAG, "onCreate");
+        super.onCreate(savedInstanceState);
+
+        Bundle arguments = getArguments();
+        NUM_ROWS = arguments.getInt(EXTRA_NUM_ROWS, BrowseTestFragment.DEFAULT_NUM_ROWS);
+        REPEAT_PER_ROW = arguments.getInt(EXTRA_REPEAT_PER_ROW,
+                DEFAULT_REPEAT_PER_ROW);
+        long LOAD_DATA_DELAY = arguments.getLong(EXTRA_LOAD_DATA_DELAY,
+                DEFAULT_LOAD_DATA_DELAY);
+        boolean TEST_ENTRANCE_TRANSITION = arguments.getBoolean(
+                EXTRA_TEST_ENTRANCE_TRANSITION,
+                DEFAULT_TEST_ENTRANCE_TRANSITION);
+        final boolean SET_ADAPTER_AFTER_DATA_LOAD = arguments.getBoolean(
+                EXTRA_SET_ADAPTER_AFTER_DATA_LOAD,
+                DEFAULT_SET_ADAPTER_AFTER_DATA_LOAD);
+
+        if (!SET_ADAPTER_AFTER_DATA_LOAD) {
+            setupRows();
+        }
+
+        setTitle("BrowseTestFragment");
+        setHeadersState(arguments.getInt(EXTRA_HEADERS_STATE, HEADERS_ENABLED));
+
+        setOnSearchClickedListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                Log.i(TAG, "onSearchClicked");
+            }
+        });
+
+        setOnItemViewClickedListener(new ItemViewClickedListener());
+        setOnItemViewSelectedListener(new OnItemViewSelectedListener() {
+            @Override
+            public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
+                    RowPresenter.ViewHolder rowViewHolder, Row row) {
+                Log.i(TAG, "onItemSelected: " + item + " row " + row.getHeaderItem().getName()
+                        + " " + rowViewHolder
+                        + " " + ((ListRowPresenter.ViewHolder) rowViewHolder).getGridView());
+            }
+        });
+        if (TEST_ENTRANCE_TRANSITION) {
+            // don't run entrance transition if fragment is restored.
+            if (savedInstanceState == null) {
+                prepareEntranceTransition();
+            }
+        }
+        // simulates in a real world use case  data being loaded two seconds later
+        new Handler().postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                if (getActivity() == null || getActivity().isDestroyed()) {
+                    return;
+                }
+                if (SET_ADAPTER_AFTER_DATA_LOAD) {
+                    setupRows();
+                }
+                loadData();
+                startEntranceTransition();
+            }
+        }, LOAD_DATA_DELAY);
+    }
+
+    private void setupRows() {
+        ListRowPresenter lrp = new ListRowPresenter();
+
+        mRowsAdapter = new ArrayObjectAdapter(lrp);
+
+        setAdapter(mRowsAdapter);
+    }
+
+    @Override
+    protected void onEntranceTransitionStart() {
+        super.onEntranceTransitionStart();
+        mEntranceTransitionStarted = true;
+    }
+
+    @Override
+    protected void onEntranceTransitionEnd() {
+        super.onEntranceTransitionEnd();
+        mEntranceTransitionEnded = true;
+    }
+
+    private void loadData() {
+        for (int i = 0; i < NUM_ROWS; ++i) {
+            ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(sCardPresenter);
+            int index = 0;
+            for (int j = 0; j < REPEAT_PER_ROW; ++j) {
+                listRowAdapter.add("Hello world-" + (index++));
+                listRowAdapter.add("This is a test-" + (index++));
+                listRowAdapter.add("Android TV-" + (index++));
+                listRowAdapter.add("Leanback-" + (index++));
+                listRowAdapter.add("Hello world-" + (index++));
+                listRowAdapter.add("Android TV-" + (index++));
+                listRowAdapter.add("Leanback-" + (index++));
+                listRowAdapter.add("GuidedStepFragment-" + (index++));
+            }
+            HeaderItem header = new HeaderItem(i, "Row " + i);
+            mRowsAdapter.add(new ListRow(header, listRowAdapter));
+        }
+    }
+
+    private final class ItemViewClickedListener implements OnItemViewClickedListener {
+        @Override
+        public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
+                RowPresenter.ViewHolder rowViewHolder, Row row) {
+            Log.i(TAG, "onItemClicked: " + item + " row " + row);
+        }
+    }
+
+    public VerticalGridView getGridView() {
+        return getRowsFragment().getVerticalGridView();
+    }
+}
diff --git a/leanback/tests/java/android/support/v17/leanback/app/BrowseTestSupportFragment.java b/leanback/tests/java/android/support/v17/leanback/app/BrowseTestSupportFragment.java
new file mode 100644
index 0000000..373d7a3
--- /dev/null
+++ b/leanback/tests/java/android/support/v17/leanback/app/BrowseTestSupportFragment.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import static android.support.v17.leanback.app.BrowseSupportFragmentTestActivity.EXTRA_HEADERS_STATE;
+import static android.support.v17.leanback.app.BrowseSupportFragmentTestActivity.EXTRA_LOAD_DATA_DELAY;
+import static android.support.v17.leanback.app.BrowseSupportFragmentTestActivity.EXTRA_NUM_ROWS;
+import static android.support.v17.leanback.app.BrowseSupportFragmentTestActivity.EXTRA_REPEAT_PER_ROW;
+import static android.support.v17.leanback.app.BrowseSupportFragmentTestActivity.EXTRA_SET_ADAPTER_AFTER_DATA_LOAD;
+import static android.support.v17.leanback.app.BrowseSupportFragmentTestActivity.EXTRA_TEST_ENTRANCE_TRANSITION;
+
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.v17.leanback.widget.ArrayObjectAdapter;
+import android.support.v17.leanback.widget.HeaderItem;
+import android.support.v17.leanback.widget.ListRow;
+import android.support.v17.leanback.widget.ListRowPresenter;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.OnItemViewSelectedListener;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.util.Log;
+import android.view.View;
+
+public class BrowseTestSupportFragment extends BrowseSupportFragment {
+    private static final String TAG = "BrowseTestSupportFragment";
+
+    final static int DEFAULT_NUM_ROWS = 100;
+    final static int DEFAULT_REPEAT_PER_ROW = 20;
+    final static long DEFAULT_LOAD_DATA_DELAY = 2000;
+    final static boolean DEFAULT_TEST_ENTRANCE_TRANSITION = true;
+    final static boolean DEFAULT_SET_ADAPTER_AFTER_DATA_LOAD = false;
+
+    private ArrayObjectAdapter mRowsAdapter;
+
+    // For good performance, it's important to use a single instance of
+    // a card presenter for all rows using that presenter.
+    final static StringPresenter sCardPresenter = new StringPresenter();
+
+    int NUM_ROWS;
+    int REPEAT_PER_ROW;
+    boolean mEntranceTransitionStarted;
+    boolean mEntranceTransitionEnded;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        Log.i(TAG, "onCreate");
+        super.onCreate(savedInstanceState);
+
+        Bundle arguments = getArguments();
+        NUM_ROWS = arguments.getInt(EXTRA_NUM_ROWS, BrowseTestSupportFragment.DEFAULT_NUM_ROWS);
+        REPEAT_PER_ROW = arguments.getInt(EXTRA_REPEAT_PER_ROW,
+                DEFAULT_REPEAT_PER_ROW);
+        long LOAD_DATA_DELAY = arguments.getLong(EXTRA_LOAD_DATA_DELAY,
+                DEFAULT_LOAD_DATA_DELAY);
+        boolean TEST_ENTRANCE_TRANSITION = arguments.getBoolean(
+                EXTRA_TEST_ENTRANCE_TRANSITION,
+                DEFAULT_TEST_ENTRANCE_TRANSITION);
+        final boolean SET_ADAPTER_AFTER_DATA_LOAD = arguments.getBoolean(
+                EXTRA_SET_ADAPTER_AFTER_DATA_LOAD,
+                DEFAULT_SET_ADAPTER_AFTER_DATA_LOAD);
+
+        if (!SET_ADAPTER_AFTER_DATA_LOAD) {
+            setupRows();
+        }
+
+        setTitle("BrowseTestSupportFragment");
+        setHeadersState(arguments.getInt(EXTRA_HEADERS_STATE, HEADERS_ENABLED));
+
+        setOnSearchClickedListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                Log.i(TAG, "onSearchClicked");
+            }
+        });
+
+        setOnItemViewClickedListener(new ItemViewClickedListener());
+        setOnItemViewSelectedListener(new OnItemViewSelectedListener() {
+            @Override
+            public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
+                    RowPresenter.ViewHolder rowViewHolder, Row row) {
+                Log.i(TAG, "onItemSelected: " + item + " row " + row.getHeaderItem().getName()
+                        + " " + rowViewHolder
+                        + " " + ((ListRowPresenter.ViewHolder) rowViewHolder).getGridView());
+            }
+        });
+        if (TEST_ENTRANCE_TRANSITION) {
+            // don't run entrance transition if fragment is restored.
+            if (savedInstanceState == null) {
+                prepareEntranceTransition();
+            }
+        }
+        // simulates in a real world use case  data being loaded two seconds later
+        new Handler().postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                if (getActivity() == null || getActivity().isDestroyed()) {
+                    return;
+                }
+                if (SET_ADAPTER_AFTER_DATA_LOAD) {
+                    setupRows();
+                }
+                loadData();
+                startEntranceTransition();
+            }
+        }, LOAD_DATA_DELAY);
+    }
+
+    private void setupRows() {
+        ListRowPresenter lrp = new ListRowPresenter();
+
+        mRowsAdapter = new ArrayObjectAdapter(lrp);
+
+        setAdapter(mRowsAdapter);
+    }
+
+    @Override
+    protected void onEntranceTransitionStart() {
+        super.onEntranceTransitionStart();
+        mEntranceTransitionStarted = true;
+    }
+
+    @Override
+    protected void onEntranceTransitionEnd() {
+        super.onEntranceTransitionEnd();
+        mEntranceTransitionEnded = true;
+    }
+
+    private void loadData() {
+        for (int i = 0; i < NUM_ROWS; ++i) {
+            ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(sCardPresenter);
+            int index = 0;
+            for (int j = 0; j < REPEAT_PER_ROW; ++j) {
+                listRowAdapter.add("Hello world-" + (index++));
+                listRowAdapter.add("This is a test-" + (index++));
+                listRowAdapter.add("Android TV-" + (index++));
+                listRowAdapter.add("Leanback-" + (index++));
+                listRowAdapter.add("Hello world-" + (index++));
+                listRowAdapter.add("Android TV-" + (index++));
+                listRowAdapter.add("Leanback-" + (index++));
+                listRowAdapter.add("GuidedStepSupportFragment-" + (index++));
+            }
+            HeaderItem header = new HeaderItem(i, "Row " + i);
+            mRowsAdapter.add(new ListRow(header, listRowAdapter));
+        }
+    }
+
+    private final class ItemViewClickedListener implements OnItemViewClickedListener {
+        @Override
+        public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
+                RowPresenter.ViewHolder rowViewHolder, Row row) {
+            Log.i(TAG, "onItemClicked: " + item + " row " + row);
+        }
+    }
+
+    public VerticalGridView getGridView() {
+        return getRowsSupportFragment().getVerticalGridView();
+    }
+}
diff --git a/leanback/tests/java/android/support/v17/leanback/app/DetailsFragmentTest.java b/leanback/tests/java/android/support/v17/leanback/app/DetailsFragmentTest.java
new file mode 100644
index 0000000..bf70fae
--- /dev/null
+++ b/leanback/tests/java/android/support/v17/leanback/app/DetailsFragmentTest.java
@@ -0,0 +1,1219 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from DetailsSupportFragmentTest.java.  DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v17.leanback.app;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.animation.PropertyValuesHolder;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Rect;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.filters.SdkSuppress;
+import android.support.v17.leanback.R;
+import android.support.v17.leanback.graphics.FitWidthBitmapDrawable;
+import android.support.v17.leanback.media.MediaPlayerGlue;
+import android.support.v17.leanback.media.PlaybackGlueHost;
+import android.support.v17.leanback.testutils.PollingCheck;
+import android.support.v17.leanback.transition.TransitionHelper;
+import android.support.v17.leanback.util.StateMachine;
+import android.support.v17.leanback.widget.DetailsParallax;
+import android.support.v17.leanback.widget.DetailsParallaxDrawable;
+import android.support.v17.leanback.widget.ParallaxTarget;
+import android.support.v17.leanback.widget.RecyclerViewParallax;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.app.Fragment;
+import android.view.KeyEvent;
+import android.view.View;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Unit tests for {@link DetailsFragment}.
+ */
+@RunWith(JUnit4.class)
+@LargeTest
+public class DetailsFragmentTest extends SingleFragmentTestBase {
+
+    static final int PARALLAX_VERTICAL_OFFSET = -300;
+
+    static int getCoverDrawableAlpha(DetailsFragmentBackgroundController controller) {
+        return ((FitWidthBitmapDrawable) controller.mParallaxDrawable.getCoverDrawable())
+                .getAlpha();
+    }
+
+    public static class DetailsFragmentParallax extends DetailsTestFragment {
+
+        private DetailsParallaxDrawable mParallaxDrawable;
+
+        public DetailsFragmentParallax() {
+            super();
+            mMinVerticalOffset = PARALLAX_VERTICAL_OFFSET;
+        }
+
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            Drawable coverDrawable = new FitWidthBitmapDrawable();
+            mParallaxDrawable = new DetailsParallaxDrawable(
+                    getActivity(),
+                    getParallax(),
+                    coverDrawable,
+                    new ParallaxTarget.PropertyValuesHolderTarget(
+                            coverDrawable,
+                            PropertyValuesHolder.ofInt("verticalOffset", 0, mMinVerticalOffset)
+                    )
+            );
+
+            BackgroundManager backgroundManager = BackgroundManager.getInstance(getActivity());
+            backgroundManager.attach(getActivity().getWindow());
+            backgroundManager.setDrawable(mParallaxDrawable);
+        }
+
+        @Override
+        public void onStart() {
+            super.onStart();
+            setItem(new PhotoItem("Hello world", "Fake content goes here",
+                    android.support.v17.leanback.test.R.drawable.spiderman));
+        }
+
+        @Override
+        public void onResume() {
+            super.onResume();
+            Bitmap bitmap = BitmapFactory.decodeResource(getActivity().getResources(),
+                    android.support.v17.leanback.test.R.drawable.spiderman);
+            ((FitWidthBitmapDrawable) mParallaxDrawable.getCoverDrawable()).setBitmap(bitmap);
+        }
+
+        DetailsParallaxDrawable getParallaxDrawable() {
+            return mParallaxDrawable;
+        }
+    }
+
+    @Test
+    public void parallaxSetupTest() {
+        SingleFragmentTestActivity activity =
+                launchAndWaitActivity(DetailsFragmentTest.DetailsFragmentParallax.class,
+                new SingleFragmentTestBase.Options().uiVisibility(
+                        View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN), 0);
+
+        double delta = 0.0002;
+        DetailsParallax dpm = ((DetailsFragment) activity.getTestFragment()).getParallax();
+
+        RecyclerViewParallax.ChildPositionProperty frameTop =
+                (RecyclerViewParallax.ChildPositionProperty) dpm.getOverviewRowTop();
+        assertEquals(0f, frameTop.getFraction(), delta);
+        assertEquals(0f, frameTop.getAdapterPosition(), delta);
+
+
+        RecyclerViewParallax.ChildPositionProperty frameBottom =
+                (RecyclerViewParallax.ChildPositionProperty) dpm.getOverviewRowBottom();
+        assertEquals(1f, frameBottom.getFraction(), delta);
+        assertEquals(0f, frameBottom.getAdapterPosition(), delta);
+    }
+
+    @Test
+    public void parallaxTest() throws Throwable {
+        SingleFragmentTestActivity activity = launchAndWaitActivity(DetailsFragmentParallax.class,
+                new Options().uiVisibility(
+                        View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN), 0);
+
+        final DetailsFragmentParallax detailsFragment =
+                (DetailsFragmentParallax) activity.getTestFragment();
+        DetailsParallaxDrawable drawable =
+                detailsFragment.getParallaxDrawable();
+        final FitWidthBitmapDrawable bitmapDrawable = (FitWidthBitmapDrawable)
+                drawable.getCoverDrawable();
+
+        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return detailsFragment.getRowsFragment().getAdapter() != null
+                        && detailsFragment.getRowsFragment().getAdapter().size() > 1;
+            }
+        });
+
+        final VerticalGridView verticalGridView = detailsFragment.getRowsFragment()
+                .getVerticalGridView();
+        final int windowHeight = verticalGridView.getHeight();
+        final int windowWidth = verticalGridView.getWidth();
+        // make sure background manager attached to window is same size as VerticalGridView
+        // i.e. no status bar.
+        assertEquals(windowHeight, activity.getWindow().getDecorView().getHeight());
+        assertEquals(windowWidth, activity.getWindow().getDecorView().getWidth());
+
+        final View detailsFrame = verticalGridView.findViewById(R.id.details_frame);
+
+        assertEquals(windowWidth, bitmapDrawable.getBounds().width());
+
+        final Rect detailsFrameRect = new Rect();
+        detailsFrameRect.set(0, 0, detailsFrame.getWidth(), detailsFrame.getHeight());
+        verticalGridView.offsetDescendantRectToMyCoords(detailsFrame, detailsFrameRect);
+
+        assertEquals(Math.min(windowHeight, detailsFrameRect.top),
+                bitmapDrawable.getBounds().height());
+        assertEquals(0, bitmapDrawable.getVerticalOffset());
+
+        assertTrue("TitleView is visible", detailsFragment.getView()
+                .findViewById(R.id.browse_title_group).getVisibility() == View.VISIBLE);
+
+        activityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                verticalGridView.scrollToPosition(1);
+            }
+        });
+
+        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return bitmapDrawable.getVerticalOffset() == PARALLAX_VERTICAL_OFFSET
+                        && detailsFragment.getView()
+                        .findViewById(R.id.browse_title_group).getVisibility() != View.VISIBLE;
+            }
+        });
+
+        detailsFrameRect.set(0, 0, detailsFrame.getWidth(), detailsFrame.getHeight());
+        verticalGridView.offsetDescendantRectToMyCoords(detailsFrame, detailsFrameRect);
+
+        assertEquals(0, bitmapDrawable.getBounds().top);
+        assertEquals(Math.max(detailsFrameRect.top, 0), bitmapDrawable.getBounds().bottom);
+        assertEquals(windowWidth, bitmapDrawable.getBounds().width());
+
+        ColorDrawable colorDrawable = (ColorDrawable) (drawable.getChildAt(1).getDrawable());
+        assertEquals(windowWidth, colorDrawable.getBounds().width());
+        assertEquals(detailsFrameRect.bottom, colorDrawable.getBounds().top);
+        assertEquals(windowHeight, colorDrawable.getBounds().bottom);
+    }
+
+    public static class DetailsFragmentWithVideo extends DetailsTestFragment {
+
+        final DetailsFragmentBackgroundController mDetailsBackground =
+                new DetailsFragmentBackgroundController(this);
+        MediaPlayerGlue mGlue;
+
+        public DetailsFragmentWithVideo() {
+            mTimeToLoadOverviewRow = mTimeToLoadRelatedRow = 100;
+        }
+
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            mDetailsBackground.enableParallax();
+            mGlue = new MediaPlayerGlue(getActivity());
+            mDetailsBackground.setupVideoPlayback(mGlue);
+
+            mGlue.setMode(MediaPlayerGlue.REPEAT_ALL);
+            mGlue.setArtist("A Googleer");
+            mGlue.setTitle("Diving with Sharks");
+            mGlue.setMediaSource(
+                    Uri.parse("android.resource://android.support.v17.leanback.test/raw/video"));
+        }
+
+        @Override
+        public void onStart() {
+            super.onStart();
+            Bitmap bitmap = BitmapFactory.decodeResource(getActivity().getResources(),
+                    android.support.v17.leanback.test.R.drawable.spiderman);
+            mDetailsBackground.setCoverBitmap(bitmap);
+        }
+
+        @Override
+        public void onStop() {
+            mDetailsBackground.setCoverBitmap(null);
+            super.onStop();
+        }
+    }
+
+    public static class DetailsFragmentWithVideo1 extends DetailsFragmentWithVideo {
+
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            setItem(new PhotoItem("Hello world", "Fake content goes here",
+                    android.support.v17.leanback.test.R.drawable.spiderman));
+        }
+    }
+
+    public static class DetailsFragmentWithVideo2 extends DetailsFragmentWithVideo {
+
+        @Override
+        public void onStart() {
+            super.onStart();
+            setItem(new PhotoItem("Hello world", "Fake content goes here",
+                    android.support.v17.leanback.test.R.drawable.spiderman));
+        }
+    }
+
+    private void navigateBetweenRowsAndVideoUsingRequestFocusInternal(Class cls)
+            throws Throwable {
+        SingleFragmentTestActivity activity = launchAndWaitActivity(cls,
+                new Options().uiVisibility(
+                        View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN), 0);
+
+        final DetailsFragmentWithVideo detailsFragment =
+                (DetailsFragmentWithVideo) activity.getTestFragment();
+        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return detailsFragment.mVideoFragment != null
+                        && detailsFragment.mVideoFragment.getView() != null
+                        && detailsFragment.mGlue.isMediaPlaying();
+            }
+        });
+
+        final int screenHeight = detailsFragment.getRowsFragment().getVerticalGridView()
+                .getHeight();
+        final View firstRow = detailsFragment.getRowsFragment().getVerticalGridView().getChildAt(0);
+        final int originalFirstRowTop = firstRow.getTop();
+        assertTrue(firstRow.hasFocus());
+        assertTrue(firstRow.getTop() > 0 && firstRow.getTop() < screenHeight);
+        assertTrue(detailsFragment.isShowingTitle());
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                detailsFragment.mVideoFragment.getView().requestFocus();
+            }
+        });
+        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return firstRow.getTop() >= screenHeight;
+            }
+        });
+        assertFalse(detailsFragment.isShowingTitle());
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                detailsFragment.getRowsFragment().getVerticalGridView().requestFocus();
+            }
+        });
+        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return firstRow.getTop() == originalFirstRowTop;
+            }
+        });
+        assertTrue(detailsFragment.isShowingTitle());
+    }
+
+    @Test
+    public void navigateBetweenRowsAndVideoUsingRequestFocus1() throws Throwable {
+        navigateBetweenRowsAndVideoUsingRequestFocusInternal(DetailsFragmentWithVideo1.class);
+    }
+
+    @Test
+    public void navigateBetweenRowsAndVideoUsingRequestFocus2() throws Throwable {
+        navigateBetweenRowsAndVideoUsingRequestFocusInternal(DetailsFragmentWithVideo2.class);
+    }
+
+    private void navigateBetweenRowsAndVideoUsingDPADInternal(Class cls) throws Throwable {
+        SingleFragmentTestActivity activity = launchAndWaitActivity(cls,
+                new Options().uiVisibility(
+                        View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN), 0);
+
+        final DetailsFragmentWithVideo detailsFragment =
+                (DetailsFragmentWithVideo) activity.getTestFragment();
+        // wait video playing
+        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return detailsFragment.mVideoFragment != null
+                        && detailsFragment.mVideoFragment.getView() != null
+                        && detailsFragment.mGlue.isMediaPlaying();
+            }
+        });
+
+        final int screenHeight = detailsFragment.getRowsFragment().getVerticalGridView()
+                .getHeight();
+        final View firstRow = detailsFragment.getRowsFragment().getVerticalGridView().getChildAt(0);
+        final int originalFirstRowTop = firstRow.getTop();
+        assertTrue(firstRow.hasFocus());
+        assertTrue(firstRow.getTop() > 0 && firstRow.getTop() < screenHeight);
+        assertTrue(detailsFragment.isShowingTitle());
+
+        // navigate to video
+        sendKeys(KeyEvent.KEYCODE_DPAD_UP);
+        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return firstRow.getTop() >= screenHeight;
+            }
+        });
+
+        // wait auto hide play controls done:
+        PollingCheck.waitFor(8000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return ((PlaybackFragment) detailsFragment.mVideoFragment).mBgAlpha == 0;
+            }
+        });
+
+        // navigate to details
+        sendKeys(KeyEvent.KEYCODE_BACK);
+        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return firstRow.getTop() == originalFirstRowTop;
+            }
+        });
+        assertTrue(detailsFragment.isShowingTitle());
+    }
+
+    @Test
+    public void navigateBetweenRowsAndVideoUsingDPAD1() throws Throwable {
+        navigateBetweenRowsAndVideoUsingDPADInternal(DetailsFragmentWithVideo1.class);
+    }
+
+    @Test
+    public void navigateBetweenRowsAndVideoUsingDPAD2() throws Throwable {
+        navigateBetweenRowsAndVideoUsingDPADInternal(DetailsFragmentWithVideo2.class);
+    }
+
+    public static class EmptyFragmentClass extends Fragment {
+        @Override
+        public void onStart() {
+            super.onStart();
+            getActivity().finish();
+        }
+    }
+
+    private void fragmentOnStartWithVideoInternal(Class cls) throws Throwable {
+        final SingleFragmentTestActivity activity = launchAndWaitActivity(cls,
+                new Options().uiVisibility(
+                        View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN), 0);
+
+        final DetailsFragmentWithVideo detailsFragment =
+                (DetailsFragmentWithVideo) activity.getTestFragment();
+        // wait video playing
+        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return detailsFragment.mVideoFragment != null
+                        && detailsFragment.mVideoFragment.getView() != null
+                        && detailsFragment.mGlue.isMediaPlaying();
+            }
+        });
+
+        final int screenHeight = detailsFragment.getRowsFragment().getVerticalGridView()
+                .getHeight();
+        final View firstRow = detailsFragment.getRowsFragment().getVerticalGridView().getChildAt(0);
+        final int originalFirstRowTop = firstRow.getTop();
+        assertTrue(firstRow.hasFocus());
+        assertTrue(firstRow.getTop() > 0 && firstRow.getTop() < screenHeight);
+        assertTrue(detailsFragment.isShowingTitle());
+
+        // navigate to video
+        sendKeys(KeyEvent.KEYCODE_DPAD_UP);
+        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return firstRow.getTop() >= screenHeight;
+            }
+        });
+
+        // start an empty activity
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        Intent intent = new Intent(activity, SingleFragmentTestActivity.class);
+                        intent.putExtra(SingleFragmentTestActivity.EXTRA_FRAGMENT_NAME,
+                                EmptyFragmentClass.class.getName());
+                        activity.startActivity(intent);
+                    }
+                }
+        );
+        PollingCheck.waitFor(2000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return detailsFragment.isResumed();
+            }
+        });
+        assertTrue(detailsFragment.mVideoFragment.getView().hasFocus());
+    }
+
+    @Test
+    public void fragmentOnStartWithVideo1() throws Throwable {
+        fragmentOnStartWithVideoInternal(DetailsFragmentWithVideo1.class);
+    }
+
+    @Test
+    public void fragmentOnStartWithVideo2() throws Throwable {
+        fragmentOnStartWithVideoInternal(DetailsFragmentWithVideo2.class);
+    }
+
+    @Test
+    public void navigateBetweenRowsAndTitle() throws Throwable {
+        SingleFragmentTestActivity activity =
+                launchAndWaitActivity(DetailsTestFragment.class, new Options().uiVisibility(
+                View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN), 0);
+        final DetailsTestFragment detailsFragment =
+                (DetailsTestFragment) activity.getTestFragment();
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                detailsFragment.setOnSearchClickedListener(new View.OnClickListener() {
+                    @Override
+                    public void onClick(View view) {
+                    }
+                });
+                detailsFragment.setItem(new PhotoItem("Hello world", "Fake content goes here",
+                        android.support.v17.leanback.test.R.drawable.spiderman));
+            }
+        });
+
+        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return detailsFragment.getRowsFragment().getVerticalGridView().getChildCount() > 0;
+            }
+        });
+        final View firstRow = detailsFragment.getRowsFragment().getVerticalGridView().getChildAt(0);
+        final int originalFirstRowTop = firstRow.getTop();
+        final int screenHeight = detailsFragment.getRowsFragment().getVerticalGridView()
+                .getHeight();
+
+        assertTrue(firstRow.hasFocus());
+        assertTrue(detailsFragment.isShowingTitle());
+        assertTrue(firstRow.getTop() > 0 && firstRow.getTop() < screenHeight);
+
+        sendKeys(KeyEvent.KEYCODE_DPAD_UP);
+        PollingCheck.waitFor(new PollingCheck.ViewStableOnScreen(firstRow));
+        assertTrue(detailsFragment.isShowingTitle());
+        assertTrue(detailsFragment.getTitleView().hasFocus());
+        assertEquals(originalFirstRowTop, firstRow.getTop());
+
+        sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+        PollingCheck.waitFor(new PollingCheck.ViewStableOnScreen(firstRow));
+        assertTrue(detailsFragment.isShowingTitle());
+        assertTrue(firstRow.hasFocus());
+        assertEquals(originalFirstRowTop, firstRow.getTop());
+    }
+
+    public static class DetailsFragmentWithNoVideo extends DetailsTestFragment {
+
+        final DetailsFragmentBackgroundController mDetailsBackground =
+                new DetailsFragmentBackgroundController(this);
+
+        public DetailsFragmentWithNoVideo() {
+            mTimeToLoadOverviewRow = mTimeToLoadRelatedRow = 100;
+        }
+
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            mDetailsBackground.enableParallax();
+
+            setItem(new PhotoItem("Hello world", "Fake content goes here",
+                    android.support.v17.leanback.test.R.drawable.spiderman));
+        }
+
+        @Override
+        public void onStart() {
+            super.onStart();
+            Bitmap bitmap = BitmapFactory.decodeResource(getActivity().getResources(),
+                    android.support.v17.leanback.test.R.drawable.spiderman);
+            mDetailsBackground.setCoverBitmap(bitmap);
+        }
+
+        @Override
+        public void onStop() {
+            mDetailsBackground.setCoverBitmap(null);
+            super.onStop();
+        }
+    }
+
+    @Test
+    public void lateSetupVideo() {
+        final SingleFragmentTestActivity activity =
+                launchAndWaitActivity(DetailsFragmentWithNoVideo.class, new Options().uiVisibility(
+                View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN), 0);
+        final DetailsFragmentWithNoVideo detailsFragment =
+                (DetailsFragmentWithNoVideo) activity.getTestFragment();
+
+        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return detailsFragment.getRowsFragment().getVerticalGridView().getChildCount() > 0;
+            }
+        });
+        final View firstRow = detailsFragment.getRowsFragment().getVerticalGridView().getChildAt(0);
+        final int screenHeight = detailsFragment.getRowsFragment().getVerticalGridView()
+                .getHeight();
+
+        assertTrue(firstRow.hasFocus());
+        assertTrue(detailsFragment.isShowingTitle());
+        assertTrue(firstRow.getTop() > 0 && firstRow.getTop() < screenHeight);
+
+        sendKeys(KeyEvent.KEYCODE_DPAD_UP);
+        assertTrue(firstRow.hasFocus());
+
+        SystemClock.sleep(1000);
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        final MediaPlayerGlue glue = new MediaPlayerGlue(activity);
+                        detailsFragment.mDetailsBackgroundController.setupVideoPlayback(glue);
+                        glue.setMode(MediaPlayerGlue.REPEAT_ALL);
+                        glue.setArtist("A Googleer");
+                        glue.setTitle("Diving with Sharks");
+                        glue.setMediaSource(Uri.parse(
+                                "android.resource://android.support.v17.leanback.test/raw/video"));
+                    }
+                }
+        );
+
+        // after setup Video Playback the DPAD up will navigate to Video Fragment.
+        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
+                @Override
+                    public boolean canProceed() {
+                        return detailsFragment.mVideoFragment != null
+                                && detailsFragment.mVideoFragment.getView() != null;
+                }
+        });
+        sendKeys(KeyEvent.KEYCODE_DPAD_UP);
+        assertTrue(detailsFragment.mVideoFragment.getView().hasFocus());
+        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return ((MediaPlayerGlue) detailsFragment.mDetailsBackgroundController
+                        .getPlaybackGlue()).isMediaPlaying();
+            }
+        });
+        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return 0 == getCoverDrawableAlpha(detailsFragment.mDetailsBackgroundController);
+            }
+        });
+
+        // wait a little bit to replace with new Glue
+        SystemClock.sleep(1000);
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        final MediaPlayerGlue glue2 = new MediaPlayerGlue(activity);
+                        detailsFragment.mDetailsBackgroundController.setupVideoPlayback(glue2);
+                        glue2.setMode(MediaPlayerGlue.REPEAT_ALL);
+                        glue2.setArtist("A Googleer");
+                        glue2.setTitle("Diving with Sharks");
+                        glue2.setMediaSource(Uri.parse(
+                                "android.resource://android.support.v17.leanback.test/raw/video"));
+                    }
+                }
+        );
+
+        // test switchToRows() and switchToVideo()
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        detailsFragment.mDetailsBackgroundController.switchToRows();
+                    }
+                }
+        );
+        assertTrue(detailsFragment.mRowsFragment.getView().hasFocus());
+        PollingCheck.waitFor(new PollingCheck.ViewStableOnScreen(firstRow));
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        detailsFragment.mDetailsBackgroundController.switchToVideo();
+                    }
+                }
+        );
+        assertTrue(detailsFragment.mVideoFragment.getView().hasFocus());
+        PollingCheck.waitFor(new PollingCheck.ViewStableOnScreen(firstRow));
+    }
+
+    @Test
+    public void sharedGlueHost() {
+        final SingleFragmentTestActivity activity =
+                launchAndWaitActivity(DetailsFragmentWithNoVideo.class, new Options().uiVisibility(
+                        View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN), 0);
+        final DetailsFragmentWithNoVideo detailsFragment =
+                (DetailsFragmentWithNoVideo) activity.getTestFragment();
+
+        SystemClock.sleep(1000);
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        final MediaPlayerGlue glue1 = new MediaPlayerGlue(activity);
+                        detailsFragment.mDetailsBackgroundController.setupVideoPlayback(glue1);
+                        glue1.setArtist("A Googleer");
+                        glue1.setTitle("Diving with Sharks");
+                        glue1.setMediaSource(Uri.parse(
+                                "android.resource://android.support.v17.leanback.test/raw/video"));
+                    }
+                }
+        );
+
+        // after setup Video Playback the DPAD up will navigate to Video Fragment.
+        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return detailsFragment.mVideoFragment != null
+                        && detailsFragment.mVideoFragment.getView() != null;
+            }
+        });
+
+        final MediaPlayerGlue glue1 = (MediaPlayerGlue) detailsFragment
+                .mDetailsBackgroundController
+                .getPlaybackGlue();
+        PlaybackGlueHost playbackGlueHost = glue1.getHost();
+
+        // wait a little bit to replace with new Glue
+        SystemClock.sleep(1000);
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        final MediaPlayerGlue glue2 = new MediaPlayerGlue(activity);
+                        detailsFragment.mDetailsBackgroundController.setupVideoPlayback(glue2);
+                        glue2.setArtist("A Googleer");
+                        glue2.setTitle("Diving with Sharks");
+                        glue2.setMediaSource(Uri.parse(
+                                "android.resource://android.support.v17.leanback.test/raw/video"));
+                    }
+                }
+        );
+
+        // wait for new glue to get its glue host
+        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                MediaPlayerGlue mediaPlayerGlue = (MediaPlayerGlue) detailsFragment
+                        .mDetailsBackgroundController
+                        .getPlaybackGlue();
+                return mediaPlayerGlue != null && mediaPlayerGlue != glue1
+                        && mediaPlayerGlue.getHost() != null;
+            }
+        });
+
+        final MediaPlayerGlue glue2 = (MediaPlayerGlue) detailsFragment
+                .mDetailsBackgroundController
+                .getPlaybackGlue();
+
+        assertTrue(glue1.getHost() == null);
+        assertTrue(glue2.getHost() == playbackGlueHost);
+    }
+
+    @Test
+    public void clearVideo() {
+        final SingleFragmentTestActivity activity =
+                launchAndWaitActivity(DetailsFragmentWithNoVideo.class, new Options().uiVisibility(
+                View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN), 0);
+        final DetailsFragmentWithNoVideo detailsFragment =
+                (DetailsFragmentWithNoVideo) activity.getTestFragment();
+
+        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return detailsFragment.getRowsFragment().getVerticalGridView().getChildCount() > 0;
+            }
+        });
+        final View firstRow = detailsFragment.getRowsFragment().getVerticalGridView().getChildAt(0);
+        final int screenHeight = detailsFragment.getRowsFragment().getVerticalGridView()
+                .getHeight();
+
+        assertTrue(firstRow.hasFocus());
+        assertTrue(detailsFragment.isShowingTitle());
+        assertTrue(firstRow.getTop() > 0 && firstRow.getTop() < screenHeight);
+
+        SystemClock.sleep(1000);
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        final MediaPlayerGlue glue = new MediaPlayerGlue(activity);
+                        detailsFragment.mDetailsBackgroundController.setupVideoPlayback(glue);
+                        glue.setMode(MediaPlayerGlue.REPEAT_ALL);
+                        glue.setArtist("A Googleer");
+                        glue.setTitle("Diving with Sharks");
+                        glue.setMediaSource(Uri.parse(
+                                "android.resource://android.support.v17.leanback.test/raw/video"));
+                    }
+                }
+        );
+
+        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return ((MediaPlayerGlue) detailsFragment.mDetailsBackgroundController
+                        .getPlaybackGlue()).isMediaPlaying();
+            }
+        });
+        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return 0 == getCoverDrawableAlpha(detailsFragment.mDetailsBackgroundController);
+            }
+        });
+
+        // wait a little bit then reset glue
+        SystemClock.sleep(1000);
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        detailsFragment.mDetailsBackgroundController.setupVideoPlayback(null);
+                    }
+                }
+        );
+        // background should fade in upon reset playback
+        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return 255 == getCoverDrawableAlpha(detailsFragment.mDetailsBackgroundController);
+            }
+        });
+    }
+
+    public static class DetailsFragmentWithNoItem extends DetailsTestFragment {
+
+        final DetailsFragmentBackgroundController mDetailsBackground =
+                new DetailsFragmentBackgroundController(this);
+
+        public DetailsFragmentWithNoItem() {
+            mTimeToLoadOverviewRow = mTimeToLoadRelatedRow = 100;
+        }
+
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            mDetailsBackground.enableParallax();
+        }
+
+        @Override
+        public void onStart() {
+            super.onStart();
+            Bitmap bitmap = BitmapFactory.decodeResource(getActivity().getResources(),
+                    android.support.v17.leanback.test.R.drawable.spiderman);
+            mDetailsBackground.setCoverBitmap(bitmap);
+        }
+
+        @Override
+        public void onStop() {
+            mDetailsBackground.setCoverBitmap(null);
+            super.onStop();
+        }
+    }
+
+    @Test
+    public void noInitialItem() {
+        SingleFragmentTestActivity activity =
+                launchAndWaitActivity(DetailsFragmentWithNoItem.class, new Options().uiVisibility(
+                View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN), 0);
+        final DetailsFragmentWithNoItem detailsFragment =
+                (DetailsFragmentWithNoItem) activity.getTestFragment();
+
+        final int recyclerViewHeight = detailsFragment.getRowsFragment().getVerticalGridView()
+                .getHeight();
+        assertTrue(recyclerViewHeight > 0);
+
+        assertEquals(255, getCoverDrawableAlpha(detailsFragment.mDetailsBackgroundController));
+        Drawable coverDrawable = detailsFragment.mDetailsBackgroundController.getCoverDrawable();
+        assertEquals(0, coverDrawable.getBounds().top);
+        assertEquals(recyclerViewHeight, coverDrawable.getBounds().bottom);
+        Drawable bottomDrawable = detailsFragment.mDetailsBackgroundController.getBottomDrawable();
+        assertEquals(recyclerViewHeight, bottomDrawable.getBounds().top);
+        assertEquals(recyclerViewHeight, bottomDrawable.getBounds().bottom);
+    }
+
+    public static class DetailsFragmentSwitchToVideoInOnCreate extends DetailsTestFragment {
+
+        final DetailsFragmentBackgroundController mDetailsBackground =
+                new DetailsFragmentBackgroundController(this);
+
+        public DetailsFragmentSwitchToVideoInOnCreate() {
+            mTimeToLoadOverviewRow = mTimeToLoadRelatedRow = 100;
+        }
+
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            mDetailsBackground.enableParallax();
+            mDetailsBackground.switchToVideo();
+        }
+
+        @Override
+        public void onStart() {
+            super.onStart();
+            Bitmap bitmap = BitmapFactory.decodeResource(getActivity().getResources(),
+                    android.support.v17.leanback.test.R.drawable.spiderman);
+            mDetailsBackground.setCoverBitmap(bitmap);
+        }
+
+        @Override
+        public void onStop() {
+            mDetailsBackground.setCoverBitmap(null);
+            super.onStop();
+        }
+    }
+
+    @Test
+    public void switchToVideoInOnCreate() {
+        final SingleFragmentTestActivity activity =
+                launchAndWaitActivity(DetailsFragmentSwitchToVideoInOnCreate.class,
+                new Options().uiVisibility(
+                        View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN), 0);
+        final DetailsFragmentSwitchToVideoInOnCreate detailsFragment =
+                (DetailsFragmentSwitchToVideoInOnCreate) activity.getTestFragment();
+
+        // the pending enter transition flag should be automatically cleared
+        assertEquals(StateMachine.STATUS_INVOKED,
+                detailsFragment.STATE_ENTER_TRANSITION_COMPLETE.getStatus());
+        assertNull(TransitionHelper.getEnterTransition(activity.getWindow()));
+        assertEquals(0, getCoverDrawableAlpha(detailsFragment.mDetailsBackgroundController));
+        assertTrue(detailsFragment.getRowsFragment().getView().hasFocus());
+        //SystemClock.sleep(5000);
+        assertFalse(detailsFragment.isShowingTitle());
+
+        SystemClock.sleep(1000);
+        assertNull(detailsFragment.mVideoFragment);
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        final MediaPlayerGlue glue = new MediaPlayerGlue(activity);
+                        detailsFragment.mDetailsBackgroundController.setupVideoPlayback(glue);
+                        glue.setMode(MediaPlayerGlue.REPEAT_ALL);
+                        glue.setArtist("A Googleer");
+                        glue.setTitle("Diving with Sharks");
+                        glue.setMediaSource(Uri.parse(
+                                "android.resource://android.support.v17.leanback.test/raw/video"));
+                    }
+                }
+        );
+        // once the video fragment is created it would be immediately assigned focus
+        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return detailsFragment.mVideoFragment != null
+                        && detailsFragment.mVideoFragment.getView() != null
+                        && detailsFragment.mVideoFragment.getView().hasFocus();
+            }
+        });
+        // wait auto hide play controls done:
+        PollingCheck.waitFor(8000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return ((PlaybackFragment) detailsFragment.mVideoFragment).mBgAlpha == 0;
+            }
+        });
+
+        // switchToRows does nothing if there is no row
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        detailsFragment.mDetailsBackgroundController.switchToRows();
+                    }
+                }
+        );
+        assertTrue(detailsFragment.mVideoFragment.getView().hasFocus());
+
+        // create item, it should be layout outside screen
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        detailsFragment.setItem(new PhotoItem("Hello world",
+                                "Fake content goes here",
+                                android.support.v17.leanback.test.R.drawable.spiderman));
+                    }
+                }
+        );
+        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return detailsFragment.getVerticalGridView().getChildCount() > 0
+                        && detailsFragment.getVerticalGridView().getChildAt(0).getTop()
+                        >= detailsFragment.getVerticalGridView().getHeight();
+            }
+        });
+
+        // pressing BACK will return to details row
+        sendKeys(KeyEvent.KEYCODE_BACK);
+        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return detailsFragment.getVerticalGridView().getChildAt(0).getTop()
+                        < (detailsFragment.getVerticalGridView().getHeight() * 0.7f);
+            }
+        });
+        assertTrue(detailsFragment.getVerticalGridView().getChildAt(0).hasFocus());
+    }
+
+    @Test
+    public void switchToVideoBackToQuit() {
+        final SingleFragmentTestActivity activity =
+                launchAndWaitActivity(DetailsFragmentSwitchToVideoInOnCreate.class,
+                new Options().uiVisibility(
+                        View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN), 0);
+        final DetailsFragmentSwitchToVideoInOnCreate detailsFragment =
+                (DetailsFragmentSwitchToVideoInOnCreate) activity.getTestFragment();
+
+        // the pending enter transition flag should be automatically cleared
+        assertEquals(StateMachine.STATUS_INVOKED,
+                detailsFragment.STATE_ENTER_TRANSITION_COMPLETE.getStatus());
+        assertNull(TransitionHelper.getEnterTransition(activity.getWindow()));
+        assertEquals(0, getCoverDrawableAlpha(detailsFragment.mDetailsBackgroundController));
+        assertTrue(detailsFragment.getRowsFragment().getView().hasFocus());
+        assertFalse(detailsFragment.isShowingTitle());
+
+        SystemClock.sleep(1000);
+        assertNull(detailsFragment.mVideoFragment);
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        final MediaPlayerGlue glue = new MediaPlayerGlue(activity);
+                        detailsFragment.mDetailsBackgroundController.setupVideoPlayback(glue);
+                        glue.setMode(MediaPlayerGlue.REPEAT_ALL);
+                        glue.setArtist("A Googleer");
+                        glue.setTitle("Diving with Sharks");
+                        glue.setMediaSource(Uri.parse(
+                                "android.resource://android.support.v17.leanback.test/raw/video"));
+                    }
+                }
+        );
+        // once the video fragment is created it would be immediately assigned focus
+        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return detailsFragment.mVideoFragment != null
+                        && detailsFragment.mVideoFragment.getView() != null
+                        && detailsFragment.mVideoFragment.getView().hasFocus();
+            }
+        });
+        // wait auto hide play controls done:
+        PollingCheck.waitFor(8000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return ((PlaybackFragment) detailsFragment.mVideoFragment).mBgAlpha == 0;
+            }
+        });
+
+        // before any details row is presented, pressing BACK will quit the activity
+        sendKeys(KeyEvent.KEYCODE_BACK);
+        PollingCheck.waitFor(4000, new PollingCheck.ActivityDestroy(activity));
+    }
+
+    public static class DetailsFragmentSwitchToVideoAndPrepareEntranceTransition
+            extends DetailsTestFragment {
+
+        final DetailsFragmentBackgroundController mDetailsBackground =
+                new DetailsFragmentBackgroundController(this);
+
+        public DetailsFragmentSwitchToVideoAndPrepareEntranceTransition() {
+            mTimeToLoadOverviewRow = mTimeToLoadRelatedRow = 100;
+        }
+
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            mDetailsBackground.enableParallax();
+            mDetailsBackground.switchToVideo();
+            prepareEntranceTransition();
+        }
+
+        @Override
+        public void onViewCreated(View view, Bundle savedInstanceState) {
+            super.onViewCreated(view, savedInstanceState);
+        }
+
+        @Override
+        public void onStart() {
+            super.onStart();
+            Bitmap bitmap = BitmapFactory.decodeResource(getActivity().getResources(),
+                    android.support.v17.leanback.test.R.drawable.spiderman);
+            mDetailsBackground.setCoverBitmap(bitmap);
+        }
+
+        @Override
+        public void onStop() {
+            mDetailsBackground.setCoverBitmap(null);
+            super.onStop();
+        }
+    }
+
+    @Test
+    public void switchToVideoInOnCreateAndPrepareEntranceTransition() {
+        SingleFragmentTestActivity activity = launchAndWaitActivity(
+                DetailsFragmentSwitchToVideoAndPrepareEntranceTransition.class,
+                new Options().uiVisibility(
+                        View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN), 0);
+        final DetailsFragmentSwitchToVideoAndPrepareEntranceTransition detailsFragment =
+                (DetailsFragmentSwitchToVideoAndPrepareEntranceTransition)
+                        activity.getTestFragment();
+
+        assertEquals(StateMachine.STATUS_INVOKED,
+                detailsFragment.STATE_ENTRANCE_COMPLETE.getStatus());
+    }
+
+    public static class DetailsFragmentEntranceTransition
+            extends DetailsTestFragment {
+
+        final DetailsFragmentBackgroundController mDetailsBackground =
+                new DetailsFragmentBackgroundController(this);
+
+        public DetailsFragmentEntranceTransition() {
+            mTimeToLoadOverviewRow = mTimeToLoadRelatedRow = 100;
+        }
+
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            mDetailsBackground.enableParallax();
+            prepareEntranceTransition();
+        }
+
+        @Override
+        public void onStart() {
+            super.onStart();
+            Bitmap bitmap = BitmapFactory.decodeResource(getActivity().getResources(),
+                    android.support.v17.leanback.test.R.drawable.spiderman);
+            mDetailsBackground.setCoverBitmap(bitmap);
+        }
+
+        @Override
+        public void onStop() {
+            mDetailsBackground.setCoverBitmap(null);
+            super.onStop();
+        }
+    }
+
+    @Test
+    public void entranceTransitionBlocksSwitchToVideo() {
+        SingleFragmentTestActivity activity =
+                launchAndWaitActivity(DetailsFragmentEntranceTransition.class,
+                new Options().uiVisibility(
+                        View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN), 0);
+        final DetailsFragmentEntranceTransition detailsFragment =
+                (DetailsFragmentEntranceTransition)
+                        activity.getTestFragment();
+
+        if (Build.VERSION.SDK_INT < 21) {
+            // when enter transition is not supported, mCanUseHost is immmediately true
+            assertTrue(detailsFragment.mDetailsBackgroundController.mCanUseHost);
+        } else {
+            // calling switchToVideo() between prepareEntranceTransition and entrance transition
+            // finishes will be ignored.
+            InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+                @Override
+                public void run() {
+                    detailsFragment.mDetailsBackgroundController.switchToVideo();
+                }
+            });
+            assertFalse(detailsFragment.mDetailsBackgroundController.mCanUseHost);
+        }
+        assertEquals(255, getCoverDrawableAlpha(detailsFragment.mDetailsBackgroundController));
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                detailsFragment.setItem(new PhotoItem("Hello world", "Fake content goes here",
+                        android.support.v17.leanback.test.R.drawable.spiderman));
+                detailsFragment.startEntranceTransition();
+            }
+        });
+        // once Entrance transition is finished, mCanUseHost will be true
+        // and we can switchToVideo and fade out the background.
+        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return detailsFragment.mDetailsBackgroundController.mCanUseHost;
+            }
+        });
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                detailsFragment.mDetailsBackgroundController.switchToVideo();
+            }
+        });
+        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return 0 == getCoverDrawableAlpha(detailsFragment.mDetailsBackgroundController);
+            }
+        });
+    }
+
+    public static class DetailsFragmentEntranceTransitionTimeout extends DetailsTestFragment {
+
+        public DetailsFragmentEntranceTransitionTimeout() {
+        }
+
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            prepareEntranceTransition();
+        }
+
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
+    public void startEntranceTransitionAfterDestroyed() {
+        SingleFragmentTestActivity activity = launchAndWaitActivity(
+                DetailsFragmentEntranceTransition.class, new Options().uiVisibility(
+                        View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN),
+                1000);
+        final DetailsFragmentEntranceTransition detailsFragment =
+                (DetailsFragmentEntranceTransition)
+                        activity.getTestFragment();
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                detailsFragment.setItem(new PhotoItem("Hello world", "Fake content goes here",
+                        android.support.v17.leanback.test.R.drawable.spiderman));
+            }
+        });
+        SystemClock.sleep(100);
+        activity.finish();
+        PollingCheck.waitFor(new PollingCheck.ActivityDestroy(activity));
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                detailsFragment.startEntranceTransition();
+            }
+        });
+    }
+}
diff --git a/leanback/tests/java/android/support/v17/leanback/app/DetailsSupportFragmentTest.java b/leanback/tests/java/android/support/v17/leanback/app/DetailsSupportFragmentTest.java
new file mode 100644
index 0000000..0178d26
--- /dev/null
+++ b/leanback/tests/java/android/support/v17/leanback/app/DetailsSupportFragmentTest.java
@@ -0,0 +1,1216 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v17.leanback.app;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.animation.PropertyValuesHolder;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Rect;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.filters.SdkSuppress;
+import android.support.v17.leanback.R;
+import android.support.v17.leanback.graphics.FitWidthBitmapDrawable;
+import android.support.v17.leanback.media.MediaPlayerGlue;
+import android.support.v17.leanback.media.PlaybackGlueHost;
+import android.support.v17.leanback.testutils.PollingCheck;
+import android.support.v17.leanback.transition.TransitionHelper;
+import android.support.v17.leanback.util.StateMachine;
+import android.support.v17.leanback.widget.DetailsParallax;
+import android.support.v17.leanback.widget.DetailsParallaxDrawable;
+import android.support.v17.leanback.widget.ParallaxTarget;
+import android.support.v17.leanback.widget.RecyclerViewParallax;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.support.v4.app.Fragment;
+import android.view.KeyEvent;
+import android.view.View;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Unit tests for {@link DetailsSupportFragment}.
+ */
+@RunWith(JUnit4.class)
+@LargeTest
+public class DetailsSupportFragmentTest extends SingleSupportFragmentTestBase {
+
+    static final int PARALLAX_VERTICAL_OFFSET = -300;
+
+    static int getCoverDrawableAlpha(DetailsSupportFragmentBackgroundController controller) {
+        return ((FitWidthBitmapDrawable) controller.mParallaxDrawable.getCoverDrawable())
+                .getAlpha();
+    }
+
+    public static class DetailsSupportFragmentParallax extends DetailsTestSupportFragment {
+
+        private DetailsParallaxDrawable mParallaxDrawable;
+
+        public DetailsSupportFragmentParallax() {
+            super();
+            mMinVerticalOffset = PARALLAX_VERTICAL_OFFSET;
+        }
+
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            Drawable coverDrawable = new FitWidthBitmapDrawable();
+            mParallaxDrawable = new DetailsParallaxDrawable(
+                    getActivity(),
+                    getParallax(),
+                    coverDrawable,
+                    new ParallaxTarget.PropertyValuesHolderTarget(
+                            coverDrawable,
+                            PropertyValuesHolder.ofInt("verticalOffset", 0, mMinVerticalOffset)
+                    )
+            );
+
+            BackgroundManager backgroundManager = BackgroundManager.getInstance(getActivity());
+            backgroundManager.attach(getActivity().getWindow());
+            backgroundManager.setDrawable(mParallaxDrawable);
+        }
+
+        @Override
+        public void onStart() {
+            super.onStart();
+            setItem(new PhotoItem("Hello world", "Fake content goes here",
+                    android.support.v17.leanback.test.R.drawable.spiderman));
+        }
+
+        @Override
+        public void onResume() {
+            super.onResume();
+            Bitmap bitmap = BitmapFactory.decodeResource(getActivity().getResources(),
+                    android.support.v17.leanback.test.R.drawable.spiderman);
+            ((FitWidthBitmapDrawable) mParallaxDrawable.getCoverDrawable()).setBitmap(bitmap);
+        }
+
+        DetailsParallaxDrawable getParallaxDrawable() {
+            return mParallaxDrawable;
+        }
+    }
+
+    @Test
+    public void parallaxSetupTest() {
+        SingleSupportFragmentTestActivity activity =
+                launchAndWaitActivity(DetailsSupportFragmentTest.DetailsSupportFragmentParallax.class,
+                new SingleSupportFragmentTestBase.Options().uiVisibility(
+                        View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN), 0);
+
+        double delta = 0.0002;
+        DetailsParallax dpm = ((DetailsSupportFragment) activity.getTestFragment()).getParallax();
+
+        RecyclerViewParallax.ChildPositionProperty frameTop =
+                (RecyclerViewParallax.ChildPositionProperty) dpm.getOverviewRowTop();
+        assertEquals(0f, frameTop.getFraction(), delta);
+        assertEquals(0f, frameTop.getAdapterPosition(), delta);
+
+
+        RecyclerViewParallax.ChildPositionProperty frameBottom =
+                (RecyclerViewParallax.ChildPositionProperty) dpm.getOverviewRowBottom();
+        assertEquals(1f, frameBottom.getFraction(), delta);
+        assertEquals(0f, frameBottom.getAdapterPosition(), delta);
+    }
+
+    @Test
+    public void parallaxTest() throws Throwable {
+        SingleSupportFragmentTestActivity activity = launchAndWaitActivity(DetailsSupportFragmentParallax.class,
+                new Options().uiVisibility(
+                        View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN), 0);
+
+        final DetailsSupportFragmentParallax detailsFragment =
+                (DetailsSupportFragmentParallax) activity.getTestFragment();
+        DetailsParallaxDrawable drawable =
+                detailsFragment.getParallaxDrawable();
+        final FitWidthBitmapDrawable bitmapDrawable = (FitWidthBitmapDrawable)
+                drawable.getCoverDrawable();
+
+        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return detailsFragment.getRowsSupportFragment().getAdapter() != null
+                        && detailsFragment.getRowsSupportFragment().getAdapter().size() > 1;
+            }
+        });
+
+        final VerticalGridView verticalGridView = detailsFragment.getRowsSupportFragment()
+                .getVerticalGridView();
+        final int windowHeight = verticalGridView.getHeight();
+        final int windowWidth = verticalGridView.getWidth();
+        // make sure background manager attached to window is same size as VerticalGridView
+        // i.e. no status bar.
+        assertEquals(windowHeight, activity.getWindow().getDecorView().getHeight());
+        assertEquals(windowWidth, activity.getWindow().getDecorView().getWidth());
+
+        final View detailsFrame = verticalGridView.findViewById(R.id.details_frame);
+
+        assertEquals(windowWidth, bitmapDrawable.getBounds().width());
+
+        final Rect detailsFrameRect = new Rect();
+        detailsFrameRect.set(0, 0, detailsFrame.getWidth(), detailsFrame.getHeight());
+        verticalGridView.offsetDescendantRectToMyCoords(detailsFrame, detailsFrameRect);
+
+        assertEquals(Math.min(windowHeight, detailsFrameRect.top),
+                bitmapDrawable.getBounds().height());
+        assertEquals(0, bitmapDrawable.getVerticalOffset());
+
+        assertTrue("TitleView is visible", detailsFragment.getView()
+                .findViewById(R.id.browse_title_group).getVisibility() == View.VISIBLE);
+
+        activityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                verticalGridView.scrollToPosition(1);
+            }
+        });
+
+        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return bitmapDrawable.getVerticalOffset() == PARALLAX_VERTICAL_OFFSET
+                        && detailsFragment.getView()
+                        .findViewById(R.id.browse_title_group).getVisibility() != View.VISIBLE;
+            }
+        });
+
+        detailsFrameRect.set(0, 0, detailsFrame.getWidth(), detailsFrame.getHeight());
+        verticalGridView.offsetDescendantRectToMyCoords(detailsFrame, detailsFrameRect);
+
+        assertEquals(0, bitmapDrawable.getBounds().top);
+        assertEquals(Math.max(detailsFrameRect.top, 0), bitmapDrawable.getBounds().bottom);
+        assertEquals(windowWidth, bitmapDrawable.getBounds().width());
+
+        ColorDrawable colorDrawable = (ColorDrawable) (drawable.getChildAt(1).getDrawable());
+        assertEquals(windowWidth, colorDrawable.getBounds().width());
+        assertEquals(detailsFrameRect.bottom, colorDrawable.getBounds().top);
+        assertEquals(windowHeight, colorDrawable.getBounds().bottom);
+    }
+
+    public static class DetailsSupportFragmentWithVideo extends DetailsTestSupportFragment {
+
+        final DetailsSupportFragmentBackgroundController mDetailsBackground =
+                new DetailsSupportFragmentBackgroundController(this);
+        MediaPlayerGlue mGlue;
+
+        public DetailsSupportFragmentWithVideo() {
+            mTimeToLoadOverviewRow = mTimeToLoadRelatedRow = 100;
+        }
+
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            mDetailsBackground.enableParallax();
+            mGlue = new MediaPlayerGlue(getActivity());
+            mDetailsBackground.setupVideoPlayback(mGlue);
+
+            mGlue.setMode(MediaPlayerGlue.REPEAT_ALL);
+            mGlue.setArtist("A Googleer");
+            mGlue.setTitle("Diving with Sharks");
+            mGlue.setMediaSource(
+                    Uri.parse("android.resource://android.support.v17.leanback.test/raw/video"));
+        }
+
+        @Override
+        public void onStart() {
+            super.onStart();
+            Bitmap bitmap = BitmapFactory.decodeResource(getActivity().getResources(),
+                    android.support.v17.leanback.test.R.drawable.spiderman);
+            mDetailsBackground.setCoverBitmap(bitmap);
+        }
+
+        @Override
+        public void onStop() {
+            mDetailsBackground.setCoverBitmap(null);
+            super.onStop();
+        }
+    }
+
+    public static class DetailsSupportFragmentWithVideo1 extends DetailsSupportFragmentWithVideo {
+
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            setItem(new PhotoItem("Hello world", "Fake content goes here",
+                    android.support.v17.leanback.test.R.drawable.spiderman));
+        }
+    }
+
+    public static class DetailsSupportFragmentWithVideo2 extends DetailsSupportFragmentWithVideo {
+
+        @Override
+        public void onStart() {
+            super.onStart();
+            setItem(new PhotoItem("Hello world", "Fake content goes here",
+                    android.support.v17.leanback.test.R.drawable.spiderman));
+        }
+    }
+
+    private void navigateBetweenRowsAndVideoUsingRequestFocusInternal(Class cls)
+            throws Throwable {
+        SingleSupportFragmentTestActivity activity = launchAndWaitActivity(cls,
+                new Options().uiVisibility(
+                        View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN), 0);
+
+        final DetailsSupportFragmentWithVideo detailsFragment =
+                (DetailsSupportFragmentWithVideo) activity.getTestFragment();
+        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return detailsFragment.mVideoSupportFragment != null
+                        && detailsFragment.mVideoSupportFragment.getView() != null
+                        && detailsFragment.mGlue.isMediaPlaying();
+            }
+        });
+
+        final int screenHeight = detailsFragment.getRowsSupportFragment().getVerticalGridView()
+                .getHeight();
+        final View firstRow = detailsFragment.getRowsSupportFragment().getVerticalGridView().getChildAt(0);
+        final int originalFirstRowTop = firstRow.getTop();
+        assertTrue(firstRow.hasFocus());
+        assertTrue(firstRow.getTop() > 0 && firstRow.getTop() < screenHeight);
+        assertTrue(detailsFragment.isShowingTitle());
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                detailsFragment.mVideoSupportFragment.getView().requestFocus();
+            }
+        });
+        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return firstRow.getTop() >= screenHeight;
+            }
+        });
+        assertFalse(detailsFragment.isShowingTitle());
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                detailsFragment.getRowsSupportFragment().getVerticalGridView().requestFocus();
+            }
+        });
+        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return firstRow.getTop() == originalFirstRowTop;
+            }
+        });
+        assertTrue(detailsFragment.isShowingTitle());
+    }
+
+    @Test
+    public void navigateBetweenRowsAndVideoUsingRequestFocus1() throws Throwable {
+        navigateBetweenRowsAndVideoUsingRequestFocusInternal(DetailsSupportFragmentWithVideo1.class);
+    }
+
+    @Test
+    public void navigateBetweenRowsAndVideoUsingRequestFocus2() throws Throwable {
+        navigateBetweenRowsAndVideoUsingRequestFocusInternal(DetailsSupportFragmentWithVideo2.class);
+    }
+
+    private void navigateBetweenRowsAndVideoUsingDPADInternal(Class cls) throws Throwable {
+        SingleSupportFragmentTestActivity activity = launchAndWaitActivity(cls,
+                new Options().uiVisibility(
+                        View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN), 0);
+
+        final DetailsSupportFragmentWithVideo detailsFragment =
+                (DetailsSupportFragmentWithVideo) activity.getTestFragment();
+        // wait video playing
+        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return detailsFragment.mVideoSupportFragment != null
+                        && detailsFragment.mVideoSupportFragment.getView() != null
+                        && detailsFragment.mGlue.isMediaPlaying();
+            }
+        });
+
+        final int screenHeight = detailsFragment.getRowsSupportFragment().getVerticalGridView()
+                .getHeight();
+        final View firstRow = detailsFragment.getRowsSupportFragment().getVerticalGridView().getChildAt(0);
+        final int originalFirstRowTop = firstRow.getTop();
+        assertTrue(firstRow.hasFocus());
+        assertTrue(firstRow.getTop() > 0 && firstRow.getTop() < screenHeight);
+        assertTrue(detailsFragment.isShowingTitle());
+
+        // navigate to video
+        sendKeys(KeyEvent.KEYCODE_DPAD_UP);
+        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return firstRow.getTop() >= screenHeight;
+            }
+        });
+
+        // wait auto hide play controls done:
+        PollingCheck.waitFor(8000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return ((PlaybackSupportFragment) detailsFragment.mVideoSupportFragment).mBgAlpha == 0;
+            }
+        });
+
+        // navigate to details
+        sendKeys(KeyEvent.KEYCODE_BACK);
+        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return firstRow.getTop() == originalFirstRowTop;
+            }
+        });
+        assertTrue(detailsFragment.isShowingTitle());
+    }
+
+    @Test
+    public void navigateBetweenRowsAndVideoUsingDPAD1() throws Throwable {
+        navigateBetweenRowsAndVideoUsingDPADInternal(DetailsSupportFragmentWithVideo1.class);
+    }
+
+    @Test
+    public void navigateBetweenRowsAndVideoUsingDPAD2() throws Throwable {
+        navigateBetweenRowsAndVideoUsingDPADInternal(DetailsSupportFragmentWithVideo2.class);
+    }
+
+    public static class EmptyFragmentClass extends Fragment {
+        @Override
+        public void onStart() {
+            super.onStart();
+            getActivity().finish();
+        }
+    }
+
+    private void fragmentOnStartWithVideoInternal(Class cls) throws Throwable {
+        final SingleSupportFragmentTestActivity activity = launchAndWaitActivity(cls,
+                new Options().uiVisibility(
+                        View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN), 0);
+
+        final DetailsSupportFragmentWithVideo detailsFragment =
+                (DetailsSupportFragmentWithVideo) activity.getTestFragment();
+        // wait video playing
+        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return detailsFragment.mVideoSupportFragment != null
+                        && detailsFragment.mVideoSupportFragment.getView() != null
+                        && detailsFragment.mGlue.isMediaPlaying();
+            }
+        });
+
+        final int screenHeight = detailsFragment.getRowsSupportFragment().getVerticalGridView()
+                .getHeight();
+        final View firstRow = detailsFragment.getRowsSupportFragment().getVerticalGridView().getChildAt(0);
+        final int originalFirstRowTop = firstRow.getTop();
+        assertTrue(firstRow.hasFocus());
+        assertTrue(firstRow.getTop() > 0 && firstRow.getTop() < screenHeight);
+        assertTrue(detailsFragment.isShowingTitle());
+
+        // navigate to video
+        sendKeys(KeyEvent.KEYCODE_DPAD_UP);
+        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return firstRow.getTop() >= screenHeight;
+            }
+        });
+
+        // start an empty activity
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        Intent intent = new Intent(activity, SingleSupportFragmentTestActivity.class);
+                        intent.putExtra(SingleSupportFragmentTestActivity.EXTRA_FRAGMENT_NAME,
+                                EmptyFragmentClass.class.getName());
+                        activity.startActivity(intent);
+                    }
+                }
+        );
+        PollingCheck.waitFor(2000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return detailsFragment.isResumed();
+            }
+        });
+        assertTrue(detailsFragment.mVideoSupportFragment.getView().hasFocus());
+    }
+
+    @Test
+    public void fragmentOnStartWithVideo1() throws Throwable {
+        fragmentOnStartWithVideoInternal(DetailsSupportFragmentWithVideo1.class);
+    }
+
+    @Test
+    public void fragmentOnStartWithVideo2() throws Throwable {
+        fragmentOnStartWithVideoInternal(DetailsSupportFragmentWithVideo2.class);
+    }
+
+    @Test
+    public void navigateBetweenRowsAndTitle() throws Throwable {
+        SingleSupportFragmentTestActivity activity =
+                launchAndWaitActivity(DetailsTestSupportFragment.class, new Options().uiVisibility(
+                View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN), 0);
+        final DetailsTestSupportFragment detailsFragment =
+                (DetailsTestSupportFragment) activity.getTestFragment();
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                detailsFragment.setOnSearchClickedListener(new View.OnClickListener() {
+                    @Override
+                    public void onClick(View view) {
+                    }
+                });
+                detailsFragment.setItem(new PhotoItem("Hello world", "Fake content goes here",
+                        android.support.v17.leanback.test.R.drawable.spiderman));
+            }
+        });
+
+        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return detailsFragment.getRowsSupportFragment().getVerticalGridView().getChildCount() > 0;
+            }
+        });
+        final View firstRow = detailsFragment.getRowsSupportFragment().getVerticalGridView().getChildAt(0);
+        final int originalFirstRowTop = firstRow.getTop();
+        final int screenHeight = detailsFragment.getRowsSupportFragment().getVerticalGridView()
+                .getHeight();
+
+        assertTrue(firstRow.hasFocus());
+        assertTrue(detailsFragment.isShowingTitle());
+        assertTrue(firstRow.getTop() > 0 && firstRow.getTop() < screenHeight);
+
+        sendKeys(KeyEvent.KEYCODE_DPAD_UP);
+        PollingCheck.waitFor(new PollingCheck.ViewStableOnScreen(firstRow));
+        assertTrue(detailsFragment.isShowingTitle());
+        assertTrue(detailsFragment.getTitleView().hasFocus());
+        assertEquals(originalFirstRowTop, firstRow.getTop());
+
+        sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+        PollingCheck.waitFor(new PollingCheck.ViewStableOnScreen(firstRow));
+        assertTrue(detailsFragment.isShowingTitle());
+        assertTrue(firstRow.hasFocus());
+        assertEquals(originalFirstRowTop, firstRow.getTop());
+    }
+
+    public static class DetailsSupportFragmentWithNoVideo extends DetailsTestSupportFragment {
+
+        final DetailsSupportFragmentBackgroundController mDetailsBackground =
+                new DetailsSupportFragmentBackgroundController(this);
+
+        public DetailsSupportFragmentWithNoVideo() {
+            mTimeToLoadOverviewRow = mTimeToLoadRelatedRow = 100;
+        }
+
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            mDetailsBackground.enableParallax();
+
+            setItem(new PhotoItem("Hello world", "Fake content goes here",
+                    android.support.v17.leanback.test.R.drawable.spiderman));
+        }
+
+        @Override
+        public void onStart() {
+            super.onStart();
+            Bitmap bitmap = BitmapFactory.decodeResource(getActivity().getResources(),
+                    android.support.v17.leanback.test.R.drawable.spiderman);
+            mDetailsBackground.setCoverBitmap(bitmap);
+        }
+
+        @Override
+        public void onStop() {
+            mDetailsBackground.setCoverBitmap(null);
+            super.onStop();
+        }
+    }
+
+    @Test
+    public void lateSetupVideo() {
+        final SingleSupportFragmentTestActivity activity =
+                launchAndWaitActivity(DetailsSupportFragmentWithNoVideo.class, new Options().uiVisibility(
+                View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN), 0);
+        final DetailsSupportFragmentWithNoVideo detailsFragment =
+                (DetailsSupportFragmentWithNoVideo) activity.getTestFragment();
+
+        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return detailsFragment.getRowsSupportFragment().getVerticalGridView().getChildCount() > 0;
+            }
+        });
+        final View firstRow = detailsFragment.getRowsSupportFragment().getVerticalGridView().getChildAt(0);
+        final int screenHeight = detailsFragment.getRowsSupportFragment().getVerticalGridView()
+                .getHeight();
+
+        assertTrue(firstRow.hasFocus());
+        assertTrue(detailsFragment.isShowingTitle());
+        assertTrue(firstRow.getTop() > 0 && firstRow.getTop() < screenHeight);
+
+        sendKeys(KeyEvent.KEYCODE_DPAD_UP);
+        assertTrue(firstRow.hasFocus());
+
+        SystemClock.sleep(1000);
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        final MediaPlayerGlue glue = new MediaPlayerGlue(activity);
+                        detailsFragment.mDetailsBackgroundController.setupVideoPlayback(glue);
+                        glue.setMode(MediaPlayerGlue.REPEAT_ALL);
+                        glue.setArtist("A Googleer");
+                        glue.setTitle("Diving with Sharks");
+                        glue.setMediaSource(Uri.parse(
+                                "android.resource://android.support.v17.leanback.test/raw/video"));
+                    }
+                }
+        );
+
+        // after setup Video Playback the DPAD up will navigate to Video Fragment.
+        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
+                @Override
+                    public boolean canProceed() {
+                        return detailsFragment.mVideoSupportFragment != null
+                                && detailsFragment.mVideoSupportFragment.getView() != null;
+                }
+        });
+        sendKeys(KeyEvent.KEYCODE_DPAD_UP);
+        assertTrue(detailsFragment.mVideoSupportFragment.getView().hasFocus());
+        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return ((MediaPlayerGlue) detailsFragment.mDetailsBackgroundController
+                        .getPlaybackGlue()).isMediaPlaying();
+            }
+        });
+        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return 0 == getCoverDrawableAlpha(detailsFragment.mDetailsBackgroundController);
+            }
+        });
+
+        // wait a little bit to replace with new Glue
+        SystemClock.sleep(1000);
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        final MediaPlayerGlue glue2 = new MediaPlayerGlue(activity);
+                        detailsFragment.mDetailsBackgroundController.setupVideoPlayback(glue2);
+                        glue2.setMode(MediaPlayerGlue.REPEAT_ALL);
+                        glue2.setArtist("A Googleer");
+                        glue2.setTitle("Diving with Sharks");
+                        glue2.setMediaSource(Uri.parse(
+                                "android.resource://android.support.v17.leanback.test/raw/video"));
+                    }
+                }
+        );
+
+        // test switchToRows() and switchToVideo()
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        detailsFragment.mDetailsBackgroundController.switchToRows();
+                    }
+                }
+        );
+        assertTrue(detailsFragment.mRowsSupportFragment.getView().hasFocus());
+        PollingCheck.waitFor(new PollingCheck.ViewStableOnScreen(firstRow));
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        detailsFragment.mDetailsBackgroundController.switchToVideo();
+                    }
+                }
+        );
+        assertTrue(detailsFragment.mVideoSupportFragment.getView().hasFocus());
+        PollingCheck.waitFor(new PollingCheck.ViewStableOnScreen(firstRow));
+    }
+
+    @Test
+    public void sharedGlueHost() {
+        final SingleSupportFragmentTestActivity activity =
+                launchAndWaitActivity(DetailsSupportFragmentWithNoVideo.class, new Options().uiVisibility(
+                        View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN), 0);
+        final DetailsSupportFragmentWithNoVideo detailsFragment =
+                (DetailsSupportFragmentWithNoVideo) activity.getTestFragment();
+
+        SystemClock.sleep(1000);
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        final MediaPlayerGlue glue1 = new MediaPlayerGlue(activity);
+                        detailsFragment.mDetailsBackgroundController.setupVideoPlayback(glue1);
+                        glue1.setArtist("A Googleer");
+                        glue1.setTitle("Diving with Sharks");
+                        glue1.setMediaSource(Uri.parse(
+                                "android.resource://android.support.v17.leanback.test/raw/video"));
+                    }
+                }
+        );
+
+        // after setup Video Playback the DPAD up will navigate to Video Fragment.
+        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return detailsFragment.mVideoSupportFragment != null
+                        && detailsFragment.mVideoSupportFragment.getView() != null;
+            }
+        });
+
+        final MediaPlayerGlue glue1 = (MediaPlayerGlue) detailsFragment
+                .mDetailsBackgroundController
+                .getPlaybackGlue();
+        PlaybackGlueHost playbackGlueHost = glue1.getHost();
+
+        // wait a little bit to replace with new Glue
+        SystemClock.sleep(1000);
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        final MediaPlayerGlue glue2 = new MediaPlayerGlue(activity);
+                        detailsFragment.mDetailsBackgroundController.setupVideoPlayback(glue2);
+                        glue2.setArtist("A Googleer");
+                        glue2.setTitle("Diving with Sharks");
+                        glue2.setMediaSource(Uri.parse(
+                                "android.resource://android.support.v17.leanback.test/raw/video"));
+                    }
+                }
+        );
+
+        // wait for new glue to get its glue host
+        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                MediaPlayerGlue mediaPlayerGlue = (MediaPlayerGlue) detailsFragment
+                        .mDetailsBackgroundController
+                        .getPlaybackGlue();
+                return mediaPlayerGlue != null && mediaPlayerGlue != glue1
+                        && mediaPlayerGlue.getHost() != null;
+            }
+        });
+
+        final MediaPlayerGlue glue2 = (MediaPlayerGlue) detailsFragment
+                .mDetailsBackgroundController
+                .getPlaybackGlue();
+
+        assertTrue(glue1.getHost() == null);
+        assertTrue(glue2.getHost() == playbackGlueHost);
+    }
+
+    @Test
+    public void clearVideo() {
+        final SingleSupportFragmentTestActivity activity =
+                launchAndWaitActivity(DetailsSupportFragmentWithNoVideo.class, new Options().uiVisibility(
+                View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN), 0);
+        final DetailsSupportFragmentWithNoVideo detailsFragment =
+                (DetailsSupportFragmentWithNoVideo) activity.getTestFragment();
+
+        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return detailsFragment.getRowsSupportFragment().getVerticalGridView().getChildCount() > 0;
+            }
+        });
+        final View firstRow = detailsFragment.getRowsSupportFragment().getVerticalGridView().getChildAt(0);
+        final int screenHeight = detailsFragment.getRowsSupportFragment().getVerticalGridView()
+                .getHeight();
+
+        assertTrue(firstRow.hasFocus());
+        assertTrue(detailsFragment.isShowingTitle());
+        assertTrue(firstRow.getTop() > 0 && firstRow.getTop() < screenHeight);
+
+        SystemClock.sleep(1000);
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        final MediaPlayerGlue glue = new MediaPlayerGlue(activity);
+                        detailsFragment.mDetailsBackgroundController.setupVideoPlayback(glue);
+                        glue.setMode(MediaPlayerGlue.REPEAT_ALL);
+                        glue.setArtist("A Googleer");
+                        glue.setTitle("Diving with Sharks");
+                        glue.setMediaSource(Uri.parse(
+                                "android.resource://android.support.v17.leanback.test/raw/video"));
+                    }
+                }
+        );
+
+        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return ((MediaPlayerGlue) detailsFragment.mDetailsBackgroundController
+                        .getPlaybackGlue()).isMediaPlaying();
+            }
+        });
+        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return 0 == getCoverDrawableAlpha(detailsFragment.mDetailsBackgroundController);
+            }
+        });
+
+        // wait a little bit then reset glue
+        SystemClock.sleep(1000);
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        detailsFragment.mDetailsBackgroundController.setupVideoPlayback(null);
+                    }
+                }
+        );
+        // background should fade in upon reset playback
+        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return 255 == getCoverDrawableAlpha(detailsFragment.mDetailsBackgroundController);
+            }
+        });
+    }
+
+    public static class DetailsSupportFragmentWithNoItem extends DetailsTestSupportFragment {
+
+        final DetailsSupportFragmentBackgroundController mDetailsBackground =
+                new DetailsSupportFragmentBackgroundController(this);
+
+        public DetailsSupportFragmentWithNoItem() {
+            mTimeToLoadOverviewRow = mTimeToLoadRelatedRow = 100;
+        }
+
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            mDetailsBackground.enableParallax();
+        }
+
+        @Override
+        public void onStart() {
+            super.onStart();
+            Bitmap bitmap = BitmapFactory.decodeResource(getActivity().getResources(),
+                    android.support.v17.leanback.test.R.drawable.spiderman);
+            mDetailsBackground.setCoverBitmap(bitmap);
+        }
+
+        @Override
+        public void onStop() {
+            mDetailsBackground.setCoverBitmap(null);
+            super.onStop();
+        }
+    }
+
+    @Test
+    public void noInitialItem() {
+        SingleSupportFragmentTestActivity activity =
+                launchAndWaitActivity(DetailsSupportFragmentWithNoItem.class, new Options().uiVisibility(
+                View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN), 0);
+        final DetailsSupportFragmentWithNoItem detailsFragment =
+                (DetailsSupportFragmentWithNoItem) activity.getTestFragment();
+
+        final int recyclerViewHeight = detailsFragment.getRowsSupportFragment().getVerticalGridView()
+                .getHeight();
+        assertTrue(recyclerViewHeight > 0);
+
+        assertEquals(255, getCoverDrawableAlpha(detailsFragment.mDetailsBackgroundController));
+        Drawable coverDrawable = detailsFragment.mDetailsBackgroundController.getCoverDrawable();
+        assertEquals(0, coverDrawable.getBounds().top);
+        assertEquals(recyclerViewHeight, coverDrawable.getBounds().bottom);
+        Drawable bottomDrawable = detailsFragment.mDetailsBackgroundController.getBottomDrawable();
+        assertEquals(recyclerViewHeight, bottomDrawable.getBounds().top);
+        assertEquals(recyclerViewHeight, bottomDrawable.getBounds().bottom);
+    }
+
+    public static class DetailsSupportFragmentSwitchToVideoInOnCreate extends DetailsTestSupportFragment {
+
+        final DetailsSupportFragmentBackgroundController mDetailsBackground =
+                new DetailsSupportFragmentBackgroundController(this);
+
+        public DetailsSupportFragmentSwitchToVideoInOnCreate() {
+            mTimeToLoadOverviewRow = mTimeToLoadRelatedRow = 100;
+        }
+
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            mDetailsBackground.enableParallax();
+            mDetailsBackground.switchToVideo();
+        }
+
+        @Override
+        public void onStart() {
+            super.onStart();
+            Bitmap bitmap = BitmapFactory.decodeResource(getActivity().getResources(),
+                    android.support.v17.leanback.test.R.drawable.spiderman);
+            mDetailsBackground.setCoverBitmap(bitmap);
+        }
+
+        @Override
+        public void onStop() {
+            mDetailsBackground.setCoverBitmap(null);
+            super.onStop();
+        }
+    }
+
+    @Test
+    public void switchToVideoInOnCreate() {
+        final SingleSupportFragmentTestActivity activity =
+                launchAndWaitActivity(DetailsSupportFragmentSwitchToVideoInOnCreate.class,
+                new Options().uiVisibility(
+                        View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN), 0);
+        final DetailsSupportFragmentSwitchToVideoInOnCreate detailsFragment =
+                (DetailsSupportFragmentSwitchToVideoInOnCreate) activity.getTestFragment();
+
+        // the pending enter transition flag should be automatically cleared
+        assertEquals(StateMachine.STATUS_INVOKED,
+                detailsFragment.STATE_ENTER_TRANSITION_COMPLETE.getStatus());
+        assertNull(TransitionHelper.getEnterTransition(activity.getWindow()));
+        assertEquals(0, getCoverDrawableAlpha(detailsFragment.mDetailsBackgroundController));
+        assertTrue(detailsFragment.getRowsSupportFragment().getView().hasFocus());
+        //SystemClock.sleep(5000);
+        assertFalse(detailsFragment.isShowingTitle());
+
+        SystemClock.sleep(1000);
+        assertNull(detailsFragment.mVideoSupportFragment);
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        final MediaPlayerGlue glue = new MediaPlayerGlue(activity);
+                        detailsFragment.mDetailsBackgroundController.setupVideoPlayback(glue);
+                        glue.setMode(MediaPlayerGlue.REPEAT_ALL);
+                        glue.setArtist("A Googleer");
+                        glue.setTitle("Diving with Sharks");
+                        glue.setMediaSource(Uri.parse(
+                                "android.resource://android.support.v17.leanback.test/raw/video"));
+                    }
+                }
+        );
+        // once the video fragment is created it would be immediately assigned focus
+        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return detailsFragment.mVideoSupportFragment != null
+                        && detailsFragment.mVideoSupportFragment.getView() != null
+                        && detailsFragment.mVideoSupportFragment.getView().hasFocus();
+            }
+        });
+        // wait auto hide play controls done:
+        PollingCheck.waitFor(8000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return ((PlaybackSupportFragment) detailsFragment.mVideoSupportFragment).mBgAlpha == 0;
+            }
+        });
+
+        // switchToRows does nothing if there is no row
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        detailsFragment.mDetailsBackgroundController.switchToRows();
+                    }
+                }
+        );
+        assertTrue(detailsFragment.mVideoSupportFragment.getView().hasFocus());
+
+        // create item, it should be layout outside screen
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        detailsFragment.setItem(new PhotoItem("Hello world",
+                                "Fake content goes here",
+                                android.support.v17.leanback.test.R.drawable.spiderman));
+                    }
+                }
+        );
+        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return detailsFragment.getVerticalGridView().getChildCount() > 0
+                        && detailsFragment.getVerticalGridView().getChildAt(0).getTop()
+                        >= detailsFragment.getVerticalGridView().getHeight();
+            }
+        });
+
+        // pressing BACK will return to details row
+        sendKeys(KeyEvent.KEYCODE_BACK);
+        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return detailsFragment.getVerticalGridView().getChildAt(0).getTop()
+                        < (detailsFragment.getVerticalGridView().getHeight() * 0.7f);
+            }
+        });
+        assertTrue(detailsFragment.getVerticalGridView().getChildAt(0).hasFocus());
+    }
+
+    @Test
+    public void switchToVideoBackToQuit() {
+        final SingleSupportFragmentTestActivity activity =
+                launchAndWaitActivity(DetailsSupportFragmentSwitchToVideoInOnCreate.class,
+                new Options().uiVisibility(
+                        View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN), 0);
+        final DetailsSupportFragmentSwitchToVideoInOnCreate detailsFragment =
+                (DetailsSupportFragmentSwitchToVideoInOnCreate) activity.getTestFragment();
+
+        // the pending enter transition flag should be automatically cleared
+        assertEquals(StateMachine.STATUS_INVOKED,
+                detailsFragment.STATE_ENTER_TRANSITION_COMPLETE.getStatus());
+        assertNull(TransitionHelper.getEnterTransition(activity.getWindow()));
+        assertEquals(0, getCoverDrawableAlpha(detailsFragment.mDetailsBackgroundController));
+        assertTrue(detailsFragment.getRowsSupportFragment().getView().hasFocus());
+        assertFalse(detailsFragment.isShowingTitle());
+
+        SystemClock.sleep(1000);
+        assertNull(detailsFragment.mVideoSupportFragment);
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        final MediaPlayerGlue glue = new MediaPlayerGlue(activity);
+                        detailsFragment.mDetailsBackgroundController.setupVideoPlayback(glue);
+                        glue.setMode(MediaPlayerGlue.REPEAT_ALL);
+                        glue.setArtist("A Googleer");
+                        glue.setTitle("Diving with Sharks");
+                        glue.setMediaSource(Uri.parse(
+                                "android.resource://android.support.v17.leanback.test/raw/video"));
+                    }
+                }
+        );
+        // once the video fragment is created it would be immediately assigned focus
+        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return detailsFragment.mVideoSupportFragment != null
+                        && detailsFragment.mVideoSupportFragment.getView() != null
+                        && detailsFragment.mVideoSupportFragment.getView().hasFocus();
+            }
+        });
+        // wait auto hide play controls done:
+        PollingCheck.waitFor(8000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return ((PlaybackSupportFragment) detailsFragment.mVideoSupportFragment).mBgAlpha == 0;
+            }
+        });
+
+        // before any details row is presented, pressing BACK will quit the activity
+        sendKeys(KeyEvent.KEYCODE_BACK);
+        PollingCheck.waitFor(4000, new PollingCheck.ActivityDestroy(activity));
+    }
+
+    public static class DetailsSupportFragmentSwitchToVideoAndPrepareEntranceTransition
+            extends DetailsTestSupportFragment {
+
+        final DetailsSupportFragmentBackgroundController mDetailsBackground =
+                new DetailsSupportFragmentBackgroundController(this);
+
+        public DetailsSupportFragmentSwitchToVideoAndPrepareEntranceTransition() {
+            mTimeToLoadOverviewRow = mTimeToLoadRelatedRow = 100;
+        }
+
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            mDetailsBackground.enableParallax();
+            mDetailsBackground.switchToVideo();
+            prepareEntranceTransition();
+        }
+
+        @Override
+        public void onViewCreated(View view, Bundle savedInstanceState) {
+            super.onViewCreated(view, savedInstanceState);
+        }
+
+        @Override
+        public void onStart() {
+            super.onStart();
+            Bitmap bitmap = BitmapFactory.decodeResource(getActivity().getResources(),
+                    android.support.v17.leanback.test.R.drawable.spiderman);
+            mDetailsBackground.setCoverBitmap(bitmap);
+        }
+
+        @Override
+        public void onStop() {
+            mDetailsBackground.setCoverBitmap(null);
+            super.onStop();
+        }
+    }
+
+    @Test
+    public void switchToVideoInOnCreateAndPrepareEntranceTransition() {
+        SingleSupportFragmentTestActivity activity = launchAndWaitActivity(
+                DetailsSupportFragmentSwitchToVideoAndPrepareEntranceTransition.class,
+                new Options().uiVisibility(
+                        View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN), 0);
+        final DetailsSupportFragmentSwitchToVideoAndPrepareEntranceTransition detailsFragment =
+                (DetailsSupportFragmentSwitchToVideoAndPrepareEntranceTransition)
+                        activity.getTestFragment();
+
+        assertEquals(StateMachine.STATUS_INVOKED,
+                detailsFragment.STATE_ENTRANCE_COMPLETE.getStatus());
+    }
+
+    public static class DetailsSupportFragmentEntranceTransition
+            extends DetailsTestSupportFragment {
+
+        final DetailsSupportFragmentBackgroundController mDetailsBackground =
+                new DetailsSupportFragmentBackgroundController(this);
+
+        public DetailsSupportFragmentEntranceTransition() {
+            mTimeToLoadOverviewRow = mTimeToLoadRelatedRow = 100;
+        }
+
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            mDetailsBackground.enableParallax();
+            prepareEntranceTransition();
+        }
+
+        @Override
+        public void onStart() {
+            super.onStart();
+            Bitmap bitmap = BitmapFactory.decodeResource(getActivity().getResources(),
+                    android.support.v17.leanback.test.R.drawable.spiderman);
+            mDetailsBackground.setCoverBitmap(bitmap);
+        }
+
+        @Override
+        public void onStop() {
+            mDetailsBackground.setCoverBitmap(null);
+            super.onStop();
+        }
+    }
+
+    @Test
+    public void entranceTransitionBlocksSwitchToVideo() {
+        SingleSupportFragmentTestActivity activity =
+                launchAndWaitActivity(DetailsSupportFragmentEntranceTransition.class,
+                new Options().uiVisibility(
+                        View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN), 0);
+        final DetailsSupportFragmentEntranceTransition detailsFragment =
+                (DetailsSupportFragmentEntranceTransition)
+                        activity.getTestFragment();
+
+        if (Build.VERSION.SDK_INT < 21) {
+            // when enter transition is not supported, mCanUseHost is immmediately true
+            assertTrue(detailsFragment.mDetailsBackgroundController.mCanUseHost);
+        } else {
+            // calling switchToVideo() between prepareEntranceTransition and entrance transition
+            // finishes will be ignored.
+            InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+                @Override
+                public void run() {
+                    detailsFragment.mDetailsBackgroundController.switchToVideo();
+                }
+            });
+            assertFalse(detailsFragment.mDetailsBackgroundController.mCanUseHost);
+        }
+        assertEquals(255, getCoverDrawableAlpha(detailsFragment.mDetailsBackgroundController));
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                detailsFragment.setItem(new PhotoItem("Hello world", "Fake content goes here",
+                        android.support.v17.leanback.test.R.drawable.spiderman));
+                detailsFragment.startEntranceTransition();
+            }
+        });
+        // once Entrance transition is finished, mCanUseHost will be true
+        // and we can switchToVideo and fade out the background.
+        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return detailsFragment.mDetailsBackgroundController.mCanUseHost;
+            }
+        });
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                detailsFragment.mDetailsBackgroundController.switchToVideo();
+            }
+        });
+        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return 0 == getCoverDrawableAlpha(detailsFragment.mDetailsBackgroundController);
+            }
+        });
+    }
+
+    public static class DetailsSupportFragmentEntranceTransitionTimeout extends DetailsTestSupportFragment {
+
+        public DetailsSupportFragmentEntranceTransitionTimeout() {
+        }
+
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            prepareEntranceTransition();
+        }
+
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
+    public void startEntranceTransitionAfterDestroyed() {
+        SingleSupportFragmentTestActivity activity = launchAndWaitActivity(
+                DetailsSupportFragmentEntranceTransition.class, new Options().uiVisibility(
+                        View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN),
+                1000);
+        final DetailsSupportFragmentEntranceTransition detailsFragment =
+                (DetailsSupportFragmentEntranceTransition)
+                        activity.getTestFragment();
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                detailsFragment.setItem(new PhotoItem("Hello world", "Fake content goes here",
+                        android.support.v17.leanback.test.R.drawable.spiderman));
+            }
+        });
+        SystemClock.sleep(100);
+        activity.finish();
+        PollingCheck.waitFor(new PollingCheck.ActivityDestroy(activity));
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                detailsFragment.startEntranceTransition();
+            }
+        });
+    }
+}
diff --git a/leanback/tests/java/android/support/v17/leanback/app/DetailsTestFragment.java b/leanback/tests/java/android/support/v17/leanback/app/DetailsTestFragment.java
new file mode 100644
index 0000000..833b344
--- /dev/null
+++ b/leanback/tests/java/android/support/v17/leanback/app/DetailsTestFragment.java
@@ -0,0 +1,148 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from DetailsTestSupportFragment.java.  DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.v17.leanback.test.R;
+import android.support.v17.leanback.widget.AbstractDetailsDescriptionPresenter;
+import android.support.v17.leanback.widget.Action;
+import android.support.v17.leanback.widget.ArrayObjectAdapter;
+import android.support.v17.leanback.widget.ClassPresenterSelector;
+import android.support.v17.leanback.widget.DetailsOverviewRow;
+import android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter;
+import android.support.v17.leanback.widget.HeaderItem;
+import android.support.v17.leanback.widget.ImageCardView;
+import android.support.v17.leanback.widget.ListRow;
+import android.support.v17.leanback.widget.ListRowPresenter;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
+import android.view.ViewGroup;
+
+/**
+ * Base class provides overview row and some related rows.
+ */
+public class DetailsTestFragment extends android.support.v17.leanback.app.DetailsFragment {
+    private static final int NUM_ROWS = 3;
+    private ArrayObjectAdapter mRowsAdapter;
+    private PhotoItem mPhotoItem;
+    private final Presenter mCardPresenter = new Presenter() {
+        @Override
+        public ViewHolder onCreateViewHolder(ViewGroup parent) {
+            ImageCardView cardView = new ImageCardView(getActivity());
+            cardView.setFocusable(true);
+            cardView.setFocusableInTouchMode(true);
+            return new ViewHolder(cardView);
+        }
+
+        @Override
+        public void onBindViewHolder(ViewHolder viewHolder, Object item) {
+            ImageCardView imageCardView = (ImageCardView) viewHolder.view;
+            imageCardView.setTitleText("Android Tv");
+            imageCardView.setContentText("Android Tv Production Inc.");
+            imageCardView.setMainImageDimensions(313, 176);
+        }
+
+        @Override
+        public void onUnbindViewHolder(ViewHolder viewHolder) {
+        }
+    };
+
+    private static final int ACTION_RENT = 2;
+    private static final int ACTION_BUY = 3;
+
+    protected long mTimeToLoadOverviewRow = 1000;
+    protected long mTimeToLoadRelatedRow = 2000;
+
+    private Action mActionRent;
+    private Action mActionBuy;
+
+    protected int mMinVerticalOffset = -100;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setTitle("Leanback Sample App");
+
+        mActionRent = new Action(ACTION_RENT, "Rent", "$3.99",
+                getResources().getDrawable(R.drawable.ic_action_a));
+        mActionBuy = new Action(ACTION_BUY, "Buy $9.99");
+
+        ClassPresenterSelector ps = new ClassPresenterSelector();
+        FullWidthDetailsOverviewRowPresenter dorPresenter =
+                new FullWidthDetailsOverviewRowPresenter(new AbstractDetailsDescriptionPresenter() {
+                    @Override
+                    protected void onBindDescription(
+                            AbstractDetailsDescriptionPresenter.ViewHolder vh, Object item) {
+                        vh.getTitle().setText("Funny Movie");
+                        vh.getSubtitle().setText("Android TV Production Inc.");
+                        vh.getBody().setText("What a great movie!");
+                    }
+                });
+
+        ps.addClassPresenter(DetailsOverviewRow.class, dorPresenter);
+        ps.addClassPresenter(ListRow.class, new ListRowPresenter());
+        mRowsAdapter = new ArrayObjectAdapter(ps);
+    }
+
+    public void setItem(PhotoItem photoItem) {
+        mPhotoItem = photoItem;
+        mRowsAdapter.clear();
+        new Handler().postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                if (getActivity() == null) {
+                    return;
+                }
+                Resources res = getActivity().getResources();
+                DetailsOverviewRow dor = new DetailsOverviewRow(mPhotoItem.getTitle());
+                dor.setImageDrawable(res.getDrawable(mPhotoItem.getImageResourceId()));
+                SparseArrayObjectAdapter adapter = new SparseArrayObjectAdapter();
+                adapter.set(ACTION_RENT, mActionRent);
+                adapter.set(ACTION_BUY, mActionBuy);
+                dor.setActionsAdapter(adapter);
+                mRowsAdapter.add(0, dor);
+                setSelectedPosition(0, true);
+            }
+        }, mTimeToLoadOverviewRow);
+
+
+        new Handler().postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                if (getActivity() == null) {
+                    return;
+                }
+                for (int i = 0; i < NUM_ROWS; ++i) {
+                    ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(mCardPresenter);
+                    listRowAdapter.add(new PhotoItem("Hello world", R.drawable.spiderman));
+                    listRowAdapter.add(new PhotoItem("This is a test", R.drawable.spiderman));
+                    listRowAdapter.add(new PhotoItem("Android TV", R.drawable.spiderman));
+                    listRowAdapter.add(new PhotoItem("Leanback", R.drawable.spiderman));
+                    HeaderItem header = new HeaderItem(i, "Row " + i);
+                    mRowsAdapter.add(new ListRow(header, listRowAdapter));
+                }
+            }
+        }, mTimeToLoadRelatedRow);
+
+        setAdapter(mRowsAdapter);
+    }
+
+}
diff --git a/leanback/tests/java/android/support/v17/leanback/app/DetailsTestSupportFragment.java b/leanback/tests/java/android/support/v17/leanback/app/DetailsTestSupportFragment.java
new file mode 100644
index 0000000..e0d60b4
--- /dev/null
+++ b/leanback/tests/java/android/support/v17/leanback/app/DetailsTestSupportFragment.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.v17.leanback.test.R;
+import android.support.v17.leanback.widget.AbstractDetailsDescriptionPresenter;
+import android.support.v17.leanback.widget.Action;
+import android.support.v17.leanback.widget.ArrayObjectAdapter;
+import android.support.v17.leanback.widget.ClassPresenterSelector;
+import android.support.v17.leanback.widget.DetailsOverviewRow;
+import android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter;
+import android.support.v17.leanback.widget.HeaderItem;
+import android.support.v17.leanback.widget.ImageCardView;
+import android.support.v17.leanback.widget.ListRow;
+import android.support.v17.leanback.widget.ListRowPresenter;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
+import android.view.ViewGroup;
+
+/**
+ * Base class provides overview row and some related rows.
+ */
+public class DetailsTestSupportFragment extends android.support.v17.leanback.app.DetailsSupportFragment {
+    private static final int NUM_ROWS = 3;
+    private ArrayObjectAdapter mRowsAdapter;
+    private PhotoItem mPhotoItem;
+    private final Presenter mCardPresenter = new Presenter() {
+        @Override
+        public ViewHolder onCreateViewHolder(ViewGroup parent) {
+            ImageCardView cardView = new ImageCardView(getActivity());
+            cardView.setFocusable(true);
+            cardView.setFocusableInTouchMode(true);
+            return new ViewHolder(cardView);
+        }
+
+        @Override
+        public void onBindViewHolder(ViewHolder viewHolder, Object item) {
+            ImageCardView imageCardView = (ImageCardView) viewHolder.view;
+            imageCardView.setTitleText("Android Tv");
+            imageCardView.setContentText("Android Tv Production Inc.");
+            imageCardView.setMainImageDimensions(313, 176);
+        }
+
+        @Override
+        public void onUnbindViewHolder(ViewHolder viewHolder) {
+        }
+    };
+
+    private static final int ACTION_RENT = 2;
+    private static final int ACTION_BUY = 3;
+
+    protected long mTimeToLoadOverviewRow = 1000;
+    protected long mTimeToLoadRelatedRow = 2000;
+
+    private Action mActionRent;
+    private Action mActionBuy;
+
+    protected int mMinVerticalOffset = -100;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setTitle("Leanback Sample App");
+
+        mActionRent = new Action(ACTION_RENT, "Rent", "$3.99",
+                getResources().getDrawable(R.drawable.ic_action_a));
+        mActionBuy = new Action(ACTION_BUY, "Buy $9.99");
+
+        ClassPresenterSelector ps = new ClassPresenterSelector();
+        FullWidthDetailsOverviewRowPresenter dorPresenter =
+                new FullWidthDetailsOverviewRowPresenter(new AbstractDetailsDescriptionPresenter() {
+                    @Override
+                    protected void onBindDescription(
+                            AbstractDetailsDescriptionPresenter.ViewHolder vh, Object item) {
+                        vh.getTitle().setText("Funny Movie");
+                        vh.getSubtitle().setText("Android TV Production Inc.");
+                        vh.getBody().setText("What a great movie!");
+                    }
+                });
+
+        ps.addClassPresenter(DetailsOverviewRow.class, dorPresenter);
+        ps.addClassPresenter(ListRow.class, new ListRowPresenter());
+        mRowsAdapter = new ArrayObjectAdapter(ps);
+    }
+
+    public void setItem(PhotoItem photoItem) {
+        mPhotoItem = photoItem;
+        mRowsAdapter.clear();
+        new Handler().postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                if (getActivity() == null) {
+                    return;
+                }
+                Resources res = getActivity().getResources();
+                DetailsOverviewRow dor = new DetailsOverviewRow(mPhotoItem.getTitle());
+                dor.setImageDrawable(res.getDrawable(mPhotoItem.getImageResourceId()));
+                SparseArrayObjectAdapter adapter = new SparseArrayObjectAdapter();
+                adapter.set(ACTION_RENT, mActionRent);
+                adapter.set(ACTION_BUY, mActionBuy);
+                dor.setActionsAdapter(adapter);
+                mRowsAdapter.add(0, dor);
+                setSelectedPosition(0, true);
+            }
+        }, mTimeToLoadOverviewRow);
+
+
+        new Handler().postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                if (getActivity() == null) {
+                    return;
+                }
+                for (int i = 0; i < NUM_ROWS; ++i) {
+                    ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(mCardPresenter);
+                    listRowAdapter.add(new PhotoItem("Hello world", R.drawable.spiderman));
+                    listRowAdapter.add(new PhotoItem("This is a test", R.drawable.spiderman));
+                    listRowAdapter.add(new PhotoItem("Android TV", R.drawable.spiderman));
+                    listRowAdapter.add(new PhotoItem("Leanback", R.drawable.spiderman));
+                    HeaderItem header = new HeaderItem(i, "Row " + i);
+                    mRowsAdapter.add(new ListRow(header, listRowAdapter));
+                }
+            }
+        }, mTimeToLoadRelatedRow);
+
+        setAdapter(mRowsAdapter);
+    }
+
+}
diff --git a/leanback/tests/java/android/support/v17/leanback/app/GuidedStepFragmentTest.java b/leanback/tests/java/android/support/v17/leanback/app/GuidedStepFragmentTest.java
new file mode 100644
index 0000000..650391d
--- /dev/null
+++ b/leanback/tests/java/android/support/v17/leanback/app/GuidedStepFragmentTest.java
@@ -0,0 +1,505 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from GuidedStepSupportFragmentTest.java.  DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v17.leanback.app;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.os.Bundle;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v17.leanback.testutils.PollingCheck;
+import android.support.v17.leanback.widget.GuidedAction;
+import android.support.v17.leanback.widget.GuidedActionsStylist;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.support.v7.widget.DefaultItemAnimator;
+import android.support.v7.widget.RecyclerView;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class GuidedStepFragmentTest extends GuidedStepFragmentTestBase {
+
+    private static final int ON_DESTROY_TIMEOUT = 5000;
+
+    @Test
+    public void nextAndBack() throws Throwable {
+        final String firstFragmentName = generateMethodTestName("first");
+        final String secondFragmentName = generateMethodTestName("second");
+        GuidedStepTestFragment.Provider first = mockProvider(firstFragmentName);
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) {
+                List actions = (List) invocation.getArguments()[0];
+                actions.add(new GuidedAction.Builder().id(1000).title("OK").build());
+                return null;
+            }
+        }).when(first).onCreateActions(any(List.class), nullable(Bundle.class));
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) {
+                GuidedAction action = (GuidedAction) invocation.getArguments()[0];
+                GuidedStepTestFragment.Provider obj = (GuidedStepTestFragment.Provider)
+                        invocation.getMock();
+                if (action.getId() == 1000) {
+                    GuidedStepFragment.add(obj.getFragmentManager(),
+                            new GuidedStepTestFragment(secondFragmentName));
+                }
+                return null;
+            }
+        }).when(first).onGuidedActionClicked(any(GuidedAction.class));
+
+        GuidedStepTestFragment.Provider second = mockProvider(secondFragmentName);
+
+        GuidedStepFragmentTestActivity activity = launchTestActivity(firstFragmentName);
+        verify(first, times(1)).onCreate(nullable(Bundle.class));
+        verify(first, times(1)).onCreateGuidance(nullable(Bundle.class));
+        verify(first, times(1)).onCreateActions(any(List.class), nullable(Bundle.class));
+        verify(first, times(1)).onCreateButtonActions(any(List.class), nullable(Bundle.class));
+        verify(first, times(1)).onCreateView(any(LayoutInflater.class), any(ViewGroup.class),
+                nullable(Bundle.class), any(View.class));
+        verify(first, times(1)).onViewStateRestored(nullable(Bundle.class));
+        verify(first, times(1)).onStart();
+        verify(first, times(1)).onResume();
+
+        sendKey(KeyEvent.KEYCODE_DPAD_CENTER);
+        verify(first, times(1)).onGuidedActionClicked(any(GuidedAction.class));
+
+        PollingCheck.waitFor(new EnterTransitionFinish(second));
+        verify(first, times(1)).onPause();
+        verify(first, times(1)).onStop();
+        verify(first, times(1)).onDestroyView();
+        verify(second, times(1)).onCreate(nullable(Bundle.class));
+        verify(second, times(1)).onCreateGuidance(nullable(Bundle.class));
+        verify(second, times(1)).onCreateActions(any(List.class), nullable(Bundle.class));
+        verify(second, times(1)).onCreateButtonActions(any(List.class), nullable(Bundle.class));
+        verify(second, times(1)).onCreateView(any(LayoutInflater.class), nullable(ViewGroup.class),
+                nullable(Bundle.class), any(View.class));
+        verify(second, times(1)).onViewStateRestored(nullable(Bundle.class));
+        verify(second, times(1)).onStart();
+        verify(second, times(1)).onResume();
+
+        sendKey(KeyEvent.KEYCODE_BACK);
+
+        PollingCheck.waitFor(new EnterTransitionFinish(first));
+        verify(second, times(1)).onPause();
+        verify(second, times(1)).onStop();
+        verify(second, times(1)).onDestroyView();
+        verify(second, times(1)).onDestroy();
+        verify(first, times(1)).onCreateActions(any(List.class), nullable(Bundle.class));
+        verify(first, times(2)).onCreateView(any(LayoutInflater.class), any(ViewGroup.class),
+                nullable(Bundle.class), any(View.class));
+        verify(first, times(2)).onViewStateRestored(nullable(Bundle.class));
+        verify(first, times(2)).onStart();
+        verify(first, times(2)).onResume();
+
+        sendKey(KeyEvent.KEYCODE_BACK);
+        PollingCheck.waitFor(new PollingCheck.ActivityDestroy(activity));
+        verify(first, timeout(ON_DESTROY_TIMEOUT).times(1)).onDestroy();
+        assertTrue(activity.isDestroyed());
+    }
+
+    @Test
+    public void restoreFragments() throws Throwable {
+        final String firstFragmentName = generateMethodTestName("first");
+        final String secondFragmentName = generateMethodTestName("second");
+        GuidedStepTestFragment.Provider first = mockProvider(firstFragmentName);
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) {
+                List actions = (List) invocation.getArguments()[0];
+                actions.add(new GuidedAction.Builder().id(1000).title("OK").build());
+                actions.add(new GuidedAction.Builder().id(1001).editable(true).title("text")
+                        .build());
+                actions.add(new GuidedAction.Builder().id(1002).editable(true).title("text")
+                        .autoSaveRestoreEnabled(false).build());
+                return null;
+            }
+        }).when(first).onCreateActions(any(List.class), nullable(Bundle.class));
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) {
+                GuidedAction action = (GuidedAction) invocation.getArguments()[0];
+                GuidedStepTestFragment.Provider obj = (GuidedStepTestFragment.Provider)
+                        invocation.getMock();
+                if (action.getId() == 1000) {
+                    GuidedStepFragment.add(obj.getFragmentManager(),
+                            new GuidedStepTestFragment(secondFragmentName));
+                }
+                return null;
+            }
+        }).when(first).onGuidedActionClicked(any(GuidedAction.class));
+
+        GuidedStepTestFragment.Provider second = mockProvider(secondFragmentName);
+
+        final GuidedStepFragmentTestActivity activity = launchTestActivity(firstFragmentName);
+        first.getFragment().findActionById(1001).setTitle("modified text");
+        first.getFragment().findActionById(1002).setTitle("modified text");
+        sendKey(KeyEvent.KEYCODE_DPAD_CENTER);
+        PollingCheck.waitFor(new EnterTransitionFinish(second));
+
+        activityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                activity.recreate();
+            }
+        });
+        PollingCheck.waitFor(new EnterTransitionFinish(second));
+        verify(first, times(1)).onCreateView(any(LayoutInflater.class), any(ViewGroup.class),
+                nullable(Bundle.class), any(View.class));
+        verify(first, times(1)).onDestroy();
+        verify(second, times(2)).onCreate(nullable(Bundle.class));
+        verify(second, times(2)).onCreateView(any(LayoutInflater.class), any(ViewGroup.class),
+                nullable(Bundle.class), any(View.class));
+        verify(second, times(1)).onDestroy();
+
+        sendKey(KeyEvent.KEYCODE_BACK);
+        PollingCheck.waitFor(new EnterTransitionFinish(first));
+        verify(second, times(2)).onPause();
+        verify(second, times(2)).onStop();
+        verify(second, times(2)).onDestroyView();
+        verify(second, times(2)).onDestroy();
+        assertEquals("modified text", first.getFragment().findActionById(1001).getTitle());
+        assertEquals("text", first.getFragment().findActionById(1002).getTitle());
+        verify(first, times(2)).onCreate(nullable(Bundle.class));
+        verify(first, times(2)).onCreateActions(any(List.class), nullable(Bundle.class));
+        verify(first, times(2)).onCreateView(any(LayoutInflater.class), any(ViewGroup.class),
+                nullable(Bundle.class), any(View.class));
+    }
+
+
+    @Test
+    public void finishGuidedStepFragment_finishes_activity() throws Throwable {
+        final String firstFragmentName = generateMethodTestName("first");
+        GuidedStepTestFragment.Provider first = mockProvider(firstFragmentName);
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) {
+                List actions = (List) invocation.getArguments()[0];
+                actions.add(new GuidedAction.Builder().id(1001).title("Finish activity").build());
+                return null;
+            }
+        }).when(first).onCreateActions(any(List.class), nullable(Bundle.class));
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) {
+                GuidedAction action = (GuidedAction) invocation.getArguments()[0];
+                GuidedStepTestFragment.Provider obj = (GuidedStepTestFragment.Provider)
+                        invocation.getMock();
+                if (action.getId() == 1001) {
+                    obj.getFragment().finishGuidedStepFragments();
+                }
+                return null;
+            }
+        }).when(first).onGuidedActionClicked(any(GuidedAction.class));
+
+        final GuidedStepFragmentTestActivity activity = launchTestActivity(firstFragmentName);
+
+        View viewFinish = first.getFragment().getActionItemView(0);
+        assertTrue(viewFinish.hasFocus());
+        sendKey(KeyEvent.KEYCODE_DPAD_CENTER);
+        PollingCheck.waitFor(new PollingCheck.ActivityDestroy(activity));
+        verify(first, timeout(ON_DESTROY_TIMEOUT).times(1)).onDestroy();
+    }
+
+    @Test
+    public void finishGuidedStepFragment_finishes_fragments() throws Throwable {
+        final String firstFragmentName = generateMethodTestName("first");
+        GuidedStepTestFragment.Provider first = mockProvider(firstFragmentName);
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) {
+                List actions = (List) invocation.getArguments()[0];
+                actions.add(new GuidedAction.Builder().id(1001).title("Finish fragments").build());
+                return null;
+            }
+        }).when(first).onCreateActions(any(List.class), nullable(Bundle.class));
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) {
+                GuidedAction action = (GuidedAction) invocation.getArguments()[0];
+                GuidedStepTestFragment.Provider obj = (GuidedStepTestFragment.Provider)
+                        invocation.getMock();
+                if (action.getId() == 1001) {
+                    obj.getFragment().finishGuidedStepFragments();
+                }
+                return null;
+            }
+        }).when(first).onGuidedActionClicked(any(GuidedAction.class));
+
+        final GuidedStepFragmentTestActivity activity = launchTestActivity(firstFragmentName,
+                false /*asRoot*/);
+
+        View viewFinish = first.getFragment().getActionItemView(0);
+        assertTrue(viewFinish.hasFocus());
+        sendKey(KeyEvent.KEYCODE_DPAD_CENTER);
+
+        // fragment should be destroyed, activity should not destroyed
+        waitOnDestroy(first, 1);
+        assertFalse(activity.isDestroyed());
+    }
+
+    @Test
+    public void subActions() throws Throwable {
+        final String firstFragmentName = generateMethodTestName("first");
+        final String secondFragmentName = generateMethodTestName("second");
+        final boolean[] expandSubActionInOnCreateView = new boolean[] {false};
+        GuidedStepTestFragment.Provider first = mockProvider(firstFragmentName);
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) {
+                GuidedStepTestFragment.Provider obj = (GuidedStepTestFragment.Provider)
+                        invocation.getMock();
+                if (expandSubActionInOnCreateView[0]) {
+                    obj.getFragment().expandAction(obj.getFragment().findActionById(1000), false);
+                }
+                return null;
+            }
+        }).when(first).onCreateView(any(LayoutInflater.class), any(ViewGroup.class),
+                nullable(Bundle.class), any(View.class));
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) {
+                List actions = (List) invocation.getArguments()[0];
+                List<GuidedAction> subActions = new ArrayList<GuidedAction>();
+                subActions.add(new GuidedAction.Builder().id(2000).title("item1").build());
+                subActions.add(new GuidedAction.Builder().id(2001).title("item2").build());
+                actions.add(new GuidedAction.Builder().id(1000).subActions(subActions)
+                        .title("list").build());
+                return null;
+            }
+        }).when(first).onCreateActions(any(List.class), nullable(Bundle.class));
+        doAnswer(new Answer<Boolean>() {
+            @Override
+            public Boolean answer(InvocationOnMock invocation) {
+                GuidedStepTestFragment.Provider obj = (GuidedStepTestFragment.Provider)
+                        invocation.getMock();
+                GuidedAction action = (GuidedAction) invocation.getArguments()[0];
+                if (action.getId() == 2000) {
+                    return true;
+                } else if (action.getId() == 2001) {
+                    GuidedStepFragment.add(obj.getFragmentManager(),
+                            new GuidedStepTestFragment(secondFragmentName));
+                    return false;
+                }
+                return false;
+            }
+        }).when(first).onSubGuidedActionClicked(any(GuidedAction.class));
+
+        GuidedStepTestFragment.Provider second = mockProvider(secondFragmentName);
+
+        final GuidedStepFragmentTestActivity activity = launchTestActivity(firstFragmentName);
+
+        // after clicked, it sub actions list should expand
+        View viewForList = first.getFragment().getActionItemView(0);
+        assertTrue(viewForList.hasFocus());
+        sendKey(KeyEvent.KEYCODE_DPAD_CENTER);
+        PollingCheck.waitFor(new ExpandTransitionFinish(first));
+        assertFalse(viewForList.hasFocus());
+
+        sendKey(KeyEvent.KEYCODE_DPAD_CENTER);
+        ArgumentCaptor<GuidedAction> actionCapture = ArgumentCaptor.forClass(GuidedAction.class);
+        verify(first, times(1)).onSubGuidedActionClicked(actionCapture.capture());
+        assertEquals(2000, actionCapture.getValue().getId());
+        // after clicked a sub action, it sub actions list should close
+        PollingCheck.waitFor(new ExpandTransitionFinish(first));
+        assertTrue(viewForList.hasFocus());
+
+        sendKey(KeyEvent.KEYCODE_DPAD_CENTER);
+        PollingCheck.waitFor(new ExpandTransitionFinish(first));
+
+        assertFalse(viewForList.hasFocus());
+        sendKey(KeyEvent.KEYCODE_DPAD_DOWN);
+        sendKey(KeyEvent.KEYCODE_DPAD_CENTER);
+        ArgumentCaptor<GuidedAction> actionCapture2 = ArgumentCaptor.forClass(GuidedAction.class);
+        verify(first, times(2)).onSubGuidedActionClicked(actionCapture2.capture());
+        assertEquals(2001, actionCapture2.getValue().getId());
+
+        PollingCheck.waitFor(new EnterTransitionFinish(second));
+        verify(second, times(1)).onCreateView(any(LayoutInflater.class), any(ViewGroup.class),
+                nullable(Bundle.class), any(View.class));
+
+        // test expand sub action when return to first fragment
+        expandSubActionInOnCreateView[0] = true;
+        sendKey(KeyEvent.KEYCODE_BACK);
+        PollingCheck.waitFor(new EnterTransitionFinish(first));
+        verify(first, times(2)).onCreateView(any(LayoutInflater.class), any(ViewGroup.class),
+                nullable(Bundle.class), any(View.class));
+        assertTrue(first.getFragment().isExpanded());
+
+        sendKey(KeyEvent.KEYCODE_BACK);
+        PollingCheck.waitFor(new ExpandTransitionFinish(first));
+        assertFalse(first.getFragment().isExpanded());
+
+        sendKey(KeyEvent.KEYCODE_BACK);
+        PollingCheck.waitFor(new PollingCheck.ActivityDestroy(activity));
+        verify(first, timeout(ON_DESTROY_TIMEOUT).times(1)).onDestroy();
+    }
+
+    @Test
+    public void setActionsWhenSubActionsExpanded() throws Throwable {
+        final String firstFragmentName = generateMethodTestName("first");
+        GuidedStepTestFragment.Provider first = mockProvider(firstFragmentName);
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) {
+                List actions = (List) invocation.getArguments()[0];
+                List<GuidedAction> subActions = new ArrayList<GuidedAction>();
+                subActions.add(new GuidedAction.Builder().id(2000).title("item1").build());
+                actions.add(new GuidedAction.Builder().id(1000).subActions(subActions)
+                        .title("list").build());
+                return null;
+            }
+        }).when(first).onCreateActions(any(List.class), nullable(Bundle.class));
+        doAnswer(new Answer<Boolean>() {
+            @Override
+            public Boolean answer(InvocationOnMock invocation) {
+                GuidedStepTestFragment.Provider obj = (GuidedStepTestFragment.Provider)
+                        invocation.getMock();
+                GuidedAction action = (GuidedAction) invocation.getArguments()[0];
+                if (action.getId() == 2000) {
+                    List<GuidedAction> newActions = new ArrayList<GuidedAction>();
+                    newActions.add(new GuidedAction.Builder().id(1001).title("item2").build());
+                    obj.getFragment().setActions(newActions);
+                    return false;
+                }
+                return false;
+            }
+        }).when(first).onSubGuidedActionClicked(any(GuidedAction.class));
+
+        final GuidedStepFragmentTestActivity activity = launchTestActivity(firstFragmentName);
+
+        // after clicked, it sub actions list should expand
+        View firstView = first.getFragment().getActionItemView(0);
+        assertTrue(firstView.hasFocus());
+        sendKey(KeyEvent.KEYCODE_DPAD_CENTER);
+        PollingCheck.waitFor(new ExpandTransitionFinish(first));
+        assertFalse(firstView.hasFocus());
+
+        sendKey(KeyEvent.KEYCODE_DPAD_CENTER);
+        ArgumentCaptor<GuidedAction> actionCapture = ArgumentCaptor.forClass(GuidedAction.class);
+        verify(first, times(1)).onSubGuidedActionClicked(actionCapture.capture());
+        // after clicked a sub action, whole action list is replaced.
+        PollingCheck.waitFor(new ExpandTransitionFinish(first));
+        assertFalse(first.getFragment().isExpanded());
+        View newFirstView  = first.getFragment().getActionItemView(0);
+        assertTrue(newFirstView.hasFocus());
+        assertTrue(newFirstView.getVisibility() == View.VISIBLE);
+        GuidedActionsStylist.ViewHolder vh = (GuidedActionsStylist.ViewHolder) first.getFragment()
+                .getGuidedActionsStylist().getActionsGridView().getChildViewHolder(newFirstView);
+        assertEquals(1001, vh.getAction().getId());
+
+    }
+
+    @Test
+    public void buttonActionsRtl() throws Throwable {
+        final String firstFragmentName = generateMethodTestName("first");
+        GuidedStepTestFragment.Provider first = mockProvider(firstFragmentName);
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) {
+                List actions = (List) invocation.getArguments()[0];
+                actions.add(new GuidedAction.Builder().id(1000).title("action").build());
+                return null;
+            }
+        }).when(first).onCreateActions(any(List.class), nullable(Bundle.class));
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) {
+                List actions = (List) invocation.getArguments()[0];
+                actions.add(new GuidedAction.Builder().id(1001).title("button action").build());
+                return null;
+            }
+        }).when(first).onCreateButtonActions(any(List.class), nullable(Bundle.class));
+
+        final GuidedStepFragmentTestActivity activity = launchTestActivity(firstFragmentName,
+                true, View.LAYOUT_DIRECTION_RTL);
+
+        assertEquals(View.LAYOUT_DIRECTION_RTL, first.getFragment().getView().getLayoutDirection());
+        View firstView = first.getFragment().getActionItemView(0);
+        assertTrue(firstView.hasFocus());
+    }
+
+    @Test
+    public void recyclerViewDiffTest() throws Throwable {
+        final String firstFragmentName = generateMethodTestName("first");
+        final GuidedStepTestFragment.Provider first = mockProvider(firstFragmentName);
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) {
+                List actions = (List) invocation.getArguments()[0];
+                actions.add(new GuidedAction.Builder().id(1000).title("action1").build());
+                actions.add(new GuidedAction.Builder().id(1001).title("action2").build());
+                return null;
+            }
+        }).when(first).onCreateActions(any(List.class), nullable(Bundle.class));
+
+        launchTestActivity(firstFragmentName, true);
+
+        final ArrayList<RecyclerView.ViewHolder> changeList = new ArrayList();
+        VerticalGridView rv = first.getFragment().mActionsStylist.getActionsGridView();
+        rv.setItemAnimator(new DefaultItemAnimator() {
+            @Override
+            public void onChangeStarting(RecyclerView.ViewHolder item, boolean oldItem) {
+                if (!oldItem) {
+                    changeList.add(item);
+                }
+                super.onChangeStarting(item, oldItem);
+            }
+        });
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                List actions = new ArrayList();
+                actions.add(new GuidedAction.Builder().id(1001).title("action2x").build());
+                actions.add(new GuidedAction.Builder().id(1000).title("action1x").build());
+                first.getFragment().setActions(actions);
+            }
+        });
+
+        // should causes two change animation.
+        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return changeList.size() == 2;
+            }
+        });
+    }
+}
diff --git a/leanback/tests/java/android/support/v17/leanback/app/GuidedStepFragmentTestActivity.java b/leanback/tests/java/android/support/v17/leanback/app/GuidedStepFragmentTestActivity.java
new file mode 100644
index 0000000..dd17fd3
--- /dev/null
+++ b/leanback/tests/java/android/support/v17/leanback/app/GuidedStepFragmentTestActivity.java
@@ -0,0 +1,66 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from GuidedStepSupportFragmentTestActivity.java.  DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package android.support.v17.leanback.app;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.app.Activity;
+
+/**
+ * @hide from javadoc
+ */
+public class GuidedStepFragmentTestActivity extends Activity {
+
+    /**
+     * Frst Test that will be included in this Activity
+     */
+    public static final String EXTRA_TEST_NAME = "testName";
+    /**
+     * True(default) to addAsRoot() for first Test, false to use add()
+     */
+    public static final String EXTRA_ADD_AS_ROOT = "addAsRoot";
+
+    /**
+     * Layout direction
+     */
+    public static final String EXTRA_LAYOUT_DIRECTION = "layoutDir";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        Intent intent = getIntent();
+
+        int layoutDirection = intent.getIntExtra(EXTRA_LAYOUT_DIRECTION, -1);
+        if (layoutDirection != -1) {
+            findViewById(android.R.id.content).setLayoutDirection(layoutDirection);
+        }
+        if (savedInstanceState == null) {
+            String firstTestName = intent.getStringExtra(EXTRA_TEST_NAME);
+            if (firstTestName != null) {
+                GuidedStepTestFragment testFragment = new GuidedStepTestFragment(firstTestName);
+                if (intent.getBooleanExtra(EXTRA_ADD_AS_ROOT, true)) {
+                    GuidedStepTestFragment.addAsRoot(this, testFragment, android.R.id.content);
+                } else {
+                    GuidedStepTestFragment.add(getFragmentManager(), testFragment,
+                            android.R.id.content);
+                }
+            }
+        }
+    }
+}
diff --git a/leanback/tests/java/android/support/v17/leanback/app/GuidedStepFragmentTestBase.java b/leanback/tests/java/android/support/v17/leanback/app/GuidedStepFragmentTestBase.java
new file mode 100644
index 0000000..34ec694
--- /dev/null
+++ b/leanback/tests/java/android/support/v17/leanback/app/GuidedStepFragmentTestBase.java
@@ -0,0 +1,149 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from GuidedStepSupportFrgamentTestBase.java.  DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v17.leanback.app;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Intent;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+import android.support.v17.leanback.R;
+import android.support.v17.leanback.testutils.PollingCheck;
+import android.view.View;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.rules.TestName;
+
+/**
+ * @hide from javadoc
+ */
+public class GuidedStepFragmentTestBase {
+
+    private static final long TIMEOUT = 5000;
+
+    @Rule public TestName mUnitTestName = new TestName();
+
+    @Rule
+    public ActivityTestRule<GuidedStepFragmentTestActivity> activityTestRule =
+            new ActivityTestRule<>(GuidedStepFragmentTestActivity.class, false, false);
+
+    @Before
+    public void clearTests() {
+        GuidedStepTestFragment.clearTests();
+    }
+
+    public static class ExpandTransitionFinish extends PollingCheck.PollingCheckCondition {
+        GuidedStepTestFragment.Provider mProvider;
+
+        public ExpandTransitionFinish(GuidedStepTestFragment.Provider provider) {
+            mProvider = provider;
+        }
+
+        @Override
+        public boolean canPreProceed() {
+            return false;
+        }
+
+        @Override
+        public boolean canProceed() {
+            GuidedStepTestFragment fragment = mProvider.getFragment();
+            if (fragment != null && fragment.getView() != null) {
+                if (!fragment.getGuidedActionsStylist().isInExpandTransition()) {
+                    // expand transition finishes
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    public static void waitOnDestroy(GuidedStepTestFragment.Provider provider,
+            int times) {
+        verify(provider, timeout((int)TIMEOUT).times(times)).onDestroy();
+    }
+
+    public static class EnterTransitionFinish extends PollingCheck.PollingCheckCondition {
+        PollingCheck.ViewScreenPositionDetector mDector =
+                new PollingCheck.ViewScreenPositionDetector();
+
+        GuidedStepTestFragment.Provider mProvider;
+
+        public EnterTransitionFinish(GuidedStepTestFragment.Provider provider) {
+            mProvider = provider;
+        }
+        @Override
+        public boolean canProceed() {
+            GuidedStepTestFragment fragment = mProvider.getFragment();
+            if (fragment != null && fragment.getView() != null) {
+                View view = fragment.getView().findViewById(R.id.guidance_title);
+                if (view != null) {
+                    if (mDector.isViewStableOnScreen(view)) {
+                        return true;
+                    }
+                }
+            }
+            return false;
+        }
+    }
+
+    public static void sendKey(int keyCode) {
+        InstrumentationRegistry.getInstrumentation().sendKeyDownUpSync(keyCode);
+    }
+
+    public String generateMethodTestName(String testName) {
+        return mUnitTestName.getMethodName() + "_" + testName;
+    }
+
+    public GuidedStepFragmentTestActivity launchTestActivity(String firstTestName) {
+        Intent intent = new Intent();
+        intent.putExtra(GuidedStepFragmentTestActivity.EXTRA_TEST_NAME, firstTestName);
+        return activityTestRule.launchActivity(intent);
+    }
+
+    public GuidedStepFragmentTestActivity launchTestActivity(String firstTestName,
+            boolean addAsRoot) {
+        Intent intent = new Intent();
+        intent.putExtra(GuidedStepFragmentTestActivity.EXTRA_TEST_NAME, firstTestName);
+        intent.putExtra(GuidedStepFragmentTestActivity.EXTRA_ADD_AS_ROOT, addAsRoot);
+        return activityTestRule.launchActivity(intent);
+    }
+
+    public GuidedStepFragmentTestActivity launchTestActivity(String firstTestName,
+            boolean addAsRoot, int layoutDirection) {
+        Intent intent = new Intent();
+        intent.putExtra(GuidedStepFragmentTestActivity.EXTRA_TEST_NAME, firstTestName);
+        intent.putExtra(GuidedStepFragmentTestActivity.EXTRA_ADD_AS_ROOT, addAsRoot);
+        intent.putExtra(GuidedStepFragmentTestActivity.EXTRA_LAYOUT_DIRECTION, layoutDirection);
+        return activityTestRule.launchActivity(intent);
+    }
+
+    public GuidedStepTestFragment.Provider mockProvider(String testName) {
+        GuidedStepTestFragment.Provider test = mock(GuidedStepTestFragment.Provider.class);
+        when(test.getActivity()).thenCallRealMethod();
+        when(test.getFragmentManager()).thenCallRealMethod();
+        when(test.getFragment()).thenCallRealMethod();
+        GuidedStepTestFragment.setupTest(testName, test);
+        return test;
+    }
+}
+
diff --git a/leanback/tests/java/android/support/v17/leanback/app/GuidedStepSupportFragmentTest.java b/leanback/tests/java/android/support/v17/leanback/app/GuidedStepSupportFragmentTest.java
new file mode 100644
index 0000000..5f015a1
--- /dev/null
+++ b/leanback/tests/java/android/support/v17/leanback/app/GuidedStepSupportFragmentTest.java
@@ -0,0 +1,502 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v17.leanback.app;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.os.Bundle;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v17.leanback.testutils.PollingCheck;
+import android.support.v17.leanback.widget.GuidedAction;
+import android.support.v17.leanback.widget.GuidedActionsStylist;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.support.v7.widget.DefaultItemAnimator;
+import android.support.v7.widget.RecyclerView;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class GuidedStepSupportFragmentTest extends GuidedStepSupportFragmentTestBase {
+
+    private static final int ON_DESTROY_TIMEOUT = 5000;
+
+    @Test
+    public void nextAndBack() throws Throwable {
+        final String firstFragmentName = generateMethodTestName("first");
+        final String secondFragmentName = generateMethodTestName("second");
+        GuidedStepTestSupportFragment.Provider first = mockProvider(firstFragmentName);
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) {
+                List actions = (List) invocation.getArguments()[0];
+                actions.add(new GuidedAction.Builder().id(1000).title("OK").build());
+                return null;
+            }
+        }).when(first).onCreateActions(any(List.class), nullable(Bundle.class));
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) {
+                GuidedAction action = (GuidedAction) invocation.getArguments()[0];
+                GuidedStepTestSupportFragment.Provider obj = (GuidedStepTestSupportFragment.Provider)
+                        invocation.getMock();
+                if (action.getId() == 1000) {
+                    GuidedStepSupportFragment.add(obj.getFragmentManager(),
+                            new GuidedStepTestSupportFragment(secondFragmentName));
+                }
+                return null;
+            }
+        }).when(first).onGuidedActionClicked(any(GuidedAction.class));
+
+        GuidedStepTestSupportFragment.Provider second = mockProvider(secondFragmentName);
+
+        GuidedStepSupportFragmentTestActivity activity = launchTestActivity(firstFragmentName);
+        verify(first, times(1)).onCreate(nullable(Bundle.class));
+        verify(first, times(1)).onCreateGuidance(nullable(Bundle.class));
+        verify(first, times(1)).onCreateActions(any(List.class), nullable(Bundle.class));
+        verify(first, times(1)).onCreateButtonActions(any(List.class), nullable(Bundle.class));
+        verify(first, times(1)).onCreateView(any(LayoutInflater.class), any(ViewGroup.class),
+                nullable(Bundle.class), any(View.class));
+        verify(first, times(1)).onViewStateRestored(nullable(Bundle.class));
+        verify(first, times(1)).onStart();
+        verify(first, times(1)).onResume();
+
+        sendKey(KeyEvent.KEYCODE_DPAD_CENTER);
+        verify(first, times(1)).onGuidedActionClicked(any(GuidedAction.class));
+
+        PollingCheck.waitFor(new EnterTransitionFinish(second));
+        verify(first, times(1)).onPause();
+        verify(first, times(1)).onStop();
+        verify(first, times(1)).onDestroyView();
+        verify(second, times(1)).onCreate(nullable(Bundle.class));
+        verify(second, times(1)).onCreateGuidance(nullable(Bundle.class));
+        verify(second, times(1)).onCreateActions(any(List.class), nullable(Bundle.class));
+        verify(second, times(1)).onCreateButtonActions(any(List.class), nullable(Bundle.class));
+        verify(second, times(1)).onCreateView(any(LayoutInflater.class), nullable(ViewGroup.class),
+                nullable(Bundle.class), any(View.class));
+        verify(second, times(1)).onViewStateRestored(nullable(Bundle.class));
+        verify(second, times(1)).onStart();
+        verify(second, times(1)).onResume();
+
+        sendKey(KeyEvent.KEYCODE_BACK);
+
+        PollingCheck.waitFor(new EnterTransitionFinish(first));
+        verify(second, times(1)).onPause();
+        verify(second, times(1)).onStop();
+        verify(second, times(1)).onDestroyView();
+        verify(second, times(1)).onDestroy();
+        verify(first, times(1)).onCreateActions(any(List.class), nullable(Bundle.class));
+        verify(first, times(2)).onCreateView(any(LayoutInflater.class), any(ViewGroup.class),
+                nullable(Bundle.class), any(View.class));
+        verify(first, times(2)).onViewStateRestored(nullable(Bundle.class));
+        verify(first, times(2)).onStart();
+        verify(first, times(2)).onResume();
+
+        sendKey(KeyEvent.KEYCODE_BACK);
+        PollingCheck.waitFor(new PollingCheck.ActivityDestroy(activity));
+        verify(first, timeout(ON_DESTROY_TIMEOUT).times(1)).onDestroy();
+        assertTrue(activity.isDestroyed());
+    }
+
+    @Test
+    public void restoreFragments() throws Throwable {
+        final String firstFragmentName = generateMethodTestName("first");
+        final String secondFragmentName = generateMethodTestName("second");
+        GuidedStepTestSupportFragment.Provider first = mockProvider(firstFragmentName);
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) {
+                List actions = (List) invocation.getArguments()[0];
+                actions.add(new GuidedAction.Builder().id(1000).title("OK").build());
+                actions.add(new GuidedAction.Builder().id(1001).editable(true).title("text")
+                        .build());
+                actions.add(new GuidedAction.Builder().id(1002).editable(true).title("text")
+                        .autoSaveRestoreEnabled(false).build());
+                return null;
+            }
+        }).when(first).onCreateActions(any(List.class), nullable(Bundle.class));
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) {
+                GuidedAction action = (GuidedAction) invocation.getArguments()[0];
+                GuidedStepTestSupportFragment.Provider obj = (GuidedStepTestSupportFragment.Provider)
+                        invocation.getMock();
+                if (action.getId() == 1000) {
+                    GuidedStepSupportFragment.add(obj.getFragmentManager(),
+                            new GuidedStepTestSupportFragment(secondFragmentName));
+                }
+                return null;
+            }
+        }).when(first).onGuidedActionClicked(any(GuidedAction.class));
+
+        GuidedStepTestSupportFragment.Provider second = mockProvider(secondFragmentName);
+
+        final GuidedStepSupportFragmentTestActivity activity = launchTestActivity(firstFragmentName);
+        first.getFragment().findActionById(1001).setTitle("modified text");
+        first.getFragment().findActionById(1002).setTitle("modified text");
+        sendKey(KeyEvent.KEYCODE_DPAD_CENTER);
+        PollingCheck.waitFor(new EnterTransitionFinish(second));
+
+        activityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                activity.recreate();
+            }
+        });
+        PollingCheck.waitFor(new EnterTransitionFinish(second));
+        verify(first, times(1)).onCreateView(any(LayoutInflater.class), any(ViewGroup.class),
+                nullable(Bundle.class), any(View.class));
+        verify(first, times(1)).onDestroy();
+        verify(second, times(2)).onCreate(nullable(Bundle.class));
+        verify(second, times(2)).onCreateView(any(LayoutInflater.class), any(ViewGroup.class),
+                nullable(Bundle.class), any(View.class));
+        verify(second, times(1)).onDestroy();
+
+        sendKey(KeyEvent.KEYCODE_BACK);
+        PollingCheck.waitFor(new EnterTransitionFinish(first));
+        verify(second, times(2)).onPause();
+        verify(second, times(2)).onStop();
+        verify(second, times(2)).onDestroyView();
+        verify(second, times(2)).onDestroy();
+        assertEquals("modified text", first.getFragment().findActionById(1001).getTitle());
+        assertEquals("text", first.getFragment().findActionById(1002).getTitle());
+        verify(first, times(2)).onCreate(nullable(Bundle.class));
+        verify(first, times(2)).onCreateActions(any(List.class), nullable(Bundle.class));
+        verify(first, times(2)).onCreateView(any(LayoutInflater.class), any(ViewGroup.class),
+                nullable(Bundle.class), any(View.class));
+    }
+
+
+    @Test
+    public void finishGuidedStepSupportFragment_finishes_activity() throws Throwable {
+        final String firstFragmentName = generateMethodTestName("first");
+        GuidedStepTestSupportFragment.Provider first = mockProvider(firstFragmentName);
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) {
+                List actions = (List) invocation.getArguments()[0];
+                actions.add(new GuidedAction.Builder().id(1001).title("Finish activity").build());
+                return null;
+            }
+        }).when(first).onCreateActions(any(List.class), nullable(Bundle.class));
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) {
+                GuidedAction action = (GuidedAction) invocation.getArguments()[0];
+                GuidedStepTestSupportFragment.Provider obj = (GuidedStepTestSupportFragment.Provider)
+                        invocation.getMock();
+                if (action.getId() == 1001) {
+                    obj.getFragment().finishGuidedStepSupportFragments();
+                }
+                return null;
+            }
+        }).when(first).onGuidedActionClicked(any(GuidedAction.class));
+
+        final GuidedStepSupportFragmentTestActivity activity = launchTestActivity(firstFragmentName);
+
+        View viewFinish = first.getFragment().getActionItemView(0);
+        assertTrue(viewFinish.hasFocus());
+        sendKey(KeyEvent.KEYCODE_DPAD_CENTER);
+        PollingCheck.waitFor(new PollingCheck.ActivityDestroy(activity));
+        verify(first, timeout(ON_DESTROY_TIMEOUT).times(1)).onDestroy();
+    }
+
+    @Test
+    public void finishGuidedStepSupportFragment_finishes_fragments() throws Throwable {
+        final String firstFragmentName = generateMethodTestName("first");
+        GuidedStepTestSupportFragment.Provider first = mockProvider(firstFragmentName);
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) {
+                List actions = (List) invocation.getArguments()[0];
+                actions.add(new GuidedAction.Builder().id(1001).title("Finish fragments").build());
+                return null;
+            }
+        }).when(first).onCreateActions(any(List.class), nullable(Bundle.class));
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) {
+                GuidedAction action = (GuidedAction) invocation.getArguments()[0];
+                GuidedStepTestSupportFragment.Provider obj = (GuidedStepTestSupportFragment.Provider)
+                        invocation.getMock();
+                if (action.getId() == 1001) {
+                    obj.getFragment().finishGuidedStepSupportFragments();
+                }
+                return null;
+            }
+        }).when(first).onGuidedActionClicked(any(GuidedAction.class));
+
+        final GuidedStepSupportFragmentTestActivity activity = launchTestActivity(firstFragmentName,
+                false /*asRoot*/);
+
+        View viewFinish = first.getFragment().getActionItemView(0);
+        assertTrue(viewFinish.hasFocus());
+        sendKey(KeyEvent.KEYCODE_DPAD_CENTER);
+
+        // fragment should be destroyed, activity should not destroyed
+        waitOnDestroy(first, 1);
+        assertFalse(activity.isDestroyed());
+    }
+
+    @Test
+    public void subActions() throws Throwable {
+        final String firstFragmentName = generateMethodTestName("first");
+        final String secondFragmentName = generateMethodTestName("second");
+        final boolean[] expandSubActionInOnCreateView = new boolean[] {false};
+        GuidedStepTestSupportFragment.Provider first = mockProvider(firstFragmentName);
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) {
+                GuidedStepTestSupportFragment.Provider obj = (GuidedStepTestSupportFragment.Provider)
+                        invocation.getMock();
+                if (expandSubActionInOnCreateView[0]) {
+                    obj.getFragment().expandAction(obj.getFragment().findActionById(1000), false);
+                }
+                return null;
+            }
+        }).when(first).onCreateView(any(LayoutInflater.class), any(ViewGroup.class),
+                nullable(Bundle.class), any(View.class));
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) {
+                List actions = (List) invocation.getArguments()[0];
+                List<GuidedAction> subActions = new ArrayList<GuidedAction>();
+                subActions.add(new GuidedAction.Builder().id(2000).title("item1").build());
+                subActions.add(new GuidedAction.Builder().id(2001).title("item2").build());
+                actions.add(new GuidedAction.Builder().id(1000).subActions(subActions)
+                        .title("list").build());
+                return null;
+            }
+        }).when(first).onCreateActions(any(List.class), nullable(Bundle.class));
+        doAnswer(new Answer<Boolean>() {
+            @Override
+            public Boolean answer(InvocationOnMock invocation) {
+                GuidedStepTestSupportFragment.Provider obj = (GuidedStepTestSupportFragment.Provider)
+                        invocation.getMock();
+                GuidedAction action = (GuidedAction) invocation.getArguments()[0];
+                if (action.getId() == 2000) {
+                    return true;
+                } else if (action.getId() == 2001) {
+                    GuidedStepSupportFragment.add(obj.getFragmentManager(),
+                            new GuidedStepTestSupportFragment(secondFragmentName));
+                    return false;
+                }
+                return false;
+            }
+        }).when(first).onSubGuidedActionClicked(any(GuidedAction.class));
+
+        GuidedStepTestSupportFragment.Provider second = mockProvider(secondFragmentName);
+
+        final GuidedStepSupportFragmentTestActivity activity = launchTestActivity(firstFragmentName);
+
+        // after clicked, it sub actions list should expand
+        View viewForList = first.getFragment().getActionItemView(0);
+        assertTrue(viewForList.hasFocus());
+        sendKey(KeyEvent.KEYCODE_DPAD_CENTER);
+        PollingCheck.waitFor(new ExpandTransitionFinish(first));
+        assertFalse(viewForList.hasFocus());
+
+        sendKey(KeyEvent.KEYCODE_DPAD_CENTER);
+        ArgumentCaptor<GuidedAction> actionCapture = ArgumentCaptor.forClass(GuidedAction.class);
+        verify(first, times(1)).onSubGuidedActionClicked(actionCapture.capture());
+        assertEquals(2000, actionCapture.getValue().getId());
+        // after clicked a sub action, it sub actions list should close
+        PollingCheck.waitFor(new ExpandTransitionFinish(first));
+        assertTrue(viewForList.hasFocus());
+
+        sendKey(KeyEvent.KEYCODE_DPAD_CENTER);
+        PollingCheck.waitFor(new ExpandTransitionFinish(first));
+
+        assertFalse(viewForList.hasFocus());
+        sendKey(KeyEvent.KEYCODE_DPAD_DOWN);
+        sendKey(KeyEvent.KEYCODE_DPAD_CENTER);
+        ArgumentCaptor<GuidedAction> actionCapture2 = ArgumentCaptor.forClass(GuidedAction.class);
+        verify(first, times(2)).onSubGuidedActionClicked(actionCapture2.capture());
+        assertEquals(2001, actionCapture2.getValue().getId());
+
+        PollingCheck.waitFor(new EnterTransitionFinish(second));
+        verify(second, times(1)).onCreateView(any(LayoutInflater.class), any(ViewGroup.class),
+                nullable(Bundle.class), any(View.class));
+
+        // test expand sub action when return to first fragment
+        expandSubActionInOnCreateView[0] = true;
+        sendKey(KeyEvent.KEYCODE_BACK);
+        PollingCheck.waitFor(new EnterTransitionFinish(first));
+        verify(first, times(2)).onCreateView(any(LayoutInflater.class), any(ViewGroup.class),
+                nullable(Bundle.class), any(View.class));
+        assertTrue(first.getFragment().isExpanded());
+
+        sendKey(KeyEvent.KEYCODE_BACK);
+        PollingCheck.waitFor(new ExpandTransitionFinish(first));
+        assertFalse(first.getFragment().isExpanded());
+
+        sendKey(KeyEvent.KEYCODE_BACK);
+        PollingCheck.waitFor(new PollingCheck.ActivityDestroy(activity));
+        verify(first, timeout(ON_DESTROY_TIMEOUT).times(1)).onDestroy();
+    }
+
+    @Test
+    public void setActionsWhenSubActionsExpanded() throws Throwable {
+        final String firstFragmentName = generateMethodTestName("first");
+        GuidedStepTestSupportFragment.Provider first = mockProvider(firstFragmentName);
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) {
+                List actions = (List) invocation.getArguments()[0];
+                List<GuidedAction> subActions = new ArrayList<GuidedAction>();
+                subActions.add(new GuidedAction.Builder().id(2000).title("item1").build());
+                actions.add(new GuidedAction.Builder().id(1000).subActions(subActions)
+                        .title("list").build());
+                return null;
+            }
+        }).when(first).onCreateActions(any(List.class), nullable(Bundle.class));
+        doAnswer(new Answer<Boolean>() {
+            @Override
+            public Boolean answer(InvocationOnMock invocation) {
+                GuidedStepTestSupportFragment.Provider obj = (GuidedStepTestSupportFragment.Provider)
+                        invocation.getMock();
+                GuidedAction action = (GuidedAction) invocation.getArguments()[0];
+                if (action.getId() == 2000) {
+                    List<GuidedAction> newActions = new ArrayList<GuidedAction>();
+                    newActions.add(new GuidedAction.Builder().id(1001).title("item2").build());
+                    obj.getFragment().setActions(newActions);
+                    return false;
+                }
+                return false;
+            }
+        }).when(first).onSubGuidedActionClicked(any(GuidedAction.class));
+
+        final GuidedStepSupportFragmentTestActivity activity = launchTestActivity(firstFragmentName);
+
+        // after clicked, it sub actions list should expand
+        View firstView = first.getFragment().getActionItemView(0);
+        assertTrue(firstView.hasFocus());
+        sendKey(KeyEvent.KEYCODE_DPAD_CENTER);
+        PollingCheck.waitFor(new ExpandTransitionFinish(first));
+        assertFalse(firstView.hasFocus());
+
+        sendKey(KeyEvent.KEYCODE_DPAD_CENTER);
+        ArgumentCaptor<GuidedAction> actionCapture = ArgumentCaptor.forClass(GuidedAction.class);
+        verify(first, times(1)).onSubGuidedActionClicked(actionCapture.capture());
+        // after clicked a sub action, whole action list is replaced.
+        PollingCheck.waitFor(new ExpandTransitionFinish(first));
+        assertFalse(first.getFragment().isExpanded());
+        View newFirstView  = first.getFragment().getActionItemView(0);
+        assertTrue(newFirstView.hasFocus());
+        assertTrue(newFirstView.getVisibility() == View.VISIBLE);
+        GuidedActionsStylist.ViewHolder vh = (GuidedActionsStylist.ViewHolder) first.getFragment()
+                .getGuidedActionsStylist().getActionsGridView().getChildViewHolder(newFirstView);
+        assertEquals(1001, vh.getAction().getId());
+
+    }
+
+    @Test
+    public void buttonActionsRtl() throws Throwable {
+        final String firstFragmentName = generateMethodTestName("first");
+        GuidedStepTestSupportFragment.Provider first = mockProvider(firstFragmentName);
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) {
+                List actions = (List) invocation.getArguments()[0];
+                actions.add(new GuidedAction.Builder().id(1000).title("action").build());
+                return null;
+            }
+        }).when(first).onCreateActions(any(List.class), nullable(Bundle.class));
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) {
+                List actions = (List) invocation.getArguments()[0];
+                actions.add(new GuidedAction.Builder().id(1001).title("button action").build());
+                return null;
+            }
+        }).when(first).onCreateButtonActions(any(List.class), nullable(Bundle.class));
+
+        final GuidedStepSupportFragmentTestActivity activity = launchTestActivity(firstFragmentName,
+                true, View.LAYOUT_DIRECTION_RTL);
+
+        assertEquals(View.LAYOUT_DIRECTION_RTL, first.getFragment().getView().getLayoutDirection());
+        View firstView = first.getFragment().getActionItemView(0);
+        assertTrue(firstView.hasFocus());
+    }
+
+    @Test
+    public void recyclerViewDiffTest() throws Throwable {
+        final String firstFragmentName = generateMethodTestName("first");
+        final GuidedStepTestSupportFragment.Provider first = mockProvider(firstFragmentName);
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) {
+                List actions = (List) invocation.getArguments()[0];
+                actions.add(new GuidedAction.Builder().id(1000).title("action1").build());
+                actions.add(new GuidedAction.Builder().id(1001).title("action2").build());
+                return null;
+            }
+        }).when(first).onCreateActions(any(List.class), nullable(Bundle.class));
+
+        launchTestActivity(firstFragmentName, true);
+
+        final ArrayList<RecyclerView.ViewHolder> changeList = new ArrayList();
+        VerticalGridView rv = first.getFragment().mActionsStylist.getActionsGridView();
+        rv.setItemAnimator(new DefaultItemAnimator() {
+            @Override
+            public void onChangeStarting(RecyclerView.ViewHolder item, boolean oldItem) {
+                if (!oldItem) {
+                    changeList.add(item);
+                }
+                super.onChangeStarting(item, oldItem);
+            }
+        });
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                List actions = new ArrayList();
+                actions.add(new GuidedAction.Builder().id(1001).title("action2x").build());
+                actions.add(new GuidedAction.Builder().id(1000).title("action1x").build());
+                first.getFragment().setActions(actions);
+            }
+        });
+
+        // should causes two change animation.
+        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return changeList.size() == 2;
+            }
+        });
+    }
+}
diff --git a/leanback/tests/java/android/support/v17/leanback/app/GuidedStepSupportFragmentTestActivity.java b/leanback/tests/java/android/support/v17/leanback/app/GuidedStepSupportFragmentTestActivity.java
new file mode 100644
index 0000000..bac2f49
--- /dev/null
+++ b/leanback/tests/java/android/support/v17/leanback/app/GuidedStepSupportFragmentTestActivity.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package android.support.v17.leanback.app;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v4.app.FragmentActivity;
+
+/**
+ * @hide from javadoc
+ */
+public class GuidedStepSupportFragmentTestActivity extends FragmentActivity {
+
+    /**
+     * Frst Test that will be included in this Activity
+     */
+    public static final String EXTRA_TEST_NAME = "testName";
+    /**
+     * True(default) to addAsRoot() for first Test, false to use add()
+     */
+    public static final String EXTRA_ADD_AS_ROOT = "addAsRoot";
+
+    /**
+     * Layout direction
+     */
+    public static final String EXTRA_LAYOUT_DIRECTION = "layoutDir";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        Intent intent = getIntent();
+
+        int layoutDirection = intent.getIntExtra(EXTRA_LAYOUT_DIRECTION, -1);
+        if (layoutDirection != -1) {
+            findViewById(android.R.id.content).setLayoutDirection(layoutDirection);
+        }
+        if (savedInstanceState == null) {
+            String firstTestName = intent.getStringExtra(EXTRA_TEST_NAME);
+            if (firstTestName != null) {
+                GuidedStepTestSupportFragment testFragment = new GuidedStepTestSupportFragment(firstTestName);
+                if (intent.getBooleanExtra(EXTRA_ADD_AS_ROOT, true)) {
+                    GuidedStepTestSupportFragment.addAsRoot(this, testFragment, android.R.id.content);
+                } else {
+                    GuidedStepTestSupportFragment.add(getSupportFragmentManager(), testFragment,
+                            android.R.id.content);
+                }
+            }
+        }
+    }
+}
diff --git a/leanback/tests/java/android/support/v17/leanback/app/GuidedStepSupportFragmentTestBase.java b/leanback/tests/java/android/support/v17/leanback/app/GuidedStepSupportFragmentTestBase.java
new file mode 100644
index 0000000..12e4d09
--- /dev/null
+++ b/leanback/tests/java/android/support/v17/leanback/app/GuidedStepSupportFragmentTestBase.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v17.leanback.app;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Intent;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+import android.support.v17.leanback.R;
+import android.support.v17.leanback.testutils.PollingCheck;
+import android.view.View;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.rules.TestName;
+
+/**
+ * @hide from javadoc
+ */
+public class GuidedStepSupportFragmentTestBase {
+
+    private static final long TIMEOUT = 5000;
+
+    @Rule public TestName mUnitTestName = new TestName();
+
+    @Rule
+    public ActivityTestRule<GuidedStepSupportFragmentTestActivity> activityTestRule =
+            new ActivityTestRule<>(GuidedStepSupportFragmentTestActivity.class, false, false);
+
+    @Before
+    public void clearTests() {
+        GuidedStepTestSupportFragment.clearTests();
+    }
+
+    public static class ExpandTransitionFinish extends PollingCheck.PollingCheckCondition {
+        GuidedStepTestSupportFragment.Provider mProvider;
+
+        public ExpandTransitionFinish(GuidedStepTestSupportFragment.Provider provider) {
+            mProvider = provider;
+        }
+
+        @Override
+        public boolean canPreProceed() {
+            return false;
+        }
+
+        @Override
+        public boolean canProceed() {
+            GuidedStepTestSupportFragment fragment = mProvider.getFragment();
+            if (fragment != null && fragment.getView() != null) {
+                if (!fragment.getGuidedActionsStylist().isInExpandTransition()) {
+                    // expand transition finishes
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    public static void waitOnDestroy(GuidedStepTestSupportFragment.Provider provider,
+            int times) {
+        verify(provider, timeout((int)TIMEOUT).times(times)).onDestroy();
+    }
+
+    public static class EnterTransitionFinish extends PollingCheck.PollingCheckCondition {
+        PollingCheck.ViewScreenPositionDetector mDector =
+                new PollingCheck.ViewScreenPositionDetector();
+
+        GuidedStepTestSupportFragment.Provider mProvider;
+
+        public EnterTransitionFinish(GuidedStepTestSupportFragment.Provider provider) {
+            mProvider = provider;
+        }
+        @Override
+        public boolean canProceed() {
+            GuidedStepTestSupportFragment fragment = mProvider.getFragment();
+            if (fragment != null && fragment.getView() != null) {
+                View view = fragment.getView().findViewById(R.id.guidance_title);
+                if (view != null) {
+                    if (mDector.isViewStableOnScreen(view)) {
+                        return true;
+                    }
+                }
+            }
+            return false;
+        }
+    }
+
+    public static void sendKey(int keyCode) {
+        InstrumentationRegistry.getInstrumentation().sendKeyDownUpSync(keyCode);
+    }
+
+    public String generateMethodTestName(String testName) {
+        return mUnitTestName.getMethodName() + "_" + testName;
+    }
+
+    public GuidedStepSupportFragmentTestActivity launchTestActivity(String firstTestName) {
+        Intent intent = new Intent();
+        intent.putExtra(GuidedStepSupportFragmentTestActivity.EXTRA_TEST_NAME, firstTestName);
+        return activityTestRule.launchActivity(intent);
+    }
+
+    public GuidedStepSupportFragmentTestActivity launchTestActivity(String firstTestName,
+            boolean addAsRoot) {
+        Intent intent = new Intent();
+        intent.putExtra(GuidedStepSupportFragmentTestActivity.EXTRA_TEST_NAME, firstTestName);
+        intent.putExtra(GuidedStepSupportFragmentTestActivity.EXTRA_ADD_AS_ROOT, addAsRoot);
+        return activityTestRule.launchActivity(intent);
+    }
+
+    public GuidedStepSupportFragmentTestActivity launchTestActivity(String firstTestName,
+            boolean addAsRoot, int layoutDirection) {
+        Intent intent = new Intent();
+        intent.putExtra(GuidedStepSupportFragmentTestActivity.EXTRA_TEST_NAME, firstTestName);
+        intent.putExtra(GuidedStepSupportFragmentTestActivity.EXTRA_ADD_AS_ROOT, addAsRoot);
+        intent.putExtra(GuidedStepSupportFragmentTestActivity.EXTRA_LAYOUT_DIRECTION, layoutDirection);
+        return activityTestRule.launchActivity(intent);
+    }
+
+    public GuidedStepTestSupportFragment.Provider mockProvider(String testName) {
+        GuidedStepTestSupportFragment.Provider test = mock(GuidedStepTestSupportFragment.Provider.class);
+        when(test.getActivity()).thenCallRealMethod();
+        when(test.getFragmentManager()).thenCallRealMethod();
+        when(test.getFragment()).thenCallRealMethod();
+        GuidedStepTestSupportFragment.setupTest(testName, test);
+        return test;
+    }
+}
+
diff --git a/leanback/tests/java/android/support/v17/leanback/app/GuidedStepTestFragment.java b/leanback/tests/java/android/support/v17/leanback/app/GuidedStepTestFragment.java
new file mode 100644
index 0000000..73e4083
--- /dev/null
+++ b/leanback/tests/java/android/support/v17/leanback/app/GuidedStepTestFragment.java
@@ -0,0 +1,240 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from GuidedStepTestSupportFragment.java.  DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package android.support.v17.leanback.app;
+
+import android.os.Bundle;
+import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
+import android.support.v17.leanback.widget.GuidedAction;
+import android.app.Activity;
+import android.app.FragmentManager;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * @hide from javadoc
+ */
+public class GuidedStepTestFragment extends GuidedStepFragment {
+
+    private static final String KEY_TEST_NAME = "key_test_name";
+
+    private static final HashMap<String, Provider> sTestMap = new HashMap<String, Provider>();
+
+    public static class Provider {
+
+        GuidedStepTestFragment mFragment;
+
+        public void onCreate(Bundle savedInstanceState) {
+        }
+
+        public void onSaveInstanceState(Bundle outState) {
+        }
+
+        public Guidance onCreateGuidance(Bundle savedInstanceState) {
+            return new Guidance("", "", "", null);
+        }
+
+        public void onCreateActions(List<GuidedAction> actions, Bundle savedInstanceState) {
+        }
+
+        public void onCreateButtonActions(List<GuidedAction> actions, Bundle savedInstanceState) {
+        }
+
+        public void onGuidedActionClicked(GuidedAction action) {
+        }
+
+        public boolean onSubGuidedActionClicked(GuidedAction action) {
+            return true;
+        }
+
+        public void onCreateView(LayoutInflater inflater, ViewGroup container,
+                Bundle savedInstanceState, View result) {
+        }
+
+        public void onDestroyView() {
+        }
+
+        public void onDestroy() {
+        }
+
+        public void onStart() {
+        }
+
+        public void onStop() {
+        }
+
+        public void onResume() {
+        }
+
+        public void onPause() {
+        }
+
+        public void onViewStateRestored(Bundle bundle) {
+        }
+
+        public void onDetach() {
+        }
+
+        public GuidedStepTestFragment getFragment() {
+            return mFragment;
+        }
+
+        public Activity getActivity() {
+            return mFragment.getActivity();
+        }
+
+        public FragmentManager getFragmentManager() {
+            return mFragment.getFragmentManager();
+        }
+    }
+
+    public static void setupTest(String testName, Provider provider) {
+        sTestMap.put(testName, provider);
+    }
+
+    public static void clearTests() {
+        sTestMap.clear();
+    }
+
+    CharSequence mTestName;
+    Provider mProvider;
+
+    public GuidedStepTestFragment() {
+    }
+
+    public GuidedStepTestFragment(String testName) {
+        setTestName(testName);
+    }
+
+    public void setTestName(CharSequence testName) {
+        mTestName = testName;
+    }
+
+    public CharSequence getTestName() {
+        return mTestName;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        if (savedInstanceState != null) {
+            mTestName = savedInstanceState.getCharSequence(KEY_TEST_NAME, null);
+        }
+        mProvider = sTestMap.get(mTestName);
+        if (mProvider == null) {
+            throw new IllegalArgumentException("you must setupTest()");
+        }
+        mProvider.mFragment = this;
+        super.onCreate(savedInstanceState);
+        mProvider.onCreate(savedInstanceState);
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putCharSequence(KEY_TEST_NAME, mTestName);
+        mProvider.onSaveInstanceState(outState);
+    }
+
+    @Override
+    public Guidance onCreateGuidance(Bundle savedInstanceState) {
+        Guidance g = mProvider.onCreateGuidance(savedInstanceState);
+        if (g == null) {
+            g = new Guidance("", "", "", null);
+        }
+        return g;
+    }
+
+    @Override
+    public void onCreateActions(List<GuidedAction> actions, Bundle savedInstanceState) {
+        mProvider.onCreateActions(actions, savedInstanceState);
+    }
+
+    @Override
+    public void onCreateButtonActions(List<GuidedAction> actions, Bundle savedInstanceState) {
+        mProvider.onCreateButtonActions(actions, savedInstanceState);
+    }
+
+    @Override
+    public void onGuidedActionClicked(GuidedAction action) {
+        mProvider.onGuidedActionClicked(action);
+    }
+
+    @Override
+    public boolean onSubGuidedActionClicked(GuidedAction action) {
+        return mProvider.onSubGuidedActionClicked(action);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle state) {
+        View view = super.onCreateView(inflater, container, state);
+        mProvider.onCreateView(inflater, container, state, view);
+        return view;
+    }
+
+    @Override
+    public void onDestroyView() {
+        mProvider.onDestroyView();
+        super.onDestroyView();
+    }
+
+    @Override
+    public void onDestroy() {
+        mProvider.onDestroy();
+        super.onDestroy();
+    }
+
+    @Override
+    public void onPause() {
+        mProvider.onPause();
+        super.onPause();
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        mProvider.onResume();
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        mProvider.onStart();
+    }
+
+    @Override
+    public void onStop() {
+        mProvider.onStop();
+        super.onStop();
+    }
+
+    @Override
+    public void onDetach() {
+        mProvider.onDetach();
+        super.onDetach();
+    }
+
+    @Override
+    public void onViewStateRestored(Bundle bundle) {
+        super.onViewStateRestored(bundle);
+        mProvider.onViewStateRestored(bundle);
+    }
+}
+
diff --git a/leanback/tests/java/android/support/v17/leanback/app/GuidedStepTestSupportFragment.java b/leanback/tests/java/android/support/v17/leanback/app/GuidedStepTestSupportFragment.java
new file mode 100644
index 0000000..95491ce
--- /dev/null
+++ b/leanback/tests/java/android/support/v17/leanback/app/GuidedStepTestSupportFragment.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package android.support.v17.leanback.app;
+
+import android.os.Bundle;
+import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
+import android.support.v17.leanback.widget.GuidedAction;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.FragmentManager;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * @hide from javadoc
+ */
+public class GuidedStepTestSupportFragment extends GuidedStepSupportFragment {
+
+    private static final String KEY_TEST_NAME = "key_test_name";
+
+    private static final HashMap<String, Provider> sTestMap = new HashMap<String, Provider>();
+
+    public static class Provider {
+
+        GuidedStepTestSupportFragment mFragment;
+
+        public void onCreate(Bundle savedInstanceState) {
+        }
+
+        public void onSaveInstanceState(Bundle outState) {
+        }
+
+        public Guidance onCreateGuidance(Bundle savedInstanceState) {
+            return new Guidance("", "", "", null);
+        }
+
+        public void onCreateActions(List<GuidedAction> actions, Bundle savedInstanceState) {
+        }
+
+        public void onCreateButtonActions(List<GuidedAction> actions, Bundle savedInstanceState) {
+        }
+
+        public void onGuidedActionClicked(GuidedAction action) {
+        }
+
+        public boolean onSubGuidedActionClicked(GuidedAction action) {
+            return true;
+        }
+
+        public void onCreateView(LayoutInflater inflater, ViewGroup container,
+                Bundle savedInstanceState, View result) {
+        }
+
+        public void onDestroyView() {
+        }
+
+        public void onDestroy() {
+        }
+
+        public void onStart() {
+        }
+
+        public void onStop() {
+        }
+
+        public void onResume() {
+        }
+
+        public void onPause() {
+        }
+
+        public void onViewStateRestored(Bundle bundle) {
+        }
+
+        public void onDetach() {
+        }
+
+        public GuidedStepTestSupportFragment getFragment() {
+            return mFragment;
+        }
+
+        public FragmentActivity getActivity() {
+            return mFragment.getActivity();
+        }
+
+        public FragmentManager getFragmentManager() {
+            return mFragment.getFragmentManager();
+        }
+    }
+
+    public static void setupTest(String testName, Provider provider) {
+        sTestMap.put(testName, provider);
+    }
+
+    public static void clearTests() {
+        sTestMap.clear();
+    }
+
+    CharSequence mTestName;
+    Provider mProvider;
+
+    public GuidedStepTestSupportFragment() {
+    }
+
+    public GuidedStepTestSupportFragment(String testName) {
+        setTestName(testName);
+    }
+
+    public void setTestName(CharSequence testName) {
+        mTestName = testName;
+    }
+
+    public CharSequence getTestName() {
+        return mTestName;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        if (savedInstanceState != null) {
+            mTestName = savedInstanceState.getCharSequence(KEY_TEST_NAME, null);
+        }
+        mProvider = sTestMap.get(mTestName);
+        if (mProvider == null) {
+            throw new IllegalArgumentException("you must setupTest()");
+        }
+        mProvider.mFragment = this;
+        super.onCreate(savedInstanceState);
+        mProvider.onCreate(savedInstanceState);
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putCharSequence(KEY_TEST_NAME, mTestName);
+        mProvider.onSaveInstanceState(outState);
+    }
+
+    @Override
+    public Guidance onCreateGuidance(Bundle savedInstanceState) {
+        Guidance g = mProvider.onCreateGuidance(savedInstanceState);
+        if (g == null) {
+            g = new Guidance("", "", "", null);
+        }
+        return g;
+    }
+
+    @Override
+    public void onCreateActions(List<GuidedAction> actions, Bundle savedInstanceState) {
+        mProvider.onCreateActions(actions, savedInstanceState);
+    }
+
+    @Override
+    public void onCreateButtonActions(List<GuidedAction> actions, Bundle savedInstanceState) {
+        mProvider.onCreateButtonActions(actions, savedInstanceState);
+    }
+
+    @Override
+    public void onGuidedActionClicked(GuidedAction action) {
+        mProvider.onGuidedActionClicked(action);
+    }
+
+    @Override
+    public boolean onSubGuidedActionClicked(GuidedAction action) {
+        return mProvider.onSubGuidedActionClicked(action);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle state) {
+        View view = super.onCreateView(inflater, container, state);
+        mProvider.onCreateView(inflater, container, state, view);
+        return view;
+    }
+
+    @Override
+    public void onDestroyView() {
+        mProvider.onDestroyView();
+        super.onDestroyView();
+    }
+
+    @Override
+    public void onDestroy() {
+        mProvider.onDestroy();
+        super.onDestroy();
+    }
+
+    @Override
+    public void onPause() {
+        mProvider.onPause();
+        super.onPause();
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        mProvider.onResume();
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        mProvider.onStart();
+    }
+
+    @Override
+    public void onStop() {
+        mProvider.onStop();
+        super.onStop();
+    }
+
+    @Override
+    public void onDetach() {
+        mProvider.onDetach();
+        super.onDetach();
+    }
+
+    @Override
+    public void onViewStateRestored(Bundle bundle) {
+        super.onViewStateRestored(bundle);
+        mProvider.onViewStateRestored(bundle);
+    }
+}
+
diff --git a/leanback/tests/java/android/support/v17/leanback/app/HeadersFragmentTest.java b/leanback/tests/java/android/support/v17/leanback/app/HeadersFragmentTest.java
new file mode 100644
index 0000000..f23e38a
--- /dev/null
+++ b/leanback/tests/java/android/support/v17/leanback/app/HeadersFragmentTest.java
@@ -0,0 +1,129 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from HeadersSupportFragmentTest.java.  DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v17.leanback.app;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Bundle;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v17.leanback.widget.ArrayObjectAdapter;
+import android.support.v17.leanback.widget.FocusHighlightHelper;
+import android.support.v17.leanback.widget.HeaderItem;
+import android.support.v17.leanback.widget.ItemBridgeAdapter;
+import android.support.v17.leanback.widget.ListRow;
+import android.support.v17.leanback.widget.ListRowPresenter;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.view.View;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class HeadersFragmentTest extends SingleFragmentTestBase {
+
+    static void loadData(ArrayObjectAdapter adapter, int numRows) {
+        for (int i = 0; i < numRows; ++i) {
+            ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter();
+            HeaderItem header = new HeaderItem(i, "Row " + i);
+            adapter.add(new ListRow(header, listRowAdapter));
+        }
+    }
+
+    public static class F_defaultScale extends HeadersFragment {
+        final ListRowPresenter mPresenter = new ListRowPresenter();
+
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            final ArrayObjectAdapter adapter = new ArrayObjectAdapter(mPresenter);
+            setAdapter(adapter);
+            loadData(adapter, 10);
+        }
+    }
+
+    @Test
+    public void defaultScale() {
+        SingleFragmentTestActivity activity = launchAndWaitActivity(F_defaultScale.class, 1000);
+
+        final VerticalGridView gridView = ((HeadersFragment) activity.getTestFragment())
+                .getVerticalGridView();
+        ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder)
+                gridView.findViewHolderForAdapterPosition(0);
+        assertTrue(vh.itemView.getScaleX() - 1.0f > 0.05f);
+        assertTrue(vh.itemView.getScaleY() - 1.0f > 0.05f);
+    }
+
+    public static class F_disableScale extends HeadersFragment {
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            final ArrayObjectAdapter adapter = new ArrayObjectAdapter(new ListRowPresenter());
+            setAdapter(adapter);
+            loadData(adapter, 10);
+        }
+
+        @Override
+        public void onViewCreated(View view, Bundle savedInstanceState) {
+            super.onViewCreated(view, savedInstanceState);
+            FocusHighlightHelper.setupHeaderItemFocusHighlight(getVerticalGridView(), false);
+        }
+    }
+
+    @Test
+    public void disableScale() {
+        SingleFragmentTestActivity activity = launchAndWaitActivity(F_disableScale.class, 1000);
+
+        final VerticalGridView gridView = ((HeadersFragment) activity.getTestFragment())
+                .getVerticalGridView();
+        ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder)
+                gridView.findViewHolderForAdapterPosition(0);
+        assertEquals(vh.itemView.getScaleX(), 1f, 0.001f);
+        assertEquals(vh.itemView.getScaleY(), 1f, 0.001f);
+    }
+
+    public static class F_disableScaleInConstructor extends HeadersFragment {
+        public F_disableScaleInConstructor() {
+            FocusHighlightHelper.setupHeaderItemFocusHighlight(getBridgeAdapter(), false);
+        }
+
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            final ArrayObjectAdapter adapter = new ArrayObjectAdapter(new ListRowPresenter());
+            setAdapter(adapter);
+            loadData(adapter, 10);
+        }
+    }
+
+    @Test
+    public void disableScaleInConstructor() {
+        SingleFragmentTestActivity activity = launchAndWaitActivity(
+                F_disableScaleInConstructor.class, 1000);
+
+        final VerticalGridView gridView = ((HeadersFragment) activity.getTestFragment())
+                .getVerticalGridView();
+        ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder)
+                gridView.findViewHolderForAdapterPosition(0);
+        assertEquals(vh.itemView.getScaleX(), 1f, 0.001f);
+        assertEquals(vh.itemView.getScaleY(), 1f, 0.001f);
+    }
+}
diff --git a/leanback/tests/java/android/support/v17/leanback/app/HeadersSupportFragmentTest.java b/leanback/tests/java/android/support/v17/leanback/app/HeadersSupportFragmentTest.java
new file mode 100644
index 0000000..436a797
--- /dev/null
+++ b/leanback/tests/java/android/support/v17/leanback/app/HeadersSupportFragmentTest.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v17.leanback.app;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Bundle;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v17.leanback.widget.ArrayObjectAdapter;
+import android.support.v17.leanback.widget.FocusHighlightHelper;
+import android.support.v17.leanback.widget.HeaderItem;
+import android.support.v17.leanback.widget.ItemBridgeAdapter;
+import android.support.v17.leanback.widget.ListRow;
+import android.support.v17.leanback.widget.ListRowPresenter;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.view.View;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class HeadersSupportFragmentTest extends SingleSupportFragmentTestBase {
+
+    static void loadData(ArrayObjectAdapter adapter, int numRows) {
+        for (int i = 0; i < numRows; ++i) {
+            ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter();
+            HeaderItem header = new HeaderItem(i, "Row " + i);
+            adapter.add(new ListRow(header, listRowAdapter));
+        }
+    }
+
+    public static class F_defaultScale extends HeadersSupportFragment {
+        final ListRowPresenter mPresenter = new ListRowPresenter();
+
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            final ArrayObjectAdapter adapter = new ArrayObjectAdapter(mPresenter);
+            setAdapter(adapter);
+            loadData(adapter, 10);
+        }
+    }
+
+    @Test
+    public void defaultScale() {
+        SingleSupportFragmentTestActivity activity = launchAndWaitActivity(F_defaultScale.class, 1000);
+
+        final VerticalGridView gridView = ((HeadersSupportFragment) activity.getTestFragment())
+                .getVerticalGridView();
+        ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder)
+                gridView.findViewHolderForAdapterPosition(0);
+        assertTrue(vh.itemView.getScaleX() - 1.0f > 0.05f);
+        assertTrue(vh.itemView.getScaleY() - 1.0f > 0.05f);
+    }
+
+    public static class F_disableScale extends HeadersSupportFragment {
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            final ArrayObjectAdapter adapter = new ArrayObjectAdapter(new ListRowPresenter());
+            setAdapter(adapter);
+            loadData(adapter, 10);
+        }
+
+        @Override
+        public void onViewCreated(View view, Bundle savedInstanceState) {
+            super.onViewCreated(view, savedInstanceState);
+            FocusHighlightHelper.setupHeaderItemFocusHighlight(getVerticalGridView(), false);
+        }
+    }
+
+    @Test
+    public void disableScale() {
+        SingleSupportFragmentTestActivity activity = launchAndWaitActivity(F_disableScale.class, 1000);
+
+        final VerticalGridView gridView = ((HeadersSupportFragment) activity.getTestFragment())
+                .getVerticalGridView();
+        ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder)
+                gridView.findViewHolderForAdapterPosition(0);
+        assertEquals(vh.itemView.getScaleX(), 1f, 0.001f);
+        assertEquals(vh.itemView.getScaleY(), 1f, 0.001f);
+    }
+
+    public static class F_disableScaleInConstructor extends HeadersSupportFragment {
+        public F_disableScaleInConstructor() {
+            FocusHighlightHelper.setupHeaderItemFocusHighlight(getBridgeAdapter(), false);
+        }
+
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            final ArrayObjectAdapter adapter = new ArrayObjectAdapter(new ListRowPresenter());
+            setAdapter(adapter);
+            loadData(adapter, 10);
+        }
+    }
+
+    @Test
+    public void disableScaleInConstructor() {
+        SingleSupportFragmentTestActivity activity = launchAndWaitActivity(
+                F_disableScaleInConstructor.class, 1000);
+
+        final VerticalGridView gridView = ((HeadersSupportFragment) activity.getTestFragment())
+                .getVerticalGridView();
+        ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder)
+                gridView.findViewHolderForAdapterPosition(0);
+        assertEquals(vh.itemView.getScaleX(), 1f, 0.001f);
+        assertEquals(vh.itemView.getScaleY(), 1f, 0.001f);
+    }
+}
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/ListRowDataAdapterTest.java b/leanback/tests/java/android/support/v17/leanback/app/ListRowDataAdapterTest.java
similarity index 100%
rename from v17/leanback/tests/java/android/support/v17/leanback/app/ListRowDataAdapterTest.java
rename to leanback/tests/java/android/support/v17/leanback/app/ListRowDataAdapterTest.java
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/PhotoItem.java b/leanback/tests/java/android/support/v17/leanback/app/PhotoItem.java
similarity index 100%
rename from v17/leanback/tests/java/android/support/v17/leanback/app/PhotoItem.java
rename to leanback/tests/java/android/support/v17/leanback/app/PhotoItem.java
diff --git a/leanback/tests/java/android/support/v17/leanback/app/PlaybackFragmentTest.java b/leanback/tests/java/android/support/v17/leanback/app/PlaybackFragmentTest.java
new file mode 100644
index 0000000..a9101a7
--- /dev/null
+++ b/leanback/tests/java/android/support/v17/leanback/app/PlaybackFragmentTest.java
@@ -0,0 +1,374 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from PlaybackSupportFragmentTest.java.  DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v17.leanback.app;
+
+import static junit.framework.Assert.assertEquals;
+
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.support.test.filters.FlakyTest;
+import android.support.test.filters.MediumTest;
+import android.support.test.filters.Suppress;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v17.leanback.media.PlaybackControlGlue;
+import android.support.v17.leanback.media.PlaybackGlue;
+import android.support.v17.leanback.testutils.PollingCheck;
+import android.support.v17.leanback.widget.ControlButtonPresenterSelector;
+import android.support.v17.leanback.widget.ListRow;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.OnItemViewSelectedListener;
+import android.support.v17.leanback.widget.PlaybackControlsRow;
+import android.support.v17.leanback.widget.PlaybackControlsRowPresenter;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
+import android.view.KeyEvent;
+import android.view.View;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class PlaybackFragmentTest extends SingleFragmentTestBase {
+
+    private static final String TAG = "PlaybackFragmentTest";
+    private static final long TRANSITION_LENGTH = 1000;
+
+    @Test
+    public void testDetachCalledWhenDestroyFragment() throws Throwable {
+        final SingleFragmentTestActivity activity =
+                launchAndWaitActivity(PlaybackTestFragment.class, 1000);
+        final PlaybackTestFragment fragment = (PlaybackTestFragment) activity.getTestFragment();
+        PlaybackGlue glue = fragment.getGlue();
+        activityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                activity.finish();
+            }
+        });
+        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return fragment.mDestroyCalled;
+            }
+        });
+        assertNull(glue.getHost());
+    }
+
+    @Test
+    public void testSelectedListener() throws Throwable {
+        SingleFragmentTestActivity activity =
+                launchAndWaitActivity(PlaybackTestFragment.class, 1000);
+        PlaybackTestFragment fragment = (PlaybackTestFragment) activity.getTestFragment();
+
+        assertTrue(fragment.getView().hasFocus());
+
+        OnItemViewSelectedListener selectedListener = Mockito.mock(
+                OnItemViewSelectedListener.class);
+        fragment.setOnItemViewSelectedListener(selectedListener);
+
+
+        PlaybackControlsRow controlsRow = fragment.getGlue().getControlsRow();
+        SparseArrayObjectAdapter primaryActionsAdapter = (SparseArrayObjectAdapter)
+                controlsRow.getPrimaryActionsAdapter();
+
+        PlaybackControlsRow.MultiAction playPause = (PlaybackControlsRow.MultiAction)
+                primaryActionsAdapter.lookup(PlaybackControlGlue.ACTION_PLAY_PAUSE);
+
+        PlaybackControlsRow.MultiAction rewind = (PlaybackControlsRow.MultiAction)
+                primaryActionsAdapter.lookup(PlaybackControlGlue.ACTION_REWIND);
+
+        PlaybackControlsRow.MultiAction thumbsUp = (PlaybackControlsRow.MultiAction)
+                primaryActionsAdapter.lookup(PlaybackControlGlue.ACTION_CUSTOM_LEFT_FIRST);
+
+        ArgumentCaptor<Presenter.ViewHolder> itemVHCaptor =
+                ArgumentCaptor.forClass(Presenter.ViewHolder.class);
+        ArgumentCaptor<Object> itemCaptor = ArgumentCaptor.forClass(Object.class);
+        ArgumentCaptor<RowPresenter.ViewHolder> rowVHCaptor =
+                ArgumentCaptor.forClass(RowPresenter.ViewHolder.class);
+        ArgumentCaptor<Row> rowCaptor = ArgumentCaptor.forClass(Row.class);
+
+
+        // First navigate left within PlaybackControlsRow items.
+        verify(selectedListener, times(0)).onItemSelected(any(Presenter.ViewHolder.class),
+                any(Object.class), any(RowPresenter.ViewHolder.class), any(Row.class));
+        sendKeys(KeyEvent.KEYCODE_DPAD_LEFT);
+        verify(selectedListener, times(1)).onItemSelected(itemVHCaptor.capture(),
+                itemCaptor.capture(), rowVHCaptor.capture(), rowCaptor.capture());
+        assertSame("Same controls row should be passed to the listener", controlsRow,
+                rowCaptor.getValue());
+        assertSame("The selected action should be rewind", rewind, itemCaptor.getValue());
+
+        sendKeys(KeyEvent.KEYCODE_DPAD_LEFT);
+        verify(selectedListener, times(2)).onItemSelected(itemVHCaptor.capture(),
+                itemCaptor.capture(), rowVHCaptor.capture(), rowCaptor.capture());
+        assertSame("Same controls row should be passed to the listener", controlsRow,
+                rowCaptor.getValue());
+        assertSame("The selected action should be thumbsUp", thumbsUp, itemCaptor.getValue());
+
+        // Now navigate down to a ListRow item.
+        ListRow listRow0 = (ListRow) fragment.getAdapter().get(1);
+
+        sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+        waitForScrollIdle(fragment.getVerticalGridView());
+        verify(selectedListener, times(3)).onItemSelected(itemVHCaptor.capture(),
+                itemCaptor.capture(), rowVHCaptor.capture(), rowCaptor.capture());
+        assertSame("Same list row should be passed to the listener", listRow0,
+                rowCaptor.getValue());
+        // Depending on the focusSearch algorithm, one of the items in the first ListRow must be
+        // selected.
+        boolean listRowItemPassed = (itemCaptor.getValue() == listRow0.getAdapter().get(0)
+                || itemCaptor.getValue() == listRow0.getAdapter().get(1));
+        assertTrue("None of the items in the first ListRow are passed to the selected listener.",
+                listRowItemPassed);
+    }
+
+    @Test
+    public void testClickedListener() throws Throwable {
+        SingleFragmentTestActivity activity =
+                launchAndWaitActivity(PlaybackTestFragment.class, 1000);
+        PlaybackTestFragment fragment = (PlaybackTestFragment) activity.getTestFragment();
+
+        assertTrue(fragment.getView().hasFocus());
+
+        OnItemViewClickedListener clickedListener = Mockito.mock(OnItemViewClickedListener.class);
+        fragment.setOnItemViewClickedListener(clickedListener);
+
+
+        PlaybackControlsRow controlsRow = fragment.getGlue().getControlsRow();
+        SparseArrayObjectAdapter primaryActionsAdapter = (SparseArrayObjectAdapter)
+                controlsRow.getPrimaryActionsAdapter();
+
+        PlaybackControlsRow.MultiAction playPause = (PlaybackControlsRow.MultiAction)
+                primaryActionsAdapter.lookup(PlaybackControlGlue.ACTION_PLAY_PAUSE);
+
+        PlaybackControlsRow.MultiAction rewind = (PlaybackControlsRow.MultiAction)
+                primaryActionsAdapter.lookup(PlaybackControlGlue.ACTION_REWIND);
+
+        PlaybackControlsRow.MultiAction thumbsUp = (PlaybackControlsRow.MultiAction)
+                primaryActionsAdapter.lookup(PlaybackControlGlue.ACTION_CUSTOM_LEFT_FIRST);
+
+        ArgumentCaptor<Presenter.ViewHolder> itemVHCaptor =
+                ArgumentCaptor.forClass(Presenter.ViewHolder.class);
+        ArgumentCaptor<Object> itemCaptor = ArgumentCaptor.forClass(Object.class);
+        ArgumentCaptor<RowPresenter.ViewHolder> rowVHCaptor =
+                ArgumentCaptor.forClass(RowPresenter.ViewHolder.class);
+        ArgumentCaptor<Row> rowCaptor = ArgumentCaptor.forClass(Row.class);
+
+
+        // First navigate left within PlaybackControlsRow items.
+        verify(clickedListener, times(0)).onItemClicked(any(Presenter.ViewHolder.class),
+                any(Object.class), any(RowPresenter.ViewHolder.class), any(Row.class));
+        sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+        verify(clickedListener, times(1)).onItemClicked(itemVHCaptor.capture(),
+                itemCaptor.capture(), rowVHCaptor.capture(), rowCaptor.capture());
+        assertSame("Same controls row should be passed to the listener", controlsRow,
+                rowCaptor.getValue());
+        assertSame("The clicked action should be playPause", playPause, itemCaptor.getValue());
+
+        sendKeys(KeyEvent.KEYCODE_DPAD_LEFT);
+        verify(clickedListener, times(1)).onItemClicked(any(Presenter.ViewHolder.class),
+                any(Object.class), any(RowPresenter.ViewHolder.class), any(Row.class));
+        sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+        verify(clickedListener, times(2)).onItemClicked(itemVHCaptor.capture(),
+                itemCaptor.capture(), rowVHCaptor.capture(), rowCaptor.capture());
+        assertSame("Same controls row should be passed to the listener", controlsRow,
+                rowCaptor.getValue());
+        assertSame("The clicked action should be rewind", rewind, itemCaptor.getValue());
+
+        sendKeys(KeyEvent.KEYCODE_DPAD_LEFT);
+        verify(clickedListener, times(2)).onItemClicked(any(Presenter.ViewHolder.class),
+                any(Object.class), any(RowPresenter.ViewHolder.class), any(Row.class));
+        sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+        verify(clickedListener, times(3)).onItemClicked(itemVHCaptor.capture(),
+                itemCaptor.capture(), rowVHCaptor.capture(), rowCaptor.capture());
+        assertSame("Same controls row should be passed to the listener", controlsRow,
+                rowCaptor.getValue());
+        assertSame("The clicked action should be thumbsUp", thumbsUp, itemCaptor.getValue());
+
+        // Now navigate down to a ListRow item.
+        ListRow listRow0 = (ListRow) fragment.getAdapter().get(1);
+
+        sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+        waitForScrollIdle(fragment.getVerticalGridView());
+        verify(clickedListener, times(3)).onItemClicked(any(Presenter.ViewHolder.class),
+                any(Object.class), any(RowPresenter.ViewHolder.class), any(Row.class));
+        sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+        verify(clickedListener, times(4)).onItemClicked(itemVHCaptor.capture(),
+                itemCaptor.capture(), rowVHCaptor.capture(), rowCaptor.capture());
+        assertSame("Same list row should be passed to the listener", listRow0,
+                rowCaptor.getValue());
+        boolean listRowItemPassed = (itemCaptor.getValue() == listRow0.getAdapter().get(0)
+                || itemCaptor.getValue() == listRow0.getAdapter().get(1));
+        assertTrue("None of the items in the first ListRow are passed to the click listener.",
+                listRowItemPassed);
+    }
+
+    @FlakyTest
+    @Suppress
+    @Test
+    public void alignmentRowToBottom() throws Throwable {
+        SingleFragmentTestActivity activity =
+                launchAndWaitActivity(PlaybackTestFragment.class, 1000);
+        final PlaybackTestFragment fragment = (PlaybackTestFragment) activity.getTestFragment();
+
+        assertTrue(fragment.getAdapter().size() > 2);
+
+        View playRow = fragment.getVerticalGridView().getChildAt(0);
+        assertTrue(playRow.hasFocus());
+        assertEquals(playRow.getResources().getDimensionPixelSize(
+                android.support.v17.leanback.test.R.dimen.lb_playback_controls_padding_bottom),
+                fragment.getVerticalGridView().getHeight() - playRow.getBottom());
+
+        activityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                fragment.getVerticalGridView().setSelectedPositionSmooth(
+                        fragment.getAdapter().size() - 1);
+            }
+        });
+        waitForScrollIdle(fragment.getVerticalGridView());
+
+        View lastRow = fragment.getVerticalGridView().getChildAt(
+                fragment.getVerticalGridView().getChildCount() - 1);
+        assertEquals(fragment.getAdapter().size() - 1,
+                fragment.getVerticalGridView().getChildAdapterPosition(lastRow));
+        assertTrue(lastRow.hasFocus());
+        assertEquals(lastRow.getResources().getDimensionPixelSize(
+                android.support.v17.leanback.test.R.dimen.lb_playback_controls_padding_bottom),
+                fragment.getVerticalGridView().getHeight() - lastRow.getBottom());
+    }
+
+    public static class PurePlaybackFragment extends PlaybackFragment {
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            setFadingEnabled(false);
+            PlaybackControlsRow row = new PlaybackControlsRow();
+            SparseArrayObjectAdapter primaryAdapter = new SparseArrayObjectAdapter(
+                    new ControlButtonPresenterSelector());
+            primaryAdapter.set(0, new PlaybackControlsRow.SkipPreviousAction(getActivity()));
+            primaryAdapter.set(1, new PlaybackControlsRow.PlayPauseAction(getActivity()));
+            primaryAdapter.set(2, new PlaybackControlsRow.SkipNextAction(getActivity()));
+            row.setPrimaryActionsAdapter(primaryAdapter);
+            row.setSecondaryActionsAdapter(null);
+            setPlaybackRow(row);
+            setPlaybackRowPresenter(new PlaybackControlsRowPresenter());
+        }
+    }
+
+    @Test
+    public void setupRowAndPresenterWithoutGlue() {
+        SingleFragmentTestActivity activity =
+                launchAndWaitActivity(PurePlaybackFragment.class, 1000);
+        final PurePlaybackFragment fragment = (PurePlaybackFragment)
+                activity.getTestFragment();
+
+        assertTrue(fragment.getAdapter().size() == 1);
+        View playRow = fragment.getVerticalGridView().getChildAt(0);
+        assertTrue(playRow.hasFocus());
+        assertEquals(playRow.getResources().getDimensionPixelSize(
+                android.support.v17.leanback.test.R.dimen.lb_playback_controls_padding_bottom),
+                fragment.getVerticalGridView().getHeight() - playRow.getBottom());
+    }
+
+    public static class ControlGlueFragment extends PlaybackFragment {
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            int[] ffspeeds = new int[] {PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0,
+                    PlaybackControlGlue.PLAYBACK_SPEED_FAST_L1};
+            PlaybackGlue glue = new PlaybackControlGlue(
+                    getActivity(), ffspeeds) {
+                @Override
+                public boolean hasValidMedia() {
+                    return true;
+                }
+
+                @Override
+                public boolean isMediaPlaying() {
+                    return false;
+                }
+
+                @Override
+                public CharSequence getMediaTitle() {
+                    return "Title";
+                }
+
+                @Override
+                public CharSequence getMediaSubtitle() {
+                    return "SubTitle";
+                }
+
+                @Override
+                public int getMediaDuration() {
+                    return 100;
+                }
+
+                @Override
+                public Drawable getMediaArt() {
+                    return null;
+                }
+
+                @Override
+                public long getSupportedActions() {
+                    return PlaybackControlGlue.ACTION_PLAY_PAUSE;
+                }
+
+                @Override
+                public int getCurrentSpeedId() {
+                    return PlaybackControlGlue.PLAYBACK_SPEED_PAUSED;
+                }
+
+                @Override
+                public int getCurrentPosition() {
+                    return 50;
+                }
+            };
+            glue.setHost(new PlaybackFragmentGlueHost(this));
+        }
+    }
+
+    @Test
+    public void setupWithControlGlue() throws Throwable {
+        SingleFragmentTestActivity activity =
+                launchAndWaitActivity(ControlGlueFragment.class, 1000);
+        final ControlGlueFragment fragment = (ControlGlueFragment)
+                activity.getTestFragment();
+
+        assertTrue(fragment.getAdapter().size() == 1);
+
+        View playRow = fragment.getVerticalGridView().getChildAt(0);
+        assertTrue(playRow.hasFocus());
+        assertEquals(playRow.getResources().getDimensionPixelSize(
+                android.support.v17.leanback.test.R.dimen.lb_playback_controls_padding_bottom),
+                fragment.getVerticalGridView().getHeight() - playRow.getBottom());
+    }
+}
diff --git a/leanback/tests/java/android/support/v17/leanback/app/PlaybackSupportFragmentTest.java b/leanback/tests/java/android/support/v17/leanback/app/PlaybackSupportFragmentTest.java
new file mode 100644
index 0000000..4aaeae8
--- /dev/null
+++ b/leanback/tests/java/android/support/v17/leanback/app/PlaybackSupportFragmentTest.java
@@ -0,0 +1,371 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v17.leanback.app;
+
+import static junit.framework.Assert.assertEquals;
+
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.support.test.filters.FlakyTest;
+import android.support.test.filters.MediumTest;
+import android.support.test.filters.Suppress;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v17.leanback.media.PlaybackControlGlue;
+import android.support.v17.leanback.media.PlaybackGlue;
+import android.support.v17.leanback.testutils.PollingCheck;
+import android.support.v17.leanback.widget.ControlButtonPresenterSelector;
+import android.support.v17.leanback.widget.ListRow;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.OnItemViewSelectedListener;
+import android.support.v17.leanback.widget.PlaybackControlsRow;
+import android.support.v17.leanback.widget.PlaybackControlsRowPresenter;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
+import android.view.KeyEvent;
+import android.view.View;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class PlaybackSupportFragmentTest extends SingleSupportFragmentTestBase {
+
+    private static final String TAG = "PlaybackSupportFragmentTest";
+    private static final long TRANSITION_LENGTH = 1000;
+
+    @Test
+    public void testDetachCalledWhenDestroyFragment() throws Throwable {
+        final SingleSupportFragmentTestActivity activity =
+                launchAndWaitActivity(PlaybackTestSupportFragment.class, 1000);
+        final PlaybackTestSupportFragment fragment = (PlaybackTestSupportFragment) activity.getTestFragment();
+        PlaybackGlue glue = fragment.getGlue();
+        activityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                activity.finish();
+            }
+        });
+        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return fragment.mDestroyCalled;
+            }
+        });
+        assertNull(glue.getHost());
+    }
+
+    @Test
+    public void testSelectedListener() throws Throwable {
+        SingleSupportFragmentTestActivity activity =
+                launchAndWaitActivity(PlaybackTestSupportFragment.class, 1000);
+        PlaybackTestSupportFragment fragment = (PlaybackTestSupportFragment) activity.getTestFragment();
+
+        assertTrue(fragment.getView().hasFocus());
+
+        OnItemViewSelectedListener selectedListener = Mockito.mock(
+                OnItemViewSelectedListener.class);
+        fragment.setOnItemViewSelectedListener(selectedListener);
+
+
+        PlaybackControlsRow controlsRow = fragment.getGlue().getControlsRow();
+        SparseArrayObjectAdapter primaryActionsAdapter = (SparseArrayObjectAdapter)
+                controlsRow.getPrimaryActionsAdapter();
+
+        PlaybackControlsRow.MultiAction playPause = (PlaybackControlsRow.MultiAction)
+                primaryActionsAdapter.lookup(PlaybackControlGlue.ACTION_PLAY_PAUSE);
+
+        PlaybackControlsRow.MultiAction rewind = (PlaybackControlsRow.MultiAction)
+                primaryActionsAdapter.lookup(PlaybackControlGlue.ACTION_REWIND);
+
+        PlaybackControlsRow.MultiAction thumbsUp = (PlaybackControlsRow.MultiAction)
+                primaryActionsAdapter.lookup(PlaybackControlGlue.ACTION_CUSTOM_LEFT_FIRST);
+
+        ArgumentCaptor<Presenter.ViewHolder> itemVHCaptor =
+                ArgumentCaptor.forClass(Presenter.ViewHolder.class);
+        ArgumentCaptor<Object> itemCaptor = ArgumentCaptor.forClass(Object.class);
+        ArgumentCaptor<RowPresenter.ViewHolder> rowVHCaptor =
+                ArgumentCaptor.forClass(RowPresenter.ViewHolder.class);
+        ArgumentCaptor<Row> rowCaptor = ArgumentCaptor.forClass(Row.class);
+
+
+        // First navigate left within PlaybackControlsRow items.
+        verify(selectedListener, times(0)).onItemSelected(any(Presenter.ViewHolder.class),
+                any(Object.class), any(RowPresenter.ViewHolder.class), any(Row.class));
+        sendKeys(KeyEvent.KEYCODE_DPAD_LEFT);
+        verify(selectedListener, times(1)).onItemSelected(itemVHCaptor.capture(),
+                itemCaptor.capture(), rowVHCaptor.capture(), rowCaptor.capture());
+        assertSame("Same controls row should be passed to the listener", controlsRow,
+                rowCaptor.getValue());
+        assertSame("The selected action should be rewind", rewind, itemCaptor.getValue());
+
+        sendKeys(KeyEvent.KEYCODE_DPAD_LEFT);
+        verify(selectedListener, times(2)).onItemSelected(itemVHCaptor.capture(),
+                itemCaptor.capture(), rowVHCaptor.capture(), rowCaptor.capture());
+        assertSame("Same controls row should be passed to the listener", controlsRow,
+                rowCaptor.getValue());
+        assertSame("The selected action should be thumbsUp", thumbsUp, itemCaptor.getValue());
+
+        // Now navigate down to a ListRow item.
+        ListRow listRow0 = (ListRow) fragment.getAdapter().get(1);
+
+        sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+        waitForScrollIdle(fragment.getVerticalGridView());
+        verify(selectedListener, times(3)).onItemSelected(itemVHCaptor.capture(),
+                itemCaptor.capture(), rowVHCaptor.capture(), rowCaptor.capture());
+        assertSame("Same list row should be passed to the listener", listRow0,
+                rowCaptor.getValue());
+        // Depending on the focusSearch algorithm, one of the items in the first ListRow must be
+        // selected.
+        boolean listRowItemPassed = (itemCaptor.getValue() == listRow0.getAdapter().get(0)
+                || itemCaptor.getValue() == listRow0.getAdapter().get(1));
+        assertTrue("None of the items in the first ListRow are passed to the selected listener.",
+                listRowItemPassed);
+    }
+
+    @Test
+    public void testClickedListener() throws Throwable {
+        SingleSupportFragmentTestActivity activity =
+                launchAndWaitActivity(PlaybackTestSupportFragment.class, 1000);
+        PlaybackTestSupportFragment fragment = (PlaybackTestSupportFragment) activity.getTestFragment();
+
+        assertTrue(fragment.getView().hasFocus());
+
+        OnItemViewClickedListener clickedListener = Mockito.mock(OnItemViewClickedListener.class);
+        fragment.setOnItemViewClickedListener(clickedListener);
+
+
+        PlaybackControlsRow controlsRow = fragment.getGlue().getControlsRow();
+        SparseArrayObjectAdapter primaryActionsAdapter = (SparseArrayObjectAdapter)
+                controlsRow.getPrimaryActionsAdapter();
+
+        PlaybackControlsRow.MultiAction playPause = (PlaybackControlsRow.MultiAction)
+                primaryActionsAdapter.lookup(PlaybackControlGlue.ACTION_PLAY_PAUSE);
+
+        PlaybackControlsRow.MultiAction rewind = (PlaybackControlsRow.MultiAction)
+                primaryActionsAdapter.lookup(PlaybackControlGlue.ACTION_REWIND);
+
+        PlaybackControlsRow.MultiAction thumbsUp = (PlaybackControlsRow.MultiAction)
+                primaryActionsAdapter.lookup(PlaybackControlGlue.ACTION_CUSTOM_LEFT_FIRST);
+
+        ArgumentCaptor<Presenter.ViewHolder> itemVHCaptor =
+                ArgumentCaptor.forClass(Presenter.ViewHolder.class);
+        ArgumentCaptor<Object> itemCaptor = ArgumentCaptor.forClass(Object.class);
+        ArgumentCaptor<RowPresenter.ViewHolder> rowVHCaptor =
+                ArgumentCaptor.forClass(RowPresenter.ViewHolder.class);
+        ArgumentCaptor<Row> rowCaptor = ArgumentCaptor.forClass(Row.class);
+
+
+        // First navigate left within PlaybackControlsRow items.
+        verify(clickedListener, times(0)).onItemClicked(any(Presenter.ViewHolder.class),
+                any(Object.class), any(RowPresenter.ViewHolder.class), any(Row.class));
+        sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+        verify(clickedListener, times(1)).onItemClicked(itemVHCaptor.capture(),
+                itemCaptor.capture(), rowVHCaptor.capture(), rowCaptor.capture());
+        assertSame("Same controls row should be passed to the listener", controlsRow,
+                rowCaptor.getValue());
+        assertSame("The clicked action should be playPause", playPause, itemCaptor.getValue());
+
+        sendKeys(KeyEvent.KEYCODE_DPAD_LEFT);
+        verify(clickedListener, times(1)).onItemClicked(any(Presenter.ViewHolder.class),
+                any(Object.class), any(RowPresenter.ViewHolder.class), any(Row.class));
+        sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+        verify(clickedListener, times(2)).onItemClicked(itemVHCaptor.capture(),
+                itemCaptor.capture(), rowVHCaptor.capture(), rowCaptor.capture());
+        assertSame("Same controls row should be passed to the listener", controlsRow,
+                rowCaptor.getValue());
+        assertSame("The clicked action should be rewind", rewind, itemCaptor.getValue());
+
+        sendKeys(KeyEvent.KEYCODE_DPAD_LEFT);
+        verify(clickedListener, times(2)).onItemClicked(any(Presenter.ViewHolder.class),
+                any(Object.class), any(RowPresenter.ViewHolder.class), any(Row.class));
+        sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+        verify(clickedListener, times(3)).onItemClicked(itemVHCaptor.capture(),
+                itemCaptor.capture(), rowVHCaptor.capture(), rowCaptor.capture());
+        assertSame("Same controls row should be passed to the listener", controlsRow,
+                rowCaptor.getValue());
+        assertSame("The clicked action should be thumbsUp", thumbsUp, itemCaptor.getValue());
+
+        // Now navigate down to a ListRow item.
+        ListRow listRow0 = (ListRow) fragment.getAdapter().get(1);
+
+        sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+        waitForScrollIdle(fragment.getVerticalGridView());
+        verify(clickedListener, times(3)).onItemClicked(any(Presenter.ViewHolder.class),
+                any(Object.class), any(RowPresenter.ViewHolder.class), any(Row.class));
+        sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+        verify(clickedListener, times(4)).onItemClicked(itemVHCaptor.capture(),
+                itemCaptor.capture(), rowVHCaptor.capture(), rowCaptor.capture());
+        assertSame("Same list row should be passed to the listener", listRow0,
+                rowCaptor.getValue());
+        boolean listRowItemPassed = (itemCaptor.getValue() == listRow0.getAdapter().get(0)
+                || itemCaptor.getValue() == listRow0.getAdapter().get(1));
+        assertTrue("None of the items in the first ListRow are passed to the click listener.",
+                listRowItemPassed);
+    }
+
+    @FlakyTest
+    @Suppress
+    @Test
+    public void alignmentRowToBottom() throws Throwable {
+        SingleSupportFragmentTestActivity activity =
+                launchAndWaitActivity(PlaybackTestSupportFragment.class, 1000);
+        final PlaybackTestSupportFragment fragment = (PlaybackTestSupportFragment) activity.getTestFragment();
+
+        assertTrue(fragment.getAdapter().size() > 2);
+
+        View playRow = fragment.getVerticalGridView().getChildAt(0);
+        assertTrue(playRow.hasFocus());
+        assertEquals(playRow.getResources().getDimensionPixelSize(
+                android.support.v17.leanback.test.R.dimen.lb_playback_controls_padding_bottom),
+                fragment.getVerticalGridView().getHeight() - playRow.getBottom());
+
+        activityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                fragment.getVerticalGridView().setSelectedPositionSmooth(
+                        fragment.getAdapter().size() - 1);
+            }
+        });
+        waitForScrollIdle(fragment.getVerticalGridView());
+
+        View lastRow = fragment.getVerticalGridView().getChildAt(
+                fragment.getVerticalGridView().getChildCount() - 1);
+        assertEquals(fragment.getAdapter().size() - 1,
+                fragment.getVerticalGridView().getChildAdapterPosition(lastRow));
+        assertTrue(lastRow.hasFocus());
+        assertEquals(lastRow.getResources().getDimensionPixelSize(
+                android.support.v17.leanback.test.R.dimen.lb_playback_controls_padding_bottom),
+                fragment.getVerticalGridView().getHeight() - lastRow.getBottom());
+    }
+
+    public static class PurePlaybackSupportFragment extends PlaybackSupportFragment {
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            setFadingEnabled(false);
+            PlaybackControlsRow row = new PlaybackControlsRow();
+            SparseArrayObjectAdapter primaryAdapter = new SparseArrayObjectAdapter(
+                    new ControlButtonPresenterSelector());
+            primaryAdapter.set(0, new PlaybackControlsRow.SkipPreviousAction(getActivity()));
+            primaryAdapter.set(1, new PlaybackControlsRow.PlayPauseAction(getActivity()));
+            primaryAdapter.set(2, new PlaybackControlsRow.SkipNextAction(getActivity()));
+            row.setPrimaryActionsAdapter(primaryAdapter);
+            row.setSecondaryActionsAdapter(null);
+            setPlaybackRow(row);
+            setPlaybackRowPresenter(new PlaybackControlsRowPresenter());
+        }
+    }
+
+    @Test
+    public void setupRowAndPresenterWithoutGlue() {
+        SingleSupportFragmentTestActivity activity =
+                launchAndWaitActivity(PurePlaybackSupportFragment.class, 1000);
+        final PurePlaybackSupportFragment fragment = (PurePlaybackSupportFragment)
+                activity.getTestFragment();
+
+        assertTrue(fragment.getAdapter().size() == 1);
+        View playRow = fragment.getVerticalGridView().getChildAt(0);
+        assertTrue(playRow.hasFocus());
+        assertEquals(playRow.getResources().getDimensionPixelSize(
+                android.support.v17.leanback.test.R.dimen.lb_playback_controls_padding_bottom),
+                fragment.getVerticalGridView().getHeight() - playRow.getBottom());
+    }
+
+    public static class ControlGlueFragment extends PlaybackSupportFragment {
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            int[] ffspeeds = new int[] {PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0,
+                    PlaybackControlGlue.PLAYBACK_SPEED_FAST_L1};
+            PlaybackGlue glue = new PlaybackControlGlue(
+                    getActivity(), ffspeeds) {
+                @Override
+                public boolean hasValidMedia() {
+                    return true;
+                }
+
+                @Override
+                public boolean isMediaPlaying() {
+                    return false;
+                }
+
+                @Override
+                public CharSequence getMediaTitle() {
+                    return "Title";
+                }
+
+                @Override
+                public CharSequence getMediaSubtitle() {
+                    return "SubTitle";
+                }
+
+                @Override
+                public int getMediaDuration() {
+                    return 100;
+                }
+
+                @Override
+                public Drawable getMediaArt() {
+                    return null;
+                }
+
+                @Override
+                public long getSupportedActions() {
+                    return PlaybackControlGlue.ACTION_PLAY_PAUSE;
+                }
+
+                @Override
+                public int getCurrentSpeedId() {
+                    return PlaybackControlGlue.PLAYBACK_SPEED_PAUSED;
+                }
+
+                @Override
+                public int getCurrentPosition() {
+                    return 50;
+                }
+            };
+            glue.setHost(new PlaybackSupportFragmentGlueHost(this));
+        }
+    }
+
+    @Test
+    public void setupWithControlGlue() throws Throwable {
+        SingleSupportFragmentTestActivity activity =
+                launchAndWaitActivity(ControlGlueFragment.class, 1000);
+        final ControlGlueFragment fragment = (ControlGlueFragment)
+                activity.getTestFragment();
+
+        assertTrue(fragment.getAdapter().size() == 1);
+
+        View playRow = fragment.getVerticalGridView().getChildAt(0);
+        assertTrue(playRow.hasFocus());
+        assertEquals(playRow.getResources().getDimensionPixelSize(
+                android.support.v17.leanback.test.R.dimen.lb_playback_controls_padding_bottom),
+                fragment.getVerticalGridView().getHeight() - playRow.getBottom());
+    }
+}
diff --git a/leanback/tests/java/android/support/v17/leanback/app/PlaybackTestFragment.java b/leanback/tests/java/android/support/v17/leanback/app/PlaybackTestFragment.java
new file mode 100644
index 0000000..47b644c
--- /dev/null
+++ b/leanback/tests/java/android/support/v17/leanback/app/PlaybackTestFragment.java
@@ -0,0 +1,371 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from PlaybackTestSupportFragment.java.  DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.v17.leanback.media.PlaybackControlGlue;
+import android.support.v17.leanback.test.R;
+import android.support.v17.leanback.widget.Action;
+import android.support.v17.leanback.widget.ArrayObjectAdapter;
+import android.support.v17.leanback.widget.ClassPresenterSelector;
+import android.support.v17.leanback.widget.HeaderItem;
+import android.support.v17.leanback.widget.ListRow;
+import android.support.v17.leanback.widget.ListRowPresenter;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.PlaybackControlsRow;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.PresenterSelector;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+import android.widget.Toast;
+
+public class PlaybackTestFragment extends PlaybackFragment {
+    private static final String TAG = "PlaybackTestFragment";
+
+    /**
+     * Change this to choose a different overlay background.
+     */
+    private static final int BACKGROUND_TYPE = PlaybackFragment.BG_LIGHT;
+
+    /**
+     * Change this to select hidden
+     */
+    private static final boolean SECONDARY_HIDDEN = false;
+
+    /**
+     * Change the number of related content rows.
+     */
+    private static final int RELATED_CONTENT_ROWS = 3;
+
+    private android.support.v17.leanback.media.PlaybackControlGlue mGlue;
+    boolean mDestroyCalled;
+
+    @Override
+    public SparseArrayObjectAdapter getAdapter() {
+        return (SparseArrayObjectAdapter) super.getAdapter();
+    }
+
+    private OnItemViewClickedListener mOnItemViewClickedListener = new OnItemViewClickedListener() {
+        @Override
+        public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
+                                  RowPresenter.ViewHolder rowViewHolder, Row row) {
+            Log.d(TAG, "onItemClicked: " + item + " row " + row);
+        }
+    };
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        mDestroyCalled = true;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        Log.i(TAG, "onCreate");
+        super.onCreate(savedInstanceState);
+
+        setBackgroundType(BACKGROUND_TYPE);
+
+        createComponents(getActivity());
+        setOnItemViewClickedListener(mOnItemViewClickedListener);
+    }
+
+    private void createComponents(Context context) {
+        mGlue = new PlaybackControlHelper(context) {
+            @Override
+            public int getUpdatePeriod() {
+                long totalTime = getControlsRow().getDuration();
+                if (getView() == null || getView().getWidth() == 0 || totalTime <= 0) {
+                    return 1000;
+                }
+                return 16;
+            }
+
+            @Override
+            public void onActionClicked(Action action) {
+                if (action.getId() == R.id.lb_control_picture_in_picture) {
+                    getActivity().enterPictureInPictureMode();
+                    return;
+                }
+                super.onActionClicked(action);
+            }
+
+            @Override
+            protected void onCreateControlsRowAndPresenter() {
+                super.onCreateControlsRowAndPresenter();
+                getControlsRowPresenter().setSecondaryActionsHidden(SECONDARY_HIDDEN);
+            }
+        };
+
+        mGlue.setHost(new PlaybackFragmentGlueHost(this));
+        ClassPresenterSelector selector = new ClassPresenterSelector();
+        selector.addClassPresenter(ListRow.class, new ListRowPresenter());
+
+        setAdapter(new SparseArrayObjectAdapter(selector));
+
+        // Add related content rows
+        for (int i = 0; i < RELATED_CONTENT_ROWS; ++i) {
+            ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(new StringPresenter());
+            listRowAdapter.add("Some related content");
+            listRowAdapter.add("Other related content");
+            HeaderItem header = new HeaderItem(i, "Row " + i);
+            getAdapter().set(1 + i, new ListRow(header, listRowAdapter));
+        }
+    }
+
+    public PlaybackControlGlue getGlue() {
+        return mGlue;
+    }
+
+    abstract static class PlaybackControlHelper extends PlaybackControlGlue {
+        /**
+         * Change the location of the thumbs up/down controls
+         */
+        private static final boolean THUMBS_PRIMARY = true;
+
+        private static final String FAUX_TITLE = "A short song of silence";
+        private static final String FAUX_SUBTITLE = "2014";
+        private static final int FAUX_DURATION = 33 * 1000;
+
+        // These should match the playback service FF behavior
+        private static int[] sFastForwardSpeeds = { 2, 3, 4, 5 };
+
+        private boolean mIsPlaying;
+        private int mSpeed = PLAYBACK_SPEED_PAUSED;
+        private long mStartTime;
+        private long mStartPosition = 0;
+
+        private PlaybackControlsRow.RepeatAction mRepeatAction;
+        private PlaybackControlsRow.ThumbsUpAction mThumbsUpAction;
+        private PlaybackControlsRow.ThumbsDownAction mThumbsDownAction;
+        private PlaybackControlsRow.PictureInPictureAction mPipAction;
+        private static Handler sProgressHandler = new Handler();
+
+        private final Runnable mUpdateProgressRunnable = new Runnable() {
+            @Override
+            public void run() {
+                updateProgress();
+                sProgressHandler.postDelayed(this, getUpdatePeriod());
+            }
+        };
+
+        PlaybackControlHelper(Context context) {
+            super(context, sFastForwardSpeeds);
+            mThumbsUpAction = new PlaybackControlsRow.ThumbsUpAction(context);
+            mThumbsUpAction.setIndex(PlaybackControlsRow.ThumbsUpAction.INDEX_OUTLINE);
+            mThumbsDownAction = new PlaybackControlsRow.ThumbsDownAction(context);
+            mThumbsDownAction.setIndex(PlaybackControlsRow.ThumbsDownAction.INDEX_OUTLINE);
+            mRepeatAction = new PlaybackControlsRow.RepeatAction(context);
+            mPipAction = new PlaybackControlsRow.PictureInPictureAction(context);
+        }
+
+        @Override
+        protected SparseArrayObjectAdapter createPrimaryActionsAdapter(
+                PresenterSelector presenterSelector) {
+            SparseArrayObjectAdapter adapter = new SparseArrayObjectAdapter(presenterSelector);
+            if (THUMBS_PRIMARY) {
+                adapter.set(PlaybackControlGlue.ACTION_CUSTOM_LEFT_FIRST, mThumbsUpAction);
+                adapter.set(PlaybackControlGlue.ACTION_CUSTOM_RIGHT_FIRST, mThumbsDownAction);
+            }
+            return adapter;
+        }
+
+        @Override
+        public void onActionClicked(Action action) {
+            if (shouldDispatchAction(action)) {
+                dispatchAction(action);
+                return;
+            }
+            super.onActionClicked(action);
+        }
+
+        @Override
+        public boolean onKey(View view, int keyCode, KeyEvent keyEvent) {
+            if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
+                Action action = getControlsRow().getActionForKeyCode(keyEvent.getKeyCode());
+                if (shouldDispatchAction(action)) {
+                    dispatchAction(action);
+                    return true;
+                }
+            }
+            return super.onKey(view, keyCode, keyEvent);
+        }
+
+        private boolean shouldDispatchAction(Action action) {
+            return action == mRepeatAction || action == mThumbsUpAction
+                    || action == mThumbsDownAction;
+        }
+
+        private void dispatchAction(Action action) {
+            Toast.makeText(getContext(), action.toString(), Toast.LENGTH_SHORT).show();
+            PlaybackControlsRow.MultiAction multiAction = (PlaybackControlsRow.MultiAction) action;
+            multiAction.nextIndex();
+            notifyActionChanged(multiAction);
+        }
+
+        private void notifyActionChanged(PlaybackControlsRow.MultiAction action) {
+            int index;
+            index = getPrimaryActionsAdapter().indexOf(action);
+            if (index >= 0) {
+                getPrimaryActionsAdapter().notifyArrayItemRangeChanged(index, 1);
+            } else {
+                index = getSecondaryActionsAdapter().indexOf(action);
+                if (index >= 0) {
+                    getSecondaryActionsAdapter().notifyArrayItemRangeChanged(index, 1);
+                }
+            }
+        }
+
+        private SparseArrayObjectAdapter getPrimaryActionsAdapter() {
+            return (SparseArrayObjectAdapter) getControlsRow().getPrimaryActionsAdapter();
+        }
+
+        private ArrayObjectAdapter getSecondaryActionsAdapter() {
+            return (ArrayObjectAdapter) getControlsRow().getSecondaryActionsAdapter();
+        }
+
+        @Override
+        public boolean hasValidMedia() {
+            return true;
+        }
+
+        @Override
+        public boolean isMediaPlaying() {
+            return mIsPlaying;
+        }
+
+        @Override
+        public CharSequence getMediaTitle() {
+            return FAUX_TITLE;
+        }
+
+        @Override
+        public CharSequence getMediaSubtitle() {
+            return FAUX_SUBTITLE;
+        }
+
+        @Override
+        public int getMediaDuration() {
+            return FAUX_DURATION;
+        }
+
+        @Override
+        public Drawable getMediaArt() {
+            return null;
+        }
+
+        @Override
+        public long getSupportedActions() {
+            return ACTION_PLAY_PAUSE | ACTION_FAST_FORWARD | ACTION_REWIND;
+        }
+
+        @Override
+        public int getCurrentSpeedId() {
+            return mSpeed;
+        }
+
+        @Override
+        public int getCurrentPosition() {
+            int speed;
+            if (mSpeed == PlaybackControlGlue.PLAYBACK_SPEED_PAUSED) {
+                speed = 0;
+            } else if (mSpeed == PlaybackControlGlue.PLAYBACK_SPEED_NORMAL) {
+                speed = 1;
+            } else if (mSpeed >= PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0) {
+                int index = mSpeed - PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0;
+                speed = getFastForwardSpeeds()[index];
+            } else if (mSpeed <= -PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0) {
+                int index = -mSpeed - PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0;
+                speed = -getRewindSpeeds()[index];
+            } else {
+                return -1;
+            }
+            long position = mStartPosition + (System.currentTimeMillis() - mStartTime) * speed;
+            if (position > getMediaDuration()) {
+                position = getMediaDuration();
+                onPlaybackComplete(true);
+            } else if (position < 0) {
+                position = 0;
+                onPlaybackComplete(false);
+            }
+            return (int) position;
+        }
+
+        void onPlaybackComplete(final boolean ended) {
+            sProgressHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    if (mRepeatAction.getIndex() == PlaybackControlsRow.RepeatAction.INDEX_NONE) {
+                        pause();
+                    } else {
+                        play(PlaybackControlGlue.PLAYBACK_SPEED_NORMAL);
+                    }
+                    mStartPosition = 0;
+                    onStateChanged();
+                }
+            });
+        }
+
+        @Override
+        public void play(int speed) {
+            if (speed == mSpeed) {
+                return;
+            }
+            mStartPosition = getCurrentPosition();
+            mSpeed = speed;
+            mIsPlaying = true;
+            mStartTime = System.currentTimeMillis();
+        }
+
+        @Override
+        public void pause() {
+            if (mSpeed == PLAYBACK_SPEED_PAUSED) {
+                return;
+            }
+            mStartPosition = getCurrentPosition();
+            mSpeed = PLAYBACK_SPEED_PAUSED;
+            mIsPlaying = false;
+        }
+
+        @Override
+        public void next() {
+            // Not supported
+        }
+
+        @Override
+        public void previous() {
+            // Not supported
+        }
+
+        @Override
+        public void enableProgressUpdating(boolean enable) {
+            sProgressHandler.removeCallbacks(mUpdateProgressRunnable);
+            if (enable) {
+                mUpdateProgressRunnable.run();
+            }
+        }
+    }
+}
diff --git a/leanback/tests/java/android/support/v17/leanback/app/PlaybackTestSupportFragment.java b/leanback/tests/java/android/support/v17/leanback/app/PlaybackTestSupportFragment.java
new file mode 100644
index 0000000..dc93a1c
--- /dev/null
+++ b/leanback/tests/java/android/support/v17/leanback/app/PlaybackTestSupportFragment.java
@@ -0,0 +1,368 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.v17.leanback.media.PlaybackControlGlue;
+import android.support.v17.leanback.test.R;
+import android.support.v17.leanback.widget.Action;
+import android.support.v17.leanback.widget.ArrayObjectAdapter;
+import android.support.v17.leanback.widget.ClassPresenterSelector;
+import android.support.v17.leanback.widget.HeaderItem;
+import android.support.v17.leanback.widget.ListRow;
+import android.support.v17.leanback.widget.ListRowPresenter;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.PlaybackControlsRow;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.PresenterSelector;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+import android.widget.Toast;
+
+public class PlaybackTestSupportFragment extends PlaybackSupportFragment {
+    private static final String TAG = "PlaybackTestSupportFragment";
+
+    /**
+     * Change this to choose a different overlay background.
+     */
+    private static final int BACKGROUND_TYPE = PlaybackSupportFragment.BG_LIGHT;
+
+    /**
+     * Change this to select hidden
+     */
+    private static final boolean SECONDARY_HIDDEN = false;
+
+    /**
+     * Change the number of related content rows.
+     */
+    private static final int RELATED_CONTENT_ROWS = 3;
+
+    private android.support.v17.leanback.media.PlaybackControlGlue mGlue;
+    boolean mDestroyCalled;
+
+    @Override
+    public SparseArrayObjectAdapter getAdapter() {
+        return (SparseArrayObjectAdapter) super.getAdapter();
+    }
+
+    private OnItemViewClickedListener mOnItemViewClickedListener = new OnItemViewClickedListener() {
+        @Override
+        public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
+                                  RowPresenter.ViewHolder rowViewHolder, Row row) {
+            Log.d(TAG, "onItemClicked: " + item + " row " + row);
+        }
+    };
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        mDestroyCalled = true;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        Log.i(TAG, "onCreate");
+        super.onCreate(savedInstanceState);
+
+        setBackgroundType(BACKGROUND_TYPE);
+
+        createComponents(getActivity());
+        setOnItemViewClickedListener(mOnItemViewClickedListener);
+    }
+
+    private void createComponents(Context context) {
+        mGlue = new PlaybackControlHelper(context) {
+            @Override
+            public int getUpdatePeriod() {
+                long totalTime = getControlsRow().getDuration();
+                if (getView() == null || getView().getWidth() == 0 || totalTime <= 0) {
+                    return 1000;
+                }
+                return 16;
+            }
+
+            @Override
+            public void onActionClicked(Action action) {
+                if (action.getId() == R.id.lb_control_picture_in_picture) {
+                    getActivity().enterPictureInPictureMode();
+                    return;
+                }
+                super.onActionClicked(action);
+            }
+
+            @Override
+            protected void onCreateControlsRowAndPresenter() {
+                super.onCreateControlsRowAndPresenter();
+                getControlsRowPresenter().setSecondaryActionsHidden(SECONDARY_HIDDEN);
+            }
+        };
+
+        mGlue.setHost(new PlaybackSupportFragmentGlueHost(this));
+        ClassPresenterSelector selector = new ClassPresenterSelector();
+        selector.addClassPresenter(ListRow.class, new ListRowPresenter());
+
+        setAdapter(new SparseArrayObjectAdapter(selector));
+
+        // Add related content rows
+        for (int i = 0; i < RELATED_CONTENT_ROWS; ++i) {
+            ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(new StringPresenter());
+            listRowAdapter.add("Some related content");
+            listRowAdapter.add("Other related content");
+            HeaderItem header = new HeaderItem(i, "Row " + i);
+            getAdapter().set(1 + i, new ListRow(header, listRowAdapter));
+        }
+    }
+
+    public PlaybackControlGlue getGlue() {
+        return mGlue;
+    }
+
+    abstract static class PlaybackControlHelper extends PlaybackControlGlue {
+        /**
+         * Change the location of the thumbs up/down controls
+         */
+        private static final boolean THUMBS_PRIMARY = true;
+
+        private static final String FAUX_TITLE = "A short song of silence";
+        private static final String FAUX_SUBTITLE = "2014";
+        private static final int FAUX_DURATION = 33 * 1000;
+
+        // These should match the playback service FF behavior
+        private static int[] sFastForwardSpeeds = { 2, 3, 4, 5 };
+
+        private boolean mIsPlaying;
+        private int mSpeed = PLAYBACK_SPEED_PAUSED;
+        private long mStartTime;
+        private long mStartPosition = 0;
+
+        private PlaybackControlsRow.RepeatAction mRepeatAction;
+        private PlaybackControlsRow.ThumbsUpAction mThumbsUpAction;
+        private PlaybackControlsRow.ThumbsDownAction mThumbsDownAction;
+        private PlaybackControlsRow.PictureInPictureAction mPipAction;
+        private static Handler sProgressHandler = new Handler();
+
+        private final Runnable mUpdateProgressRunnable = new Runnable() {
+            @Override
+            public void run() {
+                updateProgress();
+                sProgressHandler.postDelayed(this, getUpdatePeriod());
+            }
+        };
+
+        PlaybackControlHelper(Context context) {
+            super(context, sFastForwardSpeeds);
+            mThumbsUpAction = new PlaybackControlsRow.ThumbsUpAction(context);
+            mThumbsUpAction.setIndex(PlaybackControlsRow.ThumbsUpAction.INDEX_OUTLINE);
+            mThumbsDownAction = new PlaybackControlsRow.ThumbsDownAction(context);
+            mThumbsDownAction.setIndex(PlaybackControlsRow.ThumbsDownAction.INDEX_OUTLINE);
+            mRepeatAction = new PlaybackControlsRow.RepeatAction(context);
+            mPipAction = new PlaybackControlsRow.PictureInPictureAction(context);
+        }
+
+        @Override
+        protected SparseArrayObjectAdapter createPrimaryActionsAdapter(
+                PresenterSelector presenterSelector) {
+            SparseArrayObjectAdapter adapter = new SparseArrayObjectAdapter(presenterSelector);
+            if (THUMBS_PRIMARY) {
+                adapter.set(PlaybackControlGlue.ACTION_CUSTOM_LEFT_FIRST, mThumbsUpAction);
+                adapter.set(PlaybackControlGlue.ACTION_CUSTOM_RIGHT_FIRST, mThumbsDownAction);
+            }
+            return adapter;
+        }
+
+        @Override
+        public void onActionClicked(Action action) {
+            if (shouldDispatchAction(action)) {
+                dispatchAction(action);
+                return;
+            }
+            super.onActionClicked(action);
+        }
+
+        @Override
+        public boolean onKey(View view, int keyCode, KeyEvent keyEvent) {
+            if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
+                Action action = getControlsRow().getActionForKeyCode(keyEvent.getKeyCode());
+                if (shouldDispatchAction(action)) {
+                    dispatchAction(action);
+                    return true;
+                }
+            }
+            return super.onKey(view, keyCode, keyEvent);
+        }
+
+        private boolean shouldDispatchAction(Action action) {
+            return action == mRepeatAction || action == mThumbsUpAction
+                    || action == mThumbsDownAction;
+        }
+
+        private void dispatchAction(Action action) {
+            Toast.makeText(getContext(), action.toString(), Toast.LENGTH_SHORT).show();
+            PlaybackControlsRow.MultiAction multiAction = (PlaybackControlsRow.MultiAction) action;
+            multiAction.nextIndex();
+            notifyActionChanged(multiAction);
+        }
+
+        private void notifyActionChanged(PlaybackControlsRow.MultiAction action) {
+            int index;
+            index = getPrimaryActionsAdapter().indexOf(action);
+            if (index >= 0) {
+                getPrimaryActionsAdapter().notifyArrayItemRangeChanged(index, 1);
+            } else {
+                index = getSecondaryActionsAdapter().indexOf(action);
+                if (index >= 0) {
+                    getSecondaryActionsAdapter().notifyArrayItemRangeChanged(index, 1);
+                }
+            }
+        }
+
+        private SparseArrayObjectAdapter getPrimaryActionsAdapter() {
+            return (SparseArrayObjectAdapter) getControlsRow().getPrimaryActionsAdapter();
+        }
+
+        private ArrayObjectAdapter getSecondaryActionsAdapter() {
+            return (ArrayObjectAdapter) getControlsRow().getSecondaryActionsAdapter();
+        }
+
+        @Override
+        public boolean hasValidMedia() {
+            return true;
+        }
+
+        @Override
+        public boolean isMediaPlaying() {
+            return mIsPlaying;
+        }
+
+        @Override
+        public CharSequence getMediaTitle() {
+            return FAUX_TITLE;
+        }
+
+        @Override
+        public CharSequence getMediaSubtitle() {
+            return FAUX_SUBTITLE;
+        }
+
+        @Override
+        public int getMediaDuration() {
+            return FAUX_DURATION;
+        }
+
+        @Override
+        public Drawable getMediaArt() {
+            return null;
+        }
+
+        @Override
+        public long getSupportedActions() {
+            return ACTION_PLAY_PAUSE | ACTION_FAST_FORWARD | ACTION_REWIND;
+        }
+
+        @Override
+        public int getCurrentSpeedId() {
+            return mSpeed;
+        }
+
+        @Override
+        public int getCurrentPosition() {
+            int speed;
+            if (mSpeed == PlaybackControlGlue.PLAYBACK_SPEED_PAUSED) {
+                speed = 0;
+            } else if (mSpeed == PlaybackControlGlue.PLAYBACK_SPEED_NORMAL) {
+                speed = 1;
+            } else if (mSpeed >= PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0) {
+                int index = mSpeed - PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0;
+                speed = getFastForwardSpeeds()[index];
+            } else if (mSpeed <= -PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0) {
+                int index = -mSpeed - PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0;
+                speed = -getRewindSpeeds()[index];
+            } else {
+                return -1;
+            }
+            long position = mStartPosition + (System.currentTimeMillis() - mStartTime) * speed;
+            if (position > getMediaDuration()) {
+                position = getMediaDuration();
+                onPlaybackComplete(true);
+            } else if (position < 0) {
+                position = 0;
+                onPlaybackComplete(false);
+            }
+            return (int) position;
+        }
+
+        void onPlaybackComplete(final boolean ended) {
+            sProgressHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    if (mRepeatAction.getIndex() == PlaybackControlsRow.RepeatAction.INDEX_NONE) {
+                        pause();
+                    } else {
+                        play(PlaybackControlGlue.PLAYBACK_SPEED_NORMAL);
+                    }
+                    mStartPosition = 0;
+                    onStateChanged();
+                }
+            });
+        }
+
+        @Override
+        public void play(int speed) {
+            if (speed == mSpeed) {
+                return;
+            }
+            mStartPosition = getCurrentPosition();
+            mSpeed = speed;
+            mIsPlaying = true;
+            mStartTime = System.currentTimeMillis();
+        }
+
+        @Override
+        public void pause() {
+            if (mSpeed == PLAYBACK_SPEED_PAUSED) {
+                return;
+            }
+            mStartPosition = getCurrentPosition();
+            mSpeed = PLAYBACK_SPEED_PAUSED;
+            mIsPlaying = false;
+        }
+
+        @Override
+        public void next() {
+            // Not supported
+        }
+
+        @Override
+        public void previous() {
+            // Not supported
+        }
+
+        @Override
+        public void enableProgressUpdating(boolean enable) {
+            sProgressHandler.removeCallbacks(mUpdateProgressRunnable);
+            if (enable) {
+                mUpdateProgressRunnable.run();
+            }
+        }
+    }
+}
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/ProgressBarManagerTest.java b/leanback/tests/java/android/support/v17/leanback/app/ProgressBarManagerTest.java
similarity index 100%
rename from v17/leanback/tests/java/android/support/v17/leanback/app/ProgressBarManagerTest.java
rename to leanback/tests/java/android/support/v17/leanback/app/ProgressBarManagerTest.java
diff --git a/leanback/tests/java/android/support/v17/leanback/app/RowsFragmentTest.java b/leanback/tests/java/android/support/v17/leanback/app/RowsFragmentTest.java
new file mode 100644
index 0000000..dc10a05
--- /dev/null
+++ b/leanback/tests/java/android/support/v17/leanback/app/RowsFragmentTest.java
@@ -0,0 +1,1354 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from RowsSupportFragmentTest.java.  DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v17.leanback.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;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import android.graphics.Rect;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.filters.SdkSuppress;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v17.leanback.test.R;
+import android.support.v17.leanback.testutils.PollingCheck;
+import android.support.v17.leanback.widget.ArrayObjectAdapter;
+import android.support.v17.leanback.widget.HeaderItem;
+import android.support.v17.leanback.widget.HorizontalGridView;
+import android.support.v17.leanback.widget.ItemBridgeAdapter;
+import android.support.v17.leanback.widget.ListRow;
+import android.support.v17.leanback.widget.ListRowPresenter;
+import android.support.v17.leanback.widget.ObjectAdapter;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.PageRow;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.support.v17.leanback.widget.SinglePresenterSelector;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.app.Fragment;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class RowsFragmentTest extends SingleFragmentTestBase {
+
+    static final StringPresenter sCardPresenter = new StringPresenter();
+
+    static void loadData(ArrayObjectAdapter adapter, int numRows, int repeatPerRow) {
+        for (int i = 0; i < numRows; ++i) {
+            ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(sCardPresenter);
+            int index = 0;
+            for (int j = 0; j < repeatPerRow; ++j) {
+                listRowAdapter.add("Hello world-" + (index++));
+                listRowAdapter.add("This is a test-" + (index++));
+                listRowAdapter.add("Android TV-" + (index++));
+                listRowAdapter.add("Leanback-" + (index++));
+                listRowAdapter.add("Hello world-" + (index++));
+                listRowAdapter.add("Android TV-" + (index++));
+                listRowAdapter.add("Leanback-" + (index++));
+                listRowAdapter.add("GuidedStepFragment-" + (index++));
+            }
+            HeaderItem header = new HeaderItem(i, "Row " + i);
+            adapter.add(new ListRow(header, listRowAdapter));
+        }
+    }
+
+    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) {
+            super.onCreate(savedInstanceState);
+            ListRowPresenter lrp = new ListRowPresenter();
+            ArrayObjectAdapter adapter = new ArrayObjectAdapter(lrp);
+            setAdapter(adapter);
+            loadData(adapter, 10, 1);
+        }
+    }
+
+    @Test
+    public void defaultAlignment() throws Throwable {
+        SingleFragmentTestActivity activity = launchAndWaitActivity(F_defaultAlignment.class, 1000);
+
+        final Rect rect = new Rect();
+
+        final VerticalGridView gridView = ((RowsFragment) activity.getTestFragment())
+                .getVerticalGridView();
+        View row0 = gridView.findViewHolderForAdapterPosition(0).itemView;
+        rect.set(0, 0, row0.getWidth(), row0.getHeight());
+        gridView.offsetDescendantRectToMyCoords(row0, rect);
+        assertEquals("First row is initially aligned to top of screen", 0, rect.top);
+
+        sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+        waitForScrollIdle(gridView);
+        View row1 = gridView.findViewHolderForAdapterPosition(1).itemView;
+        PollingCheck.waitFor(new PollingCheck.ViewStableOnScreen(row1));
+
+        rect.set(0, 0, row1.getWidth(), row1.getHeight());
+        gridView.offsetDescendantRectToMyCoords(row1, rect);
+        assertTrue("Second row should not be aligned to top of screen", rect.top > 0);
+    }
+
+    public static class F_selectBeforeSetAdapter extends RowsFragment {
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            setSelectedPosition(7, false);
+            new Handler().postDelayed(new Runnable() {
+                @Override
+                public void run() {
+                    getVerticalGridView().requestLayout();
+                }
+            }, 100);
+            new Handler().postDelayed(new Runnable() {
+                @Override
+                public void run() {
+                    ListRowPresenter lrp = new ListRowPresenter();
+                    ArrayObjectAdapter adapter = new ArrayObjectAdapter(lrp);
+                    setAdapter(adapter);
+                    loadData(adapter, 10, 1);
+                }
+            }, 1000);
+        }
+    }
+
+    @Test
+    public void selectBeforeSetAdapter() throws InterruptedException {
+        SingleFragmentTestActivity activity =
+                launchAndWaitActivity(F_selectBeforeSetAdapter.class, 2000);
+
+        final VerticalGridView gridView = ((RowsFragment) activity.getTestFragment())
+                .getVerticalGridView();
+        assertEquals(7, gridView.getSelectedPosition());
+        assertNotNull(gridView.findViewHolderForAdapterPosition(7));
+    }
+
+    public static class F_selectBeforeAddData extends RowsFragment {
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            ListRowPresenter lrp = new ListRowPresenter();
+            final ArrayObjectAdapter adapter = new ArrayObjectAdapter(lrp);
+            setAdapter(adapter);
+            setSelectedPosition(7, false);
+            new Handler().postDelayed(new Runnable() {
+                @Override
+                public void run() {
+                    getVerticalGridView().requestLayout();
+                }
+            }, 100);
+            new Handler().postDelayed(new Runnable() {
+                @Override
+                public void run() {
+                    loadData(adapter, 10, 1);
+                }
+            }, 1000);
+        }
+    }
+
+    @Test
+    public void selectBeforeAddData() throws InterruptedException {
+        SingleFragmentTestActivity activity =
+                launchAndWaitActivity(F_selectBeforeAddData.class, 2000);
+
+        final VerticalGridView gridView = ((RowsFragment) activity.getTestFragment())
+                .getVerticalGridView();
+        assertEquals(7, gridView.getSelectedPosition());
+        assertNotNull(gridView.findViewHolderForAdapterPosition(7));
+    }
+
+    public static class F_selectAfterAddData extends RowsFragment {
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            ListRowPresenter lrp = new ListRowPresenter();
+            final ArrayObjectAdapter adapter = new ArrayObjectAdapter(lrp);
+            setAdapter(adapter);
+            loadData(adapter, 10, 1);
+            new Handler().postDelayed(new Runnable() {
+                @Override
+                public void run() {
+                    setSelectedPosition(7, false);
+                }
+            }, 1000);
+        }
+    }
+
+    @Test
+    public void selectAfterAddData() throws InterruptedException {
+        SingleFragmentTestActivity activity =
+                launchAndWaitActivity(F_selectAfterAddData.class, 2000);
+
+        final VerticalGridView gridView = ((RowsFragment) activity.getTestFragment())
+                .getVerticalGridView();
+        assertEquals(7, gridView.getSelectedPosition());
+        assertNotNull(gridView.findViewHolderForAdapterPosition(7));
+    }
+
+    static WeakReference<F_restoreSelection> sLastF_restoreSelection;
+
+    public static class F_restoreSelection extends RowsFragment {
+        public F_restoreSelection() {
+            sLastF_restoreSelection = new WeakReference<F_restoreSelection>(this);
+        }
+
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            ListRowPresenter lrp = new ListRowPresenter();
+            final ArrayObjectAdapter adapter = new ArrayObjectAdapter(lrp);
+            setAdapter(adapter);
+            loadData(adapter, 10, 1);
+            if (savedInstanceState == null) {
+                setSelectedPosition(7, false);
+            }
+        }
+    }
+
+    @Test
+    public void restoreSelection() {
+        final SingleFragmentTestActivity activity =
+                launchAndWaitActivity(F_restoreSelection.class, 1000);
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        activity.recreate();
+                    }
+                }
+        );
+        SystemClock.sleep(1000);
+
+        // mActivity is invalid after recreate(), a new Activity instance is created
+        // but we could get Fragment from static variable.
+        RowsFragment fragment = sLastF_restoreSelection.get();
+        final VerticalGridView gridView = fragment.getVerticalGridView();
+        assertEquals(7, gridView.getSelectedPosition());
+        assertNotNull(gridView.findViewHolderForAdapterPosition(7));
+
+    }
+
+    public static class F_ListRowWithOnClick extends RowsFragment {
+        Presenter.ViewHolder mLastClickedItemViewHolder;
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            setOnItemViewClickedListener(new OnItemViewClickedListener() {
+                @Override
+                public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
+                        RowPresenter.ViewHolder rowViewHolder, Row row) {
+                    mLastClickedItemViewHolder = itemViewHolder;
+                }
+            });
+            ListRowPresenter lrp = new ListRowPresenter();
+            ArrayObjectAdapter adapter = new ArrayObjectAdapter(lrp);
+            setAdapter(adapter);
+            loadData(adapter, 10, 1);
+        }
+    }
+
+    @Test
+    public void prefetchChildItemsBeforeAttach() throws Throwable {
+        SingleFragmentTestActivity activity =
+                launchAndWaitActivity(F_ListRowWithOnClick.class, 1000);
+
+        F_ListRowWithOnClick fragment = (F_ListRowWithOnClick) activity.getTestFragment();
+        final VerticalGridView gridView = fragment.getVerticalGridView();
+        View lastRow = gridView.getChildAt(gridView.getChildCount() - 1);
+        final int lastRowPos = gridView.getChildAdapterPosition(lastRow);
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                new Runnable() {
+                    public void run() {
+                        gridView.setSelectedPositionSmooth(lastRowPos);
+                    }
+                }
+        );
+        waitForScrollIdle(gridView);
+        ItemBridgeAdapter.ViewHolder prefetchedBridgeVh = (ItemBridgeAdapter.ViewHolder)
+                gridView.findViewHolderForAdapterPosition(lastRowPos + 1);
+        RowPresenter prefetchedRowPresenter = (RowPresenter) prefetchedBridgeVh.getPresenter();
+        final ListRowPresenter.ViewHolder prefetchedListRowVh = (ListRowPresenter.ViewHolder)
+                prefetchedRowPresenter.getRowViewHolder(prefetchedBridgeVh.getViewHolder());
+
+        fragment.mLastClickedItemViewHolder = null;
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                new Runnable() {
+                    public void run() {
+                        prefetchedListRowVh.getItemViewHolder(0).view.performClick();
+                    }
+                }
+        );
+        assertSame(prefetchedListRowVh.getItemViewHolder(0), fragment.mLastClickedItemViewHolder);
+    }
+
+    @Test
+    public void changeHasStableIdToTrueAfterViewCreated() throws InterruptedException {
+        SingleFragmentTestActivity activity =
+                launchAndWaitActivity(RowsFragment.class, 2000);
+        final RowsFragment fragment = (RowsFragment) activity.getTestFragment();
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                new Runnable() {
+                    public void run() {
+                        ObjectAdapter adapter = new ObjectAdapter() {
+                            @Override
+                            public int size() {
+                                return 0;
+                            }
+
+                            @Override
+                            public Object get(int position) {
+                                return null;
+                            }
+
+                            @Override
+                            public long getId(int position) {
+                                return 1;
+                            }
+                        };
+                        adapter.setHasStableIds(true);
+                        fragment.setAdapter(adapter);
+                    }
+                }
+        );
+    }
+
+    static class StableIdAdapter extends ObjectAdapter {
+        ArrayList<Integer> mList = new ArrayList();
+
+        @Override
+        public long getId(int position) {
+            return mList.get(position).longValue();
+        }
+
+        @Override
+        public Object get(int position) {
+            return mList.get(position);
+        }
+
+        @Override
+        public int size() {
+            return mList.size();
+        }
+    }
+
+    public static class F_rowNotifyItemRangeChange extends BrowseFragment {
+
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            ListRowPresenter lrp = new ListRowPresenter();
+            final ArrayObjectAdapter adapter = new ArrayObjectAdapter(lrp);
+            for (int i = 0; i < 2; i++) {
+                StableIdAdapter listRowAdapter = new StableIdAdapter();
+                listRowAdapter.setHasStableIds(true);
+                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));
+            }
+            setAdapter(adapter);
+            new Handler().postDelayed(new Runnable() {
+                @Override
+                public void run() {
+                    StableIdAdapter rowAdapter = (StableIdAdapter)
+                            ((ListRow) adapter.get(1)).getAdapter();
+                    rowAdapter.notifyItemRangeChanged(0, 3);
+                }
+            }, 500);
+        }
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
+    @Test
+    public void rowNotifyItemRangeChange() throws InterruptedException {
+        SingleFragmentTestActivity activity = launchAndWaitActivity(
+                RowsFragmentTest.F_rowNotifyItemRangeChange.class, 2000);
+
+        VerticalGridView verticalGridView = ((BrowseFragment) activity.getTestFragment())
+                .getRowsFragment().getVerticalGridView();
+        for (int i = 0; i < verticalGridView.getChildCount(); i++) {
+            HorizontalGridView horizontalGridView = verticalGridView.getChildAt(i)
+                    .findViewById(R.id.row_content);
+            for (int j = 0; j < horizontalGridView.getChildCount(); j++) {
+                assertEquals(horizontalGridView.getPaddingTop(),
+                        horizontalGridView.getChildAt(j).getTop());
+            }
+        }
+    }
+
+    public static class F_rowNotifyItemRangeChangeWithTransition extends BrowseFragment {
+
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            ListRowPresenter lrp = new ListRowPresenter();
+            prepareEntranceTransition();
+            final ArrayObjectAdapter adapter = new ArrayObjectAdapter(lrp);
+            for (int i = 0; i < 2; i++) {
+                StableIdAdapter listRowAdapter = new StableIdAdapter();
+                listRowAdapter.setHasStableIds(true);
+                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));
+            }
+            setAdapter(adapter);
+            new Handler().postDelayed(new Runnable() {
+                @Override
+                public void run() {
+                    StableIdAdapter rowAdapter = (StableIdAdapter)
+                            ((ListRow) adapter.get(1)).getAdapter();
+                    rowAdapter.notifyItemRangeChanged(0, 3);
+                }
+            }, 500);
+            new Handler().postDelayed(new Runnable() {
+                @Override
+                public void run() {
+                    startEntranceTransition();
+                }
+            }, 520);
+        }
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
+    @Test
+    public void rowNotifyItemRangeChangeWithTransition() throws InterruptedException {
+        SingleFragmentTestActivity activity = launchAndWaitActivity(
+                        RowsFragmentTest.F_rowNotifyItemRangeChangeWithTransition.class, 3000);
+
+        VerticalGridView verticalGridView = ((BrowseFragment) activity.getTestFragment())
+                .getRowsFragment().getVerticalGridView();
+        for (int i = 0; i < verticalGridView.getChildCount(); i++) {
+            HorizontalGridView horizontalGridView = verticalGridView.getChildAt(i)
+                    .findViewById(R.id.row_content);
+            for (int j = 0; j < horizontalGridView.getChildCount(); j++) {
+                assertEquals(horizontalGridView.getPaddingTop(),
+                        horizontalGridView.getChildAt(j).getTop());
+                assertEquals(0, horizontalGridView.getChildAt(j).getTranslationY(), 0.1f);
+            }
+        }
+    }
+
+    public static class F_Base extends BrowseFragment {
+
+        List<Long> mEntranceTransitionStartTS = new ArrayList();
+        List<Long> mEntranceTransitionEndTS = new ArrayList();
+
+        @Override
+        protected void onEntranceTransitionStart() {
+            super.onEntranceTransitionStart();
+            mEntranceTransitionStartTS.add(SystemClock.uptimeMillis());
+        }
+
+        @Override
+        protected void onEntranceTransitionEnd() {
+            super.onEntranceTransitionEnd();
+            mEntranceTransitionEndTS.add(SystemClock.uptimeMillis());
+        }
+
+        public void assertExecutedEntranceTransition() {
+            assertEquals(1, mEntranceTransitionStartTS.size());
+            assertEquals(1, mEntranceTransitionEndTS.size());
+            assertTrue(mEntranceTransitionEndTS.get(0) - mEntranceTransitionStartTS.get(0) > 100);
+        }
+
+        public void assertNoEntranceTransition() {
+            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;
+    }
+
+    /**
+     * Create BrowseFragmentAdapter with 3 ListRows
+     */
+    static ArrayObjectAdapter createListRowsAdapter() {
+        ListRowPresenter lrp = new ListRowPresenter();
+        final ArrayObjectAdapter adapter = new ArrayObjectAdapter(lrp);
+        for (int i = 0; i < 3; i++) {
+            ObjectAdapter listRowAdapter = createListRowAdapter();
+            HeaderItem header = new HeaderItem(i, "Row " + i);
+            adapter.add(new ListRow(header, listRowAdapter));
+        }
+        return adapter;
+    }
+
+    /**
+     * A typical BrowseFragment with multiple rows that start entrance transition
+     */
+    public static class F_standard extends F_Base {
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            if (savedInstanceState == null) {
+                prepareEntranceTransition();
+            }
+            new Handler().postDelayed(new Runnable() {
+                @Override
+                public void run() {
+                    setAdapter(createListRowsAdapter());
+                    startEntranceTransition();
+                }
+            }, 100);
+        }
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
+    @Test
+    public void browseFragmentSetNullAdapter() 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());
+        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(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.removeItems(0, 1);
+                adapter1.add(0, new MyPageRow(0));
+            }
+        });
+        fragment.waitPageFragment(SampleRowsFragment.class);
+        assertTrue(adapter1.hasObserver());
+        assertNull(fragment.mMainFragmentListRowDataAdapter);
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
+    @Test
+    public void browseFragmentNotifyItemChangeListRowToPage() 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.removeItems(0, 1);
+                adapter1.add(0, new ListRow(header, listRowAdapter));
+            }
+        });
+        assertTrue(adapter1.hasObserver());
+        assertTrue(wrappedAdapter.hasObserver());
+        assertSame(wrappedAdapter, fragment.mMainFragmentListRowDataAdapter);
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
+    @Test
+    public void browseFragmentNotifyItemChangeListRowToListRow() 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 browseFragmentNotifyItemChangePageToPage() 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.replace(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)
+    @Test
+    public void browseFragmentNotifyItemChangePageToListRow() 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.replace(0, new ListRow(header, listRowAdapter));
+            }
+        });
+        fragment.waitRowsFragment();
+        assertTrue(adapter1.hasObserver());
+        assertTrue(fragment.mMainFragmentListRowDataAdapter.hasObserver());
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
+    @Test
+    public void browseFragmentRestore() throws InterruptedException {
+        final SingleFragmentTestActivity activity = launchAndWaitActivity(
+                RowsFragmentTest.F_standard.class, 2000);
+        final F_standard fragment = ((F_standard) activity.getTestFragment());
+        fragment.assertExecutedEntranceTransition();
+
+        // select item 2 on row 1
+        selectAndWaitFragmentAnimation(fragment, 1, 2);
+        // save activity to state
+        Bundle savedState = saveActivityState(activity);
+        activity.finish();
+
+        // recreate activity with saved state
+        SingleFragmentTestActivity activity2 = launchAndWaitActivity(
+                RowsFragmentTest.F_standard.class,
+                new Options().savedInstance(savedState), 2000);
+        final F_standard fragment2 = ((F_standard) activity2.getTestFragment());
+        // validate restored activity selected row and selected item
+        fragment2.assertNoEntranceTransition();
+        assertEquals(1, fragment2.getSelectedPosition());
+        assertEquals(2, ((ListRowPresenter.ViewHolder) fragment2.getSelectedRowViewHolder())
+                .getSelectedPosition());
+        activity2.finish();
+    }
+
+    public static class MyPageRow extends PageRow {
+        public int type;
+        public MyPageRow(int type) {
+            super(new HeaderItem(100 + type, "page type " + type));
+            this.type = type;
+        }
+    }
+
+    /**
+     * A RowsFragment that is a separate page in BrowseFragment.
+     */
+    public static class SampleRowsFragment extends RowsFragment {
+        public SampleRowsFragment() {
+            // simulates late data loading:
+            new Handler().postDelayed(new Runnable() {
+                @Override
+                public void run() {
+                    setAdapter(createListRowsAdapter());
+                    if (getMainFragmentAdapter() != null) {
+                        getMainFragmentAdapter().getFragmentHost()
+                                .notifyDataReady(getMainFragmentAdapter());
+                    }
+                }
+            }, 500);
+        }
+    }
+
+    /**
+     * A custom Fragment that is a separate page in BrowseFragment.
+     */
+    public static class SampleFragment extends Fragment implements
+            BrowseFragment.MainFragmentAdapterProvider {
+
+        public static class PageFragmentAdapterImpl extends
+                BrowseFragment.MainFragmentAdapter<SampleFragment> {
+
+            public PageFragmentAdapterImpl(SampleFragment fragment) {
+                super(fragment);
+                setScalingEnabled(true);
+            }
+
+            @Override
+            public void setEntranceTransitionState(boolean state) {
+                getFragment().setEntranceTransitionState(state);
+            }
+        }
+
+        final PageFragmentAdapterImpl mMainFragmentAdapter = new PageFragmentAdapterImpl(this);
+
+        void setEntranceTransitionState(boolean state) {
+            final View view = getView();
+            int visibility = state ? View.VISIBLE : View.INVISIBLE;
+            view.findViewById(R.id.tv1).setVisibility(visibility);
+            view.findViewById(R.id.tv2).setVisibility(visibility);
+            view.findViewById(R.id.tv3).setVisibility(visibility);
+        }
+
+        @Override
+        public View onCreateView(
+                final LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+            View view = inflater.inflate(R.layout.page_fragment, container, false);
+            return view;
+        }
+
+        @Override
+        public void onViewCreated(View view, Bundle savedInstanceState) {
+            // static layout has view and data ready immediately
+            mMainFragmentAdapter.getFragmentHost().notifyViewCreated(mMainFragmentAdapter);
+            mMainFragmentAdapter.getFragmentHost().notifyDataReady(mMainFragmentAdapter);
+        }
+
+        @Override
+        public BrowseFragment.MainFragmentAdapter getMainFragmentAdapter() {
+            return mMainFragmentAdapter;
+        }
+    }
+
+    /**
+     * Create BrowseFragmentAdapter with 3 ListRows and 2 PageRows
+     */
+    private static ArrayObjectAdapter create3ListRow2PageRowAdapter() {
+        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++);
+            HeaderItem header = new HeaderItem(i, "Row " + i);
+            adapter.add(new ListRow(header, listRowAdapter));
+        }
+        adapter.add(new MyPageRow(0));
+        adapter.add(new MyPageRow(1));
+        return adapter;
+    }
+
+    /**
+     * 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 MyFragmentFactory());
+        }
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            if (savedInstanceState == null) {
+                prepareEntranceTransition();
+            }
+            new Handler().postDelayed(new Runnable() {
+                @Override
+                public void run() {
+                    setAdapter(create3ListRow2PageRowAdapter());
+                    startEntranceTransition();
+                }
+            }, 100);
+        }
+    }
+
+    /**
+     * 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 void run() {
+                    setAdapter(create2PageRow3ListRow());
+                    startEntranceTransition();
+                }
+            }, 100);
+        }
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
+    @Test
+    public void mixedBrowseFragmentRestoreToListRow() throws Throwable {
+        final SingleFragmentTestActivity activity = launchAndWaitActivity(
+                RowsFragmentTest.F_3ListRow2PageRow.class, 2000);
+        final F_3ListRow2PageRow fragment = ((F_3ListRow2PageRow) activity.getTestFragment());
+        fragment.assertExecutedEntranceTransition();
+
+        // select item 2 on row 1.
+        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), 2000);
+        final F_3ListRow2PageRow fragment2 = ((F_3ListRow2PageRow) activity2.getTestFragment());
+        assertFalse(fragment2.isShowingHeaders());
+        fragment2.assertNoEntranceTransition();
+        assertEquals(1, fragment2.getSelectedPosition());
+        assertEquals(2, ((ListRowPresenter.ViewHolder) fragment2.getSelectedRowViewHolder())
+                .getSelectedPosition());
+        activity2.finish();
+    }
+
+    void mixedBrowseFragmentRestoreToSampleRowsFragment(final boolean hideFastLane)
+            throws Throwable {
+        final SingleFragmentTestActivity activity = launchAndWaitActivity(
+                RowsFragmentTest.F_3ListRow2PageRow.class, 2000);
+        final F_3ListRow2PageRow fragment = ((F_3ListRow2PageRow) activity.getTestFragment());
+        fragment.assertExecutedEntranceTransition();
+
+        // select row 3 which is mapped to SampleRowsFragment.
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                fragment.setSelectedPosition(3, true);
+            }
+        });
+        // Wait SampleRowsFragment being created
+        final SampleRowsFragment mainFragment = (SampleRowsFragment) fragment.waitPageFragment(
+                SampleRowsFragment.class);
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                if (hideFastLane) {
+                    fragment.startHeadersTransition(false);
+                }
+            }
+        });
+        // Wait header transition finishes
+        waitForHeaderTransition(fragment);
+        // Select item 1 on row 1 in SampleRowsFragment
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                mainFragment.setSelectedPosition(1, true,
+                        new ListRowPresenter.SelectItemViewHolderTask(1));
+            }
+        });
+        // Save activity state
+        Bundle savedState = saveActivityState(activity);
+        activity.finish();
+
+        SingleFragmentTestActivity activity2 = launchAndWaitActivity(
+                RowsFragmentTest.F_3ListRow2PageRow.class,
+                new Options().savedInstance(savedState), 2000);
+        final F_3ListRow2PageRow fragment2 = ((F_3ListRow2PageRow) activity2.getTestFragment());
+        final SampleRowsFragment mainFragment2 = (SampleRowsFragment) fragment2.waitPageFragment(
+                SampleRowsFragment.class);
+        assertEquals(!hideFastLane, fragment2.isShowingHeaders());
+        fragment2.assertNoEntranceTransition();
+        // Validate BrowseFragment selected row 3 (mapped to SampleRowsFragment)
+        assertEquals(3, fragment2.getSelectedPosition());
+        // Validate SampleRowsFragment's selected row and selected item
+        assertEquals(1, mainFragment2.getSelectedPosition());
+        assertEquals(1, ((ListRowPresenter.ViewHolder) mainFragment2
+                .findRowViewHolderByPosition(1)).getSelectedPosition());
+        activity2.finish();
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
+    @Test
+    public void mixedBrowseFragmentRestoreToSampleRowsFragmentHideFastLane() throws Throwable {
+        mixedBrowseFragmentRestoreToSampleRowsFragment(true);
+
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
+    @Test
+    public void mixedBrowseFragmentRestoreToSampleRowsFragmentShowFastLane() throws Throwable {
+        mixedBrowseFragmentRestoreToSampleRowsFragment(false);
+    }
+
+    void mixedBrowseFragmentRestoreToSampleFragment(final boolean hideFastLane)
+            throws Throwable {
+        final SingleFragmentTestActivity activity = launchAndWaitActivity(
+                RowsFragmentTest.F_3ListRow2PageRow.class, 2000);
+        final F_3ListRow2PageRow fragment = ((F_3ListRow2PageRow) activity.getTestFragment());
+        fragment.assertExecutedEntranceTransition();
+
+        // select row 3 which is mapped to SampleFragment.
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                fragment.setSelectedPosition(4, true);
+            }
+        });
+        // Wait SampleFragment to be created
+        final SampleFragment mainFragment = (SampleFragment) fragment.waitPageFragment(
+                SampleFragment.class);
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                if (hideFastLane) {
+                    fragment.startHeadersTransition(false);
+                }
+            }
+        });
+        waitForHeaderTransition(fragment);
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                // change TextView content which should be saved in states.
+                TextView t = mainFragment.getView().findViewById(R.id.tv2);
+                t.setText("changed text");
+            }
+        });
+        // Save activity state
+        Bundle savedState = saveActivityState(activity);
+        activity.finish();
+
+        SingleFragmentTestActivity activity2 = launchAndWaitActivity(
+                RowsFragmentTest.F_3ListRow2PageRow.class,
+                new Options().savedInstance(savedState), 2000);
+        final F_3ListRow2PageRow fragment2 = ((F_3ListRow2PageRow) activity2.getTestFragment());
+        final SampleFragment mainFragment2 = (SampleFragment) fragment2.waitPageFragment(
+                SampleFragment.class);
+        assertEquals(!hideFastLane, fragment2.isShowingHeaders());
+        fragment2.assertNoEntranceTransition();
+        // Validate BrowseFragment selected row 3 (mapped to SampleFragment)
+        assertEquals(4, fragment2.getSelectedPosition());
+        // Validate SampleFragment's view states are restored
+        TextView t = mainFragment2.getView().findViewById(R.id.tv2);
+        assertEquals("changed text", t.getText().toString());
+        activity2.finish();
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
+    @Test
+    public void mixedBrowseFragmentRestoreToSampleFragmentHideFastLane() throws Throwable {
+        mixedBrowseFragmentRestoreToSampleFragment(true);
+
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
+    @Test
+    public void mixedBrowseFragmentRestoreToSampleFragmentShowFastLane() throws Throwable {
+        mixedBrowseFragmentRestoreToSampleFragment(false);
+    }
+
+
+}
diff --git a/leanback/tests/java/android/support/v17/leanback/app/RowsSupportFragmentTest.java b/leanback/tests/java/android/support/v17/leanback/app/RowsSupportFragmentTest.java
new file mode 100644
index 0000000..eca3f09
--- /dev/null
+++ b/leanback/tests/java/android/support/v17/leanback/app/RowsSupportFragmentTest.java
@@ -0,0 +1,1351 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v17.leanback.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;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import android.graphics.Rect;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.filters.SdkSuppress;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v17.leanback.test.R;
+import android.support.v17.leanback.testutils.PollingCheck;
+import android.support.v17.leanback.widget.ArrayObjectAdapter;
+import android.support.v17.leanback.widget.HeaderItem;
+import android.support.v17.leanback.widget.HorizontalGridView;
+import android.support.v17.leanback.widget.ItemBridgeAdapter;
+import android.support.v17.leanback.widget.ListRow;
+import android.support.v17.leanback.widget.ListRowPresenter;
+import android.support.v17.leanback.widget.ObjectAdapter;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.PageRow;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.support.v17.leanback.widget.SinglePresenterSelector;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.support.v4.app.Fragment;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class RowsSupportFragmentTest extends SingleSupportFragmentTestBase {
+
+    static final StringPresenter sCardPresenter = new StringPresenter();
+
+    static void loadData(ArrayObjectAdapter adapter, int numRows, int repeatPerRow) {
+        for (int i = 0; i < numRows; ++i) {
+            ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(sCardPresenter);
+            int index = 0;
+            for (int j = 0; j < repeatPerRow; ++j) {
+                listRowAdapter.add("Hello world-" + (index++));
+                listRowAdapter.add("This is a test-" + (index++));
+                listRowAdapter.add("Android TV-" + (index++));
+                listRowAdapter.add("Leanback-" + (index++));
+                listRowAdapter.add("Hello world-" + (index++));
+                listRowAdapter.add("Android TV-" + (index++));
+                listRowAdapter.add("Leanback-" + (index++));
+                listRowAdapter.add("GuidedStepSupportFragment-" + (index++));
+            }
+            HeaderItem header = new HeaderItem(i, "Row " + i);
+            adapter.add(new ListRow(header, listRowAdapter));
+        }
+    }
+
+    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) {
+            super.onCreate(savedInstanceState);
+            ListRowPresenter lrp = new ListRowPresenter();
+            ArrayObjectAdapter adapter = new ArrayObjectAdapter(lrp);
+            setAdapter(adapter);
+            loadData(adapter, 10, 1);
+        }
+    }
+
+    @Test
+    public void defaultAlignment() throws Throwable {
+        SingleSupportFragmentTestActivity activity = launchAndWaitActivity(F_defaultAlignment.class, 1000);
+
+        final Rect rect = new Rect();
+
+        final VerticalGridView gridView = ((RowsSupportFragment) activity.getTestFragment())
+                .getVerticalGridView();
+        View row0 = gridView.findViewHolderForAdapterPosition(0).itemView;
+        rect.set(0, 0, row0.getWidth(), row0.getHeight());
+        gridView.offsetDescendantRectToMyCoords(row0, rect);
+        assertEquals("First row is initially aligned to top of screen", 0, rect.top);
+
+        sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+        waitForScrollIdle(gridView);
+        View row1 = gridView.findViewHolderForAdapterPosition(1).itemView;
+        PollingCheck.waitFor(new PollingCheck.ViewStableOnScreen(row1));
+
+        rect.set(0, 0, row1.getWidth(), row1.getHeight());
+        gridView.offsetDescendantRectToMyCoords(row1, rect);
+        assertTrue("Second row should not be aligned to top of screen", rect.top > 0);
+    }
+
+    public static class F_selectBeforeSetAdapter extends RowsSupportFragment {
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            setSelectedPosition(7, false);
+            new Handler().postDelayed(new Runnable() {
+                @Override
+                public void run() {
+                    getVerticalGridView().requestLayout();
+                }
+            }, 100);
+            new Handler().postDelayed(new Runnable() {
+                @Override
+                public void run() {
+                    ListRowPresenter lrp = new ListRowPresenter();
+                    ArrayObjectAdapter adapter = new ArrayObjectAdapter(lrp);
+                    setAdapter(adapter);
+                    loadData(adapter, 10, 1);
+                }
+            }, 1000);
+        }
+    }
+
+    @Test
+    public void selectBeforeSetAdapter() throws InterruptedException {
+        SingleSupportFragmentTestActivity activity =
+                launchAndWaitActivity(F_selectBeforeSetAdapter.class, 2000);
+
+        final VerticalGridView gridView = ((RowsSupportFragment) activity.getTestFragment())
+                .getVerticalGridView();
+        assertEquals(7, gridView.getSelectedPosition());
+        assertNotNull(gridView.findViewHolderForAdapterPosition(7));
+    }
+
+    public static class F_selectBeforeAddData extends RowsSupportFragment {
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            ListRowPresenter lrp = new ListRowPresenter();
+            final ArrayObjectAdapter adapter = new ArrayObjectAdapter(lrp);
+            setAdapter(adapter);
+            setSelectedPosition(7, false);
+            new Handler().postDelayed(new Runnable() {
+                @Override
+                public void run() {
+                    getVerticalGridView().requestLayout();
+                }
+            }, 100);
+            new Handler().postDelayed(new Runnable() {
+                @Override
+                public void run() {
+                    loadData(adapter, 10, 1);
+                }
+            }, 1000);
+        }
+    }
+
+    @Test
+    public void selectBeforeAddData() throws InterruptedException {
+        SingleSupportFragmentTestActivity activity =
+                launchAndWaitActivity(F_selectBeforeAddData.class, 2000);
+
+        final VerticalGridView gridView = ((RowsSupportFragment) activity.getTestFragment())
+                .getVerticalGridView();
+        assertEquals(7, gridView.getSelectedPosition());
+        assertNotNull(gridView.findViewHolderForAdapterPosition(7));
+    }
+
+    public static class F_selectAfterAddData extends RowsSupportFragment {
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            ListRowPresenter lrp = new ListRowPresenter();
+            final ArrayObjectAdapter adapter = new ArrayObjectAdapter(lrp);
+            setAdapter(adapter);
+            loadData(adapter, 10, 1);
+            new Handler().postDelayed(new Runnable() {
+                @Override
+                public void run() {
+                    setSelectedPosition(7, false);
+                }
+            }, 1000);
+        }
+    }
+
+    @Test
+    public void selectAfterAddData() throws InterruptedException {
+        SingleSupportFragmentTestActivity activity =
+                launchAndWaitActivity(F_selectAfterAddData.class, 2000);
+
+        final VerticalGridView gridView = ((RowsSupportFragment) activity.getTestFragment())
+                .getVerticalGridView();
+        assertEquals(7, gridView.getSelectedPosition());
+        assertNotNull(gridView.findViewHolderForAdapterPosition(7));
+    }
+
+    static WeakReference<F_restoreSelection> sLastF_restoreSelection;
+
+    public static class F_restoreSelection extends RowsSupportFragment {
+        public F_restoreSelection() {
+            sLastF_restoreSelection = new WeakReference<F_restoreSelection>(this);
+        }
+
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            ListRowPresenter lrp = new ListRowPresenter();
+            final ArrayObjectAdapter adapter = new ArrayObjectAdapter(lrp);
+            setAdapter(adapter);
+            loadData(adapter, 10, 1);
+            if (savedInstanceState == null) {
+                setSelectedPosition(7, false);
+            }
+        }
+    }
+
+    @Test
+    public void restoreSelection() {
+        final SingleSupportFragmentTestActivity activity =
+                launchAndWaitActivity(F_restoreSelection.class, 1000);
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        activity.recreate();
+                    }
+                }
+        );
+        SystemClock.sleep(1000);
+
+        // mActivity is invalid after recreate(), a new Activity instance is created
+        // but we could get Fragment from static variable.
+        RowsSupportFragment fragment = sLastF_restoreSelection.get();
+        final VerticalGridView gridView = fragment.getVerticalGridView();
+        assertEquals(7, gridView.getSelectedPosition());
+        assertNotNull(gridView.findViewHolderForAdapterPosition(7));
+
+    }
+
+    public static class F_ListRowWithOnClick extends RowsSupportFragment {
+        Presenter.ViewHolder mLastClickedItemViewHolder;
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            setOnItemViewClickedListener(new OnItemViewClickedListener() {
+                @Override
+                public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
+                        RowPresenter.ViewHolder rowViewHolder, Row row) {
+                    mLastClickedItemViewHolder = itemViewHolder;
+                }
+            });
+            ListRowPresenter lrp = new ListRowPresenter();
+            ArrayObjectAdapter adapter = new ArrayObjectAdapter(lrp);
+            setAdapter(adapter);
+            loadData(adapter, 10, 1);
+        }
+    }
+
+    @Test
+    public void prefetchChildItemsBeforeAttach() throws Throwable {
+        SingleSupportFragmentTestActivity activity =
+                launchAndWaitActivity(F_ListRowWithOnClick.class, 1000);
+
+        F_ListRowWithOnClick fragment = (F_ListRowWithOnClick) activity.getTestFragment();
+        final VerticalGridView gridView = fragment.getVerticalGridView();
+        View lastRow = gridView.getChildAt(gridView.getChildCount() - 1);
+        final int lastRowPos = gridView.getChildAdapterPosition(lastRow);
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                new Runnable() {
+                    public void run() {
+                        gridView.setSelectedPositionSmooth(lastRowPos);
+                    }
+                }
+        );
+        waitForScrollIdle(gridView);
+        ItemBridgeAdapter.ViewHolder prefetchedBridgeVh = (ItemBridgeAdapter.ViewHolder)
+                gridView.findViewHolderForAdapterPosition(lastRowPos + 1);
+        RowPresenter prefetchedRowPresenter = (RowPresenter) prefetchedBridgeVh.getPresenter();
+        final ListRowPresenter.ViewHolder prefetchedListRowVh = (ListRowPresenter.ViewHolder)
+                prefetchedRowPresenter.getRowViewHolder(prefetchedBridgeVh.getViewHolder());
+
+        fragment.mLastClickedItemViewHolder = null;
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                new Runnable() {
+                    public void run() {
+                        prefetchedListRowVh.getItemViewHolder(0).view.performClick();
+                    }
+                }
+        );
+        assertSame(prefetchedListRowVh.getItemViewHolder(0), fragment.mLastClickedItemViewHolder);
+    }
+
+    @Test
+    public void changeHasStableIdToTrueAfterViewCreated() throws InterruptedException {
+        SingleSupportFragmentTestActivity activity =
+                launchAndWaitActivity(RowsSupportFragment.class, 2000);
+        final RowsSupportFragment fragment = (RowsSupportFragment) activity.getTestFragment();
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                new Runnable() {
+                    public void run() {
+                        ObjectAdapter adapter = new ObjectAdapter() {
+                            @Override
+                            public int size() {
+                                return 0;
+                            }
+
+                            @Override
+                            public Object get(int position) {
+                                return null;
+                            }
+
+                            @Override
+                            public long getId(int position) {
+                                return 1;
+                            }
+                        };
+                        adapter.setHasStableIds(true);
+                        fragment.setAdapter(adapter);
+                    }
+                }
+        );
+    }
+
+    static class StableIdAdapter extends ObjectAdapter {
+        ArrayList<Integer> mList = new ArrayList();
+
+        @Override
+        public long getId(int position) {
+            return mList.get(position).longValue();
+        }
+
+        @Override
+        public Object get(int position) {
+            return mList.get(position);
+        }
+
+        @Override
+        public int size() {
+            return mList.size();
+        }
+    }
+
+    public static class F_rowNotifyItemRangeChange extends BrowseSupportFragment {
+
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            ListRowPresenter lrp = new ListRowPresenter();
+            final ArrayObjectAdapter adapter = new ArrayObjectAdapter(lrp);
+            for (int i = 0; i < 2; i++) {
+                StableIdAdapter listRowAdapter = new StableIdAdapter();
+                listRowAdapter.setHasStableIds(true);
+                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));
+            }
+            setAdapter(adapter);
+            new Handler().postDelayed(new Runnable() {
+                @Override
+                public void run() {
+                    StableIdAdapter rowAdapter = (StableIdAdapter)
+                            ((ListRow) adapter.get(1)).getAdapter();
+                    rowAdapter.notifyItemRangeChanged(0, 3);
+                }
+            }, 500);
+        }
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
+    @Test
+    public void rowNotifyItemRangeChange() throws InterruptedException {
+        SingleSupportFragmentTestActivity activity = launchAndWaitActivity(
+                RowsSupportFragmentTest.F_rowNotifyItemRangeChange.class, 2000);
+
+        VerticalGridView verticalGridView = ((BrowseSupportFragment) activity.getTestFragment())
+                .getRowsSupportFragment().getVerticalGridView();
+        for (int i = 0; i < verticalGridView.getChildCount(); i++) {
+            HorizontalGridView horizontalGridView = verticalGridView.getChildAt(i)
+                    .findViewById(R.id.row_content);
+            for (int j = 0; j < horizontalGridView.getChildCount(); j++) {
+                assertEquals(horizontalGridView.getPaddingTop(),
+                        horizontalGridView.getChildAt(j).getTop());
+            }
+        }
+    }
+
+    public static class F_rowNotifyItemRangeChangeWithTransition extends BrowseSupportFragment {
+
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            ListRowPresenter lrp = new ListRowPresenter();
+            prepareEntranceTransition();
+            final ArrayObjectAdapter adapter = new ArrayObjectAdapter(lrp);
+            for (int i = 0; i < 2; i++) {
+                StableIdAdapter listRowAdapter = new StableIdAdapter();
+                listRowAdapter.setHasStableIds(true);
+                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));
+            }
+            setAdapter(adapter);
+            new Handler().postDelayed(new Runnable() {
+                @Override
+                public void run() {
+                    StableIdAdapter rowAdapter = (StableIdAdapter)
+                            ((ListRow) adapter.get(1)).getAdapter();
+                    rowAdapter.notifyItemRangeChanged(0, 3);
+                }
+            }, 500);
+            new Handler().postDelayed(new Runnable() {
+                @Override
+                public void run() {
+                    startEntranceTransition();
+                }
+            }, 520);
+        }
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
+    @Test
+    public void rowNotifyItemRangeChangeWithTransition() throws InterruptedException {
+        SingleSupportFragmentTestActivity activity = launchAndWaitActivity(
+                        RowsSupportFragmentTest.F_rowNotifyItemRangeChangeWithTransition.class, 3000);
+
+        VerticalGridView verticalGridView = ((BrowseSupportFragment) activity.getTestFragment())
+                .getRowsSupportFragment().getVerticalGridView();
+        for (int i = 0; i < verticalGridView.getChildCount(); i++) {
+            HorizontalGridView horizontalGridView = verticalGridView.getChildAt(i)
+                    .findViewById(R.id.row_content);
+            for (int j = 0; j < horizontalGridView.getChildCount(); j++) {
+                assertEquals(horizontalGridView.getPaddingTop(),
+                        horizontalGridView.getChildAt(j).getTop());
+                assertEquals(0, horizontalGridView.getChildAt(j).getTranslationY(), 0.1f);
+            }
+        }
+    }
+
+    public static class F_Base extends BrowseSupportFragment {
+
+        List<Long> mEntranceTransitionStartTS = new ArrayList();
+        List<Long> mEntranceTransitionEndTS = new ArrayList();
+
+        @Override
+        protected void onEntranceTransitionStart() {
+            super.onEntranceTransitionStart();
+            mEntranceTransitionStartTS.add(SystemClock.uptimeMillis());
+        }
+
+        @Override
+        protected void onEntranceTransitionEnd() {
+            super.onEntranceTransitionEnd();
+            mEntranceTransitionEndTS.add(SystemClock.uptimeMillis());
+        }
+
+        public void assertExecutedEntranceTransition() {
+            assertEquals(1, mEntranceTransitionStartTS.size());
+            assertEquals(1, mEntranceTransitionEndTS.size());
+            assertTrue(mEntranceTransitionEndTS.get(0) - mEntranceTransitionStartTS.get(0) > 100);
+        }
+
+        public void assertNoEntranceTransition() {
+            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;
+    }
+
+    /**
+     * Create BrowseSupportFragmentAdapter with 3 ListRows
+     */
+    static ArrayObjectAdapter createListRowsAdapter() {
+        ListRowPresenter lrp = new ListRowPresenter();
+        final ArrayObjectAdapter adapter = new ArrayObjectAdapter(lrp);
+        for (int i = 0; i < 3; i++) {
+            ObjectAdapter listRowAdapter = createListRowAdapter();
+            HeaderItem header = new HeaderItem(i, "Row " + i);
+            adapter.add(new ListRow(header, listRowAdapter));
+        }
+        return adapter;
+    }
+
+    /**
+     * A typical BrowseSupportFragment with multiple rows that start entrance transition
+     */
+    public static class F_standard extends F_Base {
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            if (savedInstanceState == null) {
+                prepareEntranceTransition();
+            }
+            new Handler().postDelayed(new Runnable() {
+                @Override
+                public void run() {
+                    setAdapter(createListRowsAdapter());
+                    startEntranceTransition();
+                }
+            }, 100);
+        }
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
+    @Test
+    public void browseFragmentSetNullAdapter() 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());
+        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(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.removeItems(0, 1);
+                adapter1.add(0, new MyPageRow(0));
+            }
+        });
+        fragment.waitPageFragment(SampleRowsSupportFragment.class);
+        assertTrue(adapter1.hasObserver());
+        assertNull(fragment.mMainFragmentListRowDataAdapter);
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
+    @Test
+    public void browseFragmentNotifyItemChangeListRowToPage() 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.removeItems(0, 1);
+                adapter1.add(0, new ListRow(header, listRowAdapter));
+            }
+        });
+        assertTrue(adapter1.hasObserver());
+        assertTrue(wrappedAdapter.hasObserver());
+        assertSame(wrappedAdapter, fragment.mMainFragmentListRowDataAdapter);
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
+    @Test
+    public void browseFragmentNotifyItemChangeListRowToListRow() 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 browseFragmentNotifyItemChangePageToPage() 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.replace(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)
+    @Test
+    public void browseFragmentNotifyItemChangePageToListRow() 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.replace(0, new ListRow(header, listRowAdapter));
+            }
+        });
+        fragment.waitRowsSupportFragment();
+        assertTrue(adapter1.hasObserver());
+        assertTrue(fragment.mMainFragmentListRowDataAdapter.hasObserver());
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
+    @Test
+    public void browseFragmentRestore() throws InterruptedException {
+        final SingleSupportFragmentTestActivity activity = launchAndWaitActivity(
+                RowsSupportFragmentTest.F_standard.class, 2000);
+        final F_standard fragment = ((F_standard) activity.getTestFragment());
+        fragment.assertExecutedEntranceTransition();
+
+        // select item 2 on row 1
+        selectAndWaitFragmentAnimation(fragment, 1, 2);
+        // save activity to state
+        Bundle savedState = saveActivityState(activity);
+        activity.finish();
+
+        // recreate activity with saved state
+        SingleSupportFragmentTestActivity activity2 = launchAndWaitActivity(
+                RowsSupportFragmentTest.F_standard.class,
+                new Options().savedInstance(savedState), 2000);
+        final F_standard fragment2 = ((F_standard) activity2.getTestFragment());
+        // validate restored activity selected row and selected item
+        fragment2.assertNoEntranceTransition();
+        assertEquals(1, fragment2.getSelectedPosition());
+        assertEquals(2, ((ListRowPresenter.ViewHolder) fragment2.getSelectedRowViewHolder())
+                .getSelectedPosition());
+        activity2.finish();
+    }
+
+    public static class MyPageRow extends PageRow {
+        public int type;
+        public MyPageRow(int type) {
+            super(new HeaderItem(100 + type, "page type " + type));
+            this.type = type;
+        }
+    }
+
+    /**
+     * A RowsSupportFragment that is a separate page in BrowseSupportFragment.
+     */
+    public static class SampleRowsSupportFragment extends RowsSupportFragment {
+        public SampleRowsSupportFragment() {
+            // simulates late data loading:
+            new Handler().postDelayed(new Runnable() {
+                @Override
+                public void run() {
+                    setAdapter(createListRowsAdapter());
+                    if (getMainFragmentAdapter() != null) {
+                        getMainFragmentAdapter().getFragmentHost()
+                                .notifyDataReady(getMainFragmentAdapter());
+                    }
+                }
+            }, 500);
+        }
+    }
+
+    /**
+     * A custom Fragment that is a separate page in BrowseSupportFragment.
+     */
+    public static class SampleFragment extends Fragment implements
+            BrowseSupportFragment.MainFragmentAdapterProvider {
+
+        public static class PageFragmentAdapterImpl extends
+                BrowseSupportFragment.MainFragmentAdapter<SampleFragment> {
+
+            public PageFragmentAdapterImpl(SampleFragment fragment) {
+                super(fragment);
+                setScalingEnabled(true);
+            }
+
+            @Override
+            public void setEntranceTransitionState(boolean state) {
+                getFragment().setEntranceTransitionState(state);
+            }
+        }
+
+        final PageFragmentAdapterImpl mMainFragmentAdapter = new PageFragmentAdapterImpl(this);
+
+        void setEntranceTransitionState(boolean state) {
+            final View view = getView();
+            int visibility = state ? View.VISIBLE : View.INVISIBLE;
+            view.findViewById(R.id.tv1).setVisibility(visibility);
+            view.findViewById(R.id.tv2).setVisibility(visibility);
+            view.findViewById(R.id.tv3).setVisibility(visibility);
+        }
+
+        @Override
+        public View onCreateView(
+                final LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+            View view = inflater.inflate(R.layout.page_fragment, container, false);
+            return view;
+        }
+
+        @Override
+        public void onViewCreated(View view, Bundle savedInstanceState) {
+            // static layout has view and data ready immediately
+            mMainFragmentAdapter.getFragmentHost().notifyViewCreated(mMainFragmentAdapter);
+            mMainFragmentAdapter.getFragmentHost().notifyDataReady(mMainFragmentAdapter);
+        }
+
+        @Override
+        public BrowseSupportFragment.MainFragmentAdapter getMainFragmentAdapter() {
+            return mMainFragmentAdapter;
+        }
+    }
+
+    /**
+     * Create BrowseSupportFragmentAdapter with 3 ListRows and 2 PageRows
+     */
+    private static ArrayObjectAdapter create3ListRow2PageRowAdapter() {
+        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++);
+            HeaderItem header = new HeaderItem(i, "Row " + i);
+            adapter.add(new ListRow(header, listRowAdapter));
+        }
+        adapter.add(new MyPageRow(0));
+        adapter.add(new MyPageRow(1));
+        return adapter;
+    }
+
+    /**
+     * 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 MyFragmentFactory());
+        }
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            if (savedInstanceState == null) {
+                prepareEntranceTransition();
+            }
+            new Handler().postDelayed(new Runnable() {
+                @Override
+                public void run() {
+                    setAdapter(create3ListRow2PageRowAdapter());
+                    startEntranceTransition();
+                }
+            }, 100);
+        }
+    }
+
+    /**
+     * 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 void run() {
+                    setAdapter(create2PageRow3ListRow());
+                    startEntranceTransition();
+                }
+            }, 100);
+        }
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
+    @Test
+    public void mixedBrowseSupportFragmentRestoreToListRow() throws Throwable {
+        final SingleSupportFragmentTestActivity activity = launchAndWaitActivity(
+                RowsSupportFragmentTest.F_3ListRow2PageRow.class, 2000);
+        final F_3ListRow2PageRow fragment = ((F_3ListRow2PageRow) activity.getTestFragment());
+        fragment.assertExecutedEntranceTransition();
+
+        // select item 2 on row 1.
+        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), 2000);
+        final F_3ListRow2PageRow fragment2 = ((F_3ListRow2PageRow) activity2.getTestFragment());
+        assertFalse(fragment2.isShowingHeaders());
+        fragment2.assertNoEntranceTransition();
+        assertEquals(1, fragment2.getSelectedPosition());
+        assertEquals(2, ((ListRowPresenter.ViewHolder) fragment2.getSelectedRowViewHolder())
+                .getSelectedPosition());
+        activity2.finish();
+    }
+
+    void mixedBrowseSupportFragmentRestoreToSampleRowsSupportFragment(final boolean hideFastLane)
+            throws Throwable {
+        final SingleSupportFragmentTestActivity activity = launchAndWaitActivity(
+                RowsSupportFragmentTest.F_3ListRow2PageRow.class, 2000);
+        final F_3ListRow2PageRow fragment = ((F_3ListRow2PageRow) activity.getTestFragment());
+        fragment.assertExecutedEntranceTransition();
+
+        // select row 3 which is mapped to SampleRowsSupportFragment.
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                fragment.setSelectedPosition(3, true);
+            }
+        });
+        // Wait SampleRowsSupportFragment being created
+        final SampleRowsSupportFragment mainFragment = (SampleRowsSupportFragment) fragment.waitPageFragment(
+                SampleRowsSupportFragment.class);
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                if (hideFastLane) {
+                    fragment.startHeadersTransition(false);
+                }
+            }
+        });
+        // Wait header transition finishes
+        waitForHeaderTransition(fragment);
+        // Select item 1 on row 1 in SampleRowsSupportFragment
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                mainFragment.setSelectedPosition(1, true,
+                        new ListRowPresenter.SelectItemViewHolderTask(1));
+            }
+        });
+        // Save activity state
+        Bundle savedState = saveActivityState(activity);
+        activity.finish();
+
+        SingleSupportFragmentTestActivity activity2 = launchAndWaitActivity(
+                RowsSupportFragmentTest.F_3ListRow2PageRow.class,
+                new Options().savedInstance(savedState), 2000);
+        final F_3ListRow2PageRow fragment2 = ((F_3ListRow2PageRow) activity2.getTestFragment());
+        final SampleRowsSupportFragment mainFragment2 = (SampleRowsSupportFragment) fragment2.waitPageFragment(
+                SampleRowsSupportFragment.class);
+        assertEquals(!hideFastLane, fragment2.isShowingHeaders());
+        fragment2.assertNoEntranceTransition();
+        // Validate BrowseSupportFragment selected row 3 (mapped to SampleRowsSupportFragment)
+        assertEquals(3, fragment2.getSelectedPosition());
+        // Validate SampleRowsSupportFragment's selected row and selected item
+        assertEquals(1, mainFragment2.getSelectedPosition());
+        assertEquals(1, ((ListRowPresenter.ViewHolder) mainFragment2
+                .findRowViewHolderByPosition(1)).getSelectedPosition());
+        activity2.finish();
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
+    @Test
+    public void mixedBrowseSupportFragmentRestoreToSampleRowsSupportFragmentHideFastLane() throws Throwable {
+        mixedBrowseSupportFragmentRestoreToSampleRowsSupportFragment(true);
+
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
+    @Test
+    public void mixedBrowseSupportFragmentRestoreToSampleRowsSupportFragmentShowFastLane() throws Throwable {
+        mixedBrowseSupportFragmentRestoreToSampleRowsSupportFragment(false);
+    }
+
+    void mixedBrowseSupportFragmentRestoreToSampleFragment(final boolean hideFastLane)
+            throws Throwable {
+        final SingleSupportFragmentTestActivity activity = launchAndWaitActivity(
+                RowsSupportFragmentTest.F_3ListRow2PageRow.class, 2000);
+        final F_3ListRow2PageRow fragment = ((F_3ListRow2PageRow) activity.getTestFragment());
+        fragment.assertExecutedEntranceTransition();
+
+        // select row 3 which is mapped to SampleFragment.
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                fragment.setSelectedPosition(4, true);
+            }
+        });
+        // Wait SampleFragment to be created
+        final SampleFragment mainFragment = (SampleFragment) fragment.waitPageFragment(
+                SampleFragment.class);
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                if (hideFastLane) {
+                    fragment.startHeadersTransition(false);
+                }
+            }
+        });
+        waitForHeaderTransition(fragment);
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                // change TextView content which should be saved in states.
+                TextView t = mainFragment.getView().findViewById(R.id.tv2);
+                t.setText("changed text");
+            }
+        });
+        // Save activity state
+        Bundle savedState = saveActivityState(activity);
+        activity.finish();
+
+        SingleSupportFragmentTestActivity activity2 = launchAndWaitActivity(
+                RowsSupportFragmentTest.F_3ListRow2PageRow.class,
+                new Options().savedInstance(savedState), 2000);
+        final F_3ListRow2PageRow fragment2 = ((F_3ListRow2PageRow) activity2.getTestFragment());
+        final SampleFragment mainFragment2 = (SampleFragment) fragment2.waitPageFragment(
+                SampleFragment.class);
+        assertEquals(!hideFastLane, fragment2.isShowingHeaders());
+        fragment2.assertNoEntranceTransition();
+        // Validate BrowseSupportFragment selected row 3 (mapped to SampleFragment)
+        assertEquals(4, fragment2.getSelectedPosition());
+        // Validate SampleFragment's view states are restored
+        TextView t = mainFragment2.getView().findViewById(R.id.tv2);
+        assertEquals("changed text", t.getText().toString());
+        activity2.finish();
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
+    @Test
+    public void mixedBrowseSupportFragmentRestoreToSampleFragmentHideFastLane() throws Throwable {
+        mixedBrowseSupportFragmentRestoreToSampleFragment(true);
+
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
+    @Test
+    public void mixedBrowseSupportFragmentRestoreToSampleFragmentShowFastLane() throws Throwable {
+        mixedBrowseSupportFragmentRestoreToSampleFragment(false);
+    }
+
+
+}
diff --git a/leanback/tests/java/android/support/v17/leanback/app/SingleFragmentTestActivity.java b/leanback/tests/java/android/support/v17/leanback/app/SingleFragmentTestActivity.java
new file mode 100644
index 0000000..6596daa
--- /dev/null
+++ b/leanback/tests/java/android/support/v17/leanback/app/SingleFragmentTestActivity.java
@@ -0,0 +1,97 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from SingleSupportFragmentTestActivity.java.  DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v17.leanback.test.R;
+import android.app.Fragment;
+import android.app.Activity;
+import android.app.FragmentTransaction;
+import android.util.Log;
+
+public class SingleFragmentTestActivity extends Activity {
+
+    /**
+     * Fragment that will be added to activity
+     */
+    public static final String EXTRA_FRAGMENT_NAME = "fragmentName";
+
+    public static final String EXTRA_ACTIVITY_LAYOUT = "activityLayout";
+
+    public static final String EXTRA_UI_VISIBILITY = "uiVisibility";
+
+    public static final String EXTRA_OVERRIDDEN_SAVED_INSTANCE_STATE =
+            "overriddenSavedInstanceState";
+
+    private static final String TAG = "TestActivity";
+
+    private Bundle overrideSavedInstance(Bundle savedInstance) {
+        Intent intent = getIntent();
+        if (intent != null) {
+            Bundle b = intent.getBundleExtra(EXTRA_OVERRIDDEN_SAVED_INSTANCE_STATE);
+            if (b != null) {
+                return b;
+            }
+        }
+        return savedInstance;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        savedInstanceState = overrideSavedInstance(savedInstanceState);
+        super.onCreate(savedInstanceState);
+        Log.d(TAG, "onCreate " + this);
+        Intent intent = getIntent();
+
+        final int uiOptions = intent.getIntExtra(EXTRA_UI_VISIBILITY, 0);
+        if (uiOptions != 0) {
+            getWindow().getDecorView().setSystemUiVisibility(uiOptions);
+        }
+
+        setContentView(intent.getIntExtra(EXTRA_ACTIVITY_LAYOUT, R.layout.single_fragment));
+        if (savedInstanceState == null && findViewById(R.id.main_frame) != null) {
+            try {
+                Fragment fragment = (Fragment) Class.forName(
+                        intent.getStringExtra(EXTRA_FRAGMENT_NAME)).newInstance();
+                FragmentTransaction ft = getFragmentManager().beginTransaction();
+                ft.replace(R.id.main_frame, fragment);
+                ft.commit();
+            } catch (Exception ex) {
+                ex.printStackTrace();
+                finish();
+            }
+        }
+    }
+
+    @Override
+    protected void onRestoreInstanceState(Bundle savedInstanceState) {
+        super.onRestoreInstanceState(overrideSavedInstance(savedInstanceState));
+    }
+
+    public Bundle performSaveInstanceState() {
+        Bundle state = new Bundle();
+        onSaveInstanceState(state);
+        return state;
+    }
+
+    public Fragment getTestFragment() {
+        return getFragmentManager().findFragmentById(R.id.main_frame);
+    }
+}
diff --git a/leanback/tests/java/android/support/v17/leanback/app/SingleFragmentTestBase.java b/leanback/tests/java/android/support/v17/leanback/app/SingleFragmentTestBase.java
new file mode 100644
index 0000000..150ccae
--- /dev/null
+++ b/leanback/tests/java/android/support/v17/leanback/app/SingleFragmentTestBase.java
@@ -0,0 +1,133 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from SingleSupportFrgamentTestBase.java.  DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+import android.support.v7.widget.RecyclerView;
+
+import org.junit.Rule;
+import org.junit.rules.TestName;
+
+public class SingleFragmentTestBase {
+
+    private static final long WAIT_FOR_SCROLL_IDLE_TIMEOUT_MS = 60000;
+
+    @Rule
+    public TestName mUnitTestName = new TestName();
+
+    @Rule
+    public ActivityTestRule<SingleFragmentTestActivity> activityTestRule =
+            new ActivityTestRule<>(SingleFragmentTestActivity.class, false, false);
+
+    public void sendKeys(int ...keys) {
+        for (int i = 0; i < keys.length; i++) {
+            InstrumentationRegistry.getInstrumentation().sendKeyDownUpSync(keys[i]);
+        }
+    }
+
+    /**
+     * Options that will be passed throught Intent to SingleFragmentTestActivity
+     */
+    public static class Options {
+        int mActivityLayoutId;
+        int mUiVisibility;
+        Bundle mSavedInstance;
+
+        public Options() {
+        }
+
+        public Options activityLayoutId(int activityLayoutId) {
+            mActivityLayoutId = activityLayoutId;
+            return this;
+        }
+
+        public Options uiVisibility(int uiVisibility) {
+            mUiVisibility = uiVisibility;
+            return this;
+        }
+
+        public Options savedInstance(Bundle savedInstance) {
+            mSavedInstance = savedInstance;
+            return this;
+        }
+
+        public void collect(Intent intent) {
+            if (mActivityLayoutId != 0) {
+                intent.putExtra(SingleFragmentTestActivity.EXTRA_ACTIVITY_LAYOUT,
+                        mActivityLayoutId);
+            }
+            if (mUiVisibility != 0) {
+                intent.putExtra(SingleFragmentTestActivity.EXTRA_UI_VISIBILITY, mUiVisibility);
+            }
+            if (mSavedInstance != null) {
+                intent.putExtra(SingleFragmentTestActivity.EXTRA_OVERRIDDEN_SAVED_INSTANCE_STATE,
+                        mSavedInstance);
+            }
+        }
+    }
+
+    public SingleFragmentTestActivity launchAndWaitActivity(Class fragmentClass, long waitTimeMs) {
+        return launchAndWaitActivity(fragmentClass.getName(), null, waitTimeMs);
+    }
+
+    public SingleFragmentTestActivity launchAndWaitActivity(Class fragmentClass, Options options,
+            long waitTimeMs) {
+        return launchAndWaitActivity(fragmentClass.getName(), options, waitTimeMs);
+    }
+
+    public SingleFragmentTestActivity launchAndWaitActivity(String firstFragmentName,
+            Options options, long waitTimeMs) {
+        Intent intent = new Intent();
+        intent.putExtra(SingleFragmentTestActivity.EXTRA_FRAGMENT_NAME, firstFragmentName);
+        if (options != null) {
+            options.collect(intent);
+        }
+        SingleFragmentTestActivity activity = activityTestRule.launchActivity(intent);
+        SystemClock.sleep(waitTimeMs);
+        return activity;
+    }
+
+    protected void waitForScrollIdle(RecyclerView recyclerView) throws Throwable {
+        waitForScrollIdle(recyclerView, null);
+    }
+
+    protected void waitForScrollIdle(RecyclerView recyclerView, Runnable verify) throws Throwable {
+        Thread.sleep(100);
+        int total = 0;
+        while (recyclerView.getLayoutManager().isSmoothScrolling()
+                || recyclerView.getScrollState() != recyclerView.SCROLL_STATE_IDLE) {
+            if ((total += 100) >= WAIT_FOR_SCROLL_IDLE_TIMEOUT_MS) {
+                throw new RuntimeException("waitForScrollIdle Timeout");
+            }
+            try {
+                Thread.sleep(100);
+            } catch (InterruptedException ex) {
+                break;
+            }
+            if (verify != null) {
+                activityTestRule.runOnUiThread(verify);
+            }
+        }
+    }
+
+}
diff --git a/leanback/tests/java/android/support/v17/leanback/app/SingleSupportFragmentTestActivity.java b/leanback/tests/java/android/support/v17/leanback/app/SingleSupportFragmentTestActivity.java
new file mode 100644
index 0000000..eeb6262
--- /dev/null
+++ b/leanback/tests/java/android/support/v17/leanback/app/SingleSupportFragmentTestActivity.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v17.leanback.test.R;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.FragmentTransaction;
+import android.util.Log;
+
+public class SingleSupportFragmentTestActivity extends FragmentActivity {
+
+    /**
+     * Fragment that will be added to activity
+     */
+    public static final String EXTRA_FRAGMENT_NAME = "fragmentName";
+
+    public static final String EXTRA_ACTIVITY_LAYOUT = "activityLayout";
+
+    public static final String EXTRA_UI_VISIBILITY = "uiVisibility";
+
+    public static final String EXTRA_OVERRIDDEN_SAVED_INSTANCE_STATE =
+            "overriddenSavedInstanceState";
+
+    private static final String TAG = "TestActivity";
+
+    private Bundle overrideSavedInstance(Bundle savedInstance) {
+        Intent intent = getIntent();
+        if (intent != null) {
+            Bundle b = intent.getBundleExtra(EXTRA_OVERRIDDEN_SAVED_INSTANCE_STATE);
+            if (b != null) {
+                return b;
+            }
+        }
+        return savedInstance;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        savedInstanceState = overrideSavedInstance(savedInstanceState);
+        super.onCreate(savedInstanceState);
+        Log.d(TAG, "onCreate " + this);
+        Intent intent = getIntent();
+
+        final int uiOptions = intent.getIntExtra(EXTRA_UI_VISIBILITY, 0);
+        if (uiOptions != 0) {
+            getWindow().getDecorView().setSystemUiVisibility(uiOptions);
+        }
+
+        setContentView(intent.getIntExtra(EXTRA_ACTIVITY_LAYOUT, R.layout.single_fragment));
+        if (savedInstanceState == null && findViewById(R.id.main_frame) != null) {
+            try {
+                Fragment fragment = (Fragment) Class.forName(
+                        intent.getStringExtra(EXTRA_FRAGMENT_NAME)).newInstance();
+                FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
+                ft.replace(R.id.main_frame, fragment);
+                ft.commit();
+            } catch (Exception ex) {
+                ex.printStackTrace();
+                finish();
+            }
+        }
+    }
+
+    @Override
+    protected void onRestoreInstanceState(Bundle savedInstanceState) {
+        super.onRestoreInstanceState(overrideSavedInstance(savedInstanceState));
+    }
+
+    public Bundle performSaveInstanceState() {
+        Bundle state = new Bundle();
+        onSaveInstanceState(state);
+        return state;
+    }
+
+    public Fragment getTestFragment() {
+        return getSupportFragmentManager().findFragmentById(R.id.main_frame);
+    }
+}
diff --git a/leanback/tests/java/android/support/v17/leanback/app/SingleSupportFragmentTestBase.java b/leanback/tests/java/android/support/v17/leanback/app/SingleSupportFragmentTestBase.java
new file mode 100644
index 0000000..8cce627
--- /dev/null
+++ b/leanback/tests/java/android/support/v17/leanback/app/SingleSupportFragmentTestBase.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+import android.support.v7.widget.RecyclerView;
+
+import org.junit.Rule;
+import org.junit.rules.TestName;
+
+public class SingleSupportFragmentTestBase {
+
+    private static final long WAIT_FOR_SCROLL_IDLE_TIMEOUT_MS = 60000;
+
+    @Rule
+    public TestName mUnitTestName = new TestName();
+
+    @Rule
+    public ActivityTestRule<SingleSupportFragmentTestActivity> activityTestRule =
+            new ActivityTestRule<>(SingleSupportFragmentTestActivity.class, false, false);
+
+    public void sendKeys(int ...keys) {
+        for (int i = 0; i < keys.length; i++) {
+            InstrumentationRegistry.getInstrumentation().sendKeyDownUpSync(keys[i]);
+        }
+    }
+
+    /**
+     * Options that will be passed throught Intent to SingleSupportFragmentTestActivity
+     */
+    public static class Options {
+        int mActivityLayoutId;
+        int mUiVisibility;
+        Bundle mSavedInstance;
+
+        public Options() {
+        }
+
+        public Options activityLayoutId(int activityLayoutId) {
+            mActivityLayoutId = activityLayoutId;
+            return this;
+        }
+
+        public Options uiVisibility(int uiVisibility) {
+            mUiVisibility = uiVisibility;
+            return this;
+        }
+
+        public Options savedInstance(Bundle savedInstance) {
+            mSavedInstance = savedInstance;
+            return this;
+        }
+
+        public void collect(Intent intent) {
+            if (mActivityLayoutId != 0) {
+                intent.putExtra(SingleSupportFragmentTestActivity.EXTRA_ACTIVITY_LAYOUT,
+                        mActivityLayoutId);
+            }
+            if (mUiVisibility != 0) {
+                intent.putExtra(SingleSupportFragmentTestActivity.EXTRA_UI_VISIBILITY, mUiVisibility);
+            }
+            if (mSavedInstance != null) {
+                intent.putExtra(SingleSupportFragmentTestActivity.EXTRA_OVERRIDDEN_SAVED_INSTANCE_STATE,
+                        mSavedInstance);
+            }
+        }
+    }
+
+    public SingleSupportFragmentTestActivity launchAndWaitActivity(Class fragmentClass, long waitTimeMs) {
+        return launchAndWaitActivity(fragmentClass.getName(), null, waitTimeMs);
+    }
+
+    public SingleSupportFragmentTestActivity launchAndWaitActivity(Class fragmentClass, Options options,
+            long waitTimeMs) {
+        return launchAndWaitActivity(fragmentClass.getName(), options, waitTimeMs);
+    }
+
+    public SingleSupportFragmentTestActivity launchAndWaitActivity(String firstFragmentName,
+            Options options, long waitTimeMs) {
+        Intent intent = new Intent();
+        intent.putExtra(SingleSupportFragmentTestActivity.EXTRA_FRAGMENT_NAME, firstFragmentName);
+        if (options != null) {
+            options.collect(intent);
+        }
+        SingleSupportFragmentTestActivity activity = activityTestRule.launchActivity(intent);
+        SystemClock.sleep(waitTimeMs);
+        return activity;
+    }
+
+    protected void waitForScrollIdle(RecyclerView recyclerView) throws Throwable {
+        waitForScrollIdle(recyclerView, null);
+    }
+
+    protected void waitForScrollIdle(RecyclerView recyclerView, Runnable verify) throws Throwable {
+        Thread.sleep(100);
+        int total = 0;
+        while (recyclerView.getLayoutManager().isSmoothScrolling()
+                || recyclerView.getScrollState() != recyclerView.SCROLL_STATE_IDLE) {
+            if ((total += 100) >= WAIT_FOR_SCROLL_IDLE_TIMEOUT_MS) {
+                throw new RuntimeException("waitForScrollIdle Timeout");
+            }
+            try {
+                Thread.sleep(100);
+            } catch (InterruptedException ex) {
+                break;
+            }
+            if (verify != null) {
+                activityTestRule.runOnUiThread(verify);
+            }
+        }
+    }
+
+}
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/StringPresenter.java b/leanback/tests/java/android/support/v17/leanback/app/StringPresenter.java
similarity index 100%
rename from v17/leanback/tests/java/android/support/v17/leanback/app/StringPresenter.java
rename to leanback/tests/java/android/support/v17/leanback/app/StringPresenter.java
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/TestActivity.java b/leanback/tests/java/android/support/v17/leanback/app/TestActivity.java
similarity index 100%
rename from v17/leanback/tests/java/android/support/v17/leanback/app/TestActivity.java
rename to leanback/tests/java/android/support/v17/leanback/app/TestActivity.java
diff --git a/leanback/tests/java/android/support/v17/leanback/app/VerticalGridFragmentTest.java b/leanback/tests/java/android/support/v17/leanback/app/VerticalGridFragmentTest.java
new file mode 100644
index 0000000..649689c
--- /dev/null
+++ b/leanback/tests/java/android/support/v17/leanback/app/VerticalGridFragmentTest.java
@@ -0,0 +1,71 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from VerticalGridSupportFragmentTest.java.  DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v17.leanback.app;
+
+import android.os.Bundle;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v17.leanback.widget.ArrayObjectAdapter;
+import android.support.v17.leanback.widget.VerticalGridPresenter;
+import android.app.Fragment;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class VerticalGridFragmentTest extends SingleFragmentTestBase {
+
+    public static class GridFragment extends VerticalGridFragment {
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            if (savedInstanceState == null) {
+                prepareEntranceTransition();
+            }
+            VerticalGridPresenter gridPresenter = new VerticalGridPresenter();
+            gridPresenter.setNumberOfColumns(3);
+            setGridPresenter(gridPresenter);
+            setAdapter(new ArrayObjectAdapter());
+        }
+    }
+
+    @Test
+    public void immediateRemoveFragment() throws Throwable {
+        final SingleFragmentTestActivity activity = launchAndWaitActivity(GridFragment.class, 500);
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                GridFragment f = new GridFragment();
+                activity.getFragmentManager().beginTransaction()
+                        .replace(android.R.id.content, f, null).commit();
+                f.startEntranceTransition();
+                activity.getFragmentManager().beginTransaction()
+                        .replace(android.R.id.content, new Fragment(), null).commit();
+            }
+        });
+
+        Thread.sleep(1000);
+        activity.finish();
+    }
+
+}
diff --git a/leanback/tests/java/android/support/v17/leanback/app/VerticalGridSupportFragmentTest.java b/leanback/tests/java/android/support/v17/leanback/app/VerticalGridSupportFragmentTest.java
new file mode 100644
index 0000000..ccbfa04
--- /dev/null
+++ b/leanback/tests/java/android/support/v17/leanback/app/VerticalGridSupportFragmentTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v17.leanback.app;
+
+import android.os.Bundle;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v17.leanback.widget.ArrayObjectAdapter;
+import android.support.v17.leanback.widget.VerticalGridPresenter;
+import android.support.v4.app.Fragment;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class VerticalGridSupportFragmentTest extends SingleSupportFragmentTestBase {
+
+    public static class GridFragment extends VerticalGridSupportFragment {
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            if (savedInstanceState == null) {
+                prepareEntranceTransition();
+            }
+            VerticalGridPresenter gridPresenter = new VerticalGridPresenter();
+            gridPresenter.setNumberOfColumns(3);
+            setGridPresenter(gridPresenter);
+            setAdapter(new ArrayObjectAdapter());
+        }
+    }
+
+    @Test
+    public void immediateRemoveFragment() throws Throwable {
+        final SingleSupportFragmentTestActivity activity = launchAndWaitActivity(GridFragment.class, 500);
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                GridFragment f = new GridFragment();
+                activity.getSupportFragmentManager().beginTransaction()
+                        .replace(android.R.id.content, f, null).commit();
+                f.startEntranceTransition();
+                activity.getSupportFragmentManager().beginTransaction()
+                        .replace(android.R.id.content, new Fragment(), null).commit();
+            }
+        });
+
+        Thread.sleep(1000);
+        activity.finish();
+    }
+
+}
diff --git a/leanback/tests/java/android/support/v17/leanback/app/VideoFragmentTest.java b/leanback/tests/java/android/support/v17/leanback/app/VideoFragmentTest.java
new file mode 100644
index 0000000..a8b65d8
--- /dev/null
+++ b/leanback/tests/java/android/support/v17/leanback/app/VideoFragmentTest.java
@@ -0,0 +1,256 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from VideoSupportFragmentTest.java.  DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v17.leanback.app;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertTrue;
+
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v17.leanback.media.MediaPlayerGlue;
+import android.support.v17.leanback.media.PlaybackGlue;
+import android.support.v17.leanback.media.PlaybackGlueHost;
+import android.support.v17.leanback.test.R;
+import android.support.v17.leanback.testutils.PollingCheck;
+import android.view.LayoutInflater;
+import android.view.SurfaceHolder;
+import android.view.View;
+import android.view.ViewGroup;
+
+import junit.framework.Assert;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class VideoFragmentTest extends SingleFragmentTestBase {
+
+    public static class Fragment_setSurfaceViewCallbackBeforeCreate extends VideoFragment {
+        boolean mSurfaceCreated;
+        @Override
+        public View onCreateView(
+                LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+
+            setSurfaceHolderCallback(new SurfaceHolder.Callback() {
+                @Override
+                public void surfaceCreated(SurfaceHolder holder) {
+                    mSurfaceCreated = true;
+                }
+
+                @Override
+                public void surfaceChanged(SurfaceHolder holder, int format, int width,
+                                           int height) {
+                }
+
+                @Override
+                public void surfaceDestroyed(SurfaceHolder holder) {
+                    mSurfaceCreated = false;
+                }
+            });
+
+            return super.onCreateView(inflater, container, savedInstanceState);
+        }
+    }
+
+    @Test
+    public void setSurfaceViewCallbackBeforeCreate() {
+        final SingleFragmentTestActivity activity =
+                launchAndWaitActivity(Fragment_setSurfaceViewCallbackBeforeCreate.class, 1000);
+        Fragment_setSurfaceViewCallbackBeforeCreate fragment1 =
+                (Fragment_setSurfaceViewCallbackBeforeCreate) activity.getTestFragment();
+        assertNotNull(fragment1);
+        assertTrue(fragment1.mSurfaceCreated);
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                activity.getFragmentManager().beginTransaction()
+                        .replace(R.id.main_frame, new Fragment_setSurfaceViewCallbackBeforeCreate())
+                        .commitAllowingStateLoss();
+            }
+        });
+        SystemClock.sleep(500);
+
+        assertFalse(fragment1.mSurfaceCreated);
+
+        Fragment_setSurfaceViewCallbackBeforeCreate fragment2 =
+                (Fragment_setSurfaceViewCallbackBeforeCreate) activity.getTestFragment();
+        assertNotNull(fragment2);
+        assertTrue(fragment2.mSurfaceCreated);
+        assertNotSame(fragment1, fragment2);
+    }
+
+    @Test
+    public void setSurfaceViewCallbackAfterCreate() {
+        SingleFragmentTestActivity activity = launchAndWaitActivity(VideoFragment.class, 1000);
+        VideoFragment fragment = (VideoFragment) activity.getTestFragment();
+
+        assertNotNull(fragment);
+
+        final boolean[] surfaceCreated = new boolean[1];
+        fragment.setSurfaceHolderCallback(new SurfaceHolder.Callback() {
+            @Override
+            public void surfaceCreated(SurfaceHolder holder) {
+                surfaceCreated[0] = true;
+            }
+
+            @Override
+            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+            }
+
+            @Override
+            public void surfaceDestroyed(SurfaceHolder holder) {
+                surfaceCreated[0] = false;
+            }
+        });
+        assertTrue(surfaceCreated[0]);
+    }
+
+    public static class Fragment_withVideoPlayer extends VideoFragment {
+        MediaPlayerGlue mGlue;
+        int mOnCreateCalled;
+        int mOnCreateViewCalled;
+        int mOnDestroyViewCalled;
+        int mOnDestroyCalled;
+        int mGlueAttachedToHost;
+        int mGlueDetachedFromHost;
+        int mGlueOnReadyForPlaybackCalled;
+
+        public Fragment_withVideoPlayer() {
+            setRetainInstance(true);
+        }
+
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            mOnCreateCalled++;
+            super.onCreate(savedInstanceState);
+            mGlue = new MediaPlayerGlue(getActivity()) {
+                @Override
+                protected void onDetachedFromHost() {
+                    mGlueDetachedFromHost++;
+                    super.onDetachedFromHost();
+                }
+
+                @Override
+                protected void onAttachedToHost(PlaybackGlueHost host) {
+                    super.onAttachedToHost(host);
+                    mGlueAttachedToHost++;
+                }
+            };
+            mGlue.setMode(MediaPlayerGlue.REPEAT_ALL);
+            mGlue.setArtist("Leanback");
+            mGlue.setTitle("Leanback team at work");
+            mGlue.setMediaSource(
+                    Uri.parse("android.resource://android.support.v17.leanback.test/raw/video"));
+            mGlue.addPlayerCallback(new PlaybackGlue.PlayerCallback() {
+                @Override
+                public void onPreparedStateChanged(PlaybackGlue glue) {
+                    if (glue.isPrepared()) {
+                        mGlueOnReadyForPlaybackCalled++;
+                        mGlue.play();
+                    }
+                }
+            });
+            mGlue.setHost(new VideoFragmentGlueHost(this));
+        }
+
+        @Override
+        public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                                 Bundle savedInstanceState) {
+            mOnCreateViewCalled++;
+            return super.onCreateView(inflater, container, savedInstanceState);
+        }
+
+        @Override
+        public void onDestroyView() {
+            mOnDestroyViewCalled++;
+            super.onDestroyView();
+        }
+
+        @Override
+        public void onDestroy() {
+            mOnDestroyCalled++;
+            super.onDestroy();
+        }
+    }
+
+    @Test
+    public void mediaPlayerGlueInVideoFragment() {
+        final SingleFragmentTestActivity activity =
+                launchAndWaitActivity(Fragment_withVideoPlayer.class, 1000);
+        final Fragment_withVideoPlayer fragment = (Fragment_withVideoPlayer)
+                activity.getTestFragment();
+
+        PollingCheck.waitFor(5000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return fragment.mGlue.isMediaPlaying();
+            }
+        });
+
+        assertEquals(1, fragment.mOnCreateCalled);
+        assertEquals(1, fragment.mOnCreateViewCalled);
+        assertEquals(0, fragment.mOnDestroyViewCalled);
+        assertEquals(1, fragment.mGlueOnReadyForPlaybackCalled);
+        View fragmentViewBeforeRecreate = fragment.getView();
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                activity.recreate();
+            }
+        });
+
+        PollingCheck.waitFor(5000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return fragment.mOnCreateViewCalled == 2 && fragment.mGlue.isMediaPlaying();
+            }
+        });
+        View fragmentViewAfterRecreate = fragment.getView();
+
+        Assert.assertNotSame(fragmentViewBeforeRecreate, fragmentViewAfterRecreate);
+        assertEquals(1, fragment.mOnCreateCalled);
+        assertEquals(2, fragment.mOnCreateViewCalled);
+        assertEquals(1, fragment.mOnDestroyViewCalled);
+
+        assertEquals(1, fragment.mGlueAttachedToHost);
+        assertEquals(0, fragment.mGlueDetachedFromHost);
+        assertEquals(1, fragment.mGlueOnReadyForPlaybackCalled);
+
+        activity.finish();
+        PollingCheck.waitFor(5000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return fragment.mGlueDetachedFromHost == 1;
+            }
+        });
+        assertEquals(2, fragment.mOnDestroyViewCalled);
+        assertEquals(1, fragment.mOnDestroyCalled);
+    }
+
+}
diff --git a/leanback/tests/java/android/support/v17/leanback/app/VideoSupportFragmentTest.java b/leanback/tests/java/android/support/v17/leanback/app/VideoSupportFragmentTest.java
new file mode 100644
index 0000000..4d66285
--- /dev/null
+++ b/leanback/tests/java/android/support/v17/leanback/app/VideoSupportFragmentTest.java
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v17.leanback.app;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertTrue;
+
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v17.leanback.media.MediaPlayerGlue;
+import android.support.v17.leanback.media.PlaybackGlue;
+import android.support.v17.leanback.media.PlaybackGlueHost;
+import android.support.v17.leanback.test.R;
+import android.support.v17.leanback.testutils.PollingCheck;
+import android.view.LayoutInflater;
+import android.view.SurfaceHolder;
+import android.view.View;
+import android.view.ViewGroup;
+
+import junit.framework.Assert;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class VideoSupportFragmentTest extends SingleSupportFragmentTestBase {
+
+    public static class Fragment_setSurfaceViewCallbackBeforeCreate extends VideoSupportFragment {
+        boolean mSurfaceCreated;
+        @Override
+        public View onCreateView(
+                LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+
+            setSurfaceHolderCallback(new SurfaceHolder.Callback() {
+                @Override
+                public void surfaceCreated(SurfaceHolder holder) {
+                    mSurfaceCreated = true;
+                }
+
+                @Override
+                public void surfaceChanged(SurfaceHolder holder, int format, int width,
+                                           int height) {
+                }
+
+                @Override
+                public void surfaceDestroyed(SurfaceHolder holder) {
+                    mSurfaceCreated = false;
+                }
+            });
+
+            return super.onCreateView(inflater, container, savedInstanceState);
+        }
+    }
+
+    @Test
+    public void setSurfaceViewCallbackBeforeCreate() {
+        final SingleSupportFragmentTestActivity activity =
+                launchAndWaitActivity(Fragment_setSurfaceViewCallbackBeforeCreate.class, 1000);
+        Fragment_setSurfaceViewCallbackBeforeCreate fragment1 =
+                (Fragment_setSurfaceViewCallbackBeforeCreate) activity.getTestFragment();
+        assertNotNull(fragment1);
+        assertTrue(fragment1.mSurfaceCreated);
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                activity.getSupportFragmentManager().beginTransaction()
+                        .replace(R.id.main_frame, new Fragment_setSurfaceViewCallbackBeforeCreate())
+                        .commitAllowingStateLoss();
+            }
+        });
+        SystemClock.sleep(500);
+
+        assertFalse(fragment1.mSurfaceCreated);
+
+        Fragment_setSurfaceViewCallbackBeforeCreate fragment2 =
+                (Fragment_setSurfaceViewCallbackBeforeCreate) activity.getTestFragment();
+        assertNotNull(fragment2);
+        assertTrue(fragment2.mSurfaceCreated);
+        assertNotSame(fragment1, fragment2);
+    }
+
+    @Test
+    public void setSurfaceViewCallbackAfterCreate() {
+        SingleSupportFragmentTestActivity activity = launchAndWaitActivity(VideoSupportFragment.class, 1000);
+        VideoSupportFragment fragment = (VideoSupportFragment) activity.getTestFragment();
+
+        assertNotNull(fragment);
+
+        final boolean[] surfaceCreated = new boolean[1];
+        fragment.setSurfaceHolderCallback(new SurfaceHolder.Callback() {
+            @Override
+            public void surfaceCreated(SurfaceHolder holder) {
+                surfaceCreated[0] = true;
+            }
+
+            @Override
+            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+            }
+
+            @Override
+            public void surfaceDestroyed(SurfaceHolder holder) {
+                surfaceCreated[0] = false;
+            }
+        });
+        assertTrue(surfaceCreated[0]);
+    }
+
+    public static class Fragment_withVideoPlayer extends VideoSupportFragment {
+        MediaPlayerGlue mGlue;
+        int mOnCreateCalled;
+        int mOnCreateViewCalled;
+        int mOnDestroyViewCalled;
+        int mOnDestroyCalled;
+        int mGlueAttachedToHost;
+        int mGlueDetachedFromHost;
+        int mGlueOnReadyForPlaybackCalled;
+
+        public Fragment_withVideoPlayer() {
+            setRetainInstance(true);
+        }
+
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            mOnCreateCalled++;
+            super.onCreate(savedInstanceState);
+            mGlue = new MediaPlayerGlue(getActivity()) {
+                @Override
+                protected void onDetachedFromHost() {
+                    mGlueDetachedFromHost++;
+                    super.onDetachedFromHost();
+                }
+
+                @Override
+                protected void onAttachedToHost(PlaybackGlueHost host) {
+                    super.onAttachedToHost(host);
+                    mGlueAttachedToHost++;
+                }
+            };
+            mGlue.setMode(MediaPlayerGlue.REPEAT_ALL);
+            mGlue.setArtist("Leanback");
+            mGlue.setTitle("Leanback team at work");
+            mGlue.setMediaSource(
+                    Uri.parse("android.resource://android.support.v17.leanback.test/raw/video"));
+            mGlue.addPlayerCallback(new PlaybackGlue.PlayerCallback() {
+                @Override
+                public void onPreparedStateChanged(PlaybackGlue glue) {
+                    if (glue.isPrepared()) {
+                        mGlueOnReadyForPlaybackCalled++;
+                        mGlue.play();
+                    }
+                }
+            });
+            mGlue.setHost(new VideoSupportFragmentGlueHost(this));
+        }
+
+        @Override
+        public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                                 Bundle savedInstanceState) {
+            mOnCreateViewCalled++;
+            return super.onCreateView(inflater, container, savedInstanceState);
+        }
+
+        @Override
+        public void onDestroyView() {
+            mOnDestroyViewCalled++;
+            super.onDestroyView();
+        }
+
+        @Override
+        public void onDestroy() {
+            mOnDestroyCalled++;
+            super.onDestroy();
+        }
+    }
+
+    @Test
+    public void mediaPlayerGlueInVideoSupportFragment() {
+        final SingleSupportFragmentTestActivity activity =
+                launchAndWaitActivity(Fragment_withVideoPlayer.class, 1000);
+        final Fragment_withVideoPlayer fragment = (Fragment_withVideoPlayer)
+                activity.getTestFragment();
+
+        PollingCheck.waitFor(5000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return fragment.mGlue.isMediaPlaying();
+            }
+        });
+
+        assertEquals(1, fragment.mOnCreateCalled);
+        assertEquals(1, fragment.mOnCreateViewCalled);
+        assertEquals(0, fragment.mOnDestroyViewCalled);
+        assertEquals(1, fragment.mGlueOnReadyForPlaybackCalled);
+        View fragmentViewBeforeRecreate = fragment.getView();
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                activity.recreate();
+            }
+        });
+
+        PollingCheck.waitFor(5000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return fragment.mOnCreateViewCalled == 2 && fragment.mGlue.isMediaPlaying();
+            }
+        });
+        View fragmentViewAfterRecreate = fragment.getView();
+
+        Assert.assertNotSame(fragmentViewBeforeRecreate, fragmentViewAfterRecreate);
+        assertEquals(1, fragment.mOnCreateCalled);
+        assertEquals(2, fragment.mOnCreateViewCalled);
+        assertEquals(1, fragment.mOnDestroyViewCalled);
+
+        assertEquals(1, fragment.mGlueAttachedToHost);
+        assertEquals(0, fragment.mGlueDetachedFromHost);
+        assertEquals(1, fragment.mGlueOnReadyForPlaybackCalled);
+
+        activity.finish();
+        PollingCheck.waitFor(5000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return fragment.mGlueDetachedFromHost == 1;
+            }
+        });
+        assertEquals(2, fragment.mOnDestroyViewCalled);
+        assertEquals(1, fragment.mOnDestroyCalled);
+    }
+
+}
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/wizard/GuidedDatePickerTest.java b/leanback/tests/java/android/support/v17/leanback/app/wizard/GuidedDatePickerTest.java
similarity index 100%
rename from v17/leanback/tests/java/android/support/v17/leanback/app/wizard/GuidedDatePickerTest.java
rename to leanback/tests/java/android/support/v17/leanback/app/wizard/GuidedDatePickerTest.java
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/wizard/GuidedStepAttributesTest.java b/leanback/tests/java/android/support/v17/leanback/app/wizard/GuidedStepAttributesTest.java
similarity index 100%
rename from v17/leanback/tests/java/android/support/v17/leanback/app/wizard/GuidedStepAttributesTest.java
rename to leanback/tests/java/android/support/v17/leanback/app/wizard/GuidedStepAttributesTest.java
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/wizard/GuidedStepAttributesTestActivity.java b/leanback/tests/java/android/support/v17/leanback/app/wizard/GuidedStepAttributesTestActivity.java
similarity index 100%
rename from v17/leanback/tests/java/android/support/v17/leanback/app/wizard/GuidedStepAttributesTestActivity.java
rename to leanback/tests/java/android/support/v17/leanback/app/wizard/GuidedStepAttributesTestActivity.java
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/wizard/GuidedStepAttributesTestFragment.java b/leanback/tests/java/android/support/v17/leanback/app/wizard/GuidedStepAttributesTestFragment.java
similarity index 100%
rename from v17/leanback/tests/java/android/support/v17/leanback/app/wizard/GuidedStepAttributesTestFragment.java
rename to leanback/tests/java/android/support/v17/leanback/app/wizard/GuidedStepAttributesTestFragment.java
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/graphics/CompositeDrawableTest.java b/leanback/tests/java/android/support/v17/leanback/graphics/CompositeDrawableTest.java
similarity index 100%
rename from v17/leanback/tests/java/android/support/v17/leanback/graphics/CompositeDrawableTest.java
rename to leanback/tests/java/android/support/v17/leanback/graphics/CompositeDrawableTest.java
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/graphics/FitWidthBitmapDrawableTest.java b/leanback/tests/java/android/support/v17/leanback/graphics/FitWidthBitmapDrawableTest.java
similarity index 100%
rename from v17/leanback/tests/java/android/support/v17/leanback/graphics/FitWidthBitmapDrawableTest.java
rename to leanback/tests/java/android/support/v17/leanback/graphics/FitWidthBitmapDrawableTest.java
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/media/MediaControllerAdapterTest.java b/leanback/tests/java/android/support/v17/leanback/media/MediaControllerAdapterTest.java
similarity index 100%
rename from v17/leanback/tests/java/android/support/v17/leanback/media/MediaControllerAdapterTest.java
rename to leanback/tests/java/android/support/v17/leanback/media/MediaControllerAdapterTest.java
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/media/MediaPlayerGlueTest.java b/leanback/tests/java/android/support/v17/leanback/media/MediaPlayerGlueTest.java
similarity index 100%
rename from v17/leanback/tests/java/android/support/v17/leanback/media/MediaPlayerGlueTest.java
rename to leanback/tests/java/android/support/v17/leanback/media/MediaPlayerGlueTest.java
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/media/PlaybackBannerControlGlueTest.java b/leanback/tests/java/android/support/v17/leanback/media/PlaybackBannerControlGlueTest.java
similarity index 100%
rename from v17/leanback/tests/java/android/support/v17/leanback/media/PlaybackBannerControlGlueTest.java
rename to leanback/tests/java/android/support/v17/leanback/media/PlaybackBannerControlGlueTest.java
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/media/PlaybackControlGlueTest.java b/leanback/tests/java/android/support/v17/leanback/media/PlaybackControlGlueTest.java
similarity index 100%
rename from v17/leanback/tests/java/android/support/v17/leanback/media/PlaybackControlGlueTest.java
rename to leanback/tests/java/android/support/v17/leanback/media/PlaybackControlGlueTest.java
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/media/PlaybackGlueHostImpl.java b/leanback/tests/java/android/support/v17/leanback/media/PlaybackGlueHostImpl.java
similarity index 100%
rename from v17/leanback/tests/java/android/support/v17/leanback/media/PlaybackGlueHostImpl.java
rename to leanback/tests/java/android/support/v17/leanback/media/PlaybackGlueHostImpl.java
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/media/PlaybackGlueTest.java b/leanback/tests/java/android/support/v17/leanback/media/PlaybackGlueTest.java
similarity index 100%
rename from v17/leanback/tests/java/android/support/v17/leanback/media/PlaybackGlueTest.java
rename to leanback/tests/java/android/support/v17/leanback/media/PlaybackGlueTest.java
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/media/PlaybackTransportControlGlueTest.java b/leanback/tests/java/android/support/v17/leanback/media/PlaybackTransportControlGlueTest.java
similarity index 100%
rename from v17/leanback/tests/java/android/support/v17/leanback/media/PlaybackTransportControlGlueTest.java
rename to leanback/tests/java/android/support/v17/leanback/media/PlaybackTransportControlGlueTest.java
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/testutils/PollingCheck.java b/leanback/tests/java/android/support/v17/leanback/testutils/PollingCheck.java
similarity index 100%
rename from v17/leanback/tests/java/android/support/v17/leanback/testutils/PollingCheck.java
rename to leanback/tests/java/android/support/v17/leanback/testutils/PollingCheck.java
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/widget/AssertHelper.java b/leanback/tests/java/android/support/v17/leanback/widget/AssertHelper.java
similarity index 100%
rename from v17/leanback/tests/java/android/support/v17/leanback/widget/AssertHelper.java
rename to leanback/tests/java/android/support/v17/leanback/widget/AssertHelper.java
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/widget/BaseCardViewTest.java b/leanback/tests/java/android/support/v17/leanback/widget/BaseCardViewTest.java
similarity index 100%
rename from v17/leanback/tests/java/android/support/v17/leanback/widget/BaseCardViewTest.java
rename to leanback/tests/java/android/support/v17/leanback/widget/BaseCardViewTest.java
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/widget/ControlBarTest.java b/leanback/tests/java/android/support/v17/leanback/widget/ControlBarTest.java
similarity index 100%
rename from v17/leanback/tests/java/android/support/v17/leanback/widget/ControlBarTest.java
rename to leanback/tests/java/android/support/v17/leanback/widget/ControlBarTest.java
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/widget/GridActivity.java b/leanback/tests/java/android/support/v17/leanback/widget/GridActivity.java
similarity index 100%
rename from v17/leanback/tests/java/android/support/v17/leanback/widget/GridActivity.java
rename to leanback/tests/java/android/support/v17/leanback/widget/GridActivity.java
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/widget/GridTest.java b/leanback/tests/java/android/support/v17/leanback/widget/GridTest.java
similarity index 100%
rename from v17/leanback/tests/java/android/support/v17/leanback/widget/GridTest.java
rename to leanback/tests/java/android/support/v17/leanback/widget/GridTest.java
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/widget/GridWidgetPrefetchTest.java b/leanback/tests/java/android/support/v17/leanback/widget/GridWidgetPrefetchTest.java
similarity index 100%
rename from v17/leanback/tests/java/android/support/v17/leanback/widget/GridWidgetPrefetchTest.java
rename to leanback/tests/java/android/support/v17/leanback/widget/GridWidgetPrefetchTest.java
diff --git a/leanback/tests/java/android/support/v17/leanback/widget/GridWidgetTest.java b/leanback/tests/java/android/support/v17/leanback/widget/GridWidgetTest.java
new file mode 100644
index 0000000..7bf96a5
--- /dev/null
+++ b/leanback/tests/java/android/support/v17/leanback/widget/GridWidgetTest.java
@@ -0,0 +1,5747 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v17.leanback.widget;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Intent;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.graphics.drawable.ColorDrawable;
+import android.os.Build;
+import android.os.Parcelable;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.filters.SdkSuppress;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v17.leanback.test.R;
+import android.support.v17.leanback.testutils.PollingCheck;
+import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
+import android.support.v7.widget.DefaultItemAnimator;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerViewAccessibilityDelegate;
+import android.text.Selection;
+import android.text.Spannable;
+import android.util.DisplayMetrics;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+import android.util.TypedValue;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import org.junit.After;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class GridWidgetTest {
+
+    private static final float DELTA = 1f;
+    private static final boolean HUMAN_DELAY = false;
+    private static final long WAIT_FOR_SCROLL_IDLE_TIMEOUT_MS = 60000;
+    private static final int WAIT_FOR_LAYOUT_PASS_TIMEOUT_MS = 2000;
+    private static final int WAIT_FOR_ITEM_ANIMATION_FINISH_TIMEOUT_MS = 6000;
+
+    protected ActivityTestRule<GridActivity> mActivityTestRule;
+    protected GridActivity mActivity;
+    protected BaseGridView mGridView;
+    protected GridLayoutManager mLayoutManager;
+    private GridLayoutManager.OnLayoutCompleteListener mWaitLayoutListener;
+    protected int mOrientation;
+    protected int mNumRows;
+    protected int[] mRemovedItems;
+
+    private final Comparator<View> mRowSortComparator = new Comparator<View>() {
+        @Override
+        public int compare(View lhs, View rhs) {
+            if (mOrientation == BaseGridView.HORIZONTAL) {
+                return lhs.getLeft() - rhs.getLeft();
+            } else {
+                return lhs.getTop() - rhs.getTop();
+            }
+        };
+    };
+
+    /**
+     * Verify margins between items on same row are same.
+     */
+    private final Runnable mVerifyLayout = new Runnable() {
+        @Override
+        public void run() {
+            verifyMargin();
+        }
+    };
+
+    @Rule public TestName testName = new TestName();
+
+    public static void sendKey(int keyCode) {
+        InstrumentationRegistry.getInstrumentation().sendKeyDownUpSync(keyCode);
+    }
+
+    public static void sendRepeatedKeys(int repeats, int keyCode) {
+        for (int i = 0; i < repeats; i++) {
+            InstrumentationRegistry.getInstrumentation().sendKeyDownUpSync(keyCode);
+        }
+    }
+
+    private void humanDelay(int delay) throws InterruptedException {
+        if (HUMAN_DELAY) Thread.sleep(delay);
+    }
+    /**
+     * Change size of the Adapter and notifyDataSetChanged.
+     */
+    private void changeArraySize(final int size) throws Throwable {
+        performAndWaitForAnimation(new Runnable() {
+            @Override
+            public void run() {
+                mActivity.changeArraySize(size);
+            }
+        });
+    }
+
+    static String dumpGridView(BaseGridView gridView) {
+        return "findFocus:" + gridView.getRootView().findFocus()
+                + " isLayoutRequested:" + gridView.isLayoutRequested()
+                + " selectedPosition:" + gridView.getSelectedPosition()
+                + " adapter.itemCount:" + gridView.getAdapter().getItemCount()
+                + " itemAnimator.isRunning:" + gridView.getItemAnimator().isRunning()
+                + " scrollState:" + gridView.getScrollState();
+    }
+
+    /**
+     * Change selected position.
+     */
+    private void setSelectedPosition(final int position, final int scrollExtra) throws Throwable {
+        startWaitLayout();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.setSelectedPosition(position, scrollExtra);
+            }
+        });
+        waitForLayout(false);
+    }
+
+    private void setSelectedPosition(final int position) throws Throwable {
+        setSelectedPosition(position, 0);
+    }
+
+    private void setSelectedPositionSmooth(final int position) throws Throwable {
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.setSelectedPositionSmooth(position);
+            }
+        });
+    }
+    /**
+     * Scrolls using given key.
+     */
+    protected void scroll(int key, Runnable verify) throws Throwable {
+        do {
+            if (verify != null) {
+                mActivityTestRule.runOnUiThread(verify);
+            }
+            sendRepeatedKeys(10, key);
+            try {
+                Thread.sleep(300);
+            } catch (InterruptedException ex) {
+                break;
+            }
+        } while (mGridView.getLayoutManager().isSmoothScrolling()
+                || mGridView.getScrollState() != BaseGridView.SCROLL_STATE_IDLE);
+    }
+
+    protected void scrollToBegin(Runnable verify) throws Throwable {
+        int key;
+        // first move to first column/row
+        if (mOrientation == BaseGridView.HORIZONTAL) {
+            key = KeyEvent.KEYCODE_DPAD_UP;
+        } else {
+            if (mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL) {
+                key = KeyEvent.KEYCODE_DPAD_RIGHT;
+            } else {
+                key = KeyEvent.KEYCODE_DPAD_LEFT;
+            }
+        }
+        scroll(key, null);
+        if (mOrientation == BaseGridView.HORIZONTAL) {
+            if (mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL) {
+                key = KeyEvent.KEYCODE_DPAD_RIGHT;
+            } else {
+                key = KeyEvent.KEYCODE_DPAD_LEFT;
+            }
+        } else {
+            key = KeyEvent.KEYCODE_DPAD_UP;
+        }
+        scroll(key, verify);
+    }
+
+    protected void scrollToEnd(Runnable verify) throws Throwable {
+        int key;
+        // first move to first column/row
+        if (mOrientation == BaseGridView.HORIZONTAL) {
+            key = KeyEvent.KEYCODE_DPAD_UP;
+        } else {
+            if (mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL) {
+                key = KeyEvent.KEYCODE_DPAD_RIGHT;
+            } else {
+                key = KeyEvent.KEYCODE_DPAD_LEFT;
+            }
+        }
+        scroll(key, null);
+        if (mOrientation == BaseGridView.HORIZONTAL) {
+            if (mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL) {
+                key = KeyEvent.KEYCODE_DPAD_LEFT;
+            } else {
+                key = KeyEvent.KEYCODE_DPAD_RIGHT;
+            }
+        } else {
+            key = KeyEvent.KEYCODE_DPAD_DOWN;
+        }
+        scroll(key, verify);
+    }
+
+    /**
+     * Group and sort children by their position on each row (HORIZONTAL) or column(VERTICAL).
+     */
+    protected View[][] sortByRows() {
+        final HashMap<Integer, ArrayList<View>> rows = new HashMap<Integer, ArrayList<View>>();
+        ArrayList<Integer> rowLocations = new ArrayList<>();
+        for (int i = 0; i < mGridView.getChildCount(); i++) {
+            View v = mGridView.getChildAt(i);
+            int rowLocation;
+            if (mOrientation == BaseGridView.HORIZONTAL) {
+                rowLocation = v.getTop();
+            } else {
+                rowLocation = mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL
+                        ? v.getRight() : v.getLeft();
+            }
+            ArrayList<View> views = rows.get(rowLocation);
+            if (views == null) {
+                views = new ArrayList<View>();
+                rows.put(rowLocation, views);
+                rowLocations.add(rowLocation);
+            }
+            views.add(v);
+        }
+        Object[] sortedLocations = rowLocations.toArray();
+        Arrays.sort(sortedLocations);
+        if (mNumRows != rows.size()) {
+            assertEquals("Dump Views by rows "+rows, mNumRows, rows.size());
+        }
+        View[][] sorted = new View[rows.size()][];
+        for (int i = 0; i < rowLocations.size(); i++) {
+            Integer rowLocation = rowLocations.get(i);
+            ArrayList<View> arr = rows.get(rowLocation);
+            View[] views = arr.toArray(new View[arr.size()]);
+            Arrays.sort(views, mRowSortComparator);
+            sorted[i] = views;
+        }
+        return sorted;
+    }
+
+    protected void verifyMargin() {
+        View[][] sorted = sortByRows();
+        for (int row = 0; row < sorted.length; row++) {
+            View[] views = sorted[row];
+            int margin = -1;
+            for (int i = 1; i < views.length; i++) {
+                if (mOrientation == BaseGridView.HORIZONTAL) {
+                    assertEquals(mGridView.getHorizontalMargin(),
+                            views[i].getLeft() - views[i - 1].getRight());
+                } else {
+                    assertEquals(mGridView.getVerticalMargin(),
+                            views[i].getTop() - views[i - 1].getBottom());
+                }
+            }
+        }
+    }
+
+    protected void verifyBeginAligned() {
+        View[][] sorted = sortByRows();
+        int alignedLocation = 0;
+        if (mOrientation == BaseGridView.HORIZONTAL) {
+            if (mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL) {
+                for (int i = 0; i < sorted.length; i++) {
+                    if (i == 0) {
+                        alignedLocation = sorted[i][sorted[i].length - 1].getRight();
+                    } else {
+                        assertEquals(alignedLocation, sorted[i][sorted[i].length - 1].getRight());
+                    }
+                }
+            } else {
+                for (int i = 0; i < sorted.length; i++) {
+                    if (i == 0) {
+                        alignedLocation = sorted[i][0].getLeft();
+                    } else {
+                        assertEquals(alignedLocation, sorted[i][0].getLeft());
+                    }
+                }
+            }
+        } else {
+            for (int i = 0; i < sorted.length; i++) {
+                if (i == 0) {
+                    alignedLocation = sorted[i][0].getTop();
+                } else {
+                    assertEquals(alignedLocation, sorted[i][0].getTop());
+                }
+            }
+        }
+    }
+
+    protected int[] getEndEdges() {
+        View[][] sorted = sortByRows();
+        int[] edges = new int[sorted.length];
+        if (mOrientation == BaseGridView.HORIZONTAL) {
+            if (mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL) {
+                for (int i = 0; i < sorted.length; i++) {
+                    edges[i] = sorted[i][0].getLeft();
+                }
+            } else {
+                for (int i = 0; i < sorted.length; i++) {
+                    edges[i] = sorted[i][sorted[i].length - 1].getRight();
+                }
+            }
+        } else {
+            for (int i = 0; i < sorted.length; i++) {
+                edges[i] = sorted[i][sorted[i].length - 1].getBottom();
+            }
+        }
+        return edges;
+    }
+
+    protected void verifyEdgesSame(int[] edges, int[] edges2) {
+        assertEquals(edges.length, edges2.length);
+        for (int i = 0; i < edges.length; i++) {
+            assertEquals(edges[i], edges2[i]);
+        }
+    }
+
+    protected void verifyBoundCount(int count) {
+        if (mActivity.getBoundCount() != count) {
+            StringBuffer b = new StringBuffer();
+            b.append("ItemsLength: ");
+            for (int i = 0; i < mActivity.mItemLengths.length; i++) {
+                b.append(mActivity.mItemLengths[i]).append(",");
+            }
+            assertEquals("Bound count does not match, ItemsLengths: "+ b,
+                    count, mActivity.getBoundCount());
+        }
+    }
+
+    private static int getCenterY(View v) {
+        return (v.getTop() + v.getBottom())/2;
+    }
+
+    private static int getCenterX(View v) {
+        return (v.getLeft() + v.getRight())/2;
+    }
+
+    private void initActivity(Intent intent) throws Throwable {
+        mActivityTestRule = new ActivityTestRule<GridActivity>(GridActivity.class, false, false);
+        mActivity = mActivityTestRule.launchActivity(intent);
+        mActivityTestRule.runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    mActivity.setTitle(testName.getMethodName());
+                }
+            });
+        Thread.sleep(1000);
+        mGridView = mActivity.mGridView;
+        mLayoutManager = (GridLayoutManager) mGridView.getLayoutManager();
+    }
+
+    @After
+    public void clearTest() {
+        mWaitLayoutListener = null;
+        mLayoutManager = null;
+        mGridView = null;
+        mActivity = null;
+        mActivityTestRule = null;
+    }
+
+    /**
+     * Must be called before waitForLayout() to prepare layout listener.
+     */
+    protected void startWaitLayout() {
+        if (mWaitLayoutListener != null) {
+            throw new IllegalStateException("startWaitLayout() already called");
+        }
+        if (mLayoutManager.mLayoutCompleteListener != null) {
+            throw new IllegalStateException("Cannot startWaitLayout()");
+        }
+        mWaitLayoutListener = mLayoutManager.mLayoutCompleteListener =
+                mock(GridLayoutManager.OnLayoutCompleteListener.class);
+    }
+
+    /**
+     * wait layout to be called and remove the listener.
+     */
+    protected void waitForLayout() {
+        waitForLayout(true);
+    }
+
+    /**
+     * wait layout to be called and remove the listener.
+     * @param force True if always wait regardless if layout requested
+     */
+    protected void waitForLayout(boolean force) {
+        if (mWaitLayoutListener == null) {
+            throw new IllegalStateException("startWaitLayout() not called");
+        }
+        if (mWaitLayoutListener != mLayoutManager.mLayoutCompleteListener) {
+            throw new IllegalStateException("layout listener inconistent");
+        }
+        try {
+            if (force || mGridView.isLayoutRequested()) {
+                verify(mWaitLayoutListener, timeout(WAIT_FOR_LAYOUT_PASS_TIMEOUT_MS).atLeastOnce())
+                        .onLayoutCompleted(any(RecyclerView.State.class));
+            }
+        } finally {
+            mWaitLayoutListener = null;
+            mLayoutManager.mLayoutCompleteListener = null;
+        }
+    }
+
+    /**
+     * If currently running animator, wait for it to finish, otherwise return immediately.
+     * To wait the ItemAnimator start, you can use waitForLayout() to make sure layout pass has
+     * processed adapter change.
+     */
+    protected void waitForItemAnimation(int timeoutMs) throws Throwable {
+        final RecyclerView.ItemAnimator.ItemAnimatorFinishedListener listener = mock(
+                RecyclerView.ItemAnimator.ItemAnimatorFinishedListener.class);
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.getItemAnimator().isRunning(listener);
+            }
+        });
+        verify(listener, timeout(timeoutMs).atLeastOnce()).onAnimationsFinished();
+    }
+
+    protected void waitForItemAnimation() throws Throwable {
+        waitForItemAnimation(WAIT_FOR_ITEM_ANIMATION_FINISH_TIMEOUT_MS);
+    }
+
+    /**
+     * wait animation start
+     */
+    protected void waitForItemAnimationStart() throws Throwable {
+        long totalWait = 0;
+        while (!mGridView.getItemAnimator().isRunning()) {
+            Thread.sleep(10);
+            if ((totalWait += 10) > WAIT_FOR_ITEM_ANIMATION_FINISH_TIMEOUT_MS) {
+                throw new RuntimeException("waitForItemAnimationStart Timeout");
+            }
+        }
+    }
+
+    /**
+     * Run task in UI thread and wait for layout and ItemAnimator finishes.
+     */
+    protected void performAndWaitForAnimation(Runnable task) throws Throwable {
+        startWaitLayout();
+        mActivityTestRule.runOnUiThread(task);
+        waitForLayout();
+        waitForItemAnimation();
+    }
+
+    protected void waitForScrollIdle() throws Throwable {
+        waitForScrollIdle(null);
+    }
+
+    /**
+     * Wait for grid view stop scroll and optionally verify state of grid view.
+     */
+    protected void waitForScrollIdle(Runnable verify) throws Throwable {
+        Thread.sleep(100);
+        int total = 0;
+        while (mGridView.getLayoutManager().isSmoothScrolling()
+                || mGridView.getScrollState() != BaseGridView.SCROLL_STATE_IDLE) {
+            if ((total += 100) >= WAIT_FOR_SCROLL_IDLE_TIMEOUT_MS) {
+                throw new RuntimeException("waitForScrollIdle Timeout");
+            }
+            try {
+                Thread.sleep(100);
+            } catch (InterruptedException ex) {
+                break;
+            }
+            if (verify != null) {
+                mActivityTestRule.runOnUiThread(verify);
+            }
+        }
+    }
+
+    @Test
+    public void testThreeRowHorizontalBasic() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_grid);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 100);
+        initActivity(intent);
+        mOrientation = BaseGridView.HORIZONTAL;
+        mNumRows = 3;
+
+        scrollToEnd(mVerifyLayout);
+
+        scrollToBegin(mVerifyLayout);
+
+        verifyBeginAligned();
+    }
+
+    static class DividerDecoration extends RecyclerView.ItemDecoration {
+
+        private ColorDrawable mTopDivider;
+        private ColorDrawable mBottomDivider;
+        private int mLeftOffset;
+        private int mRightOffset;
+        private int mTopOffset;
+        private int mBottomOffset;
+
+        DividerDecoration(int leftOffset, int topOffset, int rightOffset, int bottomOffset) {
+            mLeftOffset = leftOffset;
+            mTopOffset = topOffset;
+            mRightOffset = rightOffset;
+            mBottomOffset = bottomOffset;
+        }
+
+        @Override
+        public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
+            if (mTopDivider == null) {
+                mTopDivider = new ColorDrawable(Color.RED);
+            }
+            if (mBottomDivider == null) {
+                mBottomDivider = new ColorDrawable(Color.BLUE);
+            }
+            final int childCount = parent.getChildCount();
+            final int width = parent.getWidth();
+            for (int childViewIndex = 0; childViewIndex < childCount; childViewIndex++) {
+                final View view = parent.getChildAt(childViewIndex);
+                mTopDivider.setBounds(0, (int) view.getY() - mTopOffset, width, (int) view.getY());
+                mTopDivider.draw(c);
+                mBottomDivider.setBounds(0, (int) view.getY() + view.getHeight(), width,
+                        (int) view.getY() + view.getHeight() + mBottomOffset);
+                mBottomDivider.draw(c);
+            }
+        }
+
+        @Override
+        public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
+                                   RecyclerView.State state) {
+            outRect.left = mLeftOffset;
+            outRect.top = mTopOffset;
+            outRect.right = mRightOffset;
+            outRect.bottom = mBottomOffset;
+        }
+    }
+
+    @Test
+    public void testItemDecorationAndMargins() throws Throwable {
+
+        final int leftMargin = 3;
+        final int topMargin = 4;
+        final int rightMargin = 7;
+        final int bottomMargin = 8;
+        final int itemHeight = 100;
+
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
+        intent.putExtra(GridActivity.EXTRA_ITEMS, new int[]{itemHeight, itemHeight, itemHeight});
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_MARGINS,
+                new int[]{leftMargin, topMargin, rightMargin, bottomMargin});
+        initActivity(intent);
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = 1;
+
+        final int paddingLeft = mGridView.getPaddingLeft();
+        final int paddingTop = mGridView.getPaddingTop();
+        final int verticalSpace = mGridView.getVerticalMargin();
+        final int decorationLeft = 17;
+        final int decorationTop = 1;
+        final int decorationRight = 19;
+        final int decorationBottom = 2;
+
+        performAndWaitForAnimation(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.addItemDecoration(new DividerDecoration(decorationLeft, decorationTop,
+                        decorationRight, decorationBottom));
+            }
+        });
+
+        View child0 = mGridView.getChildAt(0);
+        View child1 = mGridView.getChildAt(1);
+        View child2 = mGridView.getChildAt(2);
+
+        assertEquals(itemHeight, child0.getBottom() - child0.getTop());
+
+        // verify left margins
+        assertEquals(paddingLeft + leftMargin + decorationLeft, child0.getLeft());
+        assertEquals(paddingLeft + leftMargin + decorationLeft, child1.getLeft());
+        assertEquals(paddingLeft + leftMargin + decorationLeft, child2.getLeft());
+        // verify top bottom margins and decoration offset
+        assertEquals(paddingTop + topMargin + decorationTop, child0.getTop());
+        assertEquals(bottomMargin + decorationBottom + verticalSpace + decorationTop + topMargin,
+                child1.getTop() - child0.getBottom());
+        assertEquals(bottomMargin + decorationBottom + verticalSpace + decorationTop + topMargin,
+                child2.getTop() - child1.getBottom());
+
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
+    @Test
+    public void testItemDecorationAndMarginsAndOpticalBounds() throws Throwable {
+        final int leftMargin = 3;
+        final int topMargin = 4;
+        final int rightMargin = 7;
+        final int bottomMargin = 8;
+        final int itemHeight = 100;
+        final int ninePatchDrawableResourceId = R.drawable.lb_card_shadow_focused;
+
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
+        intent.putExtra(GridActivity.EXTRA_ITEMS, new int[]{itemHeight, itemHeight, itemHeight});
+        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.relative_layout);
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_MARGINS,
+                new int[]{leftMargin, topMargin, rightMargin, bottomMargin});
+        intent.putExtra(GridActivity.EXTRA_NINEPATCH_SHADOW, ninePatchDrawableResourceId);
+        initActivity(intent);
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = 1;
+
+        final int paddingLeft = mGridView.getPaddingLeft();
+        final int paddingTop = mGridView.getPaddingTop();
+        final int verticalSpace = mGridView.getVerticalMargin();
+        final int decorationLeft = 17;
+        final int decorationTop = 1;
+        final int decorationRight = 19;
+        final int decorationBottom = 2;
+
+        final Rect opticalPaddings = new Rect();
+        mGridView.getResources().getDrawable(ninePatchDrawableResourceId)
+                .getPadding(opticalPaddings);
+        final int opticalInsetsLeft = opticalPaddings.left;
+        final int opticalInsetsTop = opticalPaddings.top;
+        final int opticalInsetsRight = opticalPaddings.right;
+        final int opticalInsetsBottom = opticalPaddings.bottom;
+        assertTrue(opticalInsetsLeft > 0);
+        assertTrue(opticalInsetsTop > 0);
+        assertTrue(opticalInsetsRight > 0);
+        assertTrue(opticalInsetsBottom > 0);
+
+        performAndWaitForAnimation(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.addItemDecoration(new DividerDecoration(decorationLeft, decorationTop,
+                        decorationRight, decorationBottom));
+            }
+        });
+
+        View child0 = mGridView.getChildAt(0);
+        View child1 = mGridView.getChildAt(1);
+        View child2 = mGridView.getChildAt(2);
+
+        assertEquals(itemHeight + opticalInsetsTop + opticalInsetsBottom,
+                child0.getBottom() - child0.getTop());
+
+        // verify left margins decoration and optical insets
+        assertEquals(paddingLeft + leftMargin + decorationLeft - opticalInsetsLeft,
+                child0.getLeft());
+        assertEquals(paddingLeft + leftMargin + decorationLeft - opticalInsetsLeft,
+                child1.getLeft());
+        assertEquals(paddingLeft + leftMargin + decorationLeft - opticalInsetsLeft,
+                child2.getLeft());
+        // verify top bottom margins decoration offset and optical insets
+        assertEquals(paddingTop + topMargin + decorationTop, child0.getTop() + opticalInsetsTop);
+        assertEquals(bottomMargin + decorationBottom + verticalSpace + decorationTop + topMargin,
+                (child1.getTop() + opticalInsetsTop) - (child0.getBottom() - opticalInsetsBottom));
+        assertEquals(bottomMargin + decorationBottom + verticalSpace + decorationTop + topMargin,
+                (child2.getTop() + opticalInsetsTop) - (child1.getBottom() - opticalInsetsBottom));
+
+    }
+
+    @Test
+    public void testThreeColumnVerticalBasic() throws Throwable {
+
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_grid);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 200);
+        initActivity(intent);
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = 3;
+
+        scrollToEnd(mVerifyLayout);
+
+        scrollToBegin(mVerifyLayout);
+
+        verifyBeginAligned();
+    }
+
+    @Test
+    public void testRedundantAppendRemove() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.vertical_grid_testredundantappendremove);
+        intent.putExtra(GridActivity.EXTRA_ITEMS, new int[]{
+                149,177,128,234,227,187,163,223,146,210,228,148,227,193,182,197,177,142,225,207,
+                157,171,209,204,187,184,123,221,197,153,202,179,193,214,226,173,225,143,188,159,
+                139,193,233,143,227,203,222,124,228,223,164,131,228,126,211,160,165,152,235,184,
+                155,224,149,181,171,229,200,234,177,130,164,172,188,139,132,203,179,220,147,131,
+                226,127,230,239,183,203,206,227,123,170,239,234,200,149,237,204,160,133,202,234,
+                173,122,139,149,151,153,216,231,121,145,227,153,186,174,223,180,123,215,206,216,
+                239,222,219,207,193,218,140,133,171,153,183,132,233,138,159,174,189,171,143,128,
+                152,222,141,202,224,190,134,120,181,231,230,136,132,224,136,210,207,150,128,183,
+                221,194,179,220,126,221,137,205,223,193,172,132,226,209,133,191,227,127,159,171,
+                180,149,237,177,194,207,170,202,161,144,147,199,205,186,164,140,193,203,224,129});
+        initActivity(intent);
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = 3;
+
+        scrollToEnd(mVerifyLayout);
+
+        scrollToBegin(mVerifyLayout);
+
+        verifyBeginAligned();
+    }
+
+    @Test
+    public void testRedundantAppendRemove2() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.horizontal_grid_testredundantappendremove2);
+        intent.putExtra(GridActivity.EXTRA_ITEMS, new int[]{
+                318,333,199,224,246,273,269,289,340,313,265,306,349,269,185,282,257,354,316,252,
+                237,290,283,343,196,313,290,343,191,262,342,228,343,349,251,203,226,305,265,213,
+                216,333,295,188,187,281,288,311,244,232,224,332,290,181,267,276,226,261,335,355,
+                225,217,219,183,234,285,257,304,182,250,244,223,257,219,342,185,347,205,302,315,
+                299,309,292,237,192,309,228,250,347,227,337,298,299,185,185,331,223,284,265,351});
+        initActivity(intent);
+        mOrientation = BaseGridView.HORIZONTAL;
+        mNumRows = 3;
+        mLayoutManager = (GridLayoutManager) mGridView.getLayoutManager();
+
+        // test append without staggered result cache
+        scrollToEnd(mVerifyLayout);
+
+        int[] endEdges = getEndEdges();
+
+        scrollToBegin(mVerifyLayout);
+
+        verifyBeginAligned();
+
+        // now test append with staggered result cache
+        changeArraySize(3);
+        assertEquals("Staggerd cache should be kept as is when no item size change",
+                100, ((StaggeredGrid) mLayoutManager.mGrid).mLocations.size());
+
+        changeArraySize(100);
+
+        scrollToEnd(mVerifyLayout);
+
+        // we should get same aligned end edges
+        int[] endEdges2 = getEndEdges();
+        verifyEdgesSame(endEdges, endEdges2);
+    }
+
+
+    @Test
+    public void testLayoutWhenAViewIsInvalidated() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 1000);
+        intent.putExtra(GridActivity.EXTRA_HAS_STABLE_IDS, true);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        mNumRows = 1;
+        initActivity(intent);
+        mOrientation = BaseGridView.VERTICAL;
+        waitOneUiCycle();
+
+        // push views to cache.
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mActivity.mItemLengths[0] = mActivity.mItemLengths[0] * 3;
+                mActivity.mGridView.getAdapter().notifyItemChanged(0);
+            }
+        });
+        waitForItemAnimation();
+
+        // notifyDataSetChange will mark the cached views FLAG_INVALID
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mActivity.mGridView.getAdapter().notifyDataSetChanged();
+            }
+        });
+        waitForItemAnimation();
+
+        // Cached views will be added in prelayout with FLAG_INVALID, in post layout we should
+        // handle it properly
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mActivity.mItemLengths[0] = mActivity.mItemLengths[0] / 3;
+                mActivity.mGridView.getAdapter().notifyItemChanged(0);
+            }
+        });
+
+        waitForItemAnimation();
+    }
+
+    @Test
+    public void testWrongInsertViewIndexInFastRelayout() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 2);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        mNumRows = 1;
+        initActivity(intent);
+        mOrientation = BaseGridView.VERTICAL;
+
+        // removing two children, they will be hidden views as first 2 children of RV.
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.getItemAnimator().setRemoveDuration(2000);
+                mActivity.removeItems(0, 2);
+            }
+        });
+        waitForItemAnimationStart();
+
+        // add three views and notify change of the first item.
+        startWaitLayout();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mActivity.addItems(0, new int[]{161, 161, 161});
+            }
+        });
+        waitForLayout();
+        startWaitLayout();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.getAdapter().notifyItemChanged(0);
+            }
+        });
+        waitForLayout();
+        // after layout, the viewholder should still be the first child of LayoutManager.
+        assertEquals(0, mGridView.getChildAdapterPosition(
+                mGridView.getLayoutManager().getChildAt(0)));
+    }
+
+    @Test
+    public void testMoveIntoPrelayoutItems() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 1000);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        mNumRows = 1;
+        initActivity(intent);
+        mOrientation = BaseGridView.VERTICAL;
+
+        final int lastItemPos = mGridView.getChildCount() - 1;
+        assertTrue(mGridView.getChildCount() >= 4);
+        // notify change of 3 items, so prelayout will layout extra 3 items, then move an item
+        // into the extra layout range. Post layout's fastRelayout() should handle this properly.
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.getAdapter().notifyItemChanged(lastItemPos - 3);
+                mGridView.getAdapter().notifyItemChanged(lastItemPos - 2);
+                mGridView.getAdapter().notifyItemChanged(lastItemPos - 1);
+                mActivity.moveItem(900, lastItemPos + 2, true);
+            }
+        });
+        waitForItemAnimation();
+    }
+
+    @Test
+    public void testMoveIntoPrelayoutItems2() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 1000);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        mNumRows = 1;
+        initActivity(intent);
+        mOrientation = BaseGridView.VERTICAL;
+
+        setSelectedPosition(999);
+        final int firstItemPos = mGridView.getChildAdapterPosition(mGridView.getChildAt(0));
+        assertTrue(mGridView.getChildCount() >= 4);
+        // notify change of 3 items, so prelayout will layout extra 3 items, then move an item
+        // into the extra layout range. Post layout's fastRelayout() should handle this properly.
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.getAdapter().notifyItemChanged(firstItemPos + 1);
+                mGridView.getAdapter().notifyItemChanged(firstItemPos + 2);
+                mGridView.getAdapter().notifyItemChanged(firstItemPos + 3);
+                mActivity.moveItem(0, firstItemPos - 2, true);
+            }
+        });
+        waitForItemAnimation();
+    }
+
+    void preparePredictiveLayout() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_linear);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 100);
+        initActivity(intent);
+        mOrientation = BaseGridView.HORIZONTAL;
+        mNumRows = 1;
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.getItemAnimator().setAddDuration(1000);
+                mGridView.getItemAnimator().setRemoveDuration(1000);
+                mGridView.getItemAnimator().setMoveDuration(1000);
+                mGridView.getItemAnimator().setChangeDuration(1000);
+                mGridView.setSelectedPositionSmooth(50);
+            }
+        });
+        waitForScrollIdle(mVerifyLayout);
+    }
+
+    @Test
+    public void testPredictiveLayoutAdd1() throws Throwable {
+        preparePredictiveLayout();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mActivity.addItems(51, new int[]{300, 300, 300, 300});
+            }
+        });
+        waitForItemAnimationStart();
+        waitForItemAnimation();
+        assertEquals(50, mGridView.getSelectedPosition());
+        assertEquals(RecyclerView.SCROLL_STATE_IDLE, mGridView.getScrollState());
+    }
+
+    @Test
+    public void testPredictiveLayoutAdd2() throws Throwable {
+        preparePredictiveLayout();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mActivity.addItems(50, new int[]{300, 300, 300, 300});
+            }
+        });
+        waitForItemAnimationStart();
+        waitForItemAnimation();
+        assertEquals(54, mGridView.getSelectedPosition());
+        assertEquals(RecyclerView.SCROLL_STATE_IDLE, mGridView.getScrollState());
+    }
+
+    @Test
+    public void testPredictiveLayoutRemove1() throws Throwable {
+        preparePredictiveLayout();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mActivity.removeItems(51, 3);
+            }
+        });
+        waitForItemAnimationStart();
+        waitForItemAnimation();
+        assertEquals(50, mGridView.getSelectedPosition());
+        assertEquals(RecyclerView.SCROLL_STATE_IDLE, mGridView.getScrollState());
+    }
+
+    @Test
+    public void testPredictiveLayoutRemove2() throws Throwable {
+        preparePredictiveLayout();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mActivity.removeItems(47, 3);
+            }
+        });
+        waitForItemAnimationStart();
+        waitForItemAnimation();
+        assertEquals(47, mGridView.getSelectedPosition());
+        assertEquals(RecyclerView.SCROLL_STATE_IDLE, mGridView.getScrollState());
+    }
+
+    @Test
+    public void testPredictiveLayoutRemove3() throws Throwable {
+        preparePredictiveLayout();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mActivity.removeItems(0, 51);
+            }
+        });
+        waitForItemAnimationStart();
+        waitForItemAnimation();
+        assertEquals(0, mGridView.getSelectedPosition());
+        assertEquals(RecyclerView.SCROLL_STATE_IDLE, mGridView.getScrollState());
+    }
+
+    @Test
+    public void testPredictiveOnMeasureWrapContent() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.horizontal_linear_wrap_content);
+        int count = 50;
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, count);
+        initActivity(intent);
+        mOrientation = BaseGridView.HORIZONTAL;
+        mNumRows = 1;
+
+        waitForScrollIdle(mVerifyLayout);
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.setHasFixedSize(false);
+            }
+        });
+
+        for (int i = 0; i < 30; i++) {
+            final int oldCount = count;
+            final int newCount = i;
+            mActivityTestRule.runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    if (oldCount > 0) {
+                        mActivity.removeItems(0, oldCount);
+                    }
+                    if (newCount > 0) {
+                        int[] newItems = new int[newCount];
+                        for (int i = 0; i < newCount; i++) {
+                            newItems[i] = 400;
+                        }
+                        mActivity.addItems(0, newItems);
+                    }
+                }
+            });
+            waitForItemAnimationStart();
+            waitForItemAnimation();
+            count = newCount;
+        }
+
+    }
+
+    @Test
+    public void testPredictiveLayoutRemove4() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.horizontal_grid);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 200);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        initActivity(intent);
+        mOrientation = BaseGridView.HORIZONTAL;
+        mNumRows = 3;
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.setSelectedPositionSmooth(50);
+            }
+        });
+        waitForScrollIdle();
+        performAndWaitForAnimation(new Runnable() {
+            @Override
+            public void run() {
+                mActivity.removeItems(0, 49);
+            }
+        });
+        assertEquals(1, mGridView.getSelectedPosition());
+    }
+
+    @Test
+    public void testPredictiveLayoutRemove5() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.horizontal_grid);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 200);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, true);
+        initActivity(intent);
+        mOrientation = BaseGridView.HORIZONTAL;
+        mNumRows = 3;
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.setSelectedPositionSmooth(50);
+            }
+        });
+        waitForScrollIdle();
+        performAndWaitForAnimation(new Runnable() {
+            @Override
+            public void run() {
+                mActivity.removeItems(50, 40);
+            }
+        });
+        assertEquals(50, mGridView.getSelectedPosition());
+        scrollToBegin(mVerifyLayout);
+        verifyBeginAligned();
+    }
+
+    void waitOneUiCycle() throws Throwable {
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+            }
+        });
+    }
+
+    @Test
+    public void testDontPruneMovingItem() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_linear);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 2000);
+        initActivity(intent);
+        mOrientation = BaseGridView.HORIZONTAL;
+        mNumRows = 1;
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.getItemAnimator().setMoveDuration(2000);
+                mGridView.setSelectedPosition(50);
+            }
+        });
+        waitForScrollIdle();
+        final ArrayList<RecyclerView.ViewHolder> moveViewHolders = new ArrayList();
+        for (int i = 51;; i++) {
+            RecyclerView.ViewHolder vh = mGridView.findViewHolderForAdapterPosition(i);
+            if (vh == null) {
+                break;
+            }
+            moveViewHolders.add(vh);
+        }
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                // add a lot of items, so we will push everything to right of 51 out side window
+                int[] lots_items = new int[1000];
+                for (int i = 0; i < lots_items.length; i++) {
+                    lots_items[i] = 300;
+                }
+                mActivity.addItems(51, lots_items);
+            }
+        });
+        waitOneUiCycle();
+        // run a scroll pass, the scroll pass should not remove the animating views even they are
+        // outside visible areas.
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.scrollBy(-3, 0);
+            }
+        });
+        waitOneUiCycle();
+        for (int i = 0; i < moveViewHolders.size(); i++) {
+            assertSame(mGridView, moveViewHolders.get(i).itemView.getParent());
+        }
+    }
+
+    @Test
+    public void testMoveItemToTheRight() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_linear);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 2000);
+        initActivity(intent);
+        mOrientation = BaseGridView.HORIZONTAL;
+        mNumRows = 1;
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.getItemAnimator().setAddDuration(2000);
+                mGridView.getItemAnimator().setMoveDuration(2000);
+                mGridView.setSelectedPosition(50);
+            }
+        });
+        waitForScrollIdle();
+        RecyclerView.ViewHolder moveViewHolder = mGridView.findViewHolderForAdapterPosition(51);
+
+        int lastPos = mGridView.getChildAdapterPosition(mGridView.getChildAt(
+                mGridView.getChildCount() - 1));
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mActivity.moveItem(51, 1000, true);
+            }
+        });
+        final ArrayList<View> moveInViewHolders = new ArrayList();
+        waitForItemAnimationStart();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                for (int i = 0; i < mGridView.getLayoutManager().getChildCount(); i++) {
+                    View v = mGridView.getLayoutManager().getChildAt(i);
+                    if (mGridView.getChildAdapterPosition(v) >= 51) {
+                        moveInViewHolders.add(v);
+                    }
+                }
+            }
+        });
+        waitOneUiCycle();
+        assertTrue("prelayout should layout extra items to slide in",
+                moveInViewHolders.size() > lastPos - 51);
+        // run a scroll pass, the scroll pass should not remove the animating views even they are
+        // outside visible areas.
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.scrollBy(-3, 0);
+            }
+        });
+        waitOneUiCycle();
+        for (int i = 0; i < moveInViewHolders.size(); i++) {
+            assertSame(mGridView, moveInViewHolders.get(i).getParent());
+        }
+        assertSame(mGridView, moveViewHolder.itemView.getParent());
+        assertFalse(moveViewHolder.isRecyclable());
+        waitForItemAnimation();
+        assertNull(moveViewHolder.itemView.getParent());
+        assertTrue(moveViewHolder.isRecyclable());
+    }
+
+    @Test
+    public void testMoveItemToTheLeft() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_linear);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 2000);
+        initActivity(intent);
+        mOrientation = BaseGridView.HORIZONTAL;
+        mNumRows = 1;
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.getItemAnimator().setAddDuration(2000);
+                mGridView.getItemAnimator().setMoveDuration(2000);
+                mGridView.setSelectedPosition(1500);
+            }
+        });
+        waitForScrollIdle();
+        RecyclerView.ViewHolder moveViewHolder = mGridView.findViewHolderForAdapterPosition(1499);
+
+        int firstPos = mGridView.getChildAdapterPosition(mGridView.getChildAt(0));
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mActivity.moveItem(1499, 1, true);
+            }
+        });
+        final ArrayList<View> moveInViewHolders = new ArrayList();
+        waitForItemAnimationStart();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                for (int i = 0; i < mGridView.getLayoutManager().getChildCount(); i++) {
+                    View v = mGridView.getLayoutManager().getChildAt(i);
+                    if (mGridView.getChildAdapterPosition(v) <= 1499) {
+                        moveInViewHolders.add(v);
+                    }
+                }
+            }
+        });
+        waitOneUiCycle();
+        assertTrue("prelayout should layout extra items to slide in ",
+                moveInViewHolders.size() > 1499 - firstPos);
+        // run a scroll pass, the scroll pass should not remove the animating views even they are
+        // outside visible areas.
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.scrollBy(3, 0);
+            }
+        });
+        waitOneUiCycle();
+        for (int i = 0; i < moveInViewHolders.size(); i++) {
+            assertSame(mGridView, moveInViewHolders.get(i).getParent());
+        }
+        assertSame(mGridView, moveViewHolder.itemView.getParent());
+        assertFalse(moveViewHolder.isRecyclable());
+        waitForItemAnimation();
+        assertNull(moveViewHolder.itemView.getParent());
+        assertTrue(moveViewHolder.isRecyclable());
+    }
+
+    @Test
+    public void testContinuousSwapForward() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.horizontal_linear);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 200);
+        initActivity(intent);
+        mOrientation = BaseGridView.HORIZONTAL;
+        mNumRows = 1;
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.setSelectedPositionSmooth(150);
+            }
+        });
+        waitForScrollIdle(mVerifyLayout);
+        for (int i = 150; i < 199; i++) {
+            final int swapIndex = i;
+            mActivityTestRule.runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    mActivity.swap(swapIndex, swapIndex + 1);
+                }
+            });
+            Thread.sleep(10);
+        }
+        waitForItemAnimation();
+        assertEquals(199, mGridView.getSelectedPosition());
+        // check if ItemAnimation finishes at aligned positions:
+        int leftEdge = mGridView.getLayoutManager().findViewByPosition(199).getLeft();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.requestLayout();
+            }
+        });
+        waitForScrollIdle();
+        assertEquals(leftEdge, mGridView.getLayoutManager().findViewByPosition(199).getLeft());
+    }
+
+    @Test
+    public void testContinuousSwapBackward() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.horizontal_linear);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 200);
+        initActivity(intent);
+        mOrientation = BaseGridView.HORIZONTAL;
+        mNumRows = 1;
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.setSelectedPositionSmooth(50);
+            }
+        });
+        waitForScrollIdle(mVerifyLayout);
+        for (int i = 50; i > 0; i--) {
+            final int swapIndex = i;
+            mActivityTestRule.runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    mActivity.swap(swapIndex, swapIndex - 1);
+                }
+            });
+            Thread.sleep(10);
+        }
+        waitForItemAnimation();
+        assertEquals(0, mGridView.getSelectedPosition());
+        // check if ItemAnimation finishes at aligned positions:
+        int leftEdge = mGridView.getLayoutManager().findViewByPosition(0).getLeft();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.requestLayout();
+            }
+        });
+        waitForScrollIdle();
+        assertEquals(leftEdge, mGridView.getLayoutManager().findViewByPosition(0).getLeft());
+    }
+
+    @Test
+    public void testScrollAndStuck() throws Throwable {
+        // see b/67370222 fastRelayout() may be stuck.
+        final int numItems = 19;
+        final int[] itemsLength = new int[numItems];
+        for (int i = 0; i < numItems; i++) {
+            itemsLength[i] = 288;
+        }
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.horizontal_linear);
+        intent.putExtra(GridActivity.EXTRA_ITEMS, itemsLength);
+        initActivity(intent);
+        mOrientation = BaseGridView.HORIZONTAL;
+        mNumRows = 1;
+
+        // set left right padding to 112, space between items to be 16.
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                ViewGroup.LayoutParams lp = mGridView.getLayoutParams();
+                lp.width = 1920;
+                mGridView.setLayoutParams(lp);
+                mGridView.setPadding(112, mGridView.getPaddingTop(), 112,
+                        mGridView.getPaddingBottom());
+                mGridView.setItemSpacing(16);
+            }
+        });
+        waitOneUiCycle();
+
+        int scrollPos = 0;
+        while (true) {
+            final View view = mGridView.getChildAt(mGridView.getChildCount() - 1);
+            final int pos = mGridView.getChildViewHolder(view).getAdapterPosition();
+            if (scrollPos != pos) {
+                scrollPos = pos;
+                mActivityTestRule.runOnUiThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        mGridView.smoothScrollToPosition(pos);
+                    }
+                });
+            }
+            // wait until we see 2nd from last:
+            if (pos >= 17) {
+                if (pos == 17) {
+                    // great we can test fastRelayout() bug.
+                    Thread.sleep(50);
+                    mActivityTestRule.runOnUiThread(new Runnable() {
+                        @Override
+                        public void run() {
+                            view.requestLayout();
+                        }
+                    });
+                }
+                break;
+            }
+            Thread.sleep(16);
+        }
+        waitForScrollIdle();
+    }
+
+    @Test
+    public void testSwapAfterScroll() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.horizontal_linear);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 200);
+        initActivity(intent);
+        mOrientation = BaseGridView.HORIZONTAL;
+        mNumRows = 1;
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.getItemAnimator().setMoveDuration(1000);
+                mGridView.setSelectedPositionSmooth(150);
+            }
+        });
+        waitForScrollIdle();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.setSelectedPositionSmooth(151);
+            }
+        });
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                // we want to swap and select new target which is at 150 before swap
+                mGridView.setSelectedPositionSmooth(150);
+                mActivity.swap(150, 151);
+            }
+        });
+        waitForItemAnimation();
+        waitForScrollIdle();
+        assertEquals(151, mGridView.getSelectedPosition());
+        // check if ItemAnimation finishes at aligned positions:
+        int leftEdge = mGridView.getLayoutManager().findViewByPosition(151).getLeft();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.requestLayout();
+            }
+        });
+        waitForScrollIdle();
+        assertEquals(leftEdge, mGridView.getLayoutManager().findViewByPosition(151).getLeft());
+    }
+
+    @Test
+    public void testItemMovedHorizontal() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.horizontal_grid);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 200);
+        initActivity(intent);
+        mOrientation = BaseGridView.HORIZONTAL;
+        mNumRows = 3;
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.setSelectedPositionSmooth(150);
+            }
+        });
+        waitForScrollIdle(mVerifyLayout);
+        performAndWaitForAnimation(new Runnable() {
+            @Override
+            public void run() {
+                mActivity.swap(150, 152);
+            }
+        });
+        mActivityTestRule.runOnUiThread(mVerifyLayout);
+
+        scrollToBegin(mVerifyLayout);
+
+        verifyBeginAligned();
+    }
+
+    @Test
+    public void testItemMovedHorizontalRtl() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.horizontal_linear_rtl);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        intent.putExtra(GridActivity.EXTRA_ITEMS, new int[] {40, 40, 40});
+        initActivity(intent);
+        mOrientation = BaseGridView.HORIZONTAL;
+        mNumRows = 1;
+
+        performAndWaitForAnimation(new Runnable() {
+            @Override
+            public void run() {
+                mActivity.moveItem(0, 1, true);
+            }
+        });
+        assertEquals(mGridView.getWidth() - mGridView.getPaddingRight(),
+                mGridView.findViewHolderForAdapterPosition(0).itemView.getRight());
+    }
+
+    @Test
+    public void testScrollSecondaryCannotScroll() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.horizontal_grid);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 2000);
+        initActivity(intent);
+        mOrientation = BaseGridView.HORIZONTAL;
+        mNumRows = 3;
+        final int topPadding = 2;
+        final int bottomPadding = 2;
+        final int height = mGridView.getHeight();
+        final int spacing = 2;
+        final int rowHeight = (height - topPadding - bottomPadding) / 4 - spacing;
+        final HorizontalGridView horizontalGridView = (HorizontalGridView) mGridView;
+
+        startWaitLayout();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                horizontalGridView.setPadding(0, topPadding, 0, bottomPadding);
+                horizontalGridView.setItemSpacing(spacing);
+                horizontalGridView.setNumRows(mNumRows);
+                horizontalGridView.setRowHeight(rowHeight);
+            }
+        });
+        waitForLayout();
+        // navigate vertically in first column, first row should always be aligned to top padding
+        for (int i = 0; i < 3; i++) {
+            setSelectedPosition(i);
+            assertEquals(topPadding, mGridView.findViewHolderForAdapterPosition(0).itemView
+                    .getTop());
+        }
+        // navigate vertically in 100th column, first row should always be aligned to top padding
+        for (int i = 300; i < 301; i++) {
+            setSelectedPosition(i);
+            assertEquals(topPadding, mGridView.findViewHolderForAdapterPosition(300).itemView
+                    .getTop());
+        }
+    }
+
+    @Test
+    public void testScrollSecondaryNeedScroll() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.horizontal_grid);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 2000);
+        initActivity(intent);
+        mOrientation = BaseGridView.HORIZONTAL;
+        // test a lot of rows so we have to scroll vertically to reach
+        mNumRows = 9;
+        final int topPadding = 2;
+        final int bottomPadding = 2;
+        final int height = mGridView.getHeight();
+        final int spacing = 2;
+        final int rowHeight = (height - topPadding - bottomPadding) / 4 - spacing;
+        final HorizontalGridView horizontalGridView = (HorizontalGridView) mGridView;
+
+        startWaitLayout();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                horizontalGridView.setPadding(0, topPadding, 0, bottomPadding);
+                horizontalGridView.setItemSpacing(spacing);
+                horizontalGridView.setNumRows(mNumRows);
+                horizontalGridView.setRowHeight(rowHeight);
+            }
+        });
+        waitForLayout();
+        View view;
+        // first row should be aligned to top padding
+        setSelectedPosition(0);
+        assertEquals(topPadding, mGridView.findViewHolderForAdapterPosition(0).itemView.getTop());
+        // middle row should be aligned to keyline (1/2 of screen height)
+        setSelectedPosition(4);
+        view = mGridView.findViewHolderForAdapterPosition(4).itemView;
+        assertEquals(height / 2, (view.getTop() + view.getBottom()) / 2);
+        // last row should be aligned to bottom padding.
+        setSelectedPosition(8);
+        view = mGridView.findViewHolderForAdapterPosition(8).itemView;
+        assertEquals(height, view.getTop() + rowHeight + bottomPadding);
+        setSelectedPositionSmooth(4);
+        waitForScrollIdle();
+        // middle row should be aligned to keyline (1/2 of screen height)
+        setSelectedPosition(4);
+        view = mGridView.findViewHolderForAdapterPosition(4).itemView;
+        assertEquals(height / 2, (view.getTop() + view.getBottom()) / 2);
+        // first row should be aligned to top padding
+        setSelectedPositionSmooth(0);
+        waitForScrollIdle();
+        assertEquals(topPadding, mGridView.findViewHolderForAdapterPosition(0).itemView.getTop());
+    }
+
+    @Test
+    public void testItemMovedVertical() throws Throwable {
+
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.vertical_grid);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 200);
+        initActivity(intent);
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = 3;
+
+        mGridView.setSelectedPositionSmooth(150);
+        waitForScrollIdle(mVerifyLayout);
+        performAndWaitForAnimation(new Runnable() {
+            @Override
+            public void run() {
+                mActivity.swap(150, 152);
+            }
+        });
+        mActivityTestRule.runOnUiThread(mVerifyLayout);
+
+        scrollToEnd(mVerifyLayout);
+        scrollToBegin(mVerifyLayout);
+
+        verifyBeginAligned();
+    }
+
+    @Test
+    public void testAddLastItemHorizontal() throws Throwable {
+
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.horizontal_linear);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 50);
+        initActivity(intent);
+        mOrientation = BaseGridView.HORIZONTAL;
+        mNumRows = 1;
+
+        mActivityTestRule.runOnUiThread(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        mGridView.setSelectedPositionSmooth(49);
+                    }
+                }
+        );
+        waitForScrollIdle(mVerifyLayout);
+        performAndWaitForAnimation(new Runnable() {
+            @Override
+            public void run() {
+                mActivity.addItems(50, new int[]{150});
+            }
+        });
+
+        // assert new added item aligned to right edge
+        assertEquals(mGridView.getWidth() - mGridView.getPaddingRight(),
+                mGridView.getLayoutManager().findViewByPosition(50).getRight());
+    }
+
+    @Test
+    public void testAddMultipleLastItemsHorizontal() throws Throwable {
+
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.horizontal_linear);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 50);
+        initActivity(intent);
+        mOrientation = BaseGridView.HORIZONTAL;
+        mNumRows = 1;
+
+        mActivityTestRule.runOnUiThread(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        mGridView.setWindowAlignment(BaseGridView.WINDOW_ALIGN_BOTH_EDGE);
+                        mGridView.setWindowAlignmentOffsetPercent(50);
+                        mGridView.setSelectedPositionSmooth(49);
+                    }
+                }
+        );
+        waitForScrollIdle(mVerifyLayout);
+        performAndWaitForAnimation(new Runnable() {
+            @Override
+            public void run() {
+                mActivity.addItems(50, new int[]{150, 150, 150, 150, 150, 150, 150, 150, 150,
+                        150, 150, 150, 150, 150});
+            }
+        });
+
+        // The focused item will be at center of window
+        View view = mGridView.getLayoutManager().findViewByPosition(49);
+        assertEquals(mGridView.getWidth() / 2, (view.getLeft() + view.getRight()) / 2);
+    }
+
+    @Test
+    public void testItemAddRemoveHorizontal() throws Throwable {
+
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.horizontal_grid);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 200);
+        initActivity(intent);
+        mOrientation = BaseGridView.HORIZONTAL;
+        mNumRows = 3;
+
+        scrollToEnd(mVerifyLayout);
+        int[] endEdges = getEndEdges();
+
+        mGridView.setSelectedPositionSmooth(150);
+        waitForScrollIdle(mVerifyLayout);
+        performAndWaitForAnimation(new Runnable() {
+            @Override
+            public void run() {
+                mRemovedItems = mActivity.removeItems(151, 4);
+            }
+        });
+
+        scrollToEnd(mVerifyLayout);
+        mGridView.setSelectedPositionSmooth(150);
+        waitForScrollIdle(mVerifyLayout);
+
+        performAndWaitForAnimation(new Runnable() {
+            @Override
+            public void run() {
+                mActivity.addItems(151, mRemovedItems);
+            }
+        });
+        scrollToEnd(mVerifyLayout);
+
+        // we should get same aligned end edges
+        int[] endEdges2 = getEndEdges();
+        verifyEdgesSame(endEdges, endEdges2);
+
+        scrollToBegin(mVerifyLayout);
+        verifyBeginAligned();
+    }
+
+    @Test
+    public void testSetSelectedPositionDetached() throws Throwable {
+
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.horizontal_linear);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 50);
+        initActivity(intent);
+        mOrientation = BaseGridView.HORIZONTAL;
+        mNumRows = 1;
+
+        final int focusToIndex = 49;
+        final ViewGroup parent = (ViewGroup) mGridView.getParent();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                parent.removeView(mGridView);
+            }
+        });
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.setSelectedPositionSmooth(focusToIndex);
+            }
+        });
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                parent.addView(mGridView);
+                mGridView.requestFocus();
+            }
+        });
+        waitForScrollIdle();
+        assertEquals(mGridView.getSelectedPosition(), focusToIndex);
+        assertTrue(mGridView.getLayoutManager().findViewByPosition(focusToIndex).hasFocus());
+
+        final int focusToIndex2 = 0;
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                parent.removeView(mGridView);
+            }
+        });
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.setSelectedPosition(focusToIndex2);
+            }
+        });
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                parent.addView(mGridView);
+                mGridView.requestFocus();
+            }
+        });
+        assertEquals(mGridView.getSelectedPosition(), focusToIndex2);
+        waitForScrollIdle();
+        assertTrue(mGridView.getLayoutManager().findViewByPosition(focusToIndex2).hasFocus());
+    }
+
+    @Test
+    public void testBug22209986() throws Throwable {
+
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.horizontal_linear);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 50);
+        initActivity(intent);
+        mOrientation = BaseGridView.HORIZONTAL;
+        mNumRows = 1;
+
+        final int focusToIndex = mGridView.getChildCount() - 1;
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.setSelectedPositionSmooth(focusToIndex);
+            }
+        });
+
+        waitForScrollIdle();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.setSelectedPositionSmooth(focusToIndex + 1);
+            }
+        });
+        // let the scroll running for a while and requestLayout during scroll
+        Thread.sleep(80);
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                assertEquals(mGridView.getScrollState(), BaseGridView.SCROLL_STATE_SETTLING);
+                mGridView.requestLayout();
+            }
+        });
+        waitForScrollIdle();
+
+        int leftEdge = mGridView.getLayoutManager().findViewByPosition(focusToIndex).getLeft();
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.requestLayout();
+            }
+        });
+        waitForScrollIdle();
+        assertEquals(leftEdge,
+                mGridView.getLayoutManager().findViewByPosition(focusToIndex).getLeft());
+    }
+
+    void testScrollAndRemove(int[] itemsLength, int numItems) throws Throwable {
+
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.horizontal_linear);
+        if (itemsLength != null) {
+            intent.putExtra(GridActivity.EXTRA_ITEMS, itemsLength);
+        } else {
+            intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems);
+        }
+        initActivity(intent);
+        mOrientation = BaseGridView.HORIZONTAL;
+        mNumRows = 1;
+
+        final int focusToIndex = mGridView.getChildCount() - 1;
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.setSelectedPositionSmooth(focusToIndex);
+            }
+        });
+
+        performAndWaitForAnimation(new Runnable() {
+            @Override
+            public void run() {
+                mActivity.removeItems(focusToIndex, 1);
+            }
+        });
+
+        waitOneUiCycle();
+        waitForScrollIdle();
+        int leftEdge = mGridView.getLayoutManager().findViewByPosition(focusToIndex).getLeft();
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.requestLayout();
+            }
+        });
+        waitForScrollIdle();
+        assertEquals(leftEdge,
+                mGridView.getLayoutManager().findViewByPosition(focusToIndex).getLeft(), DELTA);
+    }
+
+    @Test
+    public void testScrollAndRemove() throws Throwable {
+        // test random lengths for 50 items
+        testScrollAndRemove(null, 50);
+    }
+
+    /**
+     * This test verifies if scroll limits are ignored when onLayoutChildren compensate remaining
+     * scroll distance. b/64931938
+     * In the test, second child is long, other children are short.
+     * Test scrolls to the long child, and when scrolling, remove the long child. We made it long
+     * to have enough remaining scroll distance when the layout pass kicks in.
+     * The onLayoutChildren() would compensate the remaining scroll distance, moving all items
+     * toward right, which will make the first item's left edge bigger than left padding,
+     * which would violate the "scroll limit of left" in a regular scroll case, but
+     * in layout pass, we still honor that scroll request, ignoring the scroll limit.
+     */
+    @Test
+    public void testScrollAndRemoveSample1() throws Throwable {
+        DisplayMetrics dm = InstrumentationRegistry.getInstrumentation().getTargetContext()
+                .getResources().getDisplayMetrics();
+        // screen width for long item and 4DP for other items
+        int longItemLength = dm.widthPixels;
+        int shortItemLength = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4, dm);
+        int[] items = new int[1000];
+        for (int i = 0; i < items.length; i++) {
+            items[i] = shortItemLength;
+        }
+        items[1] = longItemLength;
+        testScrollAndRemove(items, 0);
+    }
+
+    @Test
+    public void testScrollAndInsert() throws Throwable {
+
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.vertical_grid);
+        int[] items = new int[1000];
+        for (int i = 0; i < items.length; i++) {
+            items[i] = 300 + (int)(Math.random() * 100);
+        }
+        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, true);
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = 3;
+
+        initActivity(intent);
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.setSelectedPositionSmooth(150);
+            }
+        });
+        waitForScrollIdle(mVerifyLayout);
+
+        View view =  mGridView.getChildAt(mGridView.getChildCount() - 1);
+        final int focusToIndex = mGridView.getChildAdapterPosition(view);
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.setSelectedPositionSmooth(focusToIndex);
+            }
+        });
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                int[] newItems = new int[]{300, 300, 300};
+                mActivity.addItems(0, newItems);
+            }
+        });
+        waitForScrollIdle();
+        int topEdge = mGridView.getLayoutManager().findViewByPosition(focusToIndex).getTop();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.requestLayout();
+            }
+        });
+        waitForScrollIdle();
+        assertEquals(topEdge,
+                mGridView.getLayoutManager().findViewByPosition(focusToIndex).getTop());
+    }
+
+    @Test
+    public void testScrollAndInsertBeforeVisibleItem() throws Throwable {
+
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.vertical_grid);
+        int[] items = new int[1000];
+        for (int i = 0; i < items.length; i++) {
+            items[i] = 300 + (int)(Math.random() * 100);
+        }
+        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, true);
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = 3;
+
+        initActivity(intent);
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.setSelectedPositionSmooth(150);
+            }
+        });
+        waitForScrollIdle(mVerifyLayout);
+
+        View view =  mGridView.getChildAt(mGridView.getChildCount() - 1);
+        final int focusToIndex = mGridView.getChildAdapterPosition(view);
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.setSelectedPositionSmooth(focusToIndex);
+            }
+        });
+
+        performAndWaitForAnimation(new Runnable() {
+            @Override
+            public void run() {
+                int[] newItems = new int[]{300, 300, 300};
+                mActivity.addItems(focusToIndex, newItems);
+            }
+        });
+    }
+
+    @Test
+    public void testSmoothScrollAndRemove() throws Throwable {
+
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.horizontal_linear);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 300);
+        initActivity(intent);
+        mOrientation = BaseGridView.HORIZONTAL;
+        mNumRows = 1;
+
+        final int focusToIndex = 200;
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.setSelectedPositionSmooth(focusToIndex);
+            }
+        });
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mActivity.removeItems(focusToIndex, 1);
+            }
+        });
+
+        assertTrue("removing the index of not attached child should not affect smooth scroller",
+                mGridView.getLayoutManager().isSmoothScrolling());
+        waitForScrollIdle();
+        int leftEdge = mGridView.getLayoutManager().findViewByPosition(focusToIndex).getLeft();
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.requestLayout();
+            }
+        });
+        waitForScrollIdle();
+        assertEquals(leftEdge,
+                mGridView.getLayoutManager().findViewByPosition(focusToIndex).getLeft());
+    }
+
+    @Test
+    public void testSmoothScrollAndRemove2() throws Throwable {
+
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.horizontal_linear);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 300);
+        initActivity(intent);
+        mOrientation = BaseGridView.HORIZONTAL;
+        mNumRows = 1;
+
+        final int focusToIndex = 200;
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.setSelectedPositionSmooth(focusToIndex);
+            }
+        });
+
+        startWaitLayout();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                final int removeIndex = mGridView.getChildViewHolder(
+                        mGridView.getChildAt(mGridView.getChildCount() - 1)).getAdapterPosition();
+                mActivity.removeItems(removeIndex, 1);
+            }
+        });
+        waitForLayout();
+
+        assertTrue("removing the index of attached child should not kill smooth scroller",
+                mGridView.getLayoutManager().isSmoothScrolling());
+        waitForItemAnimation();
+        waitForScrollIdle();
+        int leftEdge = mGridView.getLayoutManager().findViewByPosition(focusToIndex).getLeft();
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.requestLayout();
+            }
+        });
+        waitForScrollIdle();
+        assertEquals(leftEdge,
+                mGridView.getLayoutManager().findViewByPosition(focusToIndex).getLeft());
+    }
+
+    @Test
+    public void testPendingSmoothScrollAndRemove() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.vertical_linear);
+        intent.putExtra(GridActivity.EXTRA_REQUEST_FOCUS_ONLAYOUT, true);
+        int[] items = new int[100];
+        for (int i = 0; i < items.length; i++) {
+            items[i] = 630 + (int)(Math.random() * 100);
+        }
+        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, true);
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = 1;
+
+        initActivity(intent);
+
+        mGridView.setSelectedPositionSmooth(0);
+        waitForScrollIdle(mVerifyLayout);
+        assertTrue(mGridView.getChildAt(0).hasFocus());
+
+        // Pressing lots of key to make sure smooth scroller is running
+        mGridView.mLayoutManager.mMaxPendingMoves = 100;
+        for (int i = 0; i < 100; i++) {
+            sendKey(KeyEvent.KEYCODE_DPAD_DOWN);
+        }
+
+        assertTrue(mGridView.getLayoutManager().isSmoothScrolling());
+        startWaitLayout();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                final int removeIndex = mGridView.getChildViewHolder(
+                        mGridView.getChildAt(mGridView.getChildCount() - 1)).getAdapterPosition();
+                mActivity.removeItems(removeIndex, 1);
+            }
+        });
+        waitForLayout();
+
+        assertTrue("removing the index of attached child should not kill smooth scroller",
+                mGridView.getLayoutManager().isSmoothScrolling());
+
+        waitForItemAnimation();
+        waitForScrollIdle();
+        int focusIndex = mGridView.getSelectedPosition();
+        int topEdge = mGridView.getLayoutManager().findViewByPosition(focusIndex).getTop();
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.requestLayout();
+            }
+        });
+        waitForScrollIdle();
+        assertEquals(topEdge,
+                mGridView.getLayoutManager().findViewByPosition(focusIndex).getTop());
+    }
+
+    @Test
+    public void testFocusToFirstItem() throws Throwable {
+
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.horizontal_grid);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 200);
+        initActivity(intent);
+        mOrientation = BaseGridView.HORIZONTAL;
+        mNumRows = 3;
+
+        performAndWaitForAnimation(new Runnable() {
+            @Override
+            public void run() {
+                mRemovedItems = mActivity.removeItems(0, 200);
+            }
+        });
+
+        humanDelay(500);
+        performAndWaitForAnimation(new Runnable() {
+            @Override
+            public void run() {
+                mActivity.addItems(0, mRemovedItems);
+            }
+        });
+
+        humanDelay(500);
+        assertTrue(mGridView.getLayoutManager().findViewByPosition(0).hasFocus());
+
+        changeArraySize(0);
+
+        changeArraySize(200);
+        assertTrue(mGridView.getLayoutManager().findViewByPosition(0).hasFocus());
+    }
+
+    @Test
+    public void testNonFocusableHorizontal() throws Throwable {
+        final int numItems = 200;
+        final int startPos = 45;
+        final int skips = 20;
+        final int numColumns = 3;
+        final int endPos = startPos + numColumns * (skips + 1);
+
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.horizontal_grid);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        mOrientation = BaseGridView.HORIZONTAL;
+        mNumRows = numColumns;
+        boolean[] focusable = new boolean[numItems];
+        for (int i = 0; i < focusable.length; i++) {
+            focusable[i] = true;
+        }
+        for (int i = startPos + mNumRows, j = 0; j < skips; i += mNumRows, j++) {
+            focusable[i] = false;
+        }
+        intent.putExtra(GridActivity.EXTRA_ITEMS_FOCUSABLE, focusable);
+        initActivity(intent);
+
+        mGridView.setSelectedPositionSmooth(startPos);
+        waitForScrollIdle(mVerifyLayout);
+
+        if (mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL) {
+            sendKey(KeyEvent.KEYCODE_DPAD_LEFT);
+        } else {
+            sendKey(KeyEvent.KEYCODE_DPAD_RIGHT);
+        }
+        waitForScrollIdle(mVerifyLayout);
+        assertEquals(endPos, mGridView.getSelectedPosition());
+
+        if (mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL) {
+            sendKey(KeyEvent.KEYCODE_DPAD_RIGHT);
+        } else {
+            sendKey(KeyEvent.KEYCODE_DPAD_LEFT);
+        }
+        waitForScrollIdle(mVerifyLayout);
+        assertEquals(startPos, mGridView.getSelectedPosition());
+
+    }
+
+    @Test
+    public void testNoInitialFocusable() throws Throwable {
+
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.horizontal_linear);
+        final int numItems = 100;
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        mOrientation = BaseGridView.HORIZONTAL;
+        mNumRows = 1;
+        boolean[] focusable = new boolean[numItems];
+        final int firstFocusableIndex = 10;
+        for (int i = 0; i < firstFocusableIndex; i++) {
+            focusable[i] = false;
+        }
+        for (int i = firstFocusableIndex; i < focusable.length; i++) {
+            focusable[i] = true;
+        }
+        intent.putExtra(GridActivity.EXTRA_ITEMS_FOCUSABLE, focusable);
+        initActivity(intent);
+        assertTrue(mGridView.isFocused());
+
+        if (mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL) {
+            sendKey(KeyEvent.KEYCODE_DPAD_LEFT);
+        } else {
+            sendKey(KeyEvent.KEYCODE_DPAD_RIGHT);
+        }
+        waitForScrollIdle(mVerifyLayout);
+        assertEquals(firstFocusableIndex, mGridView.getSelectedPosition());
+        assertTrue(mGridView.getLayoutManager().findViewByPosition(firstFocusableIndex).hasFocus());
+    }
+
+    @Test
+    public void testFocusOutOfEmptyListView() throws Throwable {
+
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.horizontal_linear);
+        final int numItems = 100;
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        mOrientation = BaseGridView.HORIZONTAL;
+        mNumRows = 1;
+        initActivity(intent);
+
+        final View horizontalGridView = new HorizontalGridViewEx(mGridView.getContext());
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                horizontalGridView.setFocusable(true);
+                horizontalGridView.setFocusableInTouchMode(true);
+                horizontalGridView.setLayoutParams(new ViewGroup.LayoutParams(100, 100));
+                ((ViewGroup) mGridView.getParent()).addView(horizontalGridView, 0);
+                horizontalGridView.requestFocus();
+            }
+        });
+
+        assertTrue(horizontalGridView.isFocused());
+
+        sendKey(KeyEvent.KEYCODE_DPAD_DOWN);
+
+        assertTrue(mGridView.hasFocus());
+    }
+
+    @Test
+    public void testTransferFocusToChildWhenGainFocus() throws Throwable {
+
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.horizontal_linear);
+        final int numItems = 100;
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        mOrientation = BaseGridView.HORIZONTAL;
+        mNumRows = 1;
+        boolean[] focusable = new boolean[numItems];
+        final int firstFocusableIndex = 1;
+        for (int i = 0; i < firstFocusableIndex; i++) {
+            focusable[i] = false;
+        }
+        for (int i = firstFocusableIndex; i < focusable.length; i++) {
+            focusable[i] = true;
+        }
+        intent.putExtra(GridActivity.EXTRA_ITEMS_FOCUSABLE, focusable);
+        initActivity(intent);
+
+        assertEquals(firstFocusableIndex, mGridView.getSelectedPosition());
+        assertTrue(mGridView.getLayoutManager().findViewByPosition(firstFocusableIndex).hasFocus());
+    }
+
+    @Test
+    public void testFocusFromSecondChild() throws Throwable {
+
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.horizontal_linear);
+        final int numItems = 100;
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        mOrientation = BaseGridView.HORIZONTAL;
+        mNumRows = 1;
+        boolean[] focusable = new boolean[numItems];
+        for (int i = 0; i < focusable.length; i++) {
+            focusable[i] = false;
+        }
+        intent.putExtra(GridActivity.EXTRA_ITEMS_FOCUSABLE, focusable);
+        initActivity(intent);
+
+        // switching Adapter to cause a full rebind,  test if it will focus to second item.
+        performAndWaitForAnimation(new Runnable() {
+            @Override
+            public void run() {
+                mActivity.mNumItems = numItems;
+                mActivity.mItemFocusables[1] = true;
+                mActivity.rebindToNewAdapter();
+            }
+        });
+        assertTrue(mGridView.findViewHolderForAdapterPosition(1).itemView.hasFocus());
+    }
+
+    @Test
+    public void removeFocusableItemAndFocusableRecyclerViewGetsFocus() throws Throwable {
+        final int numItems = 100;
+        final int numColumns = 3;
+        final int focusableIndex = 2;
+
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.vertical_grid);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = numColumns;
+        boolean[] focusable = new boolean[numItems];
+        for (int i = 0; i < focusable.length; i++) {
+            focusable[i] = false;
+        }
+        focusable[focusableIndex] = true;
+        intent.putExtra(GridActivity.EXTRA_ITEMS_FOCUSABLE, focusable);
+        initActivity(intent);
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.setSelectedPositionSmooth(focusableIndex);
+            }
+        });
+        waitForScrollIdle(mVerifyLayout);
+        assertEquals(focusableIndex, mGridView.getSelectedPosition());
+
+        performAndWaitForAnimation(new Runnable() {
+            @Override
+            public void run() {
+                mActivity.removeItems(focusableIndex, 1);
+            }
+        });
+        assertTrue(dumpGridView(mGridView), mGridView.isFocused());
+    }
+
+    @Test
+    public void removeFocusableItemAndUnFocusableRecyclerViewLosesFocus() throws Throwable {
+        final int numItems = 100;
+        final int numColumns = 3;
+        final int focusableIndex = 2;
+
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.vertical_grid);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = numColumns;
+        boolean[] focusable = new boolean[numItems];
+        for (int i = 0; i < focusable.length; i++) {
+            focusable[i] = false;
+        }
+        focusable[focusableIndex] = true;
+        intent.putExtra(GridActivity.EXTRA_ITEMS_FOCUSABLE, focusable);
+        initActivity(intent);
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.setFocusableInTouchMode(false);
+                mGridView.setFocusable(false);
+                mGridView.setSelectedPositionSmooth(focusableIndex);
+            }
+        });
+        waitForScrollIdle(mVerifyLayout);
+        assertEquals(focusableIndex, mGridView.getSelectedPosition());
+
+        performAndWaitForAnimation(new Runnable() {
+            @Override
+            public void run() {
+                mActivity.removeItems(focusableIndex, 1);
+            }
+        });
+        assertFalse(dumpGridView(mGridView), mGridView.hasFocus());
+    }
+
+    @Test
+    public void testNonFocusableVertical() throws Throwable {
+        final int numItems = 200;
+        final int startPos = 44;
+        final int skips = 20;
+        final int numColumns = 3;
+        final int endPos = startPos + numColumns * (skips + 1);
+
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.vertical_grid);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = numColumns;
+        boolean[] focusable = new boolean[numItems];
+        for (int i = 0; i < focusable.length; i++) {
+            focusable[i] = true;
+        }
+        for (int i = startPos + mNumRows, j = 0; j < skips; i += mNumRows, j++) {
+            focusable[i] = false;
+        }
+        intent.putExtra(GridActivity.EXTRA_ITEMS_FOCUSABLE, focusable);
+        initActivity(intent);
+
+        mGridView.setSelectedPositionSmooth(startPos);
+        waitForScrollIdle(mVerifyLayout);
+
+        sendKey(KeyEvent.KEYCODE_DPAD_DOWN);
+        waitForScrollIdle(mVerifyLayout);
+        assertEquals(endPos, mGridView.getSelectedPosition());
+
+        sendKey(KeyEvent.KEYCODE_DPAD_UP);
+        waitForScrollIdle(mVerifyLayout);
+        assertEquals(startPos, mGridView.getSelectedPosition());
+
+    }
+
+    @Test
+    public void testLtrFocusOutStartDisabled() throws Throwable {
+        final int numItems = 200;
+
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_grid_ltr);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = 1;
+        initActivity(intent);
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.requestFocus();
+                mGridView.setSelectedPositionSmooth(0);
+            }
+        });
+        waitForScrollIdle(mVerifyLayout);
+
+        sendKey(KeyEvent.KEYCODE_DPAD_LEFT);
+        waitForScrollIdle(mVerifyLayout);
+        assertTrue(mGridView.hasFocus());
+    }
+
+    @Test
+    public void testRtlFocusOutStartDisabled() throws Throwable {
+        final int numItems = 200;
+
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_grid_rtl);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = 1;
+        initActivity(intent);
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.requestFocus();
+                mGridView.setSelectedPositionSmooth(0);
+            }
+        });
+        waitForScrollIdle(mVerifyLayout);
+
+        sendKey(KeyEvent.KEYCODE_DPAD_RIGHT);
+        waitForScrollIdle(mVerifyLayout);
+        assertTrue(mGridView.hasFocus());
+    }
+
+    @Test
+    public void testTransferFocusable() throws Throwable {
+        final int numItems = 200;
+        final int numColumns = 3;
+        final int startPos = 1;
+
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.horizontal_grid);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        mOrientation = BaseGridView.HORIZONTAL;
+        mNumRows = numColumns;
+        boolean[] focusable = new boolean[numItems];
+        for (int i = 0; i < focusable.length; i++) {
+            focusable[i] = true;
+        }
+        for (int i = 0; i < startPos; i++) {
+            focusable[i] = false;
+        }
+        intent.putExtra(GridActivity.EXTRA_ITEMS_FOCUSABLE, focusable);
+        initActivity(intent);
+
+        changeArraySize(0);
+        assertTrue(mGridView.isFocused());
+
+        changeArraySize(numItems);
+        assertTrue(mGridView.getLayoutManager().findViewByPosition(startPos).hasFocus());
+    }
+
+    @Test
+    public void testTransferFocusable2() throws Throwable {
+        final int numItems = 200;
+        final int numColumns = 3;
+        final int startPos = 3; // make sure view at startPos is in visible area.
+
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.horizontal_grid);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, true);
+        mOrientation = BaseGridView.HORIZONTAL;
+        mNumRows = numColumns;
+        boolean[] focusable = new boolean[numItems];
+        for (int i = 0; i < focusable.length; i++) {
+            focusable[i] = true;
+        }
+        for (int i = 0; i < startPos; i++) {
+            focusable[i] = false;
+        }
+        intent.putExtra(GridActivity.EXTRA_ITEMS_FOCUSABLE, focusable);
+        initActivity(intent);
+
+        assertTrue(mGridView.getLayoutManager().findViewByPosition(startPos).hasFocus());
+
+        changeArraySize(0);
+        assertTrue(mGridView.isFocused());
+
+        changeArraySize(numItems);
+        assertTrue(mGridView.getLayoutManager().findViewByPosition(startPos).hasFocus());
+    }
+
+    @Test
+    public void testNonFocusableLoseInFastLayout() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.vertical_linear);
+        int[] items = new int[300];
+        for (int i = 0; i < items.length; i++) {
+            items[i] = 480;
+        }
+        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        intent.putExtra(GridActivity.EXTRA_REQUEST_LAYOUT_ONFOCUS, true);
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = 1;
+        int pressDown = 15;
+
+        initActivity(intent);
+
+        mGridView.setSelectedPositionSmooth(0);
+        waitForScrollIdle(mVerifyLayout);
+
+        for (int i = 0; i < pressDown; i++) {
+            sendKey(KeyEvent.KEYCODE_DPAD_DOWN);
+        }
+        waitForScrollIdle(mVerifyLayout);
+        assertFalse(mGridView.isFocused());
+
+    }
+
+    @Test
+    public void testFocusableViewAvailable() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.vertical_linear);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 0);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        intent.putExtra(GridActivity.EXTRA_ITEMS_FOCUSABLE,
+                new boolean[]{false, false, true, false, false});
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = 1;
+
+        initActivity(intent);
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                // RecyclerView does not respect focusable and focusableInTouchMode flag, so
+                // set flags in code.
+                mGridView.setFocusableInTouchMode(false);
+                mGridView.setFocusable(false);
+            }
+        });
+
+        assertFalse(mGridView.isFocused());
+
+        final boolean[] scrolled = new boolean[]{false};
+        mGridView.addOnScrollListener(new RecyclerView.OnScrollListener() {
+            @Override
+            public void onScrolled(RecyclerView recyclerView, int dx, int dy){
+                if (dy > 0) {
+                    scrolled[0] = true;
+                }
+            }
+        });
+        performAndWaitForAnimation(new Runnable() {
+            @Override
+            public void run() {
+                mActivity.addItems(0, new int[]{200, 300, 500, 500, 200});
+            }
+        });
+        waitForScrollIdle(mVerifyLayout);
+
+        assertFalse("GridView should not be scrolled", scrolled[0]);
+        assertTrue(mGridView.getLayoutManager().findViewByPosition(2).hasFocus());
+
+    }
+
+    @Test
+    public void testSetSelectionWithDelta() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.vertical_linear);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 300);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = 1;
+
+        initActivity(intent);
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.setSelectedPositionSmooth(3);
+            }
+        });
+        waitForScrollIdle(mVerifyLayout);
+        int top1 = mGridView.getLayoutManager().findViewByPosition(3).getTop();
+
+        humanDelay(1000);
+
+        // scroll to position with delta
+        setSelectedPosition(3, 100);
+        int top2 = mGridView.getLayoutManager().findViewByPosition(3).getTop();
+        assertEquals(top1 - 100, top2);
+
+        // scroll to same position without delta, it will be reset
+        setSelectedPosition(3, 0);
+        int top3 = mGridView.getLayoutManager().findViewByPosition(3).getTop();
+        assertEquals(top1, top3);
+
+        // scroll invisible item after last visible item
+        final int lastVisiblePos = ((GridLayoutManager)mGridView.getLayoutManager())
+                .mGrid.getLastVisibleIndex();
+        setSelectedPosition(lastVisiblePos + 1, 100);
+        int top4 = mGridView.getLayoutManager().findViewByPosition(lastVisiblePos + 1).getTop();
+        assertEquals(top1 - 100, top4);
+
+        // scroll invisible item before first visible item
+        final int firstVisiblePos = ((GridLayoutManager)mGridView.getLayoutManager())
+                .mGrid.getFirstVisibleIndex();
+        setSelectedPosition(firstVisiblePos - 1, 100);
+        int top5 = mGridView.getLayoutManager().findViewByPosition(firstVisiblePos - 1).getTop();
+        assertEquals(top1 - 100, top5);
+
+        // scroll to invisible item that is far away.
+        setSelectedPosition(50, 100);
+        int top6 = mGridView.getLayoutManager().findViewByPosition(50).getTop();
+        assertEquals(top1 - 100, top6);
+
+        // scroll to invisible item that is far away.
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.setSelectedPositionSmooth(100);
+            }
+        });
+        waitForScrollIdle(mVerifyLayout);
+        int top7 = mGridView.getLayoutManager().findViewByPosition(100).getTop();
+        assertEquals(top1, top7);
+
+        // scroll to invisible item that is far away.
+        setSelectedPosition(10, 50);
+        int top8 = mGridView.getLayoutManager().findViewByPosition(10).getTop();
+        assertEquals(top1 - 50, top8);
+    }
+
+    @Test
+    public void testSetSelectionWithDeltaInGrid() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.vertical_grid);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 500);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, true);
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = 3;
+
+        initActivity(intent);
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.setSelectedPositionSmooth(10);
+            }
+        });
+        waitForScrollIdle(mVerifyLayout);
+        int top1 = getCenterY(mGridView.getLayoutManager().findViewByPosition(10));
+
+        humanDelay(500);
+
+        // scroll to position with delta
+        setSelectedPosition(20, 100);
+        int top2 = getCenterY(mGridView.getLayoutManager().findViewByPosition(20));
+        assertEquals(top1 - 100, top2);
+
+        // scroll to same position without delta, it will be reset
+        setSelectedPosition(20, 0);
+        int top3 = getCenterY(mGridView.getLayoutManager().findViewByPosition(20));
+        assertEquals(top1, top3);
+
+        // scroll invisible item after last visible item
+        final int lastVisiblePos = ((GridLayoutManager)mGridView.getLayoutManager())
+                .mGrid.getLastVisibleIndex();
+        setSelectedPosition(lastVisiblePos + 1, 100);
+        int top4 = getCenterY(mGridView.getLayoutManager().findViewByPosition(lastVisiblePos + 1));
+        verifyMargin();
+        assertEquals(top1 - 100, top4);
+
+        // scroll invisible item before first visible item
+        final int firstVisiblePos = ((GridLayoutManager)mGridView.getLayoutManager())
+                .mGrid.getFirstVisibleIndex();
+        setSelectedPosition(firstVisiblePos - 1, 100);
+        int top5 = getCenterY(mGridView.getLayoutManager().findViewByPosition(firstVisiblePos - 1));
+        assertEquals(top1 - 100, top5);
+
+        // scroll to invisible item that is far away.
+        setSelectedPosition(100, 100);
+        int top6 = getCenterY(mGridView.getLayoutManager().findViewByPosition(100));
+        assertEquals(top1 - 100, top6);
+
+        // scroll to invisible item that is far away.
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.setSelectedPositionSmooth(200);
+            }
+        });
+        waitForScrollIdle(mVerifyLayout);
+        Thread.sleep(500);
+        int top7 = getCenterY(mGridView.getLayoutManager().findViewByPosition(200));
+        assertEquals(top1, top7);
+
+        // scroll to invisible item that is far away.
+        setSelectedPosition(10, 50);
+        int top8 = getCenterY(mGridView.getLayoutManager().findViewByPosition(10));
+        assertEquals(top1 - 50, top8);
+    }
+
+
+    @Test
+    public void testSetSelectionWithDeltaInGrid1() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.vertical_grid);
+        intent.putExtra(GridActivity.EXTRA_ITEMS, new int[]{
+                193,176,153,141,203,184,232,139,177,206,222,136,132,237,172,137,
+                188,172,163,213,158,219,209,147,133,229,170,197,138,215,188,205,
+                223,192,225,170,195,127,229,229,210,195,134,142,160,139,130,222,
+                150,163,180,176,157,137,234,169,159,167,182,150,224,231,202,236,
+                123,140,181,223,120,185,183,221,123,210,134,158,166,208,149,128,
+                192,214,212,198,133,140,158,133,229,173,226,141,180,128,127,218,
+                192,235,183,213,216,150,143,193,125,141,219,210,195,195,192,191,
+                212,236,157,189,160,220,147,158,220,199,233,231,201,180,168,141,
+                156,204,191,183,190,153,123,210,238,151,139,221,223,200,175,191,
+                132,184,197,204,236,157,230,151,195,219,212,143,172,149,219,184,
+                164,211,132,187,172,142,174,146,127,147,206,238,188,129,199,226,
+                132,220,210,159,235,153,208,182,196,123,180,159,131,135,175,226,
+                127,134,237,211,133,225,132,124,160,226,224,200,173,137,217,169,
+                182,183,176,185,122,168,195,159,172,129,126,129,166,136,149,220,
+                178,191,192,238,180,208,234,154,222,206,239,228,129,140,203,125,
+                214,175,125,169,196,132,234,138,192,142,234,190,215,232,239,122,
+                188,158,128,221,159,237,207,157,232,138,132,214,122,199,121,191,
+                199,209,126,164,175,187,173,186,194,224,191,196,146,208,213,210,
+                164,176,202,213,123,157,179,138,217,129,186,166,237,211,157,130,
+                137,132,171,232,216,239,180,151,137,132,190,133,218,155,171,227,
+                193,147,197,164,120,218,193,154,170,196,138,222,161,235,143,154,
+                192,178,228,195,178,133,203,178,173,206,178,212,136,157,169,124,
+                172,121,128,223,238,125,217,187,184,156,169,215,231,124,210,174,
+                146,226,185,134,223,228,183,182,136,133,199,146,180,233,226,225,
+                174,233,145,235,216,170,192,171,132,132,134,223,233,148,154,162,
+                192,179,197,203,139,197,174,187,135,132,180,136,192,195,124,221,
+                120,189,233,233,146,225,234,163,215,143,132,198,156,205,151,190,
+                204,239,221,229,123,138,134,217,219,136,218,215,167,139,195,125,
+                202,225,178,226,145,208,130,194,228,197,157,215,124,147,174,123,
+                237,140,172,181,161,151,229,216,199,199,179,213,146,122,222,162,
+                139,173,165,150,160,217,207,137,165,175,129,158,134,133,178,199,
+                215,213,122,197
+        });
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, true);
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = 3;
+
+        initActivity(intent);
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.setSelectedPositionSmooth(10);
+            }
+        });
+        waitForScrollIdle(mVerifyLayout);
+        int top1 = getCenterY(mGridView.getLayoutManager().findViewByPosition(10));
+
+        humanDelay(500);
+
+        // scroll to position with delta
+        setSelectedPosition(20, 100);
+        int top2 = getCenterY(mGridView.getLayoutManager().findViewByPosition(20));
+        assertEquals(top1 - 100, top2);
+
+        // scroll to same position without delta, it will be reset
+        setSelectedPosition(20, 0);
+        int top3 = getCenterY(mGridView.getLayoutManager().findViewByPosition(20));
+        assertEquals(top1, top3);
+
+        // scroll invisible item after last visible item
+        final int lastVisiblePos = ((GridLayoutManager)mGridView.getLayoutManager())
+                .mGrid.getLastVisibleIndex();
+        setSelectedPosition(lastVisiblePos + 1, 100);
+        int top4 = getCenterY(mGridView.getLayoutManager().findViewByPosition(lastVisiblePos + 1));
+        verifyMargin();
+        assertEquals(top1 - 100, top4);
+
+        // scroll invisible item before first visible item
+        final int firstVisiblePos = ((GridLayoutManager)mGridView.getLayoutManager())
+                .mGrid.getFirstVisibleIndex();
+        setSelectedPosition(firstVisiblePos - 1, 100);
+        int top5 = getCenterY(mGridView.getLayoutManager().findViewByPosition(firstVisiblePos - 1));
+        assertEquals(top1 - 100, top5);
+
+        // scroll to invisible item that is far away.
+        setSelectedPosition(100, 100);
+        int top6 = getCenterY(mGridView.getLayoutManager().findViewByPosition(100));
+        assertEquals(top1 - 100, top6);
+
+        // scroll to invisible item that is far away.
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.setSelectedPositionSmooth(200);
+            }
+        });
+        waitForScrollIdle(mVerifyLayout);
+        Thread.sleep(500);
+        int top7 = getCenterY(mGridView.getLayoutManager().findViewByPosition(200));
+        assertEquals(top1, top7);
+
+        // scroll to invisible item that is far away.
+        setSelectedPosition(10, 50);
+        int top8 = getCenterY(mGridView.getLayoutManager().findViewByPosition(10));
+        assertEquals(top1 - 50, top8);
+    }
+
+    @Test
+    public void testSmoothScrollSelectionEvents() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.vertical_grid);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 500);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = 3;
+        initActivity(intent);
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.setSelectedPositionSmooth(30);
+            }
+        });
+        waitForScrollIdle(mVerifyLayout);
+        humanDelay(500);
+
+        final ArrayList<Integer> selectedPositions = new ArrayList<Integer>();
+        mGridView.setOnChildSelectedListener(new OnChildSelectedListener() {
+            @Override
+            public void onChildSelected(ViewGroup parent, View view, int position, long id) {
+                selectedPositions.add(position);
+            }
+        });
+
+        sendRepeatedKeys(10, KeyEvent.KEYCODE_DPAD_UP);
+        humanDelay(500);
+        waitForScrollIdle(mVerifyLayout);
+        // should only get childselected event for item 0 once
+        assertTrue(selectedPositions.size() > 0);
+        assertEquals(0, selectedPositions.get(selectedPositions.size() - 1).intValue());
+        for (int i = selectedPositions.size() - 2; i >= 0; i--) {
+            assertFalse(0 == selectedPositions.get(i).intValue());
+        }
+
+    }
+
+    @Test
+    public void testSmoothScrollSelectionEventsLinear() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.vertical_linear);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 500);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = 1;
+        initActivity(intent);
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.setSelectedPositionSmooth(10);
+            }
+        });
+        waitForScrollIdle(mVerifyLayout);
+        humanDelay(500);
+
+        final ArrayList<Integer> selectedPositions = new ArrayList<Integer>();
+        mGridView.setOnChildSelectedListener(new OnChildSelectedListener() {
+            @Override
+            public void onChildSelected(ViewGroup parent, View view, int position, long id) {
+                selectedPositions.add(position);
+            }
+        });
+
+        sendRepeatedKeys(10, KeyEvent.KEYCODE_DPAD_UP);
+        humanDelay(500);
+        waitForScrollIdle(mVerifyLayout);
+        // should only get childselected event for item 0 once
+        assertTrue(selectedPositions.size() > 0);
+        assertEquals(0, selectedPositions.get(selectedPositions.size() - 1).intValue());
+        for (int i = selectedPositions.size() - 2; i >= 0; i--) {
+            assertFalse(0 == selectedPositions.get(i).intValue());
+        }
+
+    }
+
+    @Test
+    public void testScrollToNoneExisting() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.vertical_grid);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 100);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = 3;
+        initActivity(intent);
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.setSelectedPositionSmooth(99);
+            }
+        });
+        waitForScrollIdle(mVerifyLayout);
+        humanDelay(500);
+
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.setSelectedPositionSmooth(50);
+            }
+        });
+        Thread.sleep(100);
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.requestLayout();
+                mGridView.setSelectedPositionSmooth(0);
+            }
+        });
+        waitForScrollIdle(mVerifyLayout);
+        humanDelay(500);
+
+    }
+
+    @Test
+    public void testSmoothscrollerInterrupted() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.vertical_linear);
+        intent.putExtra(GridActivity.EXTRA_REQUEST_FOCUS_ONLAYOUT, true);
+        int[] items = new int[100];
+        for (int i = 0; i < items.length; i++) {
+            items[i] = 680;
+        }
+        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = 1;
+
+        initActivity(intent);
+
+        mGridView.setSelectedPositionSmooth(0);
+        waitForScrollIdle(mVerifyLayout);
+        assertTrue(mGridView.getChildAt(0).hasFocus());
+
+        // Pressing lots of key to make sure smooth scroller is running
+        for (int i = 0; i < 20; i++) {
+            sendKey(KeyEvent.KEYCODE_DPAD_DOWN);
+        }
+        while (mGridView.getLayoutManager().isSmoothScrolling()
+                || mGridView.getScrollState() != BaseGridView.SCROLL_STATE_IDLE) {
+            // Repeatedly pressing to make sure pending keys does not drop to zero.
+            sendKey(KeyEvent.KEYCODE_DPAD_DOWN);
+        }
+    }
+
+    @Test
+    public void testSmoothscrollerCancelled() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.vertical_linear);
+        intent.putExtra(GridActivity.EXTRA_REQUEST_FOCUS_ONLAYOUT, true);
+        int[] items = new int[100];
+        for (int i = 0; i < items.length; i++) {
+            items[i] = 680;
+        }
+        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = 1;
+
+        initActivity(intent);
+
+        mGridView.setSelectedPositionSmooth(0);
+        waitForScrollIdle(mVerifyLayout);
+        assertTrue(mGridView.getChildAt(0).hasFocus());
+
+        int targetPosition = items.length - 1;
+        mGridView.setSelectedPositionSmooth(targetPosition);
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.stopScroll();
+            }
+        });
+        waitForScrollIdle();
+        waitForItemAnimation();
+        assertEquals(mGridView.getSelectedPosition(), targetPosition);
+        assertSame(mGridView.getLayoutManager().findViewByPosition(targetPosition),
+                mGridView.findFocus());
+    }
+
+    @Test
+    public void testSetNumRowsAndAddItem() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.vertical_linear);
+        intent.putExtra(GridActivity.EXTRA_REQUEST_FOCUS_ONLAYOUT, true);
+        int[] items = new int[2];
+        for (int i = 0; i < items.length; i++) {
+            items[i] = 300;
+        }
+        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = 1;
+
+        initActivity(intent);
+
+        mGridView.setSelectedPositionSmooth(0);
+        waitForScrollIdle(mVerifyLayout);
+
+        mActivity.addItems(items.length, new int[]{300});
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                ((VerticalGridView) mGridView).setNumColumns(2);
+            }
+        });
+        Thread.sleep(1000);
+        assertTrue(mGridView.getChildAt(2).getLeft() != mGridView.getChildAt(1).getLeft());
+    }
+
+
+    @Test
+    public void testRequestLayoutBugInLayout() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.vertical_linear);
+        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.relative_layout);
+        intent.putExtra(GridActivity.EXTRA_REQUEST_FOCUS_ONLAYOUT, true);
+        int[] items = new int[100];
+        for (int i = 0; i < items.length; i++) {
+            items[i] = 300;
+        }
+        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = 1;
+
+        initActivity(intent);
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.setSelectedPositionSmooth(1);
+            }
+        });
+        waitForScrollIdle(mVerifyLayout);
+
+        sendKey(KeyEvent.KEYCODE_DPAD_UP);
+        waitForScrollIdle(mVerifyLayout);
+
+        assertEquals("Line 2", ((TextView) mGridView.findFocus()).getText().toString());
+    }
+
+
+    @Test
+    public void testChangeLayoutInChild() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.vertical_linear_wrap_content);
+        intent.putExtra(GridActivity.EXTRA_REQUEST_LAYOUT_ONFOCUS, true);
+        int[] items = new int[2];
+        for (int i = 0; i < items.length; i++) {
+            items[i] = 300;
+        }
+        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = 1;
+
+        initActivity(intent);
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.setSelectedPositionSmooth(0);
+            }
+        });
+        waitForScrollIdle(mVerifyLayout);
+        verifyMargin();
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.setSelectedPositionSmooth(1);
+            }
+        });
+        waitForScrollIdle(mVerifyLayout);
+        verifyMargin();
+    }
+
+    @Test
+    public void testWrapContent() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.horizontal_grid_wrap);
+        int[] items = new int[200];
+        for (int i = 0; i < items.length; i++) {
+            items[i] = 300;
+        }
+        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
+        mOrientation = BaseGridView.HORIZONTAL;
+        mNumRows = 1;
+
+        initActivity(intent);
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mActivity.attachToNewAdapter(new int[0]);
+            }
+        });
+
+    }
+
+    @Test
+    public void testZeroFixedSecondarySize() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.vertical_linear_measured_with_zero);
+        intent.putExtra(GridActivity.EXTRA_SECONDARY_SIZE_ZERO, true);
+        int[] items = new int[2];
+        for (int i = 0; i < items.length; i++) {
+            items[i] = 0;
+        }
+        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = 1;
+
+        initActivity(intent);
+
+    }
+
+    @Test
+    public void testChildStates() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
+        int[] items = new int[100];
+        for (int i = 0; i < items.length; i++) {
+            items[i] = 200;
+        }
+        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        intent.putExtra(GridActivity.EXTRA_REQUEST_LAYOUT_ONFOCUS, true);
+        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.selectable_text_view);
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = 1;
+
+        initActivity(intent);
+        mGridView.setSaveChildrenPolicy(VerticalGridView.SAVE_ALL_CHILD);
+
+        final SparseArray<Parcelable> container = new SparseArray<Parcelable>();
+
+        // 1 Save view states
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                Selection.setSelection((Spannable)(((TextView) mGridView.getChildAt(0))
+                        .getText()), 0, 1);
+                Selection.setSelection((Spannable)(((TextView) mGridView.getChildAt(1))
+                        .getText()), 0, 1);
+                mGridView.saveHierarchyState(container);
+            }
+        });
+
+        // 2 Change view states
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                Selection.setSelection((Spannable)(((TextView) mGridView.getChildAt(0))
+                        .getText()), 1, 2);
+                Selection.setSelection((Spannable)(((TextView) mGridView.getChildAt(1))
+                        .getText()), 1, 2);
+            }
+        });
+
+        // 3 Detached and re-attached,  should still maintain state of (2)
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.setSelectedPositionSmooth(1);
+            }
+        });
+        waitForScrollIdle(mVerifyLayout);
+        assertEquals(((TextView) mGridView.getChildAt(0)).getSelectionStart(), 1);
+        assertEquals(((TextView) mGridView.getChildAt(0)).getSelectionEnd(), 2);
+        assertEquals(((TextView) mGridView.getChildAt(1)).getSelectionStart(), 1);
+        assertEquals(((TextView) mGridView.getChildAt(1)).getSelectionEnd(), 2);
+
+        // 4 Recycled and rebound, should load state from (2)
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.setSelectedPositionSmooth(20);
+            }
+        });
+        waitForScrollIdle(mVerifyLayout);
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.setSelectedPositionSmooth(0);
+            }
+        });
+        waitForScrollIdle(mVerifyLayout);
+        assertEquals(((TextView) mGridView.getChildAt(0)).getSelectionStart(), 1);
+        assertEquals(((TextView) mGridView.getChildAt(0)).getSelectionEnd(), 2);
+        assertEquals(((TextView) mGridView.getChildAt(1)).getSelectionStart(), 1);
+        assertEquals(((TextView) mGridView.getChildAt(1)).getSelectionEnd(), 2);
+    }
+
+
+    @Test
+    public void testNoDispatchSaveChildState() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
+        int[] items = new int[100];
+        for (int i = 0; i < items.length; i++) {
+            items[i] = 200;
+        }
+        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.selectable_text_view);
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = 1;
+
+        initActivity(intent);
+        mGridView.setSaveChildrenPolicy(VerticalGridView.SAVE_NO_CHILD);
+
+        final SparseArray<Parcelable> container = new SparseArray<Parcelable>();
+
+        // 1. Set text selection, save view states should do nothing on child
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                for (int i = 0; i < mGridView.getChildCount(); i++) {
+                    Selection.setSelection((Spannable)(((TextView) mGridView.getChildAt(i))
+                            .getText()), 0, 1);
+                }
+                mGridView.saveHierarchyState(container);
+            }
+        });
+
+        // 2. clear the text selection
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                for (int i = 0; i < mGridView.getChildCount(); i++) {
+                    Selection.removeSelection((Spannable)(((TextView) mGridView.getChildAt(i))
+                            .getText()));
+                }
+            }
+        });
+
+        // 3. Restore view states should be a no-op for child
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.restoreHierarchyState(container);
+                for (int i = 0; i < mGridView.getChildCount(); i++) {
+                    assertEquals(-1, ((TextView) mGridView.getChildAt(i)).getSelectionStart());
+                    assertEquals(-1, ((TextView) mGridView.getChildAt(i)).getSelectionEnd());
+                }
+            }
+        });
+    }
+
+
+    static interface ViewTypeProvider {
+        public int getViewType(int position);
+    }
+
+    static interface ItemAlignmentFacetProvider {
+        public ItemAlignmentFacet getItemAlignmentFacet(int viewType);
+    }
+
+    static class TwoViewTypesProvider implements ViewTypeProvider {
+        static int VIEW_TYPE_FIRST = 1;
+        static int VIEW_TYPE_DEFAULT = 0;
+        @Override
+        public int getViewType(int position) {
+            if (position == 0) {
+                return VIEW_TYPE_FIRST;
+            } else {
+                return VIEW_TYPE_DEFAULT;
+            }
+        }
+    }
+
+    static class ChangeableViewTypesProvider implements ViewTypeProvider {
+        static SparseIntArray sViewTypes = new SparseIntArray();
+        @Override
+        public int getViewType(int position) {
+            return sViewTypes.get(position);
+        }
+        public static void clear() {
+            sViewTypes.clear();
+        }
+        public static void setViewType(int position, int type) {
+            sViewTypes.put(position, type);
+        }
+    }
+
+    static class PositionItemAlignmentFacetProviderForRelativeLayout1
+            implements ItemAlignmentFacetProvider {
+        ItemAlignmentFacet mMultipleFacet;
+
+        PositionItemAlignmentFacetProviderForRelativeLayout1() {
+            mMultipleFacet = new ItemAlignmentFacet();
+            ItemAlignmentFacet.ItemAlignmentDef[] defs =
+                    new ItemAlignmentFacet.ItemAlignmentDef[2];
+            defs[0] = new ItemAlignmentFacet.ItemAlignmentDef();
+            defs[0].setItemAlignmentViewId(R.id.t1);
+            defs[1] = new ItemAlignmentFacet.ItemAlignmentDef();
+            defs[1].setItemAlignmentViewId(R.id.t2);
+            defs[1].setItemAlignmentOffsetPercent(100);
+            defs[1].setItemAlignmentOffset(-10);
+            mMultipleFacet.setAlignmentDefs(defs);
+        }
+
+        @Override
+        public ItemAlignmentFacet getItemAlignmentFacet(int position) {
+            if (position == 0) {
+                return mMultipleFacet;
+            } else {
+                return null;
+            }
+        }
+    }
+
+    @Test
+    public void testMultipleScrollPosition1() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.vertical_linear);
+        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.relative_layout);
+        intent.putExtra(GridActivity.EXTRA_REQUEST_FOCUS_ONLAYOUT, true);
+        int[] items = new int[100];
+        for (int i = 0; i < items.length; i++) {
+            items[i] = 300;
+        }
+        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        intent.putExtra(GridActivity.EXTRA_VIEWTYPEPROVIDER_CLASS,
+                TwoViewTypesProvider.class.getName());
+        // Set ItemAlignment for each ViewHolder and view type,  ViewHolder should
+        // override the view type settings.
+        intent.putExtra(GridActivity.EXTRA_ITEMALIGNMENTPROVIDER_CLASS,
+                PositionItemAlignmentFacetProviderForRelativeLayout1.class.getName());
+        intent.putExtra(GridActivity.EXTRA_ITEMALIGNMENTPROVIDER_VIEWTYPE_CLASS,
+                ViewTypePositionItemAlignmentFacetProviderForRelativeLayout2.class.getName());
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = 1;
+
+        initActivity(intent);
+
+        assertEquals("First view is aligned with padding top",
+                mGridView.getPaddingTop(), mGridView.getChildAt(0).getTop());
+
+        sendKey(KeyEvent.KEYCODE_DPAD_DOWN);
+        waitForScrollIdle(mVerifyLayout);
+
+        final View v = mGridView.getChildAt(0);
+        View t1 = v.findViewById(R.id.t1);
+        int t1align = (t1.getTop() + t1.getBottom()) / 2;
+        View t2 = v.findViewById(R.id.t2);
+        int t2align = t2.getBottom() - 10;
+        assertEquals("Expected alignment for 2nd textview",
+                mGridView.getPaddingTop() - (t2align - t1align),
+                v.getTop());
+    }
+
+    static class PositionItemAlignmentFacetProviderForRelativeLayout2 implements
+            ItemAlignmentFacetProvider {
+        ItemAlignmentFacet mMultipleFacet;
+
+        PositionItemAlignmentFacetProviderForRelativeLayout2() {
+            mMultipleFacet = new ItemAlignmentFacet();
+            ItemAlignmentFacet.ItemAlignmentDef[] defs = new ItemAlignmentFacet.ItemAlignmentDef[2];
+            defs[0] = new ItemAlignmentFacet.ItemAlignmentDef();
+            defs[0].setItemAlignmentViewId(R.id.t1);
+            defs[0].setItemAlignmentOffsetPercent(0);
+            defs[1] = new ItemAlignmentFacet.ItemAlignmentDef();
+            defs[1].setItemAlignmentViewId(R.id.t2);
+            defs[1].setItemAlignmentOffsetPercent(ItemAlignmentFacet.ITEM_ALIGN_OFFSET_PERCENT_DISABLED);
+            defs[1].setItemAlignmentOffset(-10);
+            mMultipleFacet.setAlignmentDefs(defs);
+        }
+
+        @Override
+        public ItemAlignmentFacet getItemAlignmentFacet(int position) {
+            if (position == 0) {
+                return mMultipleFacet;
+            } else {
+                return null;
+            }
+        }
+    }
+
+    @Test
+    public void testMultipleScrollPosition2() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
+        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.relative_layout);
+        intent.putExtra(GridActivity.EXTRA_REQUEST_FOCUS_ONLAYOUT, true);
+        int[] items = new int[100];
+        for (int i = 0; i < items.length; i++) {
+            items[i] = 300;
+        }
+        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        intent.putExtra(GridActivity.EXTRA_VIEWTYPEPROVIDER_CLASS,
+                TwoViewTypesProvider.class.getName());
+        intent.putExtra(GridActivity.EXTRA_ITEMALIGNMENTPROVIDER_CLASS,
+                PositionItemAlignmentFacetProviderForRelativeLayout2.class.getName());
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = 1;
+
+        initActivity(intent);
+
+        assertEquals("First view is aligned with padding top", mGridView.getPaddingTop(),
+                mGridView.getChildAt(0).getTop());
+
+        sendKey(KeyEvent.KEYCODE_DPAD_DOWN);
+        waitForScrollIdle(mVerifyLayout);
+
+        final View v = mGridView.getChildAt(0);
+        View t1 = v.findViewById(R.id.t1);
+        int t1align = t1.getTop();
+        View t2 = v.findViewById(R.id.t2);
+        int t2align = t2.getTop() - 10;
+        assertEquals("Expected alignment for 2nd textview",
+                mGridView.getPaddingTop() - (t2align - t1align), v.getTop());
+    }
+
+    static class ViewTypePositionItemAlignmentFacetProviderForRelativeLayout2 implements
+            ItemAlignmentFacetProvider {
+        ItemAlignmentFacet mMultipleFacet;
+
+        ViewTypePositionItemAlignmentFacetProviderForRelativeLayout2() {
+            mMultipleFacet = new ItemAlignmentFacet();
+            ItemAlignmentFacet.ItemAlignmentDef[] defs = new ItemAlignmentFacet.ItemAlignmentDef[2];
+            defs[0] = new ItemAlignmentFacet.ItemAlignmentDef();
+            defs[0].setItemAlignmentViewId(R.id.t1);
+            defs[0].setItemAlignmentOffsetPercent(0);
+            defs[1] = new ItemAlignmentFacet.ItemAlignmentDef();
+            defs[1].setItemAlignmentViewId(R.id.t2);
+            defs[1].setItemAlignmentOffsetPercent(100);
+            defs[1].setItemAlignmentOffset(-10);
+            mMultipleFacet.setAlignmentDefs(defs);
+        }
+
+        @Override
+        public ItemAlignmentFacet getItemAlignmentFacet(int viewType) {
+            if (viewType == TwoViewTypesProvider.VIEW_TYPE_FIRST) {
+                return mMultipleFacet;
+            } else {
+                return null;
+            }
+        }
+    }
+
+    @Test
+    public void testMultipleScrollPosition3() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
+        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.relative_layout);
+        intent.putExtra(GridActivity.EXTRA_REQUEST_FOCUS_ONLAYOUT, true);
+        int[] items = new int[100];
+        for (int i = 0; i < items.length; i++) {
+            items[i] = 300;
+        }
+        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        intent.putExtra(GridActivity.EXTRA_VIEWTYPEPROVIDER_CLASS,
+                TwoViewTypesProvider.class.getName());
+        intent.putExtra(GridActivity.EXTRA_ITEMALIGNMENTPROVIDER_VIEWTYPE_CLASS,
+                ViewTypePositionItemAlignmentFacetProviderForRelativeLayout2.class.getName());
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = 1;
+
+        initActivity(intent);
+
+        assertEquals("First view is aligned with padding top", mGridView.getPaddingTop(),
+                mGridView.getChildAt(0).getTop());
+
+        sendKey(KeyEvent.KEYCODE_DPAD_DOWN);
+        waitForScrollIdle(mVerifyLayout);
+
+        final View v = mGridView.getChildAt(0);
+        View t1 = v.findViewById(R.id.t1);
+        int t1align = t1.getTop();
+        View t2 = v.findViewById(R.id.t2);
+        int t2align = t2.getBottom() - 10;
+        assertEquals("Expected alignment for 2nd textview",
+                mGridView.getPaddingTop() - (t2align - t1align), v.getTop());
+    }
+
+    @Test
+    public void testSelectionAndAddItemInOneCycle() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.vertical_linear);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 0);
+        initActivity(intent);
+        mOrientation = BaseGridView.HORIZONTAL;
+        mNumRows = 1;
+
+        performAndWaitForAnimation(new Runnable() {
+            @Override
+            public void run() {
+                mActivity.addItems(0, new int[]{300, 300});
+                mGridView.setSelectedPosition(0);
+            }
+        });
+        assertEquals(0, mGridView.getSelectedPosition());
+    }
+
+    @Test
+    public void testSelectViewTaskSmoothWithAdapterChange() throws Throwable {
+        testSelectViewTaskWithAdapterChange(true /*smooth*/);
+    }
+
+    @Test
+    public void testSelectViewTaskWithAdapterChange() throws Throwable {
+        testSelectViewTaskWithAdapterChange(false /*smooth*/);
+    }
+
+    private void testSelectViewTaskWithAdapterChange(final boolean smooth) throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.vertical_linear);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 2);
+        initActivity(intent);
+        mOrientation = BaseGridView.HORIZONTAL;
+        mNumRows = 1;
+
+        final View firstView = mGridView.getLayoutManager().findViewByPosition(0);
+        final View[] selectedViewByTask = new View[1];
+        final ViewHolderTask task = new ViewHolderTask() {
+            @Override
+            public void run(RecyclerView.ViewHolder viewHolder) {
+                selectedViewByTask[0] = viewHolder.itemView;
+            }
+        };
+        performAndWaitForAnimation(new Runnable() {
+            @Override
+            public void run() {
+                mActivity.removeItems(0, 1);
+                if (smooth) {
+                    mGridView.setSelectedPositionSmooth(0, task);
+                } else {
+                    mGridView.setSelectedPosition(0, task);
+                }
+            }
+        });
+        assertEquals(0, mGridView.getSelectedPosition());
+        assertNotNull(selectedViewByTask[0]);
+        assertNotSame(firstView, selectedViewByTask[0]);
+        assertSame(mGridView.getLayoutManager().findViewByPosition(0), selectedViewByTask[0]);
+    }
+
+    @Test
+    public void testNotifyItemTypeChangedSelectionEvent() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.vertical_linear);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 10);
+        intent.putExtra(GridActivity.EXTRA_VIEWTYPEPROVIDER_CLASS,
+                ChangeableViewTypesProvider.class.getName());
+        ChangeableViewTypesProvider.clear();
+        initActivity(intent);
+        mOrientation = BaseGridView.HORIZONTAL;
+        mNumRows = 1;
+
+        final ArrayList<Integer> selectedLog = new ArrayList<Integer>();
+        mGridView.setOnChildSelectedListener(new OnChildSelectedListener() {
+            @Override
+            public void onChildSelected(ViewGroup parent, View view, int position, long id) {
+                selectedLog.add(position);
+            }
+        });
+
+        performAndWaitForAnimation(new Runnable() {
+            @Override
+            public void run() {
+                ChangeableViewTypesProvider.setViewType(0, 1);
+                mGridView.getAdapter().notifyItemChanged(0, 1);
+            }
+        });
+        assertEquals(0, mGridView.getSelectedPosition());
+        assertEquals(selectedLog.size(), 1);
+        assertEquals((int) selectedLog.get(0), 0);
+    }
+
+    @Test
+    public void testNotifyItemChangedSelectionEvent() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.vertical_linear);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 10);
+        initActivity(intent);
+        mOrientation = BaseGridView.HORIZONTAL;
+        mNumRows = 1;
+
+        OnChildViewHolderSelectedListener listener =
+                Mockito.mock(OnChildViewHolderSelectedListener.class);
+        mGridView.setOnChildViewHolderSelectedListener(listener);
+
+        performAndWaitForAnimation(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.getAdapter().notifyItemChanged(0, 1);
+            }
+        });
+        Mockito.verify(listener, times(1)).onChildViewHolderSelected(any(RecyclerView.class),
+                any(RecyclerView.ViewHolder.class), anyInt(), anyInt());
+        assertEquals(0, mGridView.getSelectedPosition());
+    }
+
+    @Test
+    public void testSelectionSmoothAndAddItemInOneCycle() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.vertical_linear);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 0);
+        initActivity(intent);
+        mOrientation = BaseGridView.HORIZONTAL;
+        mNumRows = 1;
+
+        performAndWaitForAnimation(new Runnable() {
+            @Override
+            public void run() {
+                mActivity.addItems(0, new int[]{300, 300});
+                mGridView.setSelectedPositionSmooth(0);
+            }
+        });
+        assertEquals(0, mGridView.getSelectedPosition());
+    }
+
+    @Test
+    public void testExtraLayoutSpace() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.vertical_linear);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 1000);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        initActivity(intent);
+
+        final int windowSize = mGridView.getHeight();
+        final int extraLayoutSize = windowSize;
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = 1;
+
+        // add extra layout space
+        startWaitLayout();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.setExtraLayoutSpace(extraLayoutSize);
+            }
+        });
+        waitForLayout();
+        View v;
+        v = mGridView.getChildAt(mGridView.getChildCount() - 1);
+        assertTrue(v.getTop() < windowSize + extraLayoutSize);
+        assertTrue(v.getBottom() >= windowSize + extraLayoutSize - mGridView.getVerticalMargin());
+
+        mGridView.setSelectedPositionSmooth(150);
+        waitForScrollIdle(mVerifyLayout);
+        v = mGridView.getChildAt(0);
+        assertTrue(v.getBottom() > - extraLayoutSize);
+        assertTrue(v.getTop() <= -extraLayoutSize + mGridView.getVerticalMargin());
+
+        // clear extra layout space
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.setExtraLayoutSpace(0);
+                verifyMargin();
+            }
+        });
+        Thread.sleep(50);
+        v = mGridView.getChildAt(mGridView.getChildCount() - 1);
+        assertTrue(v.getTop() < windowSize);
+        assertTrue(v.getBottom() >= windowSize - mGridView.getVerticalMargin());
+    }
+
+    @Test
+    public void testFocusFinder() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.vertical_linear_with_button);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 3);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        initActivity(intent);
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = 1;
+
+        // test focus from button to vertical grid view
+        final View button = mActivity.findViewById(R.id.button);
+        assertTrue(button.isFocused());
+        sendKey(KeyEvent.KEYCODE_DPAD_DOWN);
+        assertFalse(mGridView.isFocused());
+        assertTrue(mGridView.hasFocus());
+
+        // FocusFinder should find last focused(2nd) item on DPAD_DOWN
+        final View secondChild = mGridView.getChildAt(1);
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                secondChild.requestFocus();
+                button.requestFocus();
+            }
+        });
+        assertTrue(button.isFocused());
+        sendKey(KeyEvent.KEYCODE_DPAD_DOWN);
+        assertTrue(secondChild.isFocused());
+
+        // Bug 26918143 Even VerticalGridView is not focusable, FocusFinder should find last focused
+        // (2nd) item on DPAD_DOWN.
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                button.requestFocus();
+            }
+        });
+        mGridView.setFocusable(false);
+        mGridView.setFocusableInTouchMode(false);
+        assertTrue(button.isFocused());
+        sendKey(KeyEvent.KEYCODE_DPAD_DOWN);
+        assertTrue(secondChild.isFocused());
+    }
+
+    @Test
+    public void testRestoreIndexAndAddItems() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.vertical_linear);
+        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.horizontal_item);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 4);
+        initActivity(intent);
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = 1;
+
+        assertEquals(mGridView.getSelectedPosition(), 0);
+        final SparseArray<Parcelable> states = new SparseArray<>();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.saveHierarchyState(states);
+                mGridView.setAdapter(null);
+            }
+
+        });
+        performAndWaitForAnimation(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.restoreHierarchyState(states);
+                mActivity.attachToNewAdapter(new int[0]);
+                mActivity.addItems(0, new int[]{100, 100, 100, 100});
+            }
+
+        });
+        assertEquals(mGridView.getSelectedPosition(), 0);
+    }
+
+    @Test
+    public void testRestoreIndexAndAddItemsSelect1() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.vertical_linear);
+        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.horizontal_item);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 4);
+        initActivity(intent);
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = 1;
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.setSelectedPosition(1);
+            }
+
+        });
+        assertEquals(mGridView.getSelectedPosition(), 1);
+        final SparseArray<Parcelable> states = new SparseArray<>();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.saveHierarchyState(states);
+                mGridView.setAdapter(null);
+            }
+
+        });
+        performAndWaitForAnimation(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.restoreHierarchyState(states);
+                mActivity.attachToNewAdapter(new int[0]);
+                mActivity.addItems(0, new int[]{100, 100, 100, 100});
+            }
+
+        });
+        assertEquals(mGridView.getSelectedPosition(), 1);
+    }
+
+    @Test
+    public void testRestoreStateAfterAdapterChange() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.vertical_linear);
+        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.selectable_text_view);
+        intent.putExtra(GridActivity.EXTRA_ITEMS, new int[]{50, 50, 50, 50});
+        initActivity(intent);
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = 1;
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.setSelectedPosition(1);
+                mGridView.setSaveChildrenPolicy(VerticalGridView.SAVE_ALL_CHILD);
+            }
+
+        });
+        assertEquals(mGridView.getSelectedPosition(), 1);
+        final SparseArray<Parcelable> states = new SparseArray<>();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                Selection.setSelection((Spannable) (((TextView) mGridView.getChildAt(0))
+                        .getText()), 1, 2);
+                Selection.setSelection((Spannable) (((TextView) mGridView.getChildAt(1))
+                        .getText()), 0, 1);
+                mGridView.saveHierarchyState(states);
+                mGridView.setAdapter(null);
+            }
+
+        });
+        performAndWaitForAnimation(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.restoreHierarchyState(states);
+                mActivity.attachToNewAdapter(new int[]{50, 50, 50, 50});
+            }
+
+        });
+        assertEquals(mGridView.getSelectedPosition(), 1);
+        assertEquals(1, ((TextView) mGridView.getChildAt(0)).getSelectionStart());
+        assertEquals(2, ((TextView) mGridView.getChildAt(0)).getSelectionEnd());
+        assertEquals(0, ((TextView) mGridView.getChildAt(1)).getSelectionStart());
+        assertEquals(1, ((TextView) mGridView.getChildAt(1)).getSelectionEnd());
+    }
+
+    @Test
+    public void test27766012() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.vertical_linear_with_button_onleft);
+        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.horizontal_item);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 2);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        intent.putExtra(GridActivity.EXTRA_UPDATE_SIZE, false);
+        initActivity(intent);
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = 1;
+
+        // set remove animator two seconds
+        mGridView.getItemAnimator().setRemoveDuration(2000);
+        final View view = mGridView.getChildAt(1);
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                view.requestFocus();
+            }
+        });
+        assertTrue(view.hasFocus());
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mActivity.removeItems(0, 2);
+            }
+
+        });
+        // wait one second, removing second view is still attached to parent
+        Thread.sleep(1000);
+        assertSame(view.getParent(), mGridView);
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                // refocus to the removed item and do a focus search.
+                view.requestFocus();
+                view.focusSearch(View.FOCUS_UP);
+            }
+
+        });
+    }
+
+    @Test
+    public void testBug27258366() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.vertical_linear_with_button_onleft);
+        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.horizontal_item);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 10);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        intent.putExtra(GridActivity.EXTRA_UPDATE_SIZE, false);
+        initActivity(intent);
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = 1;
+
+        // move item1 500 pixels right, when focus is on item1, default focus finder will pick
+        // item0 and item2 for the best match of focusSearch(FOCUS_LEFT).  The grid widget
+        // must override default addFocusables(), not to add item0 or item2.
+        mActivity.mAdapterListener = new GridActivity.AdapterListener() {
+            @Override
+            public void onBind(RecyclerView.ViewHolder vh, int position) {
+                if (position == 1) {
+                    vh.itemView.setPaddingRelative(500, 0, 0, 0);
+                } else {
+                    vh.itemView.setPaddingRelative(0, 0, 0, 0);
+                }
+            }
+        };
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.getAdapter().notifyDataSetChanged();
+            }
+        });
+        Thread.sleep(100);
+
+        final ViewGroup secondChild = (ViewGroup) mGridView.getChildAt(1);
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                secondChild.requestFocus();
+            }
+        });
+        sendKey(KeyEvent.KEYCODE_DPAD_LEFT);
+        Thread.sleep(100);
+        final View button = mActivity.findViewById(R.id.button);
+        assertTrue(button.isFocused());
+    }
+
+    @Test
+    public void testUpdateHeightScrollHorizontal() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.horizontal_linear);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 30);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        intent.putExtra(GridActivity.EXTRA_REQUEST_LAYOUT_ONFOCUS, true);
+        intent.putExtra(GridActivity.EXTRA_UPDATE_SIZE, false);
+        intent.putExtra(GridActivity.EXTRA_UPDATE_SIZE_SECONDARY, true);
+        initActivity(intent);
+        mOrientation = BaseGridView.HORIZONTAL;
+        mNumRows = 1;
+
+        final int childTop = mGridView.getChildAt(0).getTop();
+        // scroll to end, all children's top should not change.
+        scrollToEnd(new Runnable() {
+            @Override
+            public void run() {
+                for (int i = 0; i < mGridView.getChildCount(); i++) {
+                    assertEquals(childTop, mGridView.getChildAt(i).getTop());
+                }
+            }
+        });
+        // sanity check last child has focus with a larger height.
+        assertTrue(mGridView.getChildAt(0).getHeight()
+                < mGridView.getChildAt(mGridView.getChildCount() - 1).getHeight());
+    }
+
+    @Test
+    public void testUpdateWidthScrollHorizontal() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.horizontal_linear);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 30);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        intent.putExtra(GridActivity.EXTRA_REQUEST_LAYOUT_ONFOCUS, true);
+        intent.putExtra(GridActivity.EXTRA_UPDATE_SIZE, true);
+        intent.putExtra(GridActivity.EXTRA_UPDATE_SIZE_SECONDARY, false);
+        initActivity(intent);
+        mOrientation = BaseGridView.HORIZONTAL;
+        mNumRows = 1;
+
+        final int childTop = mGridView.getChildAt(0).getTop();
+        // scroll to end, all children's top should not change.
+        scrollToEnd(new Runnable() {
+            @Override
+            public void run() {
+                for (int i = 0; i < mGridView.getChildCount(); i++) {
+                    assertEquals(childTop, mGridView.getChildAt(i).getTop());
+                }
+            }
+        });
+        // sanity check last child has focus with a larger width.
+        assertTrue(mGridView.getChildAt(0).getWidth()
+                < mGridView.getChildAt(mGridView.getChildCount() - 1).getWidth());
+        if (mGridView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
+            assertEquals(mGridView.getPaddingLeft(),
+                    mGridView.getChildAt(mGridView.getChildCount() - 1).getLeft());
+        } else {
+            assertEquals(mGridView.getWidth() - mGridView.getPaddingRight(),
+                    mGridView.getChildAt(mGridView.getChildCount() - 1).getRight());
+        }
+    }
+
+    @Test
+    public void testUpdateWidthScrollHorizontalRtl() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.horizontal_linear_rtl);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 30);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        intent.putExtra(GridActivity.EXTRA_REQUEST_LAYOUT_ONFOCUS, true);
+        intent.putExtra(GridActivity.EXTRA_UPDATE_SIZE, true);
+        intent.putExtra(GridActivity.EXTRA_UPDATE_SIZE_SECONDARY, false);
+        initActivity(intent);
+        mOrientation = BaseGridView.HORIZONTAL;
+        mNumRows = 1;
+
+        final int childTop = mGridView.getChildAt(0).getTop();
+        // scroll to end, all children's top should not change.
+        scrollToEnd(new Runnable() {
+            @Override
+            public void run() {
+                for (int i = 0; i < mGridView.getChildCount(); i++) {
+                    assertEquals(childTop, mGridView.getChildAt(i).getTop());
+                }
+            }
+        });
+        // sanity check last child has focus with a larger width.
+        assertTrue(mGridView.getChildAt(0).getWidth()
+                < mGridView.getChildAt(mGridView.getChildCount() - 1).getWidth());
+        assertEquals(mGridView.getPaddingLeft(),
+                mGridView.getChildAt(mGridView.getChildCount() - 1).getLeft());
+    }
+
+    @Test
+    public void testAccessibility() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.vertical_linear);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 1000);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        initActivity(intent);
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = 1;
+
+        assertTrue(0 == mGridView.getSelectedPosition());
+
+        final RecyclerViewAccessibilityDelegate delegateCompat = mGridView
+                .getCompatAccessibilityDelegate();
+        final AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                delegateCompat.onInitializeAccessibilityNodeInfo(mGridView, info);
+            }
+        });
+        assertTrue("test sanity", info.isScrollable());
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                delegateCompat.performAccessibilityAction(mGridView,
+                        AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD, null);
+            }
+        });
+        waitForScrollIdle(mVerifyLayout);
+        int selectedPosition1 = mGridView.getSelectedPosition();
+        assertTrue(0 < selectedPosition1);
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                delegateCompat.onInitializeAccessibilityNodeInfo(mGridView, info);
+            }
+        });
+        assertTrue("test sanity", info.isScrollable());
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                delegateCompat.performAccessibilityAction(mGridView,
+                        AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD, null);
+            }
+        });
+        waitForScrollIdle(mVerifyLayout);
+        int selectedPosition2 = mGridView.getSelectedPosition();
+        assertTrue(selectedPosition2 < selectedPosition1);
+    }
+
+    @Test
+    public void testAccessibilityScrollForwardHalfVisible() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
+        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.item_button_at_bottom);
+        intent.putExtra(GridActivity.EXTRA_ITEMS,  new int[]{});
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        initActivity(intent);
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = 1;
+
+        int height = mGridView.getHeight() - mGridView.getPaddingTop()
+                - mGridView.getPaddingBottom();
+        final int childHeight = height - mGridView.getVerticalSpacing() - 100;
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.setWindowAlignment(BaseGridView.WINDOW_ALIGN_NO_EDGE);
+                mGridView.setWindowAlignmentOffset(100);
+                mGridView.setWindowAlignmentOffsetPercent(BaseGridView
+                        .WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
+                mGridView.setItemAlignmentOffset(0);
+                mGridView.setItemAlignmentOffsetPercent(BaseGridView
+                        .ITEM_ALIGN_OFFSET_PERCENT_DISABLED);
+            }
+        });
+        mActivity.addItems(0, new int[]{childHeight, childHeight});
+        waitForItemAnimation();
+        setSelectedPosition(0);
+
+        final RecyclerViewAccessibilityDelegate delegateCompat = mGridView
+                .getCompatAccessibilityDelegate();
+        final AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                delegateCompat.onInitializeAccessibilityNodeInfo(mGridView, info);
+            }
+        });
+        assertTrue("test sanity", info.isScrollable());
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                delegateCompat.performAccessibilityAction(mGridView,
+                        AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD, null);
+            }
+        });
+        waitForScrollIdle(mVerifyLayout);
+        assertEquals(1, mGridView.getSelectedPosition());
+    }
+
+    @Test
+    public void testAccessibilityScrollBackwardHalfVisible() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
+        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.item_button_at_top);
+        intent.putExtra(GridActivity.EXTRA_ITEMS,  new int[]{});
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        initActivity(intent);
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = 1;
+
+        int height = mGridView.getHeight() - mGridView.getPaddingTop()
+                - mGridView.getPaddingBottom();
+        final int childHeight = height - mGridView.getVerticalSpacing() - 100;
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.setWindowAlignment(BaseGridView.WINDOW_ALIGN_NO_EDGE);
+                mGridView.setWindowAlignmentOffset(100);
+                mGridView.setWindowAlignmentOffsetPercent(BaseGridView
+                        .WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
+                mGridView.setItemAlignmentOffset(0);
+                mGridView.setItemAlignmentOffsetPercent(BaseGridView
+                        .ITEM_ALIGN_OFFSET_PERCENT_DISABLED);
+            }
+        });
+        mActivity.addItems(0, new int[]{childHeight, childHeight});
+        waitForItemAnimation();
+        setSelectedPosition(1);
+
+        final RecyclerViewAccessibilityDelegate delegateCompat = mGridView
+                .getCompatAccessibilityDelegate();
+        final AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                delegateCompat.onInitializeAccessibilityNodeInfo(mGridView, info);
+            }
+        });
+        assertTrue("test sanity", info.isScrollable());
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                delegateCompat.performAccessibilityAction(mGridView,
+                        AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD, null);
+            }
+        });
+        waitForScrollIdle(mVerifyLayout);
+        assertEquals(0, mGridView.getSelectedPosition());
+    }
+
+    void slideInAndWaitIdle() throws Throwable {
+        slideInAndWaitIdle(5000);
+    }
+
+    void slideInAndWaitIdle(long timeout) throws Throwable {
+        // animateIn() would reset position
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.animateIn();
+            }
+        });
+        PollingCheck.waitFor(timeout, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return !mGridView.getLayoutManager().isSmoothScrolling()
+                        && mGridView.getScrollState() == RecyclerView.SCROLL_STATE_IDLE;
+            }
+        });
+    }
+
+    @Test
+    public void testAnimateOutBlockScrollTo() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.vertical_linear_with_button_onleft);
+        int[] items = new int[100];
+        for (int i = 0; i < items.length; i++) {
+            items[i] = 300;
+        }
+        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = 1;
+
+        initActivity(intent);
+
+        assertEquals("First view is aligned with padding top", mGridView.getPaddingTop(),
+                mGridView.getChildAt(0).getTop());
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.animateOut();
+            }
+        });
+        // wait until sliding out.
+        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return mGridView.getChildAt(0).getTop() > mGridView.getPaddingTop();
+            }
+        });
+        // scrollToPosition() should not affect slideOut status
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.scrollToPosition(0);
+            }
+        });
+        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return mGridView.getScrollState() == RecyclerView.SCROLL_STATE_IDLE;
+            }
+        });
+        assertTrue("First view slided Out", mGridView.getChildAt(0).getTop()
+                >= mGridView.getHeight());
+
+        slideInAndWaitIdle();
+        assertEquals("First view is aligned with padding top", mGridView.getPaddingTop(),
+                mGridView.getChildAt(0).getTop());
+    }
+
+    @Test
+    public void testAnimateOutBlockSmoothScrolling() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.vertical_linear_with_button_onleft);
+        int[] items = new int[30];
+        for (int i = 0; i < items.length; i++) {
+            items[i] = 300;
+        }
+        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = 1;
+
+        initActivity(intent);
+
+        assertEquals("First view is aligned with padding top", mGridView.getPaddingTop(),
+                mGridView.getChildAt(0).getTop());
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.animateOut();
+            }
+        });
+        // wait until sliding out.
+        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return mGridView.getChildAt(0).getTop() > mGridView.getPaddingTop();
+            }
+        });
+        // smoothScrollToPosition() should not affect slideOut status
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.smoothScrollToPosition(29);
+            }
+        });
+        PollingCheck.waitFor(10000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return mGridView.getScrollState() == RecyclerView.SCROLL_STATE_IDLE;
+            }
+        });
+        assertTrue("First view slided Out", mGridView.getChildAt(0).getTop()
+                >= mGridView.getHeight());
+
+        slideInAndWaitIdle();
+        View lastChild = mGridView.getChildAt(mGridView.getChildCount() - 1);
+        assertSame("Scrolled to last child",
+                mGridView.findViewHolderForAdapterPosition(29).itemView, lastChild);
+    }
+
+    @Test
+    public void testAnimateOutBlockLongScrollTo() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.vertical_linear_with_button_onleft);
+        int[] items = new int[30];
+        for (int i = 0; i < items.length; i++) {
+            items[i] = 300;
+        }
+        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = 1;
+
+        initActivity(intent);
+
+        assertEquals("First view is aligned with padding top", mGridView.getPaddingTop(),
+                mGridView.getChildAt(0).getTop());
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.animateOut();
+            }
+        });
+        // wait until sliding out.
+        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return mGridView.getChildAt(0).getTop() > mGridView.getPaddingTop();
+            }
+        });
+        // smoothScrollToPosition() should not affect slideOut status
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.scrollToPosition(29);
+            }
+        });
+        PollingCheck.waitFor(10000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return mGridView.getScrollState() == RecyclerView.SCROLL_STATE_IDLE;
+            }
+        });
+        assertTrue("First view slided Out", mGridView.getChildAt(0).getTop()
+                >= mGridView.getHeight());
+
+        slideInAndWaitIdle();
+        View lastChild = mGridView.getChildAt(mGridView.getChildCount() - 1);
+        assertSame("Scrolled to last child",
+                mGridView.findViewHolderForAdapterPosition(29).itemView, lastChild);
+    }
+
+    @Test
+    public void testAnimateOutBlockLayout() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.vertical_linear_with_button_onleft);
+        int[] items = new int[100];
+        for (int i = 0; i < items.length; i++) {
+            items[i] = 300;
+        }
+        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = 1;
+
+        initActivity(intent);
+
+        assertEquals("First view is aligned with padding top", mGridView.getPaddingTop(),
+                mGridView.getChildAt(0).getTop());
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.animateOut();
+            }
+        });
+        // wait until sliding out.
+        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return mGridView.getChildAt(0).getTop() > mGridView.getPaddingTop();
+            }
+        });
+        // change adapter should not affect slideOut status
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mActivity.changeItem(0, 200);
+            }
+        });
+        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return mGridView.getScrollState() == RecyclerView.SCROLL_STATE_IDLE;
+            }
+        });
+        assertTrue("First view slided Out", mGridView.getChildAt(0).getTop()
+                >= mGridView.getHeight());
+        assertEquals("onLayout suppressed during slide out", 300,
+                mGridView.getChildAt(0).getHeight());
+
+        slideInAndWaitIdle();
+        assertEquals("First view is aligned with padding top", mGridView.getPaddingTop(),
+                mGridView.getChildAt(0).getTop());
+        // size of item should be updated immediately after slide in animation finishes:
+        PollingCheck.waitFor(1000, new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return 200 == mGridView.getChildAt(0).getHeight();
+            }
+        });
+    }
+
+    @Test
+    public void testAnimateOutBlockFocusChange() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.vertical_linear_with_button_onleft);
+        int[] items = new int[100];
+        for (int i = 0; i < items.length; i++) {
+            items[i] = 300;
+        }
+        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = 1;
+
+        initActivity(intent);
+
+        assertEquals("First view is aligned with padding top", mGridView.getPaddingTop(),
+                mGridView.getChildAt(0).getTop());
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.animateOut();
+                mActivity.findViewById(R.id.button).requestFocus();
+            }
+        });
+        assertTrue(mActivity.findViewById(R.id.button).hasFocus());
+        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return mGridView.getChildAt(0).getTop() > mGridView.getPaddingTop();
+            }
+        });
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.requestFocus();
+            }
+        });
+        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return mGridView.getScrollState() == RecyclerView.SCROLL_STATE_IDLE;
+            }
+        });
+        assertTrue("First view slided Out", mGridView.getChildAt(0).getTop()
+                >= mGridView.getHeight());
+
+        slideInAndWaitIdle();
+        assertEquals("First view is aligned with padding top", mGridView.getPaddingTop(),
+                mGridView.getChildAt(0).getTop());
+    }
+
+    @Test
+    public void testHorizontalAnimateOutBlockScrollTo() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.horizontal_linear);
+        int[] items = new int[100];
+        for (int i = 0; i < items.length; i++) {
+            items[i] = 300;
+        }
+        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        mOrientation = BaseGridView.HORIZONTAL;
+        mNumRows = 1;
+
+        initActivity(intent);
+
+        assertEquals("First view is aligned with padding left", mGridView.getPaddingLeft(),
+                mGridView.getChildAt(0).getLeft());
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.animateOut();
+            }
+        });
+        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return mGridView.getChildAt(0).getLeft() > mGridView.getPaddingLeft();
+            }
+        });
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.scrollToPosition(0);
+            }
+        });
+        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return mGridView.getScrollState() == RecyclerView.SCROLL_STATE_IDLE;
+            }
+        });
+
+        assertTrue("First view is slided out", mGridView.getChildAt(0).getLeft()
+                > mGridView.getWidth());
+
+        slideInAndWaitIdle();
+        assertEquals("First view is aligned with padding left", mGridView.getPaddingLeft(),
+                mGridView.getChildAt(0).getLeft());
+
+    }
+
+    @Test
+    public void testHorizontalAnimateOutRtl() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.horizontal_linear_rtl);
+        int[] items = new int[100];
+        for (int i = 0; i < items.length; i++) {
+            items[i] = 300;
+        }
+        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        mOrientation = BaseGridView.HORIZONTAL;
+        mNumRows = 1;
+
+        initActivity(intent);
+
+        assertEquals("First view is aligned with padding right",
+                mGridView.getWidth() - mGridView.getPaddingRight(),
+                mGridView.getChildAt(0).getRight());
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.animateOut();
+            }
+        });
+        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return mGridView.getChildAt(0).getRight()
+                        < mGridView.getWidth() - mGridView.getPaddingRight();
+            }
+        });
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.smoothScrollToPosition(0);
+            }
+        });
+        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return mGridView.getScrollState() == RecyclerView.SCROLL_STATE_IDLE;
+            }
+        });
+
+        assertTrue("First view is slided out", mGridView.getChildAt(0).getRight() < 0);
+
+        slideInAndWaitIdle();
+        assertEquals("First view is aligned with padding right",
+                mGridView.getWidth() - mGridView.getPaddingRight(),
+                mGridView.getChildAt(0).getRight());
+    }
+
+    @Test
+    public void testSmoothScrollerOutRange() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.vertical_linear_with_button_onleft);
+        intent.putExtra(GridActivity.EXTRA_REQUEST_FOCUS_ONLAYOUT, true);
+        int[] items = new int[30];
+        for (int i = 0; i < items.length; i++) {
+            items[i] = 680;
+        }
+        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = 1;
+
+        initActivity(intent);
+
+        final View button = mActivity.findViewById(R.id.button);
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            public void run() {
+                button.requestFocus();
+            }
+        });
+
+        mGridView.setSelectedPositionSmooth(0);
+        waitForScrollIdle(mVerifyLayout);
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            public void run() {
+                mGridView.setSelectedPositionSmooth(120);
+            }
+        });
+        waitForScrollIdle(mVerifyLayout);
+        assertTrue(button.hasFocus());
+        int key;
+        if (mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL) {
+            key = KeyEvent.KEYCODE_DPAD_LEFT;
+        } else {
+            key = KeyEvent.KEYCODE_DPAD_RIGHT;
+        }
+        sendKey(key);
+        // the GridView should has focus in its children
+        assertTrue(mGridView.hasFocus());
+        assertFalse(mGridView.isFocused());
+        assertEquals(29, mGridView.getSelectedPosition());
+    }
+
+    @Test
+    public void testRemoveLastItemWithStableId() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
+        intent.putExtra(GridActivity.EXTRA_HAS_STABLE_IDS, true);
+        int[] items = new int[1];
+        for (int i = 0; i < items.length; i++) {
+            items[i] = 680;
+        }
+        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = 1;
+
+        initActivity(intent);
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.getItemAnimator().setRemoveDuration(2000);
+                mActivity.removeItems(0, 1, false);
+                mGridView.getAdapter().notifyDataSetChanged();
+            }
+        });
+        Thread.sleep(500);
+        assertEquals(-1, mGridView.getSelectedPosition());
+    }
+
+    @Test
+    public void testUpdateAndSelect1() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
+        intent.putExtra(GridActivity.EXTRA_HAS_STABLE_IDS, false);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 10);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = 1;
+
+        initActivity(intent);
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.getAdapter().notifyDataSetChanged();
+                mGridView.setSelectedPosition(1);
+            }
+        });
+        waitOneUiCycle();
+        assertEquals(1, mGridView.getSelectedPosition());
+    }
+
+    @Test
+    public void testUpdateAndSelect2() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
+        intent.putExtra(GridActivity.EXTRA_HAS_STABLE_IDS, false);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 100);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = 1;
+
+        initActivity(intent);
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.getAdapter().notifyDataSetChanged();
+                mGridView.setSelectedPosition(50);
+            }
+        });
+        waitOneUiCycle();
+        assertEquals(50, mGridView.getSelectedPosition());
+    }
+
+    @Test
+    public void testUpdateAndSelect3() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
+        intent.putExtra(GridActivity.EXTRA_HAS_STABLE_IDS, false);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 10);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = 1;
+
+        initActivity(intent);
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                int[] newItems = new int[100];
+                for (int i = 0; i < newItems.length; i++) {
+                    newItems[i] = mActivity.mItemLengths[0];
+                }
+                mActivity.addItems(0, newItems, false);
+                mGridView.getAdapter().notifyDataSetChanged();
+                mGridView.setSelectedPosition(50);
+            }
+        });
+        waitOneUiCycle();
+        assertEquals(50, mGridView.getSelectedPosition());
+    }
+
+    @Test
+    public void testFocusedPositonAfterRemoved1() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
+        final int[] items = new int[2];
+        for (int i = 0; i < items.length; i++) {
+            items[i] = 300;
+        }
+        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = 1;
+
+        initActivity(intent);
+        setSelectedPosition(1);
+        assertEquals(1, mGridView.getSelectedPosition());
+
+        final int[] newItems = new int[3];
+        for (int i = 0; i < newItems.length; i++) {
+            newItems[i] = 300;
+        }
+        performAndWaitForAnimation(new Runnable() {
+            @Override
+            public void run() {
+                mActivity.removeItems(0, 2, true);
+                mActivity.addItems(0, newItems, true);
+            }
+        });
+        assertEquals(0, mGridView.getSelectedPosition());
+    }
+
+    @Test
+    public void testFocusedPositonAfterRemoved2() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
+        final int[] items = new int[2];
+        for (int i = 0; i < items.length; i++) {
+            items[i] = 300;
+        }
+        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = 1;
+
+        initActivity(intent);
+        setSelectedPosition(1);
+        assertEquals(1, mGridView.getSelectedPosition());
+
+        final int[] newItems = new int[3];
+        for (int i = 0; i < newItems.length; i++) {
+            newItems[i] = 300;
+        }
+        performAndWaitForAnimation(new Runnable() {
+            @Override
+            public void run() {
+                mActivity.removeItems(1, 1, true);
+                mActivity.addItems(1, newItems, true);
+            }
+        });
+        assertEquals(1, mGridView.getSelectedPosition());
+    }
+
+    static void assertNoCollectionItemInfo(AccessibilityNodeInfoCompat info) {
+        AccessibilityNodeInfoCompat.CollectionItemInfoCompat nodeInfoCompat =
+                info.getCollectionItemInfo();
+        if (nodeInfoCompat == null) {
+            return;
+        }
+        assertTrue(nodeInfoCompat.getRowIndex() < 0);
+        assertTrue(nodeInfoCompat.getColumnIndex() < 0);
+    }
+
+    /**
+     * This test would need talkback on.
+     */
+    @Test
+    public void testAccessibilityOfItemsBeingPushedOut() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_grid);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 100);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        mOrientation = BaseGridView.HORIZONTAL;
+        mNumRows = 3;
+
+        initActivity(intent);
+
+        final int lastPos = mGridView.getChildAdapterPosition(
+                mGridView.getChildAt(mGridView.getChildCount() - 1));
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.getLayoutManager().setItemPrefetchEnabled(false);
+            }
+        });
+        final int numItemsToPushOut = mNumRows;
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                // set longer enough so that accessibility service will initialize node
+                // within setImportantForAccessibility().
+                mGridView.getItemAnimator().setRemoveDuration(2000);
+                mGridView.getItemAnimator().setAddDuration(2000);
+                final int[] newItems = new int[numItemsToPushOut];
+                final int newItemValue = mActivity.mItemLengths[0];
+                for (int i = 0; i < newItems.length; i++) {
+                    newItems[i] = newItemValue;
+                }
+                mActivity.addItems(lastPos - numItemsToPushOut + 1, newItems);
+            }
+        });
+        waitForItemAnimation();
+    }
+
+    /**
+     * This test simulates talkback by calling setImportanceForAccessibility at end of animation
+     */
+    @Test
+    public void simulatesAccessibilityOfItemsBeingPushedOut() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_grid);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 100);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        mOrientation = BaseGridView.HORIZONTAL;
+        mNumRows = 3;
+
+        initActivity(intent);
+
+        final HashSet<View> moveAnimationViews = new HashSet();
+        mActivity.mImportantForAccessibilityListener =
+                new GridActivity.ImportantForAccessibilityListener() {
+            RecyclerView.LayoutManager mLM = mGridView.getLayoutManager();
+            @Override
+            public void onImportantForAccessibilityChanged(View view, int newValue) {
+                // simulates talkack, having setImportantForAccessibility to call
+                // onInitializeAccessibilityNodeInfoForItem() for the DISAPPEARING items.
+                if (moveAnimationViews.contains(view)) {
+                    AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain();
+                    mLM.onInitializeAccessibilityNodeInfoForItem(
+                            null, null, view, info);
+                }
+            }
+        };
+        final int lastPos = mGridView.getChildAdapterPosition(
+                mGridView.getChildAt(mGridView.getChildCount() - 1));
+        final int numItemsToPushOut = mNumRows;
+        for (int i = 0; i < numItemsToPushOut; i++) {
+            moveAnimationViews.add(
+                    mGridView.getChildAt(mGridView.getChildCount() - 1 - i));
+        }
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.setItemAnimator(new DefaultItemAnimator() {
+                    @Override
+                    public void onMoveFinished(RecyclerView.ViewHolder item) {
+                        moveAnimationViews.remove(item.itemView);
+                    }
+                });
+                mGridView.getLayoutManager().setItemPrefetchEnabled(false);
+            }
+        });
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                final int[] newItems = new int[numItemsToPushOut];
+                final int newItemValue = mActivity.mItemLengths[0] + 1;
+                for (int i = 0; i < newItems.length; i++) {
+                    newItems[i] = newItemValue;
+                }
+                mActivity.addItems(lastPos - numItemsToPushOut + 1, newItems);
+            }
+        });
+        while (moveAnimationViews.size() != 0) {
+            Thread.sleep(100);
+        }
+    }
+
+    @Test
+    public void testAccessibilityNodeInfoOnRemovedFirstItem() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_grid);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 6);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        mOrientation = BaseGridView.HORIZONTAL;
+        mNumRows = 3;
+
+        initActivity(intent);
+
+        final View lastView = mGridView.findViewHolderForAdapterPosition(0).itemView;
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.getItemAnimator().setRemoveDuration(20000);
+                mActivity.removeItems(0, 1);
+            }
+        });
+        waitForItemAnimationStart();
+        AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain(lastView);
+        mGridView.getLayoutManager().onInitializeAccessibilityNodeInfoForItem(null, null,
+                lastView, info);
+        assertNoCollectionItemInfo(info);
+    }
+
+    @Test
+    public void testAccessibilityNodeInfoOnRemovedLastItem() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_grid);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 6);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        mOrientation = BaseGridView.HORIZONTAL;
+        mNumRows = 3;
+
+        initActivity(intent);
+
+        final View lastView = mGridView.findViewHolderForAdapterPosition(5).itemView;
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.getItemAnimator().setRemoveDuration(20000);
+                mActivity.removeItems(5, 1);
+            }
+        });
+        waitForItemAnimationStart();
+        AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain(lastView);
+        mGridView.getLayoutManager().onInitializeAccessibilityNodeInfoForItem(null, null,
+                lastView, info);
+        assertNoCollectionItemInfo(info);
+    }
+
+    static class FiveViewTypesProvider implements ViewTypeProvider {
+
+        @Override
+        public int getViewType(int position) {
+            switch (position) {
+                case 0:
+                    return 0;
+                case 1:
+                    return 1;
+                case 2:
+                    return 2;
+                case 3:
+                    return 3;
+                case 4:
+                    return 4;
+            }
+            return 199;
+        }
+    }
+
+    // Used by testItemAlignmentVertical() testItemAlignmentHorizontal()
+    static class ItemAlignmentWithPaddingFacetProvider implements
+            ItemAlignmentFacetProvider {
+        final ItemAlignmentFacet mFacet0;
+        final ItemAlignmentFacet mFacet1;
+        final ItemAlignmentFacet mFacet2;
+        final ItemAlignmentFacet mFacet3;
+        final ItemAlignmentFacet mFacet4;
+
+        ItemAlignmentWithPaddingFacetProvider() {
+            ItemAlignmentFacet.ItemAlignmentDef[] defs;
+            mFacet0 = new ItemAlignmentFacet();
+            defs = new ItemAlignmentFacet.ItemAlignmentDef[1];
+            defs[0] = new ItemAlignmentFacet.ItemAlignmentDef();
+            defs[0].setItemAlignmentViewId(R.id.t1);
+            defs[0].setItemAlignmentOffsetPercent(0);
+            defs[0].setItemAlignmentOffsetWithPadding(false);
+            mFacet0.setAlignmentDefs(defs);
+            mFacet1 = new ItemAlignmentFacet();
+            defs = new ItemAlignmentFacet.ItemAlignmentDef[1];
+            defs[0] = new ItemAlignmentFacet.ItemAlignmentDef();
+            defs[0].setItemAlignmentViewId(R.id.t1);
+            defs[0].setItemAlignmentOffsetPercent(0);
+            defs[0].setItemAlignmentOffsetWithPadding(true);
+            mFacet1.setAlignmentDefs(defs);
+            mFacet2 = new ItemAlignmentFacet();
+            defs = new ItemAlignmentFacet.ItemAlignmentDef[1];
+            defs[0] = new ItemAlignmentFacet.ItemAlignmentDef();
+            defs[0].setItemAlignmentViewId(R.id.t2);
+            defs[0].setItemAlignmentOffsetPercent(100);
+            defs[0].setItemAlignmentOffsetWithPadding(true);
+            mFacet2.setAlignmentDefs(defs);
+            mFacet3 = new ItemAlignmentFacet();
+            defs = new ItemAlignmentFacet.ItemAlignmentDef[1];
+            defs[0] = new ItemAlignmentFacet.ItemAlignmentDef();
+            defs[0].setItemAlignmentViewId(R.id.t2);
+            defs[0].setItemAlignmentOffsetPercent(50);
+            defs[0].setItemAlignmentOffsetWithPadding(true);
+            mFacet3.setAlignmentDefs(defs);
+            mFacet4 = new ItemAlignmentFacet();
+            defs = new ItemAlignmentFacet.ItemAlignmentDef[1];
+            defs[0] = new ItemAlignmentFacet.ItemAlignmentDef();
+            defs[0].setItemAlignmentViewId(R.id.t2);
+            defs[0].setItemAlignmentOffsetPercent(50);
+            defs[0].setItemAlignmentOffsetWithPadding(false);
+            mFacet4.setAlignmentDefs(defs);
+        }
+
+        @Override
+        public ItemAlignmentFacet getItemAlignmentFacet(int viewType) {
+            switch (viewType) {
+                case 0:
+                    return mFacet0;
+                case 1:
+                    return mFacet1;
+                case 2:
+                    return mFacet2;
+                case 3:
+                    return mFacet3;
+                case 4:
+                    return mFacet4;
+            }
+            return null;
+        }
+    }
+
+    @Test
+    public void testItemAlignmentVertical() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
+        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.relative_layout2);
+        int[] items = new int[5];
+        for (int i = 0; i < items.length; i++) {
+            items[i] = 300;
+        }
+        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        intent.putExtra(GridActivity.EXTRA_VIEWTYPEPROVIDER_CLASS,
+                FiveViewTypesProvider.class.getName());
+        intent.putExtra(GridActivity.EXTRA_ITEMALIGNMENTPROVIDER_CLASS,
+                ItemAlignmentWithPaddingFacetProvider.class.getName());
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = 1;
+
+        initActivity(intent);
+        startWaitLayout();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.setWindowAlignment(BaseGridView.WINDOW_ALIGN_NO_EDGE);
+                mGridView.setWindowAlignmentOffsetPercent(50);
+                mGridView.setWindowAlignmentOffset(0);
+            }
+        });
+        waitForLayout();
+
+        final float windowAlignCenter = mGridView.getHeight() / 2f;
+        Rect rect = new Rect();
+        View textView;
+
+        // test 1: does not include padding
+        textView = mGridView.findViewHolderForAdapterPosition(0).itemView.findViewById(R.id.t1);
+        rect.set(0, 0, textView.getWidth(), textView.getHeight());
+        mGridView.offsetDescendantRectToMyCoords(textView, rect);
+        assertEquals(windowAlignCenter, rect.top, DELTA);
+
+        // test 2: including low padding
+        setSelectedPosition(1);
+        textView = mGridView.findViewHolderForAdapterPosition(1).itemView.findViewById(R.id.t1);
+        assertTrue(textView.getPaddingTop() > 0);
+        rect.set(0, textView.getPaddingTop(), textView.getWidth(), textView.getHeight());
+        mGridView.offsetDescendantRectToMyCoords(textView, rect);
+        assertEquals(windowAlignCenter, rect.top, DELTA);
+
+        // test 3: including high padding
+        setSelectedPosition(2);
+        textView = mGridView.findViewHolderForAdapterPosition(2).itemView.findViewById(R.id.t2);
+        assertTrue(textView.getPaddingBottom() > 0);
+        rect.set(0, 0, textView.getWidth(),
+                textView.getHeight() - textView.getPaddingBottom());
+        mGridView.offsetDescendantRectToMyCoords(textView, rect);
+        assertEquals(windowAlignCenter, rect.bottom, DELTA);
+
+        // test 4: including padding will be ignored if offsetPercent is not 0 or 100
+        setSelectedPosition(3);
+        textView = mGridView.findViewHolderForAdapterPosition(3).itemView.findViewById(R.id.t2);
+        assertTrue(textView.getPaddingTop() != textView.getPaddingBottom());
+        rect.set(0, 0, textView.getWidth(), textView.getHeight() / 2);
+        mGridView.offsetDescendantRectToMyCoords(textView, rect);
+        assertEquals(windowAlignCenter, rect.bottom, DELTA);
+
+        // test 5: does not include padding
+        setSelectedPosition(4);
+        textView = mGridView.findViewHolderForAdapterPosition(4).itemView.findViewById(R.id.t2);
+        assertTrue(textView.getPaddingTop() != textView.getPaddingBottom());
+        rect.set(0, 0, textView.getWidth(), textView.getHeight() / 2);
+        mGridView.offsetDescendantRectToMyCoords(textView, rect);
+        assertEquals(windowAlignCenter, rect.bottom, DELTA);
+    }
+
+    @Test
+    public void testItemAlignmentHorizontal() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_linear);
+        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.relative_layout3);
+        int[] items = new int[5];
+        for (int i = 0; i < items.length; i++) {
+            items[i] = 300;
+        }
+        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        intent.putExtra(GridActivity.EXTRA_VIEWTYPEPROVIDER_CLASS,
+                FiveViewTypesProvider.class.getName());
+        intent.putExtra(GridActivity.EXTRA_ITEMALIGNMENTPROVIDER_CLASS,
+                ItemAlignmentWithPaddingFacetProvider.class.getName());
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = 1;
+
+        initActivity(intent);
+        startWaitLayout();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.setWindowAlignment(BaseGridView.WINDOW_ALIGN_NO_EDGE);
+                mGridView.setWindowAlignmentOffsetPercent(50);
+                mGridView.setWindowAlignmentOffset(0);
+            }
+        });
+        waitForLayout();
+
+        final float windowAlignCenter = mGridView.getWidth() / 2f;
+        Rect rect = new Rect();
+        View textView;
+
+        // test 1: does not include padding
+        textView = mGridView.findViewHolderForAdapterPosition(0).itemView.findViewById(R.id.t1);
+        rect.set(0, 0, textView.getWidth(), textView.getHeight());
+        mGridView.offsetDescendantRectToMyCoords(textView, rect);
+        assertEquals(windowAlignCenter, rect.left, DELTA);
+
+        // test 2: including low padding
+        setSelectedPosition(1);
+        textView = mGridView.findViewHolderForAdapterPosition(1).itemView.findViewById(R.id.t1);
+        assertTrue(textView.getPaddingLeft() > 0);
+        rect.set(textView.getPaddingLeft(), 0, textView.getWidth(), textView.getHeight());
+        mGridView.offsetDescendantRectToMyCoords(textView, rect);
+        assertEquals(windowAlignCenter, rect.left, DELTA);
+
+        // test 3: including high padding
+        setSelectedPosition(2);
+        textView = mGridView.findViewHolderForAdapterPosition(2).itemView.findViewById(R.id.t2);
+        assertTrue(textView.getPaddingRight() > 0);
+        rect.set(0, 0, textView.getWidth() - textView.getPaddingRight(),
+                textView.getHeight());
+        mGridView.offsetDescendantRectToMyCoords(textView, rect);
+        assertEquals(windowAlignCenter, rect.right, DELTA);
+
+        // test 4: including padding will be ignored if offsetPercent is not 0 or 100
+        setSelectedPosition(3);
+        textView = mGridView.findViewHolderForAdapterPosition(3).itemView.findViewById(R.id.t2);
+        assertTrue(textView.getPaddingLeft() != textView.getPaddingRight());
+        rect.set(0, 0, textView.getWidth() / 2, textView.getHeight());
+        mGridView.offsetDescendantRectToMyCoords(textView, rect);
+        assertEquals(windowAlignCenter, rect.right, DELTA);
+
+        // test 5: does not include padding
+        setSelectedPosition(4);
+        textView = mGridView.findViewHolderForAdapterPosition(4).itemView.findViewById(R.id.t2);
+        assertTrue(textView.getPaddingLeft() != textView.getPaddingRight());
+        rect.set(0, 0, textView.getWidth() / 2, textView.getHeight());
+        mGridView.offsetDescendantRectToMyCoords(textView, rect);
+        assertEquals(windowAlignCenter, rect.right, DELTA);
+    }
+
+    @Test
+    public void testItemAlignmentHorizontalRtl() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_linear);
+        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.relative_layout3);
+        int[] items = new int[5];
+        for (int i = 0; i < items.length; i++) {
+            items[i] = 300;
+        }
+        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        intent.putExtra(GridActivity.EXTRA_VIEWTYPEPROVIDER_CLASS,
+                FiveViewTypesProvider.class.getName());
+        intent.putExtra(GridActivity.EXTRA_ITEMALIGNMENTPROVIDER_CLASS,
+                ItemAlignmentWithPaddingFacetProvider.class.getName());
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = 1;
+
+        initActivity(intent);
+        startWaitLayout();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.setLayoutDirection(View.LAYOUT_DIRECTION_RTL);
+                mGridView.setWindowAlignment(BaseGridView.WINDOW_ALIGN_NO_EDGE);
+                mGridView.setWindowAlignmentOffsetPercent(50);
+                mGridView.setWindowAlignmentOffset(0);
+            }
+        });
+        waitForLayout();
+
+        final float windowAlignCenter = mGridView.getWidth() / 2f;
+        Rect rect = new Rect();
+        View textView;
+
+        // test 1: does not include padding
+        textView = mGridView.findViewHolderForAdapterPosition(0).itemView.findViewById(R.id.t1);
+        rect.set(0, 0, textView.getWidth(), textView.getHeight());
+        mGridView.offsetDescendantRectToMyCoords(textView, rect);
+        assertEquals(windowAlignCenter, rect.right, DELTA);
+
+        // test 2: including low padding
+        setSelectedPosition(1);
+        textView = mGridView.findViewHolderForAdapterPosition(1).itemView.findViewById(R.id.t1);
+        assertTrue(textView.getPaddingRight() > 0);
+        rect.set(0, 0, textView.getWidth() - textView.getPaddingRight(),
+                textView.getHeight());
+        mGridView.offsetDescendantRectToMyCoords(textView, rect);
+        assertEquals(windowAlignCenter, rect.right, DELTA);
+
+        // test 3: including high padding
+        setSelectedPosition(2);
+        textView = mGridView.findViewHolderForAdapterPosition(2).itemView.findViewById(R.id.t2);
+        assertTrue(textView.getPaddingLeft() > 0);
+        rect.set(textView.getPaddingLeft(), 0, textView.getWidth(),
+                textView.getHeight());
+        mGridView.offsetDescendantRectToMyCoords(textView, rect);
+        assertEquals(windowAlignCenter, rect.left, DELTA);
+
+        // test 4: including padding will be ignored if offsetPercent is not 0 or 100
+        setSelectedPosition(3);
+        textView = mGridView.findViewHolderForAdapterPosition(3).itemView.findViewById(R.id.t2);
+        assertTrue(textView.getPaddingLeft() != textView.getPaddingRight());
+        rect.set(0, 0, textView.getWidth() / 2, textView.getHeight());
+        mGridView.offsetDescendantRectToMyCoords(textView, rect);
+        assertEquals(windowAlignCenter, rect.right, DELTA);
+
+        // test 5: does not include padding
+        setSelectedPosition(4);
+        textView = mGridView.findViewHolderForAdapterPosition(4).itemView.findViewById(R.id.t2);
+        assertTrue(textView.getPaddingLeft() != textView.getPaddingRight());
+        rect.set(0, 0, textView.getWidth() / 2, textView.getHeight());
+        mGridView.offsetDescendantRectToMyCoords(textView, rect);
+        assertEquals(windowAlignCenter, rect.right, DELTA);
+    }
+
+    enum ItemLocation {
+        ITEM_AT_LOW,
+        ITEM_AT_KEY_LINE,
+        ITEM_AT_HIGH
+    };
+
+    static class ItemAt {
+        final int mScrollPosition;
+        final int mPosition;
+        final ItemLocation mLocation;
+
+        ItemAt(int scrollPosition, int position, ItemLocation loc) {
+            mScrollPosition = scrollPosition;
+            mPosition = position;
+            mLocation = loc;
+        }
+
+        ItemAt(int position, ItemLocation loc) {
+            mScrollPosition = position;
+            mPosition = position;
+            mLocation = loc;
+        }
+    }
+
+    /**
+     * When scroll to position, item at position is expected at given location.
+     */
+    static ItemAt itemAt(int position, ItemLocation location) {
+        return new ItemAt(position, location);
+    }
+
+    /**
+     * When scroll to scrollPosition, item at position is expected at given location.
+     */
+    static ItemAt itemAt(int scrollPosition, int position, ItemLocation location) {
+        return new ItemAt(scrollPosition, position, location);
+    }
+
+    void prepareKeyLineTest(int numItems) throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_linear);
+        int[] items = new int[numItems];
+        for (int i = 0; i < items.length; i++) {
+            items[i] = 32;
+        }
+        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        mOrientation = BaseGridView.HORIZONTAL;
+        mNumRows = 1;
+
+        initActivity(intent);
+    }
+
+    public void testPreferKeyLine(final int windowAlignment,
+            final boolean preferKeyLineOverLow,
+            final boolean preferKeyLineOverHigh,
+            ItemLocation assertFirstItemLocation,
+            ItemLocation assertLastItemLocation) throws Throwable {
+        testPreferKeyLine(windowAlignment, preferKeyLineOverLow, preferKeyLineOverHigh,
+                itemAt(0, assertFirstItemLocation),
+                itemAt(mActivity.mNumItems - 1, assertLastItemLocation));
+    }
+
+    public void testPreferKeyLine(final int windowAlignment,
+            final boolean preferKeyLineOverLow,
+            final boolean preferKeyLineOverHigh,
+            ItemLocation assertFirstItemLocation,
+            ItemAt assertLastItemLocation) throws Throwable {
+        testPreferKeyLine(windowAlignment, preferKeyLineOverLow, preferKeyLineOverHigh,
+                itemAt(0, assertFirstItemLocation),
+                assertLastItemLocation);
+    }
+
+    public void testPreferKeyLine(final int windowAlignment,
+            final boolean preferKeyLineOverLow,
+            final boolean preferKeyLineOverHigh,
+            ItemAt assertFirstItemLocation,
+            ItemLocation assertLastItemLocation) throws Throwable {
+        testPreferKeyLine(windowAlignment, preferKeyLineOverLow, preferKeyLineOverHigh,
+                assertFirstItemLocation,
+                itemAt(mActivity.mNumItems - 1, assertLastItemLocation));
+    }
+
+    public void testPreferKeyLine(final int windowAlignment,
+            final boolean preferKeyLineOverLow,
+            final boolean preferKeyLineOverHigh,
+            ItemAt assertFirstItemLocation,
+            ItemAt assertLastItemLocation) throws Throwable {
+        TestPreferKeyLineOptions options = new TestPreferKeyLineOptions();
+        options.mAssertItemLocations = new ItemAt[] {assertFirstItemLocation,
+                assertLastItemLocation};
+        options.mPreferKeyLineOverLow = preferKeyLineOverLow;
+        options.mPreferKeyLineOverHigh = preferKeyLineOverHigh;
+        options.mWindowAlignment = windowAlignment;
+
+        options.mRtl = false;
+        testPreferKeyLine(options);
+
+        options.mRtl = true;
+        testPreferKeyLine(options);
+    }
+
+    static class TestPreferKeyLineOptions {
+        int mWindowAlignment;
+        boolean mPreferKeyLineOverLow;
+        boolean mPreferKeyLineOverHigh;
+        ItemAt[] mAssertItemLocations;
+        boolean mRtl;
+    }
+
+    public void testPreferKeyLine(final TestPreferKeyLineOptions options) throws Throwable {
+        startWaitLayout();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                if (options.mRtl) {
+                    mGridView.setLayoutDirection(View.LAYOUT_DIRECTION_RTL);
+                } else {
+                    mGridView.setLayoutDirection(View.LAYOUT_DIRECTION_LTR);
+                }
+                mGridView.setWindowAlignment(options.mWindowAlignment);
+                mGridView.setWindowAlignmentOffsetPercent(50);
+                mGridView.setWindowAlignmentOffset(0);
+                mGridView.setWindowAlignmentPreferKeyLineOverLowEdge(options.mPreferKeyLineOverLow);
+                mGridView.setWindowAlignmentPreferKeyLineOverHighEdge(
+                        options.mPreferKeyLineOverHigh);
+            }
+        });
+        waitForLayout();
+
+        final int paddingStart = mGridView.getPaddingStart();
+        final int paddingEnd = mGridView.getPaddingEnd();
+        final int windowAlignCenter = mGridView.getWidth() / 2;
+
+        for (int i = 0; i < options.mAssertItemLocations.length; i++) {
+            ItemAt assertItemLocation = options.mAssertItemLocations[i];
+            setSelectedPosition(assertItemLocation.mScrollPosition);
+            View view = mGridView.findViewHolderForAdapterPosition(assertItemLocation.mPosition)
+                    .itemView;
+            switch (assertItemLocation.mLocation) {
+                case ITEM_AT_LOW:
+                    if (options.mRtl) {
+                        assertEquals(mGridView.getWidth() - paddingStart, view.getRight());
+                    } else {
+                        assertEquals(paddingStart, view.getLeft());
+                    }
+                    break;
+                case ITEM_AT_HIGH:
+                    if (options.mRtl) {
+                        assertEquals(paddingEnd, view.getLeft());
+                    } else {
+                        assertEquals(mGridView.getWidth() - paddingEnd, view.getRight());
+                    }
+                    break;
+                case ITEM_AT_KEY_LINE:
+                    assertEquals(windowAlignCenter, (view.getLeft() + view.getRight()) / 2, DELTA);
+                    break;
+            }
+        }
+    }
+
+    @Test
+    public void testPreferKeyLine1() throws Throwable {
+        prepareKeyLineTest(1);
+        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_NO_EDGE, false, false,
+                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE);
+        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_NO_EDGE, false, true,
+                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE);
+        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_NO_EDGE, true, false,
+                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE);
+        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_NO_EDGE, true, true,
+                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE);
+
+        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_LOW_EDGE, false, false,
+                ItemLocation.ITEM_AT_LOW, ItemLocation.ITEM_AT_LOW);
+        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_LOW_EDGE, false, true,
+                ItemLocation.ITEM_AT_LOW, ItemLocation.ITEM_AT_LOW);
+        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_LOW_EDGE, true, false,
+                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE);
+        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_LOW_EDGE, true, true,
+                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE);
+
+        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE, false, false,
+                ItemLocation.ITEM_AT_HIGH, ItemLocation.ITEM_AT_HIGH);
+        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE, false, true,
+                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE);
+        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE, true, false,
+                ItemLocation.ITEM_AT_HIGH, ItemLocation.ITEM_AT_HIGH);
+        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE, true, true,
+                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE);
+
+        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_BOTH_EDGE, false, false,
+                ItemLocation.ITEM_AT_LOW, ItemLocation.ITEM_AT_LOW);
+        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_BOTH_EDGE, false, true,
+                ItemLocation.ITEM_AT_LOW, ItemLocation.ITEM_AT_LOW);
+        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_BOTH_EDGE, true, false,
+                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE);
+        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_BOTH_EDGE, true, true,
+                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE);
+    }
+
+    @Test
+    public void testPreferKeyLine2() throws Throwable {
+        prepareKeyLineTest(2);
+        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_NO_EDGE, false, false,
+                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE);
+        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_NO_EDGE, false, true,
+                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE);
+        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_NO_EDGE, true, false,
+                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE);
+        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_NO_EDGE, true, true,
+                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE);
+
+        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_LOW_EDGE, false, false,
+                ItemLocation.ITEM_AT_LOW, itemAt(1, 0, ItemLocation.ITEM_AT_LOW));
+        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_LOW_EDGE, false, true,
+                ItemLocation.ITEM_AT_LOW, itemAt(1, 0, ItemLocation.ITEM_AT_LOW));
+        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_LOW_EDGE, true, false,
+                itemAt(0, 1, ItemLocation.ITEM_AT_KEY_LINE),
+                itemAt(1, 1, ItemLocation.ITEM_AT_KEY_LINE));
+        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_LOW_EDGE, true, true,
+                itemAt(0, 1, ItemLocation.ITEM_AT_KEY_LINE),
+                itemAt(1, 1, ItemLocation.ITEM_AT_KEY_LINE));
+
+        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE, false, false,
+                itemAt(0, 1, ItemLocation.ITEM_AT_HIGH),
+                itemAt(1, 1, ItemLocation.ITEM_AT_HIGH));
+        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE, false, true,
+                itemAt(0, 0, ItemLocation.ITEM_AT_KEY_LINE),
+                itemAt(1, 0, ItemLocation.ITEM_AT_KEY_LINE));
+        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE, true, false,
+                itemAt(0, 1, ItemLocation.ITEM_AT_HIGH),
+                itemAt(1, 1, ItemLocation.ITEM_AT_HIGH));
+        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE, true, true,
+                itemAt(0, 0, ItemLocation.ITEM_AT_KEY_LINE),
+                itemAt(1, 0, ItemLocation.ITEM_AT_KEY_LINE));
+
+        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_BOTH_EDGE, false, false,
+                ItemLocation.ITEM_AT_LOW, itemAt(1, 0, ItemLocation.ITEM_AT_LOW));
+        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_BOTH_EDGE, false, true,
+                ItemLocation.ITEM_AT_LOW, itemAt(1, 0, ItemLocation.ITEM_AT_LOW));
+        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_BOTH_EDGE, true, false,
+                itemAt(0, 1, ItemLocation.ITEM_AT_KEY_LINE),
+                itemAt(1, 1, ItemLocation.ITEM_AT_KEY_LINE));
+        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_BOTH_EDGE, true, true,
+                itemAt(0, 1, ItemLocation.ITEM_AT_KEY_LINE),
+                itemAt(1, 1, ItemLocation.ITEM_AT_KEY_LINE));
+    }
+
+    @Test
+    public void testPreferKeyLine10000() throws Throwable {
+        prepareKeyLineTest(10000);
+        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_NO_EDGE, false, false,
+                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE);
+        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_NO_EDGE, false, true,
+                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE);
+        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_NO_EDGE, true, false,
+                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE);
+        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_NO_EDGE, true, true,
+                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE);
+
+        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_LOW_EDGE, false, false,
+                ItemLocation.ITEM_AT_LOW, ItemLocation.ITEM_AT_KEY_LINE);
+        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_LOW_EDGE, false, true,
+                ItemLocation.ITEM_AT_LOW, ItemLocation.ITEM_AT_KEY_LINE);
+        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_LOW_EDGE, true, false,
+                ItemLocation.ITEM_AT_LOW, ItemLocation.ITEM_AT_KEY_LINE);
+        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_LOW_EDGE, true, true,
+                ItemLocation.ITEM_AT_LOW, ItemLocation.ITEM_AT_KEY_LINE);
+
+        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE, false, false,
+                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_HIGH);
+        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE, false, true,
+                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_HIGH);
+        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE, true, false,
+                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_HIGH);
+        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE, true, true,
+                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_HIGH);
+
+        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_BOTH_EDGE, false, false,
+                ItemLocation.ITEM_AT_LOW, ItemLocation.ITEM_AT_HIGH);
+        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_BOTH_EDGE, false, true,
+                ItemLocation.ITEM_AT_LOW, ItemLocation.ITEM_AT_HIGH);
+        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_BOTH_EDGE, true, false,
+                ItemLocation.ITEM_AT_LOW, ItemLocation.ITEM_AT_HIGH);
+        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_BOTH_EDGE, true, true,
+                ItemLocation.ITEM_AT_LOW, ItemLocation.ITEM_AT_HIGH);
+    }
+}
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/widget/GuidedActionStylistTest.java b/leanback/tests/java/android/support/v17/leanback/widget/GuidedActionStylistTest.java
similarity index 100%
rename from v17/leanback/tests/java/android/support/v17/leanback/widget/GuidedActionStylistTest.java
rename to leanback/tests/java/android/support/v17/leanback/widget/GuidedActionStylistTest.java
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/widget/HorizontalGridViewEx.java b/leanback/tests/java/android/support/v17/leanback/widget/HorizontalGridViewEx.java
similarity index 100%
rename from v17/leanback/tests/java/android/support/v17/leanback/widget/HorizontalGridViewEx.java
rename to leanback/tests/java/android/support/v17/leanback/widget/HorizontalGridViewEx.java
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/widget/ImageCardViewTest.java b/leanback/tests/java/android/support/v17/leanback/widget/ImageCardViewTest.java
similarity index 100%
rename from v17/leanback/tests/java/android/support/v17/leanback/widget/ImageCardViewTest.java
rename to leanback/tests/java/android/support/v17/leanback/widget/ImageCardViewTest.java
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/widget/ListRowPresenterTest.java b/leanback/tests/java/android/support/v17/leanback/widget/ListRowPresenterTest.java
similarity index 100%
rename from v17/leanback/tests/java/android/support/v17/leanback/widget/ListRowPresenterTest.java
rename to leanback/tests/java/android/support/v17/leanback/widget/ListRowPresenterTest.java
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/widget/MediaNowPlayingViewTest.java b/leanback/tests/java/android/support/v17/leanback/widget/MediaNowPlayingViewTest.java
similarity index 100%
rename from v17/leanback/tests/java/android/support/v17/leanback/widget/MediaNowPlayingViewTest.java
rename to leanback/tests/java/android/support/v17/leanback/widget/MediaNowPlayingViewTest.java
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/widget/ObjectAdapterTest.java b/leanback/tests/java/android/support/v17/leanback/widget/ObjectAdapterTest.java
similarity index 100%
rename from v17/leanback/tests/java/android/support/v17/leanback/widget/ObjectAdapterTest.java
rename to leanback/tests/java/android/support/v17/leanback/widget/ObjectAdapterTest.java
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/widget/PagingIndicatorTest.java b/leanback/tests/java/android/support/v17/leanback/widget/PagingIndicatorTest.java
similarity index 100%
rename from v17/leanback/tests/java/android/support/v17/leanback/widget/PagingIndicatorTest.java
rename to leanback/tests/java/android/support/v17/leanback/widget/PagingIndicatorTest.java
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/widget/ParallaxFloatEffectTest.java b/leanback/tests/java/android/support/v17/leanback/widget/ParallaxFloatEffectTest.java
similarity index 100%
rename from v17/leanback/tests/java/android/support/v17/leanback/widget/ParallaxFloatEffectTest.java
rename to leanback/tests/java/android/support/v17/leanback/widget/ParallaxFloatEffectTest.java
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/widget/ParallaxFloatTest.java b/leanback/tests/java/android/support/v17/leanback/widget/ParallaxFloatTest.java
similarity index 100%
rename from v17/leanback/tests/java/android/support/v17/leanback/widget/ParallaxFloatTest.java
rename to leanback/tests/java/android/support/v17/leanback/widget/ParallaxFloatTest.java
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/widget/ParallaxIntEffectTest.java b/leanback/tests/java/android/support/v17/leanback/widget/ParallaxIntEffectTest.java
similarity index 100%
rename from v17/leanback/tests/java/android/support/v17/leanback/widget/ParallaxIntEffectTest.java
rename to leanback/tests/java/android/support/v17/leanback/widget/ParallaxIntEffectTest.java
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/widget/ParallaxIntTest.java b/leanback/tests/java/android/support/v17/leanback/widget/ParallaxIntTest.java
similarity index 100%
rename from v17/leanback/tests/java/android/support/v17/leanback/widget/ParallaxIntTest.java
rename to leanback/tests/java/android/support/v17/leanback/widget/ParallaxIntTest.java
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/widget/PlaybackGlueHostImplWithViewHolder.java b/leanback/tests/java/android/support/v17/leanback/widget/PlaybackGlueHostImplWithViewHolder.java
similarity index 100%
rename from v17/leanback/tests/java/android/support/v17/leanback/widget/PlaybackGlueHostImplWithViewHolder.java
rename to leanback/tests/java/android/support/v17/leanback/widget/PlaybackGlueHostImplWithViewHolder.java
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/widget/PlaybackSeekProviderSample.java b/leanback/tests/java/android/support/v17/leanback/widget/PlaybackSeekProviderSample.java
similarity index 100%
rename from v17/leanback/tests/java/android/support/v17/leanback/widget/PlaybackSeekProviderSample.java
rename to leanback/tests/java/android/support/v17/leanback/widget/PlaybackSeekProviderSample.java
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/widget/PlaybackTransportRowPresenterTest.java b/leanback/tests/java/android/support/v17/leanback/widget/PlaybackTransportRowPresenterTest.java
similarity index 100%
rename from v17/leanback/tests/java/android/support/v17/leanback/widget/PlaybackTransportRowPresenterTest.java
rename to leanback/tests/java/android/support/v17/leanback/widget/PlaybackTransportRowPresenterTest.java
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/widget/PresenterTest.java b/leanback/tests/java/android/support/v17/leanback/widget/PresenterTest.java
similarity index 100%
rename from v17/leanback/tests/java/android/support/v17/leanback/widget/PresenterTest.java
rename to leanback/tests/java/android/support/v17/leanback/widget/PresenterTest.java
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/widget/ShadowOverlayContainerTest.java b/leanback/tests/java/android/support/v17/leanback/widget/ShadowOverlayContainerTest.java
similarity index 100%
rename from v17/leanback/tests/java/android/support/v17/leanback/widget/ShadowOverlayContainerTest.java
rename to leanback/tests/java/android/support/v17/leanback/widget/ShadowOverlayContainerTest.java
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/widget/SingleRowTest.java b/leanback/tests/java/android/support/v17/leanback/widget/SingleRowTest.java
similarity index 100%
rename from v17/leanback/tests/java/android/support/v17/leanback/widget/SingleRowTest.java
rename to leanback/tests/java/android/support/v17/leanback/widget/SingleRowTest.java
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/widget/StaggeredGridDefaultTest.java b/leanback/tests/java/android/support/v17/leanback/widget/StaggeredGridDefaultTest.java
similarity index 100%
rename from v17/leanback/tests/java/android/support/v17/leanback/widget/StaggeredGridDefaultTest.java
rename to leanback/tests/java/android/support/v17/leanback/widget/StaggeredGridDefaultTest.java
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/widget/ThumbsBarTest.java b/leanback/tests/java/android/support/v17/leanback/widget/ThumbsBarTest.java
similarity index 100%
rename from v17/leanback/tests/java/android/support/v17/leanback/widget/ThumbsBarTest.java
rename to leanback/tests/java/android/support/v17/leanback/widget/ThumbsBarTest.java
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/widget/TitleViewAdapterTest.java b/leanback/tests/java/android/support/v17/leanback/widget/TitleViewAdapterTest.java
similarity index 100%
rename from v17/leanback/tests/java/android/support/v17/leanback/widget/TitleViewAdapterTest.java
rename to leanback/tests/java/android/support/v17/leanback/widget/TitleViewAdapterTest.java
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/widget/VerticalGridViewEx.java b/leanback/tests/java/android/support/v17/leanback/widget/VerticalGridViewEx.java
similarity index 100%
rename from v17/leanback/tests/java/android/support/v17/leanback/widget/VerticalGridViewEx.java
rename to leanback/tests/java/android/support/v17/leanback/widget/VerticalGridViewEx.java
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/widget/picker/DatePickerActivity.java b/leanback/tests/java/android/support/v17/leanback/widget/picker/DatePickerActivity.java
similarity index 100%
rename from v17/leanback/tests/java/android/support/v17/leanback/widget/picker/DatePickerActivity.java
rename to leanback/tests/java/android/support/v17/leanback/widget/picker/DatePickerActivity.java
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/widget/picker/DatePickerTest.java b/leanback/tests/java/android/support/v17/leanback/widget/picker/DatePickerTest.java
similarity index 100%
rename from v17/leanback/tests/java/android/support/v17/leanback/widget/picker/DatePickerTest.java
rename to leanback/tests/java/android/support/v17/leanback/widget/picker/DatePickerTest.java
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/widget/picker/TimePickerActivity.java b/leanback/tests/java/android/support/v17/leanback/widget/picker/TimePickerActivity.java
similarity index 100%
rename from v17/leanback/tests/java/android/support/v17/leanback/widget/picker/TimePickerActivity.java
rename to leanback/tests/java/android/support/v17/leanback/widget/picker/TimePickerActivity.java
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/widget/picker/TimePickerTest.java b/leanback/tests/java/android/support/v17/leanback/widget/picker/TimePickerTest.java
similarity index 100%
rename from v17/leanback/tests/java/android/support/v17/leanback/widget/picker/TimePickerTest.java
rename to leanback/tests/java/android/support/v17/leanback/widget/picker/TimePickerTest.java
diff --git a/v17/leanback/tests/res/drawable/ic_action_a.png b/leanback/tests/res/drawable/ic_action_a.png
similarity index 100%
rename from v17/leanback/tests/res/drawable/ic_action_a.png
rename to leanback/tests/res/drawable/ic_action_a.png
Binary files differ
diff --git a/v17/leanback/tests/res/drawable/spiderman.jpg b/leanback/tests/res/drawable/spiderman.jpg
similarity index 100%
rename from v17/leanback/tests/res/drawable/spiderman.jpg
rename to leanback/tests/res/drawable/spiderman.jpg
Binary files differ
diff --git a/v17/leanback/tests/res/layout/browse.xml b/leanback/tests/res/layout/browse.xml
similarity index 100%
rename from v17/leanback/tests/res/layout/browse.xml
rename to leanback/tests/res/layout/browse.xml
diff --git a/v17/leanback/tests/res/layout/datepicker_alone.xml b/leanback/tests/res/layout/datepicker_alone.xml
similarity index 100%
rename from v17/leanback/tests/res/layout/datepicker_alone.xml
rename to leanback/tests/res/layout/datepicker_alone.xml
diff --git a/v17/leanback/tests/res/layout/datepicker_with_other_widgets.xml b/leanback/tests/res/layout/datepicker_with_other_widgets.xml
similarity index 100%
rename from v17/leanback/tests/res/layout/datepicker_with_other_widgets.xml
rename to leanback/tests/res/layout/datepicker_with_other_widgets.xml
diff --git a/v17/leanback/tests/res/layout/details.xml b/leanback/tests/res/layout/details.xml
similarity index 100%
rename from v17/leanback/tests/res/layout/details.xml
rename to leanback/tests/res/layout/details.xml
diff --git a/v17/leanback/tests/res/layout/horizontal_grid.xml b/leanback/tests/res/layout/horizontal_grid.xml
similarity index 100%
rename from v17/leanback/tests/res/layout/horizontal_grid.xml
rename to leanback/tests/res/layout/horizontal_grid.xml
diff --git a/v17/leanback/tests/res/layout/horizontal_grid_testredundantappendremove2.xml b/leanback/tests/res/layout/horizontal_grid_testredundantappendremove2.xml
similarity index 100%
rename from v17/leanback/tests/res/layout/horizontal_grid_testredundantappendremove2.xml
rename to leanback/tests/res/layout/horizontal_grid_testredundantappendremove2.xml
diff --git a/v17/leanback/tests/res/layout/horizontal_grid_wrap.xml b/leanback/tests/res/layout/horizontal_grid_wrap.xml
similarity index 100%
rename from v17/leanback/tests/res/layout/horizontal_grid_wrap.xml
rename to leanback/tests/res/layout/horizontal_grid_wrap.xml
diff --git a/v17/leanback/tests/res/layout/horizontal_item.xml b/leanback/tests/res/layout/horizontal_item.xml
similarity index 100%
rename from v17/leanback/tests/res/layout/horizontal_item.xml
rename to leanback/tests/res/layout/horizontal_item.xml
diff --git a/v17/leanback/tests/res/layout/horizontal_linear.xml b/leanback/tests/res/layout/horizontal_linear.xml
similarity index 100%
rename from v17/leanback/tests/res/layout/horizontal_linear.xml
rename to leanback/tests/res/layout/horizontal_linear.xml
diff --git a/v17/leanback/tests/res/layout/horizontal_linear_rtl.xml b/leanback/tests/res/layout/horizontal_linear_rtl.xml
similarity index 100%
rename from v17/leanback/tests/res/layout/horizontal_linear_rtl.xml
rename to leanback/tests/res/layout/horizontal_linear_rtl.xml
diff --git a/v17/leanback/tests/res/layout/horizontal_linear_wrap_content.xml b/leanback/tests/res/layout/horizontal_linear_wrap_content.xml
similarity index 100%
rename from v17/leanback/tests/res/layout/horizontal_linear_wrap_content.xml
rename to leanback/tests/res/layout/horizontal_linear_wrap_content.xml
diff --git a/v17/leanback/tests/res/layout/item_button_at_bottom.xml b/leanback/tests/res/layout/item_button_at_bottom.xml
similarity index 100%
rename from v17/leanback/tests/res/layout/item_button_at_bottom.xml
rename to leanback/tests/res/layout/item_button_at_bottom.xml
diff --git a/v17/leanback/tests/res/layout/item_button_at_top.xml b/leanback/tests/res/layout/item_button_at_top.xml
similarity index 100%
rename from v17/leanback/tests/res/layout/item_button_at_top.xml
rename to leanback/tests/res/layout/item_button_at_top.xml
diff --git a/leanback/tests/res/layout/page_fragment.xml b/leanback/tests/res/layout/page_fragment.xml
new file mode 100644
index 0000000..9273f6f
--- /dev/null
+++ b/leanback/tests/res/layout/page_fragment.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/container_list"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+    <LinearLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        android:layout_alignParentRight="true"
+        android:layout_marginRight="128dp"
+        android:layout_centerVertical="true">
+
+        <EditText
+            android:id="@+id/tv1"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Header 1"
+            android:layout_margin="16dp"
+            android:focusable="true"
+            android:textAppearance="@android:style/TextAppearance.DeviceDefault.Large" />
+
+        <EditText
+            android:id="@+id/tv2"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Header 2"
+            android:layout_margin="16dp"
+            android:focusable="true"
+            android:textAppearance="@android:style/TextAppearance.DeviceDefault.Medium" />
+
+        <EditText
+            android:id="@+id/tv3"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Header 3"
+            android:layout_margin="16dp"
+            android:focusable="true"
+            android:textAppearance="@android:style/TextAppearance.DeviceDefault.Small" />
+
+    </LinearLayout>
+</RelativeLayout>
\ No newline at end of file
diff --git a/v17/leanback/tests/res/layout/playback_controls_with_video.xml b/leanback/tests/res/layout/playback_controls_with_video.xml
similarity index 100%
rename from v17/leanback/tests/res/layout/playback_controls_with_video.xml
rename to leanback/tests/res/layout/playback_controls_with_video.xml
diff --git a/v17/leanback/tests/res/layout/relative_layout.xml b/leanback/tests/res/layout/relative_layout.xml
similarity index 100%
rename from v17/leanback/tests/res/layout/relative_layout.xml
rename to leanback/tests/res/layout/relative_layout.xml
diff --git a/v17/leanback/tests/res/layout/relative_layout2.xml b/leanback/tests/res/layout/relative_layout2.xml
similarity index 100%
rename from v17/leanback/tests/res/layout/relative_layout2.xml
rename to leanback/tests/res/layout/relative_layout2.xml
diff --git a/v17/leanback/tests/res/layout/relative_layout3.xml b/leanback/tests/res/layout/relative_layout3.xml
similarity index 100%
rename from v17/leanback/tests/res/layout/relative_layout3.xml
rename to leanback/tests/res/layout/relative_layout3.xml
diff --git a/v17/leanback/tests/res/layout/selectable_text_view.xml b/leanback/tests/res/layout/selectable_text_view.xml
similarity index 100%
rename from v17/leanback/tests/res/layout/selectable_text_view.xml
rename to leanback/tests/res/layout/selectable_text_view.xml
diff --git a/v17/leanback/tests/res/layout/single_fragment.xml b/leanback/tests/res/layout/single_fragment.xml
similarity index 100%
rename from v17/leanback/tests/res/layout/single_fragment.xml
rename to leanback/tests/res/layout/single_fragment.xml
diff --git a/v17/leanback/tests/res/layout/timepicker_alone.xml b/leanback/tests/res/layout/timepicker_alone.xml
similarity index 100%
rename from v17/leanback/tests/res/layout/timepicker_alone.xml
rename to leanback/tests/res/layout/timepicker_alone.xml
diff --git a/v17/leanback/tests/res/layout/timepicker_with_other_widgets.xml b/leanback/tests/res/layout/timepicker_with_other_widgets.xml
similarity index 100%
rename from v17/leanback/tests/res/layout/timepicker_with_other_widgets.xml
rename to leanback/tests/res/layout/timepicker_with_other_widgets.xml
diff --git a/v17/leanback/tests/res/layout/vertical_grid.xml b/leanback/tests/res/layout/vertical_grid.xml
similarity index 100%
rename from v17/leanback/tests/res/layout/vertical_grid.xml
rename to leanback/tests/res/layout/vertical_grid.xml
diff --git a/v17/leanback/tests/res/layout/vertical_grid_ltr.xml b/leanback/tests/res/layout/vertical_grid_ltr.xml
similarity index 100%
rename from v17/leanback/tests/res/layout/vertical_grid_ltr.xml
rename to leanback/tests/res/layout/vertical_grid_ltr.xml
diff --git a/v17/leanback/tests/res/layout/vertical_grid_rtl.xml b/leanback/tests/res/layout/vertical_grid_rtl.xml
similarity index 100%
rename from v17/leanback/tests/res/layout/vertical_grid_rtl.xml
rename to leanback/tests/res/layout/vertical_grid_rtl.xml
diff --git a/v17/leanback/tests/res/layout/vertical_grid_testredundantappendremove.xml b/leanback/tests/res/layout/vertical_grid_testredundantappendremove.xml
similarity index 100%
rename from v17/leanback/tests/res/layout/vertical_grid_testredundantappendremove.xml
rename to leanback/tests/res/layout/vertical_grid_testredundantappendremove.xml
diff --git a/v17/leanback/tests/res/layout/vertical_linear.xml b/leanback/tests/res/layout/vertical_linear.xml
similarity index 100%
rename from v17/leanback/tests/res/layout/vertical_linear.xml
rename to leanback/tests/res/layout/vertical_linear.xml
diff --git a/v17/leanback/tests/res/layout/vertical_linear_measured_with_zero.xml b/leanback/tests/res/layout/vertical_linear_measured_with_zero.xml
similarity index 100%
rename from v17/leanback/tests/res/layout/vertical_linear_measured_with_zero.xml
rename to leanback/tests/res/layout/vertical_linear_measured_with_zero.xml
diff --git a/v17/leanback/tests/res/layout/vertical_linear_with_button.xml b/leanback/tests/res/layout/vertical_linear_with_button.xml
similarity index 100%
rename from v17/leanback/tests/res/layout/vertical_linear_with_button.xml
rename to leanback/tests/res/layout/vertical_linear_with_button.xml
diff --git a/v17/leanback/tests/res/layout/vertical_linear_with_button_onleft.xml b/leanback/tests/res/layout/vertical_linear_with_button_onleft.xml
similarity index 100%
rename from v17/leanback/tests/res/layout/vertical_linear_with_button_onleft.xml
rename to leanback/tests/res/layout/vertical_linear_with_button_onleft.xml
diff --git a/v17/leanback/tests/res/layout/vertical_linear_wrap_content.xml b/leanback/tests/res/layout/vertical_linear_wrap_content.xml
similarity index 100%
rename from v17/leanback/tests/res/layout/vertical_linear_wrap_content.xml
rename to leanback/tests/res/layout/vertical_linear_wrap_content.xml
diff --git a/v17/leanback/tests/res/layout/video_fragment_with_controls.xml b/leanback/tests/res/layout/video_fragment_with_controls.xml
similarity index 100%
rename from v17/leanback/tests/res/layout/video_fragment_with_controls.xml
rename to leanback/tests/res/layout/video_fragment_with_controls.xml
diff --git a/v17/leanback/tests/res/raw/track_01.mp3 b/leanback/tests/res/raw/track_01.mp3
similarity index 100%
rename from v17/leanback/tests/res/raw/track_01.mp3
rename to leanback/tests/res/raw/track_01.mp3
Binary files differ
diff --git a/v17/leanback/tests/res/raw/video.mp4 b/leanback/tests/res/raw/video.mp4
similarity index 100%
rename from v17/leanback/tests/res/raw/video.mp4
rename to leanback/tests/res/raw/video.mp4
Binary files differ
diff --git a/v17/leanback/tests/res/values/strings.xml b/leanback/tests/res/values/strings.xml
similarity index 100%
rename from v17/leanback/tests/res/values/strings.xml
rename to leanback/tests/res/values/strings.xml
diff --git a/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/writer.kt b/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/writer.kt
index 81345ff..5811c61 100644
--- a/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/writer.kt
+++ b/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/writer.kt
@@ -194,4 +194,4 @@
 
 private fun takeParams(count: Int, vararg params: Any) = params.take(count).toTypedArray()
 
-private fun generateParamString(count: Int) = (0 until count).joinToString(",") { N }
\ No newline at end of file
+private fun generateParamString(count: Int) = (0 until count).joinToString(",") { N }
diff --git a/lifecycle/gradle/wrapper/gradle-wrapper.properties b/lifecycle/gradle/wrapper/gradle-wrapper.properties
index b519e0a..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.4-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.3-bin.zip
diff --git a/media-compat-test-client/AndroidManifest.xml b/media-compat-test-client/AndroidManifest.xml
deleted file mode 100644
index 290b67e..0000000
--- a/media-compat-test-client/AndroidManifest.xml
+++ /dev/null
@@ -1,17 +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.
--->
-<manifest package="android.support.mediacompat.client"/>
diff --git a/media-compat-test-client/build.gradle b/media-compat-test-client/build.gradle
deleted file mode 100644
index 5441793..0000000
--- a/media-compat-test-client/build.gradle
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * 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.
- */
-
-import static android.support.dependencies.DependenciesKt.*
-
-plugins {
-    id("SupportAndroidLibraryPlugin")
-}
-
-dependencies {
-    androidTestImplementation(project(":support-annotations"))
-    androidTestImplementation(project(":support-media-compat"))
-    androidTestImplementation(project(":support-media-compat-test-lib"))
-    androidTestImplementation(project(":support-testutils"))
-
-    androidTestImplementation(TEST_RUNNER, libs.exclude_annotations)
-}
-
-android {
-    defaultConfig {
-        minSdkVersion 14
-    }
-}
-
-supportLibrary {
-    legacySourceLocation = true
-}
\ No newline at end of file
diff --git a/media-compat-test-client/tests/AndroidManifest.xml b/media-compat-test-client/tests/AndroidManifest.xml
deleted file mode 100644
index 8938399..0000000
--- a/media-compat-test-client/tests/AndroidManifest.xml
+++ /dev/null
@@ -1,20 +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.
--->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.support.mediacompat.client.test">
-    <uses-sdk android:targetSdkVersion="${target-sdk-version}"/>
-</manifest>
diff --git a/media-compat-test-client/tests/src/android/support/mediacompat/client/MediaBrowserCompatTest.java b/media-compat-test-client/tests/src/android/support/mediacompat/client/MediaBrowserCompatTest.java
deleted file mode 100644
index f9f24a0..0000000
--- a/media-compat-test-client/tests/src/android/support/mediacompat/client/MediaBrowserCompatTest.java
+++ /dev/null
@@ -1,672 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.mediacompat.client;
-
-import static android.support.mediacompat.testlib.MediaBrowserConstants.EXTRAS_KEY;
-import static android.support.mediacompat.testlib.MediaBrowserConstants.EXTRAS_VALUE;
-import static android.support.mediacompat.testlib.MediaBrowserConstants.MEDIA_ID_CHILDREN;
-import static android.support.mediacompat.testlib.MediaBrowserConstants.MEDIA_ID_INVALID;
-import static android.support.mediacompat.testlib.MediaBrowserConstants
-        .MEDIA_ID_ON_LOAD_ITEM_NOT_IMPLEMENTED;
-import static android.support.mediacompat.testlib.MediaBrowserConstants.MEDIA_ID_ROOT;
-import static android.support.mediacompat.testlib.MediaBrowserConstants.NOTIFY_CHILDREN_CHANGED;
-import static android.support.test.InstrumentationRegistry.getContext;
-import static android.support.test.InstrumentationRegistry.getInstrumentation;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertNotNull;
-import static junit.framework.Assert.assertNull;
-import static junit.framework.Assert.assertTrue;
-import static junit.framework.Assert.fail;
-
-import android.content.ComponentName;
-import android.os.Bundle;
-import android.support.mediacompat.client.util.IntentUtil;
-import android.support.test.filters.LargeTest;
-import android.support.test.filters.MediumTest;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-import android.support.testutils.PollingCheck;
-import android.support.v4.media.MediaBrowserCompat;
-import android.support.v4.media.MediaBrowserCompat.MediaItem;
-import android.support.v4.media.MediaBrowserServiceCompat;
-import android.support.v4.media.MediaDescriptionCompat;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Test {@link android.support.v4.media.MediaBrowserCompat}.
- */
-@RunWith(AndroidJUnit4.class)
-public class MediaBrowserCompatTest {
-
-    // The maximum time to wait for an operation.
-    private static final long TIME_OUT_MS = 3000L;
-
-    /**
-     * To check {@link MediaBrowserCompat#unsubscribe} works properly,
-     * we notify to the browser after the unsubscription that the media items have changed.
-     * Then {@link MediaBrowserCompat.SubscriptionCallback#onChildrenLoaded} should not be called.
-     *
-     * The measured time from calling {@link MediaBrowserServiceCompat#notifyChildrenChanged}
-     * to {@link MediaBrowserCompat.SubscriptionCallback#onChildrenLoaded} being called is about
-     * 50ms.
-     * So we make the thread sleep for 100ms to properly check that the callback is not called.
-     */
-    private static final long SLEEP_MS = 100L;
-    private static final ComponentName TEST_BROWSER_SERVICE = new ComponentName(
-            "android.support.mediacompat.service.test",
-            "android.support.mediacompat.service.StubMediaBrowserServiceCompat");
-    private static final ComponentName TEST_INVALID_BROWSER_SERVICE = new ComponentName(
-            "invalid.package", "invalid.ServiceClassName");
-
-    private MediaBrowserCompat mMediaBrowser;
-    private StubConnectionCallback mConnectionCallback;
-    private StubSubscriptionCallback mSubscriptionCallback;
-    private StubItemCallback mItemCallback;
-    private Bundle mRootHints;
-
-    @Before
-    public void setUp() {
-        mConnectionCallback = new StubConnectionCallback();
-        mSubscriptionCallback = new StubSubscriptionCallback();
-        mItemCallback = new StubItemCallback();
-
-        mRootHints = new Bundle();
-        mRootHints.putBoolean(MediaBrowserServiceCompat.BrowserRoot.EXTRA_RECENT, true);
-        mRootHints.putBoolean(MediaBrowserServiceCompat.BrowserRoot.EXTRA_OFFLINE, true);
-        mRootHints.putBoolean(MediaBrowserServiceCompat.BrowserRoot.EXTRA_SUGGESTED, true);
-    }
-
-    @After
-    public void tearDown() {
-        if (mMediaBrowser != null && mMediaBrowser.isConnected()) {
-            mMediaBrowser.disconnect();
-        }
-    }
-
-    @Test
-    @SmallTest
-    public void testMediaBrowser() throws Exception {
-        createMediaBrowser(TEST_BROWSER_SERVICE);
-        assertFalse(mMediaBrowser.isConnected());
-
-        connectMediaBrowserService();
-        assertTrue(mMediaBrowser.isConnected());
-
-        assertEquals(TEST_BROWSER_SERVICE, mMediaBrowser.getServiceComponent());
-        assertEquals(MEDIA_ID_ROOT, mMediaBrowser.getRoot());
-        assertEquals(EXTRAS_VALUE, mMediaBrowser.getExtras().getString(EXTRAS_KEY));
-
-        mMediaBrowser.disconnect();
-        new PollingCheck(TIME_OUT_MS) {
-            @Override
-            protected boolean check() {
-                return !mMediaBrowser.isConnected();
-            }
-        }.run();
-    }
-
-    @Test
-    @SmallTest
-    public void testConnectTwice() throws Exception {
-        createMediaBrowser(TEST_BROWSER_SERVICE);
-        connectMediaBrowserService();
-        try {
-            mMediaBrowser.connect();
-            fail();
-        } catch (IllegalStateException e) {
-            // expected
-        }
-    }
-
-    @Test
-    @SmallTest
-    public void testConnectionFailed() throws Exception {
-        createMediaBrowser(TEST_INVALID_BROWSER_SERVICE);
-
-        synchronized (mConnectionCallback.mWaitLock) {
-            mMediaBrowser.connect();
-            mConnectionCallback.mWaitLock.wait(TIME_OUT_MS);
-        }
-        assertTrue(mConnectionCallback.mConnectionFailedCount > 0);
-        assertEquals(0, mConnectionCallback.mConnectedCount);
-        assertEquals(0, mConnectionCallback.mConnectionSuspendedCount);
-    }
-
-    @Test
-    @SmallTest
-    public void testReconnection() throws Exception {
-        createMediaBrowser(TEST_BROWSER_SERVICE);
-
-        getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                mMediaBrowser.connect();
-                // Reconnect before the first connection was established.
-                mMediaBrowser.disconnect();
-                mMediaBrowser.connect();
-            }
-        });
-
-        synchronized (mConnectionCallback.mWaitLock) {
-            mConnectionCallback.mWaitLock.wait(TIME_OUT_MS);
-            assertEquals(1, mConnectionCallback.mConnectedCount);
-        }
-
-        synchronized (mSubscriptionCallback.mWaitLock) {
-            // Test subscribe.
-            resetCallbacks();
-            mMediaBrowser.subscribe(MEDIA_ID_ROOT, mSubscriptionCallback);
-            mSubscriptionCallback.mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mSubscriptionCallback.mChildrenLoadedCount > 0);
-            assertEquals(MEDIA_ID_ROOT, mSubscriptionCallback.mLastParentId);
-        }
-
-        synchronized (mItemCallback.mWaitLock) {
-            // Test getItem.
-            resetCallbacks();
-            mMediaBrowser.getItem(MEDIA_ID_CHILDREN[0], mItemCallback);
-            mItemCallback.mWaitLock.wait(TIME_OUT_MS);
-            assertEquals(MEDIA_ID_CHILDREN[0], mItemCallback.mLastMediaItem.getMediaId());
-        }
-
-        // Reconnect after connection was established.
-        mMediaBrowser.disconnect();
-        resetCallbacks();
-        connectMediaBrowserService();
-
-        synchronized (mItemCallback.mWaitLock) {
-            // Test getItem.
-            resetCallbacks();
-            mMediaBrowser.getItem(MEDIA_ID_CHILDREN[0], mItemCallback);
-            mItemCallback.mWaitLock.wait(TIME_OUT_MS);
-            assertEquals(MEDIA_ID_CHILDREN[0], mItemCallback.mLastMediaItem.getMediaId());
-        }
-    }
-
-    @Test
-    @SmallTest
-    public void testConnectionCallbackNotCalledAfterDisconnect() {
-        createMediaBrowser(TEST_BROWSER_SERVICE);
-
-        getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                mMediaBrowser.connect();
-                mMediaBrowser.disconnect();
-                resetCallbacks();
-            }
-        });
-
-        try {
-            Thread.sleep(SLEEP_MS);
-        } catch (InterruptedException e) {
-            fail("Unexpected InterruptedException occurred.");
-        }
-        assertEquals(0, mConnectionCallback.mConnectedCount);
-        assertEquals(0, mConnectionCallback.mConnectionFailedCount);
-        assertEquals(0, mConnectionCallback.mConnectionSuspendedCount);
-    }
-
-    @Test
-    @SmallTest
-    public void testGetServiceComponentBeforeConnection() {
-        createMediaBrowser(TEST_BROWSER_SERVICE);
-        try {
-            ComponentName serviceComponent = mMediaBrowser.getServiceComponent();
-            fail();
-        } catch (IllegalStateException e) {
-            // expected
-        }
-    }
-
-    @Test
-    @SmallTest
-    public void testSubscribe() throws Exception {
-        createMediaBrowser(TEST_BROWSER_SERVICE);
-        connectMediaBrowserService();
-
-        synchronized (mSubscriptionCallback.mWaitLock) {
-            mMediaBrowser.subscribe(MEDIA_ID_ROOT, mSubscriptionCallback);
-            mSubscriptionCallback.mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mSubscriptionCallback.mChildrenLoadedCount > 0);
-            assertEquals(MEDIA_ID_ROOT, mSubscriptionCallback.mLastParentId);
-            assertEquals(MEDIA_ID_CHILDREN.length,
-                    mSubscriptionCallback.mLastChildMediaItems.size());
-            for (int i = 0; i < MEDIA_ID_CHILDREN.length; ++i) {
-                assertEquals(MEDIA_ID_CHILDREN[i],
-                        mSubscriptionCallback.mLastChildMediaItems.get(i).getMediaId());
-            }
-
-            // Test MediaBrowserServiceCompat.notifyChildrenChanged()
-            mSubscriptionCallback.reset();
-            callMediaBrowserServiceMethod(NOTIFY_CHILDREN_CHANGED, MEDIA_ID_ROOT);
-            mSubscriptionCallback.mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mSubscriptionCallback.mChildrenLoadedCount > 0);
-        }
-
-        // Test unsubscribe.
-        resetCallbacks();
-        mMediaBrowser.unsubscribe(MEDIA_ID_ROOT);
-
-        // After unsubscribing, make StubMediaBrowserServiceCompat notify that the children are
-        // changed.
-        callMediaBrowserServiceMethod(NOTIFY_CHILDREN_CHANGED, MEDIA_ID_ROOT);
-        try {
-            Thread.sleep(SLEEP_MS);
-        } catch (InterruptedException e) {
-            fail("Unexpected InterruptedException occurred.");
-        }
-        // onChildrenLoaded should not be called.
-        assertEquals(0, mSubscriptionCallback.mChildrenLoadedCount);
-    }
-
-    @Test
-    @SmallTest
-    public void testSubscribeWithOptions() throws Exception {
-        createMediaBrowser(TEST_BROWSER_SERVICE);
-        connectMediaBrowserService();
-        final int pageSize = 3;
-        final int lastPage = (MEDIA_ID_CHILDREN.length - 1) / pageSize;
-        Bundle options = new Bundle();
-        options.putInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, pageSize);
-
-        synchronized (mSubscriptionCallback.mWaitLock) {
-            for (int page = 0; page <= lastPage; ++page) {
-                resetCallbacks();
-                options.putInt(MediaBrowserCompat.EXTRA_PAGE, page);
-                mMediaBrowser.subscribe(MEDIA_ID_ROOT, options, mSubscriptionCallback);
-                mSubscriptionCallback.mWaitLock.wait(TIME_OUT_MS);
-                assertTrue(mSubscriptionCallback.mChildrenLoadedWithOptionCount > 0);
-                assertEquals(MEDIA_ID_ROOT, mSubscriptionCallback.mLastParentId);
-                if (page != lastPage) {
-                    assertEquals(pageSize, mSubscriptionCallback.mLastChildMediaItems.size());
-                } else {
-                    assertEquals((MEDIA_ID_CHILDREN.length - 1) % pageSize + 1,
-                            mSubscriptionCallback.mLastChildMediaItems.size());
-                }
-                // Check whether all the items in the current page are loaded.
-                for (int i = 0; i < mSubscriptionCallback.mLastChildMediaItems.size(); ++i) {
-                    assertEquals(MEDIA_ID_CHILDREN[page * pageSize + i],
-                            mSubscriptionCallback.mLastChildMediaItems.get(i).getMediaId());
-                }
-            }
-
-            // Test MediaBrowserServiceCompat.notifyChildrenChanged()
-            mSubscriptionCallback.reset();
-            callMediaBrowserServiceMethod(NOTIFY_CHILDREN_CHANGED, MEDIA_ID_ROOT);
-            mSubscriptionCallback.mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mSubscriptionCallback.mChildrenLoadedWithOptionCount > 0);
-        }
-
-        // Test unsubscribe with callback argument.
-        resetCallbacks();
-        mMediaBrowser.unsubscribe(MEDIA_ID_ROOT, mSubscriptionCallback);
-
-        // After unsubscribing, make StubMediaBrowserServiceCompat notify that the children are
-        // changed.
-        callMediaBrowserServiceMethod(NOTIFY_CHILDREN_CHANGED, MEDIA_ID_ROOT);
-        try {
-            Thread.sleep(SLEEP_MS);
-        } catch (InterruptedException e) {
-            fail("Unexpected InterruptedException occurred.");
-        }
-        // onChildrenLoaded should not be called.
-        assertEquals(0, mSubscriptionCallback.mChildrenLoadedCount);
-    }
-
-    @Test
-    @SmallTest
-    public void testSubscribeInvalidItem() throws Exception {
-        createMediaBrowser(TEST_BROWSER_SERVICE);
-        connectMediaBrowserService();
-
-        synchronized (mSubscriptionCallback.mWaitLock) {
-            mMediaBrowser.subscribe(MEDIA_ID_INVALID, mSubscriptionCallback);
-            mSubscriptionCallback.mWaitLock.wait(TIME_OUT_MS);
-            assertEquals(MEDIA_ID_INVALID, mSubscriptionCallback.mLastErrorId);
-        }
-    }
-
-    @Test
-    @SmallTest
-    public void testSubscribeInvalidItemWithOptions() throws Exception {
-        createMediaBrowser(TEST_BROWSER_SERVICE);
-        connectMediaBrowserService();
-
-        final int pageSize = 5;
-        final int page = 2;
-        Bundle options = new Bundle();
-        options.putInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, pageSize);
-        options.putInt(MediaBrowserCompat.EXTRA_PAGE, page);
-
-        synchronized (mSubscriptionCallback.mWaitLock) {
-            mMediaBrowser.subscribe(MEDIA_ID_INVALID, options, mSubscriptionCallback);
-            mSubscriptionCallback.mWaitLock.wait(TIME_OUT_MS);
-            assertEquals(MEDIA_ID_INVALID, mSubscriptionCallback.mLastErrorId);
-            assertNotNull(mSubscriptionCallback.mLastOptions);
-            assertEquals(page,
-                    mSubscriptionCallback.mLastOptions.getInt(MediaBrowserCompat.EXTRA_PAGE));
-            assertEquals(pageSize,
-                    mSubscriptionCallback.mLastOptions.getInt(MediaBrowserCompat.EXTRA_PAGE_SIZE));
-        }
-    }
-
-    @Test
-    @SmallTest
-    public void testUnsubscribeForMultipleSubscriptions() throws Exception {
-        createMediaBrowser(TEST_BROWSER_SERVICE);
-        connectMediaBrowserService();
-        final List<StubSubscriptionCallback> subscriptionCallbacks = new ArrayList<>();
-        final int pageSize = 1;
-
-        // Subscribe four pages, one item per page.
-        for (int page = 0; page < 4; page++) {
-            final StubSubscriptionCallback callback = new StubSubscriptionCallback();
-            subscriptionCallbacks.add(callback);
-
-            Bundle options = new Bundle();
-            options.putInt(MediaBrowserCompat.EXTRA_PAGE, page);
-            options.putInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, pageSize);
-            mMediaBrowser.subscribe(MEDIA_ID_ROOT, options, callback);
-            synchronized (callback.mWaitLock) {
-                callback.mWaitLock.wait(TIME_OUT_MS);
-            }
-            // Each onChildrenLoaded() must be called.
-            assertEquals(1, callback.mChildrenLoadedWithOptionCount);
-        }
-
-        // Reset callbacks and unsubscribe.
-        for (StubSubscriptionCallback callback : subscriptionCallbacks) {
-            callback.reset();
-        }
-        mMediaBrowser.unsubscribe(MEDIA_ID_ROOT);
-
-        // After unsubscribing, make StubMediaBrowserServiceCompat notify that the children are
-        // changed.
-        callMediaBrowserServiceMethod(NOTIFY_CHILDREN_CHANGED, MEDIA_ID_ROOT);
-        try {
-            Thread.sleep(SLEEP_MS);
-        } catch (InterruptedException e) {
-            fail("Unexpected InterruptedException occurred.");
-        }
-
-        // onChildrenLoaded should not be called.
-        for (StubSubscriptionCallback callback : subscriptionCallbacks) {
-            assertEquals(0, callback.mChildrenLoadedWithOptionCount);
-        }
-    }
-
-    @Test
-    @MediumTest
-    public void testUnsubscribeWithSubscriptionCallbackForMultipleSubscriptions() throws Exception {
-        createMediaBrowser(TEST_BROWSER_SERVICE);
-        connectMediaBrowserService();
-        final List<StubSubscriptionCallback> subscriptionCallbacks = new ArrayList<>();
-        final int pageSize = 1;
-
-        // Subscribe four pages, one item per page.
-        for (int page = 0; page < 4; page++) {
-            final StubSubscriptionCallback callback = new StubSubscriptionCallback();
-            subscriptionCallbacks.add(callback);
-
-            Bundle options = new Bundle();
-            options.putInt(MediaBrowserCompat.EXTRA_PAGE, page);
-            options.putInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, pageSize);
-            mMediaBrowser.subscribe(MEDIA_ID_ROOT, options,
-                    callback);
-            synchronized (callback.mWaitLock) {
-                callback.mWaitLock.wait(TIME_OUT_MS);
-            }
-            // Each onChildrenLoaded() must be called.
-            assertEquals(1, callback.mChildrenLoadedWithOptionCount);
-        }
-
-        // Unsubscribe existing subscriptions one-by-one.
-        final int[] orderOfRemovingCallbacks = {2, 0, 3, 1};
-        for (int i = 0; i < orderOfRemovingCallbacks.length; i++) {
-            // Reset callbacks
-            for (StubSubscriptionCallback callback : subscriptionCallbacks) {
-                callback.reset();
-            }
-
-            // Remove one subscription
-            mMediaBrowser.unsubscribe(MEDIA_ID_ROOT,
-                    subscriptionCallbacks.get(orderOfRemovingCallbacks[i]));
-
-            // Make StubMediaBrowserServiceCompat notify that the children are changed.
-            callMediaBrowserServiceMethod(NOTIFY_CHILDREN_CHANGED, MEDIA_ID_ROOT);
-            try {
-                Thread.sleep(SLEEP_MS);
-            } catch (InterruptedException e) {
-                fail("Unexpected InterruptedException occurred.");
-            }
-
-            // Only the remaining subscriptionCallbacks should be called.
-            for (int j = 0; j < 4; j++) {
-                int childrenLoadedWithOptionsCount = subscriptionCallbacks
-                        .get(orderOfRemovingCallbacks[j]).mChildrenLoadedWithOptionCount;
-                if (j <= i) {
-                    assertEquals(0, childrenLoadedWithOptionsCount);
-                } else {
-                    assertEquals(1, childrenLoadedWithOptionsCount);
-                }
-            }
-        }
-    }
-
-    @Test
-    @SmallTest
-    public void testGetItem() throws Exception {
-        createMediaBrowser(TEST_BROWSER_SERVICE);
-        connectMediaBrowserService();
-
-        synchronized (mItemCallback.mWaitLock) {
-            mMediaBrowser.getItem(MEDIA_ID_CHILDREN[0], mItemCallback);
-            mItemCallback.mWaitLock.wait(TIME_OUT_MS);
-            assertNotNull(mItemCallback.mLastMediaItem);
-            assertEquals(MEDIA_ID_CHILDREN[0], mItemCallback.mLastMediaItem.getMediaId());
-        }
-    }
-
-    @Test
-    @LargeTest
-    public void testGetItemWhenOnLoadItemIsNotImplemented() throws Exception {
-        createMediaBrowser(TEST_BROWSER_SERVICE);
-        connectMediaBrowserService();
-        synchronized (mItemCallback.mWaitLock) {
-            mMediaBrowser.getItem(MEDIA_ID_ON_LOAD_ITEM_NOT_IMPLEMENTED, mItemCallback);
-            mItemCallback.mWaitLock.wait(TIME_OUT_MS);
-            assertEquals(MEDIA_ID_ON_LOAD_ITEM_NOT_IMPLEMENTED, mItemCallback.mLastErrorId);
-        }
-    }
-
-    @Test
-    @SmallTest
-    public void testGetItemWhenMediaIdIsInvalid() throws Exception {
-        mItemCallback.mLastMediaItem = new MediaItem(new MediaDescriptionCompat.Builder()
-                .setMediaId("dummy_id").build(), MediaItem.FLAG_BROWSABLE);
-
-        createMediaBrowser(TEST_BROWSER_SERVICE);
-        connectMediaBrowserService();
-        synchronized (mItemCallback.mWaitLock) {
-            mMediaBrowser.getItem(MEDIA_ID_INVALID, mItemCallback);
-            mItemCallback.mWaitLock.wait(TIME_OUT_MS);
-            assertNull(mItemCallback.mLastMediaItem);
-            assertNull(mItemCallback.mLastErrorId);
-        }
-    }
-
-    private void createMediaBrowser(final ComponentName component) {
-        getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                mMediaBrowser = new MediaBrowserCompat(getInstrumentation().getTargetContext(),
-                        component, mConnectionCallback, mRootHints);
-            }
-        });
-    }
-
-    private void connectMediaBrowserService() throws Exception {
-        synchronized (mConnectionCallback.mWaitLock) {
-            mMediaBrowser.connect();
-            mConnectionCallback.mWaitLock.wait(TIME_OUT_MS);
-            if (!mMediaBrowser.isConnected()) {
-                fail("Browser failed to connect!");
-            }
-        }
-    }
-
-    private void callMediaBrowserServiceMethod(int methodId, Object arg) {
-        IntentUtil.callMediaBrowserServiceMethod(methodId, arg, getContext());
-    }
-
-    private void resetCallbacks() {
-        mConnectionCallback.reset();
-        mSubscriptionCallback.reset();
-        mItemCallback.reset();
-    }
-
-    private class StubConnectionCallback extends MediaBrowserCompat.ConnectionCallback {
-        Object mWaitLock = new Object();
-        volatile int mConnectedCount;
-        volatile int mConnectionFailedCount;
-        volatile int mConnectionSuspendedCount;
-
-        public void reset() {
-            mConnectedCount = 0;
-            mConnectionFailedCount = 0;
-            mConnectionSuspendedCount = 0;
-        }
-
-        @Override
-        public void onConnected() {
-            synchronized (mWaitLock) {
-                mConnectedCount++;
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onConnectionFailed() {
-            synchronized (mWaitLock) {
-                mConnectionFailedCount++;
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onConnectionSuspended() {
-            synchronized (mWaitLock) {
-                mConnectionSuspendedCount++;
-                mWaitLock.notify();
-            }
-        }
-    }
-
-    private class StubSubscriptionCallback extends MediaBrowserCompat.SubscriptionCallback {
-        final Object mWaitLock = new Object();
-        private volatile int mChildrenLoadedCount;
-        private volatile int mChildrenLoadedWithOptionCount;
-        private volatile String mLastErrorId;
-        private volatile String mLastParentId;
-        private volatile Bundle mLastOptions;
-        private volatile List<MediaItem> mLastChildMediaItems;
-
-        public void reset() {
-            mChildrenLoadedCount = 0;
-            mChildrenLoadedWithOptionCount = 0;
-            mLastErrorId = null;
-            mLastParentId = null;
-            mLastOptions = null;
-            mLastChildMediaItems = null;
-        }
-
-        @Override
-        public void onChildrenLoaded(String parentId, List<MediaItem> children) {
-            synchronized (mWaitLock) {
-                mChildrenLoadedCount++;
-                mLastParentId = parentId;
-                mLastChildMediaItems = children;
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onChildrenLoaded(String parentId, List<MediaItem> children, Bundle options) {
-            synchronized (mWaitLock) {
-                mChildrenLoadedWithOptionCount++;
-                mLastParentId = parentId;
-                mLastOptions = options;
-                mLastChildMediaItems = children;
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onError(String id) {
-            synchronized (mWaitLock) {
-                mLastErrorId = id;
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onError(String id, Bundle options) {
-            synchronized (mWaitLock) {
-                mLastErrorId = id;
-                mLastOptions = options;
-                mWaitLock.notify();
-            }
-        }
-    }
-
-    private class StubItemCallback extends MediaBrowserCompat.ItemCallback {
-        final Object mWaitLock = new Object();
-        private volatile MediaItem mLastMediaItem;
-        private volatile String mLastErrorId;
-
-        public void reset() {
-            mLastMediaItem = null;
-            mLastErrorId = null;
-        }
-
-        @Override
-        public void onItemLoaded(MediaItem item) {
-            synchronized (mWaitLock) {
-                mLastMediaItem = item;
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onError(String id) {
-            synchronized (mWaitLock) {
-                mLastErrorId = id;
-                mWaitLock.notify();
-            }
-        }
-    }
-}
diff --git a/media-compat-test-client/tests/src/android/support/mediacompat/client/util/IntentUtil.java b/media-compat-test-client/tests/src/android/support/mediacompat/client/util/IntentUtil.java
deleted file mode 100644
index bcf33a4..0000000
--- a/media-compat-test-client/tests/src/android/support/mediacompat/client/util/IntentUtil.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.mediacompat.client.util;
-
-import static android.support.mediacompat.testlib.IntentConstants
-        .ACTION_CALL_MEDIA_BROWSER_SERVICE_METHOD;
-import static android.support.mediacompat.testlib.IntentConstants.KEY_ARGUMENT;
-import static android.support.mediacompat.testlib.IntentConstants.KEY_METHOD_ID;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Parcelable;
-
-import java.util.ArrayList;
-
-public class IntentUtil {
-
-    public static final ComponentName SERVICE_RECEIVER_COMPONENT_NAME = new ComponentName(
-            "android.support.mediacompat.service.test",
-            "android.support.mediacompat.service.ServiceBroadcastReceiver");
-
-    public static void callMediaBrowserServiceMethod(int methodId, Object arg, Context context) {
-        Intent intent = createIntent(SERVICE_RECEIVER_COMPONENT_NAME, methodId, arg);
-        intent.setAction(ACTION_CALL_MEDIA_BROWSER_SERVICE_METHOD);
-        if (Build.VERSION.SDK_INT >= 16) {
-            intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-        }
-        context.sendBroadcast(intent);
-    }
-
-    private static Intent createIntent(ComponentName componentName, int methodId, Object arg) {
-        Intent intent = new Intent();
-        intent.setComponent(componentName);
-        intent.putExtra(KEY_METHOD_ID, methodId);
-
-        if (arg instanceof String) {
-            intent.putExtra(KEY_ARGUMENT, (String) arg);
-        } else if (arg instanceof Integer) {
-            intent.putExtra(KEY_ARGUMENT, (int) arg);
-        } else if (arg instanceof Long) {
-            intent.putExtra(KEY_ARGUMENT, (long) arg);
-        } else if (arg instanceof Boolean) {
-            intent.putExtra(KEY_ARGUMENT, (boolean) arg);
-        } else if (arg instanceof Parcelable) {
-            intent.putExtra(KEY_ARGUMENT, (Parcelable) arg);
-        } else if (arg instanceof ArrayList<?>) {
-            Bundle bundle = new Bundle();
-            bundle.putParcelableArrayList(KEY_ARGUMENT, (ArrayList<? extends Parcelable>) arg);
-            intent.putExtras(bundle);
-        } else if (arg instanceof Bundle) {
-            Bundle bundle = new Bundle();
-            bundle.putBundle(KEY_ARGUMENT, (Bundle) arg);
-            intent.putExtras(bundle);
-        }
-        return intent;
-    }
-}
diff --git a/media-compat-test-lib/OWNERS b/media-compat-test-lib/OWNERS
deleted file mode 100644
index 5529026..0000000
--- a/media-compat-test-lib/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-hdmoon@google.com
-sungsoo@google.com
\ No newline at end of file
diff --git a/media-compat-test-lib/build.gradle b/media-compat-test-lib/build.gradle
deleted file mode 100644
index 26594e5..0000000
--- a/media-compat-test-lib/build.gradle
+++ /dev/null
@@ -1,17 +0,0 @@
-/*
- * 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.
- */
-
-apply plugin: 'java'
diff --git a/media-compat-test-lib/src/main/java/android/support/mediacompat/testlib/IntentConstants.java b/media-compat-test-lib/src/main/java/android/support/mediacompat/testlib/IntentConstants.java
deleted file mode 100644
index bc35935..0000000
--- a/media-compat-test-lib/src/main/java/android/support/mediacompat/testlib/IntentConstants.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.mediacompat.testlib;
-
-/**
- * Constants used for sending intent between client and service apks.
- */
-public class IntentConstants {
-    public static final String ACTION_CALL_MEDIA_BROWSER_SERVICE_METHOD =
-            "android.support.mediacompat.service.action.CALL_MEDIA_BROWSER_SERVICE_METHOD";
-    public static final String KEY_METHOD_ID = "method_id";
-    public static final String KEY_ARGUMENT = "argument";
-}
diff --git a/media-compat-test-lib/src/main/java/android/support/mediacompat/testlib/MediaBrowserConstants.java b/media-compat-test-lib/src/main/java/android/support/mediacompat/testlib/MediaBrowserConstants.java
deleted file mode 100644
index 8ef0a35..0000000
--- a/media-compat-test-lib/src/main/java/android/support/mediacompat/testlib/MediaBrowserConstants.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.mediacompat.testlib;
-
-/**
- * Constants for testing the media browser and service.
- */
-public class MediaBrowserConstants {
-
-    // MediaBrowserServiceCompat methods.
-    public static final int NOTIFY_CHILDREN_CHANGED = 1;
-    public static final int SEND_DELAYED_NOTIFY_CHILDREN_CHANGED = 2;
-    public static final int SEND_DELAYED_ITEM_LOADED = 3;
-    public static final int CUSTOM_ACTION_SEND_PROGRESS_UPDATE = 4;
-    public static final int CUSTOM_ACTION_SEND_ERROR = 5;
-    public static final int CUSTOM_ACTION_SEND_RESULT = 6;
-    public static final int SET_SESSION_TOKEN = 7;
-
-    public static final String MEDIA_ID_ROOT = "test_media_id_root";
-
-    public static final String EXTRAS_KEY = "test_extras_key";
-    public static final String EXTRAS_VALUE = "test_extras_value";
-
-    public static final String MEDIA_ID_INVALID = "test_media_id_invalid";
-    public static final String MEDIA_ID_CHILDREN_DELAYED = "test_media_id_children_delayed";
-    public static final String MEDIA_ID_ON_LOAD_ITEM_NOT_IMPLEMENTED =
-            "test_media_id_on_load_item_not_implemented";
-
-    public static final String SEARCH_QUERY = "children_2";
-    public static final String SEARCH_QUERY_FOR_NO_RESULT = "query no result";
-    public static final String SEARCH_QUERY_FOR_ERROR = "query for error";
-
-    public static final String CUSTOM_ACTION = "CUSTOM_ACTION";
-    public static final String CUSTOM_ACTION_FOR_ERROR = "CUSTOM_ACTION_FOR_ERROR";
-
-    public static final String TEST_KEY_1 = "key_1";
-    public static final String TEST_VALUE_1 = "value_1";
-    public static final String TEST_KEY_2 = "key_2";
-    public static final String TEST_VALUE_2 = "value_2";
-    public static final String TEST_KEY_3 = "key_3";
-    public static final String TEST_VALUE_3 = "value_3";
-    public static final String TEST_KEY_4 = "key_4";
-    public static final String TEST_VALUE_4 = "value_4";
-
-    public static final String[] MEDIA_ID_CHILDREN = new String[]{
-            "test_media_id_children_0", "test_media_id_children_1",
-            "test_media_id_children_2", "test_media_id_children_3",
-            MEDIA_ID_CHILDREN_DELAYED
-    };
-}
diff --git a/media-compat-test-service/AndroidManifest.xml b/media-compat-test-service/AndroidManifest.xml
deleted file mode 100644
index 0390e8a..0000000
--- a/media-compat-test-service/AndroidManifest.xml
+++ /dev/null
@@ -1,17 +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.
--->
-<manifest package="android.support.mediacompat.service"/>
diff --git a/media-compat-test-service/OWNERS b/media-compat-test-service/OWNERS
deleted file mode 100644
index 5529026..0000000
--- a/media-compat-test-service/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-hdmoon@google.com
-sungsoo@google.com
\ No newline at end of file
diff --git a/media-compat-test-service/build.gradle b/media-compat-test-service/build.gradle
deleted file mode 100644
index aaf120f..0000000
--- a/media-compat-test-service/build.gradle
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * 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.
- */
-
-import static android.support.dependencies.DependenciesKt.*
-
-plugins {
-    id("SupportAndroidLibraryPlugin")
-}
-
-dependencies {
-    androidTestImplementation(project(":support-annotations"))
-    androidTestImplementation(project(":support-media-compat"))
-    androidTestImplementation(project(":support-media-compat-test-lib"))
-
-    androidTestImplementation(TEST_RUNNER, libs.exclude_annotations)
-}
-
-android {
-    defaultConfig {
-        minSdkVersion 14
-    }
-}
-
-supportLibrary {
-    legacySourceLocation = true
-}
diff --git a/media-compat-test-service/tests/AndroidManifest.xml b/media-compat-test-service/tests/AndroidManifest.xml
deleted file mode 100644
index 3e1eff9..0000000
--- a/media-compat-test-service/tests/AndroidManifest.xml
+++ /dev/null
@@ -1,44 +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.
--->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.support.mediacompat.service.test">
-    <application>
-        <receiver android:name="android.support.mediacompat.service.ServiceBroadcastReceiver">
-            <intent-filter>
-                <action android:name="android.support.mediacompat.service.action.CALL_MEDIA_BROWSER_SERVICE_METHOD"/>
-            </intent-filter>
-        </receiver>
-
-        <receiver android:name="android.support.v4.media.session.MediaButtonReceiver" >
-            <intent-filter>
-                <action android:name="android.intent.action.MEDIA_BUTTON" />
-            </intent-filter>
-        </receiver>
-
-        <service android:name="android.support.mediacompat.service.StubMediaBrowserServiceCompat">
-            <intent-filter>
-                <action android:name="android.media.browse.MediaBrowserService"/>
-            </intent-filter>
-        </service>
-
-        <service android:name="android.support.mediacompat.service.StubMediaBrowserServiceCompatWithDelayedMediaSession">
-            <intent-filter>
-                <action android:name="android.media.browse.MediaBrowserService"/>
-            </intent-filter>
-        </service>
-    </application>
-</manifest>
diff --git a/media-compat-test-service/tests/NO_DOCS b/media-compat-test-service/tests/NO_DOCS
deleted file mode 100644
index 4dad694..0000000
--- a/media-compat-test-service/tests/NO_DOCS
+++ /dev/null
@@ -1,17 +0,0 @@
-# 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.
-
-Having this file, named NO_DOCS, in a directory will prevent
-Android javadocs from being generated for java files under
-the directory. This is especially useful for test projects.
diff --git a/media-compat-test-service/tests/src/android/support/mediacompat/service/ServiceBroadcastReceiver.java b/media-compat-test-service/tests/src/android/support/mediacompat/service/ServiceBroadcastReceiver.java
deleted file mode 100644
index d987fd8..0000000
--- a/media-compat-test-service/tests/src/android/support/mediacompat/service/ServiceBroadcastReceiver.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.mediacompat.service;
-
-
-import static android.support.mediacompat.testlib.IntentConstants
-        .ACTION_CALL_MEDIA_BROWSER_SERVICE_METHOD;
-import static android.support.mediacompat.testlib.IntentConstants.KEY_ARGUMENT;
-import static android.support.mediacompat.testlib.IntentConstants.KEY_METHOD_ID;
-import static android.support.mediacompat.testlib.MediaBrowserConstants.CUSTOM_ACTION_SEND_ERROR;
-import static android.support.mediacompat.testlib.MediaBrowserConstants
-        .CUSTOM_ACTION_SEND_PROGRESS_UPDATE;
-import static android.support.mediacompat.testlib.MediaBrowserConstants.CUSTOM_ACTION_SEND_RESULT;
-import static android.support.mediacompat.testlib.MediaBrowserConstants.NOTIFY_CHILDREN_CHANGED;
-import static android.support.mediacompat.testlib.MediaBrowserConstants.SEND_DELAYED_ITEM_LOADED;
-import static android.support.mediacompat.testlib.MediaBrowserConstants
-        .SEND_DELAYED_NOTIFY_CHILDREN_CHANGED;
-import static android.support.mediacompat.testlib.MediaBrowserConstants.SET_SESSION_TOKEN;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-
-public class ServiceBroadcastReceiver extends BroadcastReceiver {
-
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        Bundle extras = intent.getExtras();
-        if (ACTION_CALL_MEDIA_BROWSER_SERVICE_METHOD.equals(intent.getAction()) && extras != null) {
-            StubMediaBrowserServiceCompat service = StubMediaBrowserServiceCompat.sInstance;
-            int method = extras.getInt(KEY_METHOD_ID, 0);
-
-            switch (method) {
-                case NOTIFY_CHILDREN_CHANGED:
-                    service.notifyChildrenChanged(extras.getString(KEY_ARGUMENT));
-                    break;
-                case SEND_DELAYED_NOTIFY_CHILDREN_CHANGED:
-                    service.sendDelayedNotifyChildrenChanged();
-                    break;
-                case SEND_DELAYED_ITEM_LOADED:
-                    service.sendDelayedItemLoaded();
-                    break;
-                case CUSTOM_ACTION_SEND_PROGRESS_UPDATE:
-                    service.mCustomActionResult.sendProgressUpdate(extras.getBundle(KEY_ARGUMENT));
-                    break;
-                case CUSTOM_ACTION_SEND_ERROR:
-                    service.mCustomActionResult.sendError(extras.getBundle(KEY_ARGUMENT));
-                    break;
-                case CUSTOM_ACTION_SEND_RESULT:
-                    service.mCustomActionResult.sendResult(extras.getBundle(KEY_ARGUMENT));
-                    break;
-                case SET_SESSION_TOKEN:
-                    StubMediaBrowserServiceCompatWithDelayedMediaSession.sInstance
-                            .callSetSessionToken();
-                    break;
-            }
-        }
-    }
-}
diff --git a/media-compat-test-service/tests/src/android/support/mediacompat/service/StubMediaBrowserServiceCompat.java b/media-compat-test-service/tests/src/android/support/mediacompat/service/StubMediaBrowserServiceCompat.java
deleted file mode 100644
index fa9f1c5..0000000
--- a/media-compat-test-service/tests/src/android/support/mediacompat/service/StubMediaBrowserServiceCompat.java
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.mediacompat.service;
-
-import static android.support.mediacompat.testlib.MediaBrowserConstants.CUSTOM_ACTION;
-import static android.support.mediacompat.testlib.MediaBrowserConstants.CUSTOM_ACTION_FOR_ERROR;
-import static android.support.mediacompat.testlib.MediaBrowserConstants.EXTRAS_KEY;
-import static android.support.mediacompat.testlib.MediaBrowserConstants.EXTRAS_VALUE;
-import static android.support.mediacompat.testlib.MediaBrowserConstants.MEDIA_ID_CHILDREN;
-import static android.support.mediacompat.testlib.MediaBrowserConstants.MEDIA_ID_CHILDREN_DELAYED;
-import static android.support.mediacompat.testlib.MediaBrowserConstants.MEDIA_ID_INVALID;
-import static android.support.mediacompat.testlib.MediaBrowserConstants.MEDIA_ID_ROOT;
-import static android.support.mediacompat.testlib.MediaBrowserConstants.SEARCH_QUERY;
-import static android.support.mediacompat.testlib.MediaBrowserConstants.SEARCH_QUERY_FOR_ERROR;
-import static android.support.mediacompat.testlib.MediaBrowserConstants.SEARCH_QUERY_FOR_NO_RESULT;
-
-import android.os.Bundle;
-import android.support.v4.media.MediaBrowserCompat.MediaItem;
-import android.support.v4.media.MediaBrowserServiceCompat;
-import android.support.v4.media.MediaDescriptionCompat;
-import android.support.v4.media.session.MediaSessionCompat;
-
-import junit.framework.Assert;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Stub implementation of {@link android.support.v4.media.MediaBrowserServiceCompat}.
- */
-public class StubMediaBrowserServiceCompat extends MediaBrowserServiceCompat {
-
-    public static StubMediaBrowserServiceCompat sInstance;
-
-    public static MediaSessionCompat sSession;
-    private Bundle mExtras;
-    private Result<List<MediaItem>> mPendingLoadChildrenResult;
-    private Result<MediaItem> mPendingLoadItemResult;
-    private Bundle mPendingRootHints;
-
-    public Bundle mCustomActionExtras;
-    public Result<Bundle> mCustomActionResult;
-
-    @Override
-    public void onCreate() {
-        super.onCreate();
-        sInstance = this;
-        sSession = new MediaSessionCompat(this, "StubMediaBrowserServiceCompat");
-        setSessionToken(sSession.getSessionToken());
-    }
-
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-        sSession.release();
-        sSession = null;
-    }
-
-    @Override
-    public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) {
-        mExtras = new Bundle();
-        mExtras.putString(EXTRAS_KEY, EXTRAS_VALUE);
-        return new BrowserRoot(MEDIA_ID_ROOT, mExtras);
-    }
-
-    @Override
-    public void onLoadChildren(final String parentMediaId, final Result<List<MediaItem>> result) {
-        List<MediaItem> mediaItems = new ArrayList<>();
-        if (MEDIA_ID_ROOT.equals(parentMediaId)) {
-            Bundle rootHints = getBrowserRootHints();
-            for (String id : MEDIA_ID_CHILDREN) {
-                mediaItems.add(createMediaItem(id));
-            }
-            result.sendResult(mediaItems);
-        } else if (MEDIA_ID_CHILDREN_DELAYED.equals(parentMediaId)) {
-            Assert.assertNull(mPendingLoadChildrenResult);
-            mPendingLoadChildrenResult = result;
-            mPendingRootHints = getBrowserRootHints();
-            result.detach();
-        } else if (MEDIA_ID_INVALID.equals(parentMediaId)) {
-            result.sendResult(null);
-        }
-    }
-
-    @Override
-    public void onLoadItem(String itemId, Result<MediaItem> result) {
-        if (MEDIA_ID_CHILDREN_DELAYED.equals(itemId)) {
-            mPendingLoadItemResult = result;
-            mPendingRootHints = getBrowserRootHints();
-            result.detach();
-            return;
-        }
-
-        if (MEDIA_ID_INVALID.equals(itemId)) {
-            result.sendResult(null);
-            return;
-        }
-
-        for (String id : MEDIA_ID_CHILDREN) {
-            if (id.equals(itemId)) {
-                result.sendResult(createMediaItem(id));
-                return;
-            }
-        }
-
-        // Test the case where onLoadItem is not implemented.
-        super.onLoadItem(itemId, result);
-    }
-
-    @Override
-    public void onSearch(String query, Bundle extras, Result<List<MediaItem>> result) {
-        if (SEARCH_QUERY_FOR_NO_RESULT.equals(query)) {
-            result.sendResult(Collections.<MediaItem>emptyList());
-        } else if (SEARCH_QUERY_FOR_ERROR.equals(query)) {
-            result.sendResult(null);
-        } else if (SEARCH_QUERY.equals(query)) {
-            List<MediaItem> items = new ArrayList<>();
-            for (String id : MEDIA_ID_CHILDREN) {
-                if (id.contains(query)) {
-                    items.add(createMediaItem(id));
-                }
-            }
-            result.sendResult(items);
-        }
-    }
-
-    @Override
-    public void onCustomAction(String action, Bundle extras, Result<Bundle> result) {
-        mCustomActionResult = result;
-        mCustomActionExtras = extras;
-        if (CUSTOM_ACTION_FOR_ERROR.equals(action)) {
-            result.sendError(null);
-        } else if (CUSTOM_ACTION.equals(action)) {
-            result.detach();
-        }
-    }
-
-    public void sendDelayedNotifyChildrenChanged() {
-        if (mPendingLoadChildrenResult != null) {
-            mPendingLoadChildrenResult.sendResult(Collections.<MediaItem>emptyList());
-            mPendingRootHints = null;
-            mPendingLoadChildrenResult = null;
-        }
-    }
-
-    public void sendDelayedItemLoaded() {
-        if (mPendingLoadItemResult != null) {
-            mPendingLoadItemResult.sendResult(new MediaItem(new MediaDescriptionCompat.Builder()
-                    .setMediaId(MEDIA_ID_CHILDREN_DELAYED).setExtras(mPendingRootHints).build(),
-                    MediaItem.FLAG_BROWSABLE));
-            mPendingRootHints = null;
-            mPendingLoadItemResult = null;
-        }
-    }
-
-    private MediaItem createMediaItem(String id) {
-        return new MediaItem(new MediaDescriptionCompat.Builder().setMediaId(id).build(),
-                MediaItem.FLAG_BROWSABLE);
-    }
-}
diff --git a/media-compat-test-service/tests/src/android/support/mediacompat/service/StubMediaBrowserServiceCompatWithDelayedMediaSession.java b/media-compat-test-service/tests/src/android/support/mediacompat/service/StubMediaBrowserServiceCompatWithDelayedMediaSession.java
deleted file mode 100644
index 12cb358..0000000
--- a/media-compat-test-service/tests/src/android/support/mediacompat/service/StubMediaBrowserServiceCompatWithDelayedMediaSession.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.mediacompat.service;
-
-import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.v4.media.MediaBrowserCompat;
-import android.support.v4.media.MediaBrowserServiceCompat;
-import android.support.v4.media.session.MediaSessionCompat;
-
-import java.util.List;
-
-/**
- * Stub implementation of {@link MediaBrowserServiceCompat}.
- * This implementation does not call
- * {@link MediaBrowserServiceCompat#setSessionToken(MediaSessionCompat.Token)} in its
- * {@link android.app.Service#onCreate}.
- */
-public class StubMediaBrowserServiceCompatWithDelayedMediaSession extends
-        MediaBrowserServiceCompat {
-
-    static StubMediaBrowserServiceCompatWithDelayedMediaSession sInstance;
-    private MediaSessionCompat mSession;
-
-    @Override
-    public void onCreate() {
-        super.onCreate();
-        sInstance = this;
-        mSession = new MediaSessionCompat(
-                this, "StubMediaBrowserServiceCompatWithDelayedMediaSession");
-    }
-
-    @Nullable
-    @Override
-    public BrowserRoot onGetRoot(@NonNull String clientPackageName,
-            int clientUid, @Nullable Bundle rootHints) {
-        return new BrowserRoot("StubRootId", null);
-    }
-
-    @Override
-    public void onLoadChildren(@NonNull String parentId,
-            @NonNull Result<List<MediaBrowserCompat.MediaItem>> result) {
-        result.detach();
-    }
-
-    public void callSetSessionToken() {
-        setSessionToken(mSession.getSessionToken());
-    }
-}
diff --git a/media-compat/build.gradle b/media-compat/build.gradle
index b832de6..18ff5a3 100644
--- a/media-compat/build.gradle
+++ b/media-compat/build.gradle
@@ -10,8 +10,8 @@
     api(project(":support-annotations"))
     api(project(":support-compat"))
 
-    androidTestImplementation(TEST_RUNNER, libs.exclude_annotations)
-    androidTestImplementation(ESPRESSO_CORE, libs.exclude_annotations)
+    androidTestImplementation(TEST_RUNNER)
+    androidTestImplementation(ESPRESSO_CORE)
     androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
     androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
     androidTestImplementation project(':support-testutils')
diff --git a/media-compat/java/android/support/v4/media/session/IMediaSession.aidl b/media-compat/java/android/support/v4/media/session/IMediaSession.aidl
index 56c04ac..f32f449 100644
--- a/media-compat/java/android/support/v4/media/session/IMediaSession.aidl
+++ b/media-compat/java/android/support/v4/media/session/IMediaSession.aidl
@@ -30,7 +30,7 @@
 import java.util.List;
 
 /**
- * Interface to a MediaSessionCompat. This is only used on pre-Lollipop systems.
+ * Interface to a MediaSessionCompat.
  * @hide
  */
 interface IMediaSession {
diff --git a/media-compat/java/android/support/v4/media/session/MediaControllerCompat.java b/media-compat/java/android/support/v4/media/session/MediaControllerCompat.java
index 2509cd4..f24da1e 100644
--- a/media-compat/java/android/support/v4/media/session/MediaControllerCompat.java
+++ b/media-compat/java/android/support/v4/media/session/MediaControllerCompat.java
@@ -1919,6 +1919,7 @@
                 }
             } else {
                 synchronized (mPendingCallbacks) {
+                    callback.mHasExtraCallback = false;
                     mPendingCallbacks.add(callback);
                 }
             }
@@ -1931,6 +1932,7 @@
                 try {
                     ExtraCallback extraCallback = mCallbackMap.remove(callback);
                     if (extraCallback != null) {
+                        callback.mHasExtraCallback = false;
                         mExtraBinder.unregisterCallbackListener(extraCallback);
                     }
                 } catch (RemoteException e) {
diff --git a/media-compat/tests/AndroidManifest.xml b/media-compat/tests/AndroidManifest.xml
deleted file mode 100644
index 6d9a4f6..0000000
--- a/media-compat/tests/AndroidManifest.xml
+++ /dev/null
@@ -1,46 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-   Copyright (C) 2016 The Android Open Source Project
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-        http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
--->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.support.mediacompat.test">
-    <uses-sdk android:targetSdkVersion="${target-sdk-version}"/>
-
-    <application android:supportsRtl="true">
-        <receiver android:name="android.support.v4.media.session.MediaButtonReceiver">
-            <intent-filter>
-                <action android:name="android.intent.action.MEDIA_BUTTON"/>
-            </intent-filter>
-        </receiver>
-        <service android:name="android.support.v4.media.StubMediaBrowserServiceCompat">
-            <intent-filter>
-                <action android:name="android.media.browse.MediaBrowserService"/>
-            </intent-filter>
-        </service>
-        <service android:name="android.support.v4.media.StubRemoteMediaBrowserServiceCompat"
-                 android:process=":remote">
-            <intent-filter>
-                <action android:name="android.media.browse.MediaBrowserService"/>
-            </intent-filter>
-        </service>
-        <service
-            android:name="android.support.v4.media.StubMediaBrowserServiceCompatWithDelayedMediaSession">
-            <intent-filter>
-                <action android:name="android.media.browse.MediaBrowserService"/>
-            </intent-filter>
-        </service>
-    </application>
-
-</manifest>
diff --git a/media-compat/tests/src/android/support/v4/media/AudioAttributesCompatTest.java b/media-compat/tests/src/android/support/v4/media/AudioAttributesCompatTest.java
deleted file mode 100644
index d66c7fc..0000000
--- a/media-compat/tests/src/android/support/v4/media/AudioAttributesCompatTest.java
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.support.v4.media;
-
-import static org.hamcrest.core.IsEqual.equalTo;
-import static org.hamcrest.core.IsNot.not;
-import static org.junit.Assert.assertThat;
-
-import android.media.AudioAttributes;
-import android.media.AudioManager;
-import android.os.Build;
-import android.support.test.filters.SdkSuppress;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/** Test {@link AudioAttributesCompat}. */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class AudioAttributesCompatTest {
-    // some macros for conciseness
-    static final AudioAttributesCompat.Builder mkBuilder(
-            @AudioAttributesCompat.AttributeContentType int type,
-            @AudioAttributesCompat.AttributeUsage int usage) {
-        return new AudioAttributesCompat.Builder().setContentType(type).setUsage(usage);
-    }
-
-    static final AudioAttributesCompat.Builder mkBuilder(int legacyStream) {
-        return new AudioAttributesCompat.Builder().setLegacyStreamType(legacyStream);
-    }
-
-    // some objects we'll toss around
-    Object mMediaAA;
-    AudioAttributesCompat mMediaAAC,
-            mMediaLegacyAAC,
-            mMediaAACFromAA,
-            mNotificationAAC,
-            mNotificationLegacyAAC;
-
-    @Before
-    @SdkSuppress(minSdkVersion = 21)
-    public void setUpApi21() {
-        if (Build.VERSION.SDK_INT < 21) return;
-        mMediaAA =
-                new AudioAttributes.Builder()
-                        .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
-                        .setUsage(AudioAttributes.USAGE_MEDIA)
-                        .build();
-        mMediaAACFromAA = AudioAttributesCompat.wrap((AudioAttributes) mMediaAA);
-    }
-
-    @Before
-    public void setUp() {
-        mMediaAAC =
-                mkBuilder(AudioAttributesCompat.CONTENT_TYPE_MUSIC,
-                        AudioAttributesCompat.USAGE_MEDIA).build();
-        mMediaLegacyAAC = mkBuilder(AudioManager.STREAM_MUSIC).build();
-        mNotificationAAC =
-                mkBuilder(AudioAttributesCompat.CONTENT_TYPE_SONIFICATION,
-                        AudioAttributesCompat.USAGE_NOTIFICATION)
-                        .build();
-        mNotificationLegacyAAC = mkBuilder(AudioManager.STREAM_NOTIFICATION).build();
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 21)
-    public void testCreateWithAudioAttributesApi21() {
-        assertThat(mMediaAACFromAA, not(equalTo(null)));
-        assertThat((AudioAttributes) mMediaAACFromAA.unwrap(), equalTo(mMediaAA));
-        assertThat(
-                (AudioAttributes) mMediaAACFromAA.unwrap(),
-                equalTo(new AudioAttributes.Builder((AudioAttributes) mMediaAA).build()));
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 21)
-    public void testEqualityApi21() {
-        assertThat("self equality", mMediaAACFromAA, equalTo(mMediaAACFromAA));
-        assertThat("different things", mMediaAACFromAA, not(equalTo(mNotificationAAC)));
-    }
-
-    @Test
-    public void testEquality() {
-        assertThat("self equality", mMediaAAC, equalTo(mMediaAAC));
-        assertThat(
-                "equal to clone",
-                mMediaAAC,
-                equalTo(new AudioAttributesCompat.Builder(mMediaAAC).build()));
-        assertThat("different things are different", mMediaAAC, not(equalTo(mNotificationAAC)));
-        assertThat("different things are different 2", mNotificationAAC, not(equalTo(mMediaAAC)));
-        assertThat(
-                "equal to clone 2",
-                mNotificationAAC,
-                equalTo(new AudioAttributesCompat.Builder(mNotificationAAC).build()));
-    }
-
-    @Test
-    public void testGetters() {
-        assertThat(mMediaAAC.getContentType(), equalTo(AudioAttributesCompat.CONTENT_TYPE_MUSIC));
-        assertThat(mMediaAAC.getUsage(), equalTo(AudioAttributesCompat.USAGE_MEDIA));
-        assertThat(mMediaAAC.getFlags(), equalTo(0));
-    }
-
-    @Test
-    public void testLegacyStreamTypeInference() {
-        assertThat(mMediaAAC.getLegacyStreamType(), equalTo(AudioManager.STREAM_MUSIC));
-        assertThat(mMediaLegacyAAC.getLegacyStreamType(), equalTo(AudioManager.STREAM_MUSIC));
-        assertThat(
-                mNotificationAAC.getLegacyStreamType(), equalTo(AudioManager.STREAM_NOTIFICATION));
-        assertThat(
-                mNotificationLegacyAAC.getLegacyStreamType(),
-                equalTo(AudioManager.STREAM_NOTIFICATION));
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 21)
-    public void testLegacyStreamTypeInferenceApi21() {
-        assertThat(mMediaAACFromAA.getLegacyStreamType(), equalTo(AudioManager.STREAM_MUSIC));
-    }
-
-    @Test
-    public void testLegacyStreamTypeInferenceInLegacyMode() {
-        // the builders behave differently based on the value of this only-for-testing global
-        // so we need our very own objects inside this method
-        AudioAttributesCompat.setForceLegacyBehavior(true);
-
-        AudioAttributesCompat mediaAAC =
-                mkBuilder(AudioAttributesCompat.CONTENT_TYPE_MUSIC,
-                        AudioAttributesCompat.USAGE_MEDIA).build();
-        AudioAttributesCompat mediaLegacyAAC = mkBuilder(AudioManager.STREAM_MUSIC).build();
-
-        AudioAttributesCompat notificationAAC =
-                mkBuilder(AudioAttributesCompat.CONTENT_TYPE_SONIFICATION,
-                        AudioAttributesCompat.USAGE_NOTIFICATION)
-                        .build();
-        AudioAttributesCompat notificationLegacyAAC =
-                mkBuilder(AudioManager.STREAM_NOTIFICATION).build();
-
-        assertThat(mediaAAC.getLegacyStreamType(), equalTo(AudioManager.STREAM_MUSIC));
-        assertThat(mediaLegacyAAC.getLegacyStreamType(), equalTo(AudioManager.STREAM_MUSIC));
-        assertThat(
-                notificationAAC.getLegacyStreamType(), equalTo(AudioManager.STREAM_NOTIFICATION));
-        assertThat(
-                notificationLegacyAAC.getLegacyStreamType(),
-                equalTo(AudioManager.STREAM_NOTIFICATION));
-    }
-
-    @After
-    public void cleanUp() {
-        AudioAttributesCompat.setForceLegacyBehavior(false);
-    }
-}
diff --git a/media-compat/tests/src/android/support/v4/media/MediaBrowserCompatTest.java b/media-compat/tests/src/android/support/v4/media/MediaBrowserCompatTest.java
deleted file mode 100644
index 6356e33..0000000
--- a/media-compat/tests/src/android/support/v4/media/MediaBrowserCompatTest.java
+++ /dev/null
@@ -1,786 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.v4.media;
-
-import static android.support.test.InstrumentationRegistry.getInstrumentation;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertNotNull;
-import static junit.framework.Assert.assertNull;
-import static junit.framework.Assert.assertTrue;
-import static junit.framework.Assert.fail;
-
-import android.content.ComponentName;
-import android.os.Bundle;
-import android.os.Handler;
-import android.support.test.filters.LargeTest;
-import android.support.test.filters.MediumTest;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-import android.support.testutils.PollingCheck;
-import android.support.v4.media.MediaBrowserCompat.MediaItem;
-import android.support.v4.media.session.MediaControllerCompat;
-import android.support.v4.media.session.MediaSessionCompat;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Test {@link android.support.v4.media.MediaBrowserCompat}.
- */
-@RunWith(AndroidJUnit4.class)
-public class MediaBrowserCompatTest {
-
-    // The maximum time to wait for an operation.
-    private static final long TIME_OUT_MS = 3000L;
-
-    /**
-     * To check {@link MediaBrowserCompat#unsubscribe} works properly,
-     * we notify to the browser after the unsubscription that the media items have changed.
-     * Then {@link MediaBrowserCompat.SubscriptionCallback#onChildrenLoaded} should not be called.
-     *
-     * The measured time from calling {@link StubMediaBrowserServiceCompat#notifyChildrenChanged}
-     * to {@link MediaBrowserCompat.SubscriptionCallback#onChildrenLoaded} being called is about
-     * 50ms.
-     * So we make the thread sleep for 100ms to properly check that the callback is not called.
-     */
-    private static final long SLEEP_MS = 100L;
-    private static final ComponentName TEST_BROWSER_SERVICE = new ComponentName(
-            "android.support.mediacompat.test",
-            "android.support.v4.media.StubMediaBrowserServiceCompat");
-    private static final ComponentName TEST_REMOTE_BROWSER_SERVICE = new ComponentName(
-            "android.support.mediacompat.test",
-            "android.support.v4.media.StubRemoteMediaBrowserServiceCompat");
-    private static final ComponentName TEST_INVALID_BROWSER_SERVICE = new ComponentName(
-            "invalid.package", "invalid.ServiceClassName");
-
-    private MediaBrowserCompat mMediaBrowser;
-    private StubConnectionCallback mConnectionCallback;
-    private StubSubscriptionCallback mSubscriptionCallback;
-    private StubItemCallback mItemCallback;
-
-    @Before
-    public void setUp() {
-        mConnectionCallback = new StubConnectionCallback();
-        mSubscriptionCallback = new StubSubscriptionCallback();
-        mItemCallback = new StubItemCallback();
-    }
-
-    @Test
-    @SmallTest
-    public void testMediaBrowser() throws Exception {
-        createMediaBrowser(TEST_BROWSER_SERVICE);
-        assertFalse(mMediaBrowser.isConnected());
-
-        connectMediaBrowserService();
-        assertTrue(mMediaBrowser.isConnected());
-
-        assertEquals(TEST_BROWSER_SERVICE, mMediaBrowser.getServiceComponent());
-        assertEquals(StubMediaBrowserServiceCompat.MEDIA_ID_ROOT, mMediaBrowser.getRoot());
-        assertEquals(StubMediaBrowserServiceCompat.EXTRAS_VALUE,
-                mMediaBrowser.getExtras().getString(StubMediaBrowserServiceCompat.EXTRAS_KEY));
-        assertEquals(StubMediaBrowserServiceCompat.sSession.getSessionToken(),
-                mMediaBrowser.getSessionToken());
-
-        mMediaBrowser.disconnect();
-        new PollingCheck(TIME_OUT_MS) {
-            @Override
-            protected boolean check() {
-                return !mMediaBrowser.isConnected();
-            }
-        }.run();
-    }
-
-    @Test
-    @SmallTest
-    public void testMediaBrowserWithRemoteService() throws Exception {
-        createMediaBrowser(TEST_REMOTE_BROWSER_SERVICE);
-        assertFalse(mMediaBrowser.isConnected());
-
-        connectMediaBrowserService();
-        assertTrue(mMediaBrowser.isConnected());
-
-        assertEquals(TEST_REMOTE_BROWSER_SERVICE, mMediaBrowser.getServiceComponent());
-        assertEquals(StubRemoteMediaBrowserServiceCompat.MEDIA_ID_ROOT, mMediaBrowser.getRoot());
-        assertEquals(StubRemoteMediaBrowserServiceCompat.EXTRAS_VALUE,
-                mMediaBrowser.getExtras().getString(
-                        StubRemoteMediaBrowserServiceCompat.EXTRAS_KEY));
-
-        mMediaBrowser.disconnect();
-        new PollingCheck(TIME_OUT_MS) {
-            @Override
-            protected boolean check() {
-                return !mMediaBrowser.isConnected();
-            }
-        }.run();
-    }
-
-    @Test
-    @SmallTest
-    public void testSessionReadyWithRemoteService() throws Exception {
-        if (android.os.Build.VERSION.SDK_INT < 21) {
-            // This test is for API 21+
-            return;
-        }
-
-        createMediaBrowser(TEST_REMOTE_BROWSER_SERVICE);
-        assertFalse(mMediaBrowser.isConnected());
-
-        connectMediaBrowserService();
-        assertTrue(mMediaBrowser.isConnected());
-
-        // Create a session token by removing the extra binder of the token from MediaBrowserCompat.
-        final MediaSessionCompat.Token tokenWithoutExtraBinder = MediaSessionCompat.Token.fromToken(
-                mMediaBrowser.getSessionToken().getToken());
-
-        final MediaControllerCallback callback = new MediaControllerCallback();
-        synchronized (callback.mWaitLock) {
-            getInstrumentation().runOnMainSync(new Runnable() {
-                @Override
-                public void run() {
-                    try {
-                        MediaControllerCompat controller = new MediaControllerCompat(
-                                getInstrumentation().getTargetContext(), tokenWithoutExtraBinder);
-                        controller.registerCallback(callback, new Handler());
-                        assertFalse(controller.isSessionReady());
-                    } catch (Exception e) {
-                        fail();
-                    }
-                }
-            });
-            callback.mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(callback.mOnSessionReadyCalled);
-        }
-
-        mMediaBrowser.disconnect();
-    }
-
-    @Test
-    @SmallTest
-    public void testSubscriptionWithCustomOptionsWithRemoteService() throws Exception {
-        final String mediaId = "1000";
-        createMediaBrowser(TEST_REMOTE_BROWSER_SERVICE);
-        assertFalse(mMediaBrowser.isConnected());
-
-        connectMediaBrowserService();
-        assertTrue(mMediaBrowser.isConnected());
-
-        MediaMetadataCompat mediaMetadataCompat = new MediaMetadataCompat.Builder()
-                .putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, mediaId)
-                .putString(MediaMetadataCompat.METADATA_KEY_TITLE, "title")
-                .putRating(MediaMetadataCompat.METADATA_KEY_RATING,
-                        RatingCompat.newPercentageRating(0.5f))
-                .putRating(MediaMetadataCompat.METADATA_KEY_USER_RATING,
-                        RatingCompat.newPercentageRating(0.8f))
-                .build();
-        Bundle options = new Bundle();
-        options.putParcelable(
-                StubRemoteMediaBrowserServiceCompat.MEDIA_METADATA, mediaMetadataCompat);
-
-        // Remote MediaBrowserService will create a media item with the given MediaMetadataCompat
-        mMediaBrowser.subscribe(mMediaBrowser.getRoot(), options, mSubscriptionCallback);
-        synchronized (mSubscriptionCallback.mWaitLock) {
-            mSubscriptionCallback.mWaitLock.wait(TIME_OUT_MS);
-            assertEquals(1, mSubscriptionCallback.mChildrenLoadedWithOptionCount);
-            assertEquals(1, mSubscriptionCallback.mLastChildMediaItems.size());
-            assertEquals(mediaId, mSubscriptionCallback.mLastChildMediaItems.get(0).getMediaId());
-        }
-        mMediaBrowser.disconnect();
-    }
-
-    @Test
-    @SmallTest
-    public void testConnectTwice() throws Exception {
-        createMediaBrowser(TEST_BROWSER_SERVICE);
-        connectMediaBrowserService();
-        try {
-            mMediaBrowser.connect();
-            fail();
-        } catch (IllegalStateException e) {
-            // expected
-        }
-        mMediaBrowser.disconnect();
-    }
-
-    @Test
-    @SmallTest
-    public void testConnectionFailed() throws Exception {
-        createMediaBrowser(TEST_INVALID_BROWSER_SERVICE);
-
-        synchronized (mConnectionCallback.mWaitLock) {
-            mMediaBrowser.connect();
-            mConnectionCallback.mWaitLock.wait(TIME_OUT_MS);
-        }
-        assertTrue(mConnectionCallback.mConnectionFailedCount > 0);
-        assertEquals(0, mConnectionCallback.mConnectedCount);
-        assertEquals(0, mConnectionCallback.mConnectionSuspendedCount);
-        mMediaBrowser.disconnect();
-    }
-
-    @Test
-    @SmallTest
-    public void testReconnection() throws Exception {
-        createMediaBrowser(TEST_BROWSER_SERVICE);
-
-        getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                mMediaBrowser.connect();
-                // Reconnect before the first connection was established.
-                mMediaBrowser.disconnect();
-                mMediaBrowser.connect();
-            }
-        });
-
-        synchronized (mConnectionCallback.mWaitLock) {
-            mConnectionCallback.mWaitLock.wait(TIME_OUT_MS);
-            assertEquals(1, mConnectionCallback.mConnectedCount);
-        }
-
-        synchronized (mSubscriptionCallback.mWaitLock) {
-            // Test subscribe.
-            resetCallbacks();
-            mMediaBrowser.subscribe(
-                    StubMediaBrowserServiceCompat.MEDIA_ID_ROOT, mSubscriptionCallback);
-            mSubscriptionCallback.mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mSubscriptionCallback.mChildrenLoadedCount > 0);
-            assertEquals(StubMediaBrowserServiceCompat.MEDIA_ID_ROOT,
-                    mSubscriptionCallback.mLastParentId);
-        }
-
-        synchronized (mItemCallback.mWaitLock) {
-            // Test getItem.
-            resetCallbacks();
-            mMediaBrowser.getItem(
-                    StubMediaBrowserServiceCompat.MEDIA_ID_CHILDREN[0], mItemCallback);
-            mItemCallback.mWaitLock.wait(TIME_OUT_MS);
-            assertEquals(StubMediaBrowserServiceCompat.MEDIA_ID_CHILDREN[0],
-                    mItemCallback.mLastMediaItem.getMediaId());
-        }
-
-        // Reconnect after connection was established.
-        mMediaBrowser.disconnect();
-        resetCallbacks();
-        connectMediaBrowserService();
-
-        synchronized (mItemCallback.mWaitLock) {
-            // Test getItem.
-            resetCallbacks();
-            mMediaBrowser.getItem(
-                    StubMediaBrowserServiceCompat.MEDIA_ID_CHILDREN[0], mItemCallback);
-            mItemCallback.mWaitLock.wait(TIME_OUT_MS);
-            assertEquals(StubMediaBrowserServiceCompat.MEDIA_ID_CHILDREN[0],
-                    mItemCallback.mLastMediaItem.getMediaId());
-        }
-        mMediaBrowser.disconnect();
-    }
-
-    @Test
-    @SmallTest
-    public void testConnectionCallbackNotCalledAfterDisconnect() {
-        createMediaBrowser(TEST_BROWSER_SERVICE);
-
-        getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                mMediaBrowser.connect();
-                mMediaBrowser.disconnect();
-                resetCallbacks();
-            }
-        });
-
-        try {
-            Thread.sleep(SLEEP_MS);
-        } catch (InterruptedException e) {
-            fail("Unexpected InterruptedException occurred.");
-        }
-        assertEquals(0, mConnectionCallback.mConnectedCount);
-        assertEquals(0, mConnectionCallback.mConnectionFailedCount);
-        assertEquals(0, mConnectionCallback.mConnectionSuspendedCount);
-    }
-
-    @Test
-    @SmallTest
-    public void testGetServiceComponentBeforeConnection() {
-        createMediaBrowser(TEST_BROWSER_SERVICE);
-        try {
-            ComponentName serviceComponent = mMediaBrowser.getServiceComponent();
-            fail();
-        } catch (IllegalStateException e) {
-            // expected
-        }
-    }
-
-    @Test
-    @SmallTest
-    public void testSubscribe() throws Exception {
-        createMediaBrowser(TEST_BROWSER_SERVICE);
-        connectMediaBrowserService();
-
-        synchronized (mSubscriptionCallback.mWaitLock) {
-            mMediaBrowser.subscribe(
-                    StubMediaBrowserServiceCompat.MEDIA_ID_ROOT, mSubscriptionCallback);
-            mSubscriptionCallback.mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mSubscriptionCallback.mChildrenLoadedCount > 0);
-            assertEquals(StubMediaBrowserServiceCompat.MEDIA_ID_ROOT,
-                    mSubscriptionCallback.mLastParentId);
-            assertEquals(StubMediaBrowserServiceCompat.MEDIA_ID_CHILDREN.length,
-                    mSubscriptionCallback.mLastChildMediaItems.size());
-            for (int i = 0; i < StubMediaBrowserServiceCompat.MEDIA_ID_CHILDREN.length; ++i) {
-                assertEquals(StubMediaBrowserServiceCompat.MEDIA_ID_CHILDREN[i],
-                        mSubscriptionCallback.mLastChildMediaItems.get(i).getMediaId());
-            }
-        }
-
-        // Test unsubscribe.
-        resetCallbacks();
-        mMediaBrowser.unsubscribe(StubMediaBrowserServiceCompat.MEDIA_ID_ROOT);
-
-        // After unsubscribing, make StubMediaBrowserServiceCompat notify that the children are
-        // changed.
-        StubMediaBrowserServiceCompat.sInstance.notifyChildrenChanged(
-                StubMediaBrowserServiceCompat.MEDIA_ID_ROOT);
-        try {
-            Thread.sleep(SLEEP_MS);
-        } catch (InterruptedException e) {
-            fail("Unexpected InterruptedException occurred.");
-        }
-        // onChildrenLoaded should not be called.
-        assertEquals(0, mSubscriptionCallback.mChildrenLoadedCount);
-        mMediaBrowser.disconnect();
-    }
-
-    @Test
-    @SmallTest
-    public void testSubscribeWithOptions() throws Exception {
-        createMediaBrowser(TEST_BROWSER_SERVICE);
-        connectMediaBrowserService();
-        final int pageSize = 3;
-        final int lastPage =
-                (StubMediaBrowserServiceCompat.MEDIA_ID_CHILDREN.length - 1) / pageSize;
-        Bundle options = new Bundle();
-        options.putInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, pageSize);
-
-        synchronized (mSubscriptionCallback.mWaitLock) {
-            for (int page = 0; page <= lastPage; ++page) {
-                resetCallbacks();
-                options.putInt(MediaBrowserCompat.EXTRA_PAGE, page);
-                mMediaBrowser.subscribe(StubMediaBrowserServiceCompat.MEDIA_ID_ROOT, options,
-                        mSubscriptionCallback);
-                mSubscriptionCallback.mWaitLock.wait(TIME_OUT_MS);
-                assertTrue(mSubscriptionCallback.mChildrenLoadedWithOptionCount > 0);
-                assertEquals(StubMediaBrowserServiceCompat.MEDIA_ID_ROOT,
-                        mSubscriptionCallback.mLastParentId);
-                if (page != lastPage) {
-                    assertEquals(pageSize, mSubscriptionCallback.mLastChildMediaItems.size());
-                } else {
-                    assertEquals(
-                            (StubMediaBrowserServiceCompat.MEDIA_ID_CHILDREN.length - 1) % pageSize
-                                    + 1,
-                            mSubscriptionCallback.mLastChildMediaItems.size());
-                }
-                // Check whether all the items in the current page are loaded.
-                for (int i = 0; i < mSubscriptionCallback.mLastChildMediaItems.size(); ++i) {
-                    assertEquals(
-                            StubMediaBrowserServiceCompat.MEDIA_ID_CHILDREN[page * pageSize + i],
-                            mSubscriptionCallback.mLastChildMediaItems.get(i).getMediaId());
-                }
-            }
-        }
-
-        // Test unsubscribe with callback argument.
-        resetCallbacks();
-        mMediaBrowser.unsubscribe(StubMediaBrowserServiceCompat.MEDIA_ID_ROOT,
-                mSubscriptionCallback);
-
-        // After unsubscribing, make StubMediaBrowserServiceCompat notify that the children are
-        // changed.
-        StubMediaBrowserServiceCompat.sInstance.notifyChildrenChanged(
-                StubMediaBrowserServiceCompat.MEDIA_ID_ROOT);
-        try {
-            Thread.sleep(SLEEP_MS);
-        } catch (InterruptedException e) {
-            fail("Unexpected InterruptedException occurred.");
-        }
-        // onChildrenLoaded should not be called.
-        assertEquals(0, mSubscriptionCallback.mChildrenLoadedCount);
-        mMediaBrowser.disconnect();
-    }
-
-    @Test
-    @SmallTest
-    public void testSubscribeInvalidItem() throws Exception {
-        createMediaBrowser(TEST_BROWSER_SERVICE);
-        connectMediaBrowserService();
-
-        synchronized (mSubscriptionCallback.mWaitLock) {
-            mMediaBrowser.subscribe(StubMediaBrowserServiceCompat.MEDIA_ID_INVALID,
-                    mSubscriptionCallback);
-            mSubscriptionCallback.mWaitLock.wait(TIME_OUT_MS);
-            assertEquals(StubMediaBrowserServiceCompat.MEDIA_ID_INVALID,
-                    mSubscriptionCallback.mLastErrorId);
-        }
-        mMediaBrowser.disconnect();
-    }
-
-    @Test
-    @SmallTest
-    public void testSubscribeInvalidItemWithOptions() throws Exception {
-        createMediaBrowser(TEST_BROWSER_SERVICE);
-        connectMediaBrowserService();
-
-        final int pageSize = 5;
-        final int page = 2;
-        Bundle options = new Bundle();
-        options.putInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, pageSize);
-        options.putInt(MediaBrowserCompat.EXTRA_PAGE, page);
-
-        synchronized (mSubscriptionCallback.mWaitLock) {
-            mMediaBrowser.subscribe(StubMediaBrowserServiceCompat.MEDIA_ID_INVALID, options,
-                    mSubscriptionCallback);
-            mSubscriptionCallback.mWaitLock.wait(TIME_OUT_MS);
-            assertEquals(StubMediaBrowserServiceCompat.MEDIA_ID_INVALID,
-                    mSubscriptionCallback.mLastErrorId);
-            assertNotNull(mSubscriptionCallback.mLastOptions);
-            assertEquals(page,
-                    mSubscriptionCallback.mLastOptions.getInt(MediaBrowserCompat.EXTRA_PAGE));
-            assertEquals(pageSize,
-                    mSubscriptionCallback.mLastOptions.getInt(MediaBrowserCompat.EXTRA_PAGE_SIZE));
-        }
-        mMediaBrowser.disconnect();
-    }
-
-    @Test
-    @SmallTest
-    public void testUnsubscribeForMultipleSubscriptions() throws Exception {
-        createMediaBrowser(TEST_BROWSER_SERVICE);
-        connectMediaBrowserService();
-        final List<StubSubscriptionCallback> subscriptionCallbacks = new ArrayList<>();
-        final int pageSize = 1;
-
-        // Subscribe four pages, one item per page.
-        for (int page = 0; page < 4; page++) {
-            final StubSubscriptionCallback callback = new StubSubscriptionCallback();
-            subscriptionCallbacks.add(callback);
-
-            Bundle options = new Bundle();
-            options.putInt(MediaBrowserCompat.EXTRA_PAGE, page);
-            options.putInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, pageSize);
-            mMediaBrowser.subscribe(StubMediaBrowserServiceCompat.MEDIA_ID_ROOT, options,
-                    callback);
-            synchronized (callback.mWaitLock) {
-                callback.mWaitLock.wait(TIME_OUT_MS);
-            }
-            // Each onChildrenLoaded() must be called.
-            assertEquals(1, callback.mChildrenLoadedWithOptionCount);
-        }
-
-        // Reset callbacks and unsubscribe.
-        for (StubSubscriptionCallback callback : subscriptionCallbacks) {
-            callback.reset();
-        }
-        mMediaBrowser.unsubscribe(StubMediaBrowserServiceCompat.MEDIA_ID_ROOT);
-
-        // After unsubscribing, make StubMediaBrowserServiceCompat notify that the children are
-        // changed.
-        StubMediaBrowserServiceCompat.sInstance.notifyChildrenChanged(
-                StubMediaBrowserServiceCompat.MEDIA_ID_ROOT);
-        try {
-            Thread.sleep(SLEEP_MS);
-        } catch (InterruptedException e) {
-            fail("Unexpected InterruptedException occurred.");
-        }
-
-        // onChildrenLoaded should not be called.
-        for (StubSubscriptionCallback callback : subscriptionCallbacks) {
-            assertEquals(0, callback.mChildrenLoadedWithOptionCount);
-        }
-        mMediaBrowser.disconnect();
-    }
-
-    @Test
-    @MediumTest
-    public void testUnsubscribeWithSubscriptionCallbackForMultipleSubscriptions() throws Exception {
-        createMediaBrowser(TEST_BROWSER_SERVICE);
-        connectMediaBrowserService();
-        final List<StubSubscriptionCallback> subscriptionCallbacks = new ArrayList<>();
-        final int pageSize = 1;
-
-        // Subscribe four pages, one item per page.
-        for (int page = 0; page < 4; page++) {
-            final StubSubscriptionCallback callback = new StubSubscriptionCallback();
-            subscriptionCallbacks.add(callback);
-
-            Bundle options = new Bundle();
-            options.putInt(MediaBrowserCompat.EXTRA_PAGE, page);
-            options.putInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, pageSize);
-            mMediaBrowser.subscribe(StubMediaBrowserServiceCompat.MEDIA_ID_ROOT, options,
-                    callback);
-            synchronized (callback.mWaitLock) {
-                callback.mWaitLock.wait(TIME_OUT_MS);
-            }
-            // Each onChildrenLoaded() must be called.
-            assertEquals(1, callback.mChildrenLoadedWithOptionCount);
-        }
-
-        // Unsubscribe existing subscriptions one-by-one.
-        final int[] orderOfRemovingCallbacks = {2, 0, 3, 1};
-        for (int i = 0; i < orderOfRemovingCallbacks.length; i++) {
-            // Reset callbacks
-            for (StubSubscriptionCallback callback : subscriptionCallbacks) {
-                callback.reset();
-            }
-
-            // Remove one subscription
-            mMediaBrowser.unsubscribe(StubMediaBrowserServiceCompat.MEDIA_ID_ROOT,
-                    subscriptionCallbacks.get(orderOfRemovingCallbacks[i]));
-
-            // Make StubMediaBrowserServiceCompat notify that the children are changed.
-            StubMediaBrowserServiceCompat.sInstance.notifyChildrenChanged(
-                    StubMediaBrowserServiceCompat.MEDIA_ID_ROOT);
-            try {
-                Thread.sleep(SLEEP_MS);
-            } catch (InterruptedException e) {
-                fail("Unexpected InterruptedException occurred.");
-            }
-
-            // Only the remaining subscriptionCallbacks should be called.
-            for (int j = 0; j < 4; j++) {
-                int childrenLoadedWithOptionsCount = subscriptionCallbacks
-                        .get(orderOfRemovingCallbacks[j]).mChildrenLoadedWithOptionCount;
-                if (j <= i) {
-                    assertEquals(0, childrenLoadedWithOptionsCount);
-                } else {
-                    assertEquals(1, childrenLoadedWithOptionsCount);
-                }
-            }
-        }
-        mMediaBrowser.disconnect();
-    }
-
-    @Test
-    @SmallTest
-    public void testGetItem() throws Exception {
-        createMediaBrowser(TEST_BROWSER_SERVICE);
-        connectMediaBrowserService();
-
-        synchronized (mItemCallback.mWaitLock) {
-            mMediaBrowser.getItem(StubMediaBrowserServiceCompat.MEDIA_ID_CHILDREN[0],
-                    mItemCallback);
-            mItemCallback.mWaitLock.wait(TIME_OUT_MS);
-            assertNotNull(mItemCallback.mLastMediaItem);
-            assertEquals(StubMediaBrowserServiceCompat.MEDIA_ID_CHILDREN[0],
-                    mItemCallback.mLastMediaItem.getMediaId());
-        }
-        mMediaBrowser.disconnect();
-    }
-
-    @Test
-    @LargeTest
-    public void testGetItemWhenOnLoadItemIsNotImplemented() throws Exception {
-        createMediaBrowser(TEST_BROWSER_SERVICE);
-        connectMediaBrowserService();
-        synchronized (mItemCallback.mWaitLock) {
-            mMediaBrowser.getItem(
-                    StubMediaBrowserServiceCompat.MEDIA_ID_ON_LOAD_ITEM_NOT_IMPLEMENTED,
-                    mItemCallback);
-            mItemCallback.mWaitLock.wait(TIME_OUT_MS);
-            assertEquals(StubMediaBrowserServiceCompat.MEDIA_ID_ON_LOAD_ITEM_NOT_IMPLEMENTED,
-                    mItemCallback.mLastErrorId);
-        }
-        mMediaBrowser.disconnect();
-    }
-
-    @Test
-    @SmallTest
-    public void testGetItemWhenMediaIdIsInvalid() throws Exception {
-        mItemCallback.mLastMediaItem = new MediaItem(new MediaDescriptionCompat.Builder()
-                .setMediaId("dummy_id").build(), MediaItem.FLAG_BROWSABLE);
-
-        createMediaBrowser(TEST_BROWSER_SERVICE);
-        connectMediaBrowserService();
-        synchronized (mItemCallback.mWaitLock) {
-            mMediaBrowser.getItem(StubMediaBrowserServiceCompat.MEDIA_ID_INVALID, mItemCallback);
-            mItemCallback.mWaitLock.wait(TIME_OUT_MS);
-            assertNull(mItemCallback.mLastMediaItem);
-            assertNull(mItemCallback.mLastErrorId);
-        }
-        mMediaBrowser.disconnect();
-    }
-
-    private void createMediaBrowser(final ComponentName component) {
-        getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                mMediaBrowser = new MediaBrowserCompat(getInstrumentation().getTargetContext(),
-                        component, mConnectionCallback, null);
-            }
-        });
-    }
-
-    private void connectMediaBrowserService() throws Exception {
-        synchronized (mConnectionCallback.mWaitLock) {
-            mMediaBrowser.connect();
-            mConnectionCallback.mWaitLock.wait(TIME_OUT_MS);
-        }
-    }
-
-    private void resetCallbacks() {
-        mConnectionCallback.reset();
-        mSubscriptionCallback.reset();
-        mItemCallback.reset();
-    }
-
-    private class StubConnectionCallback extends MediaBrowserCompat.ConnectionCallback {
-        Object mWaitLock = new Object();
-        volatile int mConnectedCount;
-        volatile int mConnectionFailedCount;
-        volatile int mConnectionSuspendedCount;
-
-        public void reset() {
-            mConnectedCount = 0;
-            mConnectionFailedCount = 0;
-            mConnectionSuspendedCount = 0;
-        }
-
-        @Override
-        public void onConnected() {
-            synchronized (mWaitLock) {
-                mConnectedCount++;
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onConnectionFailed() {
-            synchronized (mWaitLock) {
-                mConnectionFailedCount++;
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onConnectionSuspended() {
-            synchronized (mWaitLock) {
-                mConnectionSuspendedCount++;
-                mWaitLock.notify();
-            }
-        }
-    }
-
-    private class StubSubscriptionCallback extends MediaBrowserCompat.SubscriptionCallback {
-        Object mWaitLock = new Object();
-        private volatile int mChildrenLoadedCount;
-        private volatile int mChildrenLoadedWithOptionCount;
-        private volatile String mLastErrorId;
-        private volatile String mLastParentId;
-        private volatile Bundle mLastOptions;
-        private volatile List<MediaItem> mLastChildMediaItems;
-
-        public void reset() {
-            mChildrenLoadedCount = 0;
-            mChildrenLoadedWithOptionCount = 0;
-            mLastErrorId = null;
-            mLastParentId = null;
-            mLastOptions = null;
-            mLastChildMediaItems = null;
-        }
-
-        @Override
-        public void onChildrenLoaded(String parentId, List<MediaItem> children) {
-            synchronized (mWaitLock) {
-                mChildrenLoadedCount++;
-                mLastParentId = parentId;
-                mLastChildMediaItems = children;
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onChildrenLoaded(String parentId, List<MediaItem> children, Bundle options) {
-            synchronized (mWaitLock) {
-                mChildrenLoadedWithOptionCount++;
-                mLastParentId = parentId;
-                mLastOptions = options;
-                mLastChildMediaItems = children;
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onError(String id) {
-            synchronized (mWaitLock) {
-                mLastErrorId = id;
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onError(String id, Bundle options) {
-            synchronized (mWaitLock) {
-                mLastErrorId = id;
-                mLastOptions = options;
-                mWaitLock.notify();
-            }
-        }
-    }
-
-    private class StubItemCallback extends MediaBrowserCompat.ItemCallback {
-        Object mWaitLock = new Object();
-        private volatile MediaItem mLastMediaItem;
-        private volatile String mLastErrorId;
-
-        public void reset() {
-            mLastMediaItem = null;
-            mLastErrorId = null;
-        }
-
-        @Override
-        public void onItemLoaded(MediaItem item) {
-            synchronized (mWaitLock) {
-                mLastMediaItem = item;
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onError(String id) {
-            synchronized (mWaitLock) {
-                mLastErrorId = id;
-                mWaitLock.notify();
-            }
-        }
-    }
-
-    private class MediaControllerCallback extends MediaControllerCompat.Callback {
-        Object mWaitLock = new Object();
-        private volatile boolean mOnSessionReadyCalled;
-
-        @Override
-        public void onSessionReady() {
-            synchronized (mWaitLock) {
-                mOnSessionReadyCalled = true;
-                mWaitLock.notify();
-            }
-        }
-    }
-}
diff --git a/media-compat/tests/src/android/support/v4/media/MediaBrowserServiceCompatTest.java b/media-compat/tests/src/android/support/v4/media/MediaBrowserServiceCompatTest.java
deleted file mode 100644
index 4ceac10..0000000
--- a/media-compat/tests/src/android/support/v4/media/MediaBrowserServiceCompatTest.java
+++ /dev/null
@@ -1,586 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.support.v4.media;
-
-import static android.support.test.InstrumentationRegistry.getInstrumentation;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertNotNull;
-import static junit.framework.Assert.assertNull;
-import static junit.framework.Assert.assertTrue;
-
-import android.content.ComponentName;
-import android.os.Build;
-import android.os.Bundle;
-import android.support.test.filters.MediumTest;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-import android.support.testutils.PollingCheck;
-import android.support.v4.media.MediaBrowserCompat.MediaItem;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.List;
-
-/**
- * Test {@link android.support.v4.media.MediaBrowserServiceCompat}.
- */
-@RunWith(AndroidJUnit4.class)
-public class MediaBrowserServiceCompatTest {
-    // 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 = 500L;
-    private static final ComponentName TEST_BROWSER_SERVICE = new ComponentName(
-            "android.support.mediacompat.test",
-            "android.support.v4.media.StubMediaBrowserServiceCompat");
-    private static final ComponentName TEST_BROWSER_SERVICE_DELAYED_MEDIA_SESSION =
-            new ComponentName(
-                    "android.support.mediacompat.test",
-                    "android.support.v4.media"
-                            + ".StubMediaBrowserServiceCompatWithDelayedMediaSession");
-    private static final String TEST_KEY_1 = "key_1";
-    private static final String TEST_VALUE_1 = "value_1";
-    private static final String TEST_KEY_2 = "key_2";
-    private static final String TEST_VALUE_2 = "value_2";
-    private static final String TEST_KEY_3 = "key_3";
-    private static final String TEST_VALUE_3 = "value_3";
-    private static final String TEST_KEY_4 = "key_4";
-    private static final String TEST_VALUE_4 = "value_4";
-    private final Object mWaitLock = new Object();
-
-    private final ConnectionCallback mConnectionCallback = new ConnectionCallback();
-    private final SubscriptionCallback mSubscriptionCallback = new SubscriptionCallback();
-    private final ItemCallback mItemCallback = new ItemCallback();
-    private final SearchCallback mSearchCallback = new SearchCallback();
-
-    private MediaBrowserCompat mMediaBrowser;
-    private MediaBrowserCompat mMediaBrowserForDelayedMediaSession;
-    private StubMediaBrowserServiceCompat mMediaBrowserService;
-    private Bundle mRootHints;
-
-    @Before
-    public void setUp() throws Exception {
-        getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                mRootHints = new Bundle();
-                mRootHints.putBoolean(MediaBrowserServiceCompat.BrowserRoot.EXTRA_RECENT, true);
-                mRootHints.putBoolean(MediaBrowserServiceCompat.BrowserRoot.EXTRA_OFFLINE, true);
-                mRootHints.putBoolean(MediaBrowserServiceCompat.BrowserRoot.EXTRA_SUGGESTED, true);
-                mMediaBrowser = new MediaBrowserCompat(getInstrumentation().getTargetContext(),
-                        TEST_BROWSER_SERVICE, mConnectionCallback, mRootHints);
-            }
-        });
-        synchronized (mWaitLock) {
-            mMediaBrowser.connect();
-            mWaitLock.wait(TIME_OUT_MS);
-        }
-        assertNotNull(mMediaBrowserService);
-        mMediaBrowserService.mCustomActionExtras = null;
-        mMediaBrowserService.mCustomActionResult = null;
-    }
-
-    @Test
-    @SmallTest
-    public void testGetSessionToken() {
-        assertEquals(StubMediaBrowserServiceCompat.sSession.getSessionToken(),
-                mMediaBrowserService.getSessionToken());
-    }
-
-    @Test
-    @SmallTest
-    public void testNotifyChildrenChanged() throws Exception {
-        synchronized (mWaitLock) {
-            mSubscriptionCallback.reset();
-            mMediaBrowser.subscribe(
-                    StubMediaBrowserServiceCompat.MEDIA_ID_ROOT, mSubscriptionCallback);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mSubscriptionCallback.mOnChildrenLoaded);
-
-            mSubscriptionCallback.reset();
-            mMediaBrowserService.notifyChildrenChanged(StubMediaBrowserServiceCompat.MEDIA_ID_ROOT);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mSubscriptionCallback.mOnChildrenLoaded);
-        }
-    }
-
-    @Test
-    @SmallTest
-    public void testNotifyChildrenChangedWithPagination() throws Exception {
-        synchronized (mWaitLock) {
-            final int pageSize = 5;
-            final int page = 2;
-            Bundle options = new Bundle();
-            options.putInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, pageSize);
-            options.putInt(MediaBrowserCompat.EXTRA_PAGE, page);
-
-            mSubscriptionCallback.reset();
-            mMediaBrowser.subscribe(StubMediaBrowserServiceCompat.MEDIA_ID_ROOT, options,
-                    mSubscriptionCallback);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mSubscriptionCallback.mOnChildrenLoadedWithOptions);
-
-            mSubscriptionCallback.reset();
-            mMediaBrowserService.notifyChildrenChanged(StubMediaBrowserServiceCompat.MEDIA_ID_ROOT);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mSubscriptionCallback.mOnChildrenLoadedWithOptions);
-        }
-    }
-
-    @Test
-    @MediumTest
-    public void testDelayedNotifyChildrenChanged() throws Exception {
-        synchronized (mWaitLock) {
-            mSubscriptionCallback.reset();
-            mMediaBrowser.subscribe(StubMediaBrowserServiceCompat.MEDIA_ID_CHILDREN_DELAYED,
-                    mSubscriptionCallback);
-            mWaitLock.wait(WAIT_TIME_FOR_NO_RESPONSE_MS);
-            assertFalse(mSubscriptionCallback.mOnChildrenLoaded);
-
-            mMediaBrowserService.sendDelayedNotifyChildrenChanged();
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mSubscriptionCallback.mOnChildrenLoaded);
-
-            mSubscriptionCallback.reset();
-            mMediaBrowserService.notifyChildrenChanged(
-                    StubMediaBrowserServiceCompat.MEDIA_ID_CHILDREN_DELAYED);
-            mWaitLock.wait(WAIT_TIME_FOR_NO_RESPONSE_MS);
-            assertFalse(mSubscriptionCallback.mOnChildrenLoaded);
-
-            mMediaBrowserService.sendDelayedNotifyChildrenChanged();
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mSubscriptionCallback.mOnChildrenLoaded);
-        }
-    }
-
-    @Test
-    @MediumTest
-    public void testDelayedItem() throws Exception {
-        synchronized (mWaitLock) {
-            mItemCallback.reset();
-            mMediaBrowser.getItem(
-                    StubMediaBrowserServiceCompat.MEDIA_ID_CHILDREN_DELAYED, mItemCallback);
-            mWaitLock.wait(WAIT_TIME_FOR_NO_RESPONSE_MS);
-            assertFalse(mItemCallback.mOnItemLoaded);
-
-            mItemCallback.reset();
-            mMediaBrowserService.sendDelayedItemLoaded();
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mItemCallback.mOnItemLoaded);
-        }
-    }
-
-    @Test
-    @SmallTest
-    public void testSearch() throws Exception {
-        final String key = "test-key";
-        final String val = "test-val";
-
-        synchronized (mWaitLock) {
-            mSearchCallback.reset();
-            mMediaBrowser.search(StubMediaBrowserServiceCompat.SEARCH_QUERY_FOR_NO_RESULT, null,
-                    mSearchCallback);
-            mWaitLock.wait(WAIT_TIME_FOR_NO_RESPONSE_MS);
-            assertTrue(mSearchCallback.mOnSearchResult);
-            assertTrue(mSearchCallback.mSearchResults != null
-                    && mSearchCallback.mSearchResults.size() == 0);
-            assertEquals(null, mSearchCallback.mSearchExtras);
-
-            mSearchCallback.reset();
-            mMediaBrowser.search(StubMediaBrowserServiceCompat.SEARCH_QUERY_FOR_ERROR, null,
-                    mSearchCallback);
-            mWaitLock.wait(WAIT_TIME_FOR_NO_RESPONSE_MS);
-            assertTrue(mSearchCallback.mOnSearchResult);
-            assertNull(mSearchCallback.mSearchResults);
-            assertEquals(null, mSearchCallback.mSearchExtras);
-
-            mSearchCallback.reset();
-            Bundle extras = new Bundle();
-            extras.putString(key, val);
-            mMediaBrowser.search(StubMediaBrowserServiceCompat.SEARCH_QUERY, extras,
-                    mSearchCallback);
-            mWaitLock.wait(WAIT_TIME_FOR_NO_RESPONSE_MS);
-            assertTrue(mSearchCallback.mOnSearchResult);
-            assertNotNull(mSearchCallback.mSearchResults);
-            for (MediaItem item : mSearchCallback.mSearchResults) {
-                assertNotNull(item.getMediaId());
-                assertTrue(item.getMediaId().contains(StubMediaBrowserServiceCompat.SEARCH_QUERY));
-            }
-            assertNotNull(mSearchCallback.mSearchExtras);
-            assertEquals(val, mSearchCallback.mSearchExtras.getString(key));
-        }
-    }
-
-    @Test
-    @SmallTest
-    public void testSendCustomAction() throws Exception {
-        synchronized (mWaitLock) {
-            CustomActionCallback callback = new CustomActionCallback();
-            Bundle extras = new Bundle();
-            extras.putString(TEST_KEY_1, TEST_VALUE_1);
-            mMediaBrowser.sendCustomAction(StubMediaBrowserServiceCompat.CUSTOM_ACTION, extras,
-                    callback);
-            new PollingCheck(TIME_OUT_MS) {
-                @Override
-                protected boolean check() {
-                    return mMediaBrowserService.mCustomActionResult != null;
-                }
-            }.run();
-            assertNotNull(mMediaBrowserService.mCustomActionResult);
-            assertNotNull(mMediaBrowserService.mCustomActionExtras);
-            assertEquals(TEST_VALUE_1,
-                    mMediaBrowserService.mCustomActionExtras.getString(TEST_KEY_1));
-
-            callback.reset();
-            Bundle bundle1 = new Bundle();
-            bundle1.putString(TEST_KEY_2, TEST_VALUE_2);
-            mMediaBrowserService.mCustomActionResult.sendProgressUpdate(bundle1);
-            mWaitLock.wait(WAIT_TIME_FOR_NO_RESPONSE_MS);
-            assertTrue(callback.mOnProgressUpdateCalled);
-            assertNotNull(callback.mExtras);
-            assertEquals(TEST_VALUE_1, callback.mExtras.getString(TEST_KEY_1));
-            assertNotNull(callback.mData);
-            assertEquals(TEST_VALUE_2, callback.mData.getString(TEST_KEY_2));
-
-            callback.reset();
-            Bundle bundle2 = new Bundle();
-            bundle2.putString(TEST_KEY_3, TEST_VALUE_3);
-            mMediaBrowserService.mCustomActionResult.sendProgressUpdate(bundle2);
-            mWaitLock.wait(WAIT_TIME_FOR_NO_RESPONSE_MS);
-            assertTrue(callback.mOnProgressUpdateCalled);
-            assertNotNull(callback.mExtras);
-            assertEquals(TEST_VALUE_1, callback.mExtras.getString(TEST_KEY_1));
-            assertNotNull(callback.mData);
-            assertEquals(TEST_VALUE_3, callback.mData.getString(TEST_KEY_3));
-
-            Bundle bundle3 = new Bundle();
-            bundle3.putString(TEST_KEY_4, TEST_VALUE_4);
-            callback.reset();
-            mMediaBrowserService.mCustomActionResult.sendResult(bundle3);
-            mWaitLock.wait(WAIT_TIME_FOR_NO_RESPONSE_MS);
-            assertTrue(callback.mOnResultCalled);
-            assertNotNull(callback.mExtras);
-            assertEquals(TEST_VALUE_1, callback.mExtras.getString(TEST_KEY_1));
-            assertNotNull(callback.mData);
-            assertEquals(TEST_VALUE_4, callback.mData.getString(TEST_KEY_4));
-        }
-    }
-
-    @Test
-    @SmallTest
-    public void testSendCustomActionWithDetachedError() throws Exception {
-        synchronized (mWaitLock) {
-            CustomActionCallback callback = new CustomActionCallback();
-            Bundle extras = new Bundle();
-            extras.putString(TEST_KEY_1, TEST_VALUE_1);
-            mMediaBrowser.sendCustomAction(StubMediaBrowserServiceCompat.CUSTOM_ACTION, extras,
-                    callback);
-            new PollingCheck(TIME_OUT_MS) {
-                @Override
-                protected boolean check() {
-                    return mMediaBrowserService.mCustomActionResult != null;
-                }
-            }.run();
-            assertNotNull(mMediaBrowserService.mCustomActionResult);
-            assertNotNull(mMediaBrowserService.mCustomActionExtras);
-            assertEquals(TEST_VALUE_1,
-                    mMediaBrowserService.mCustomActionExtras.getString(TEST_KEY_1));
-
-            callback.reset();
-            Bundle bundle1 = new Bundle();
-            bundle1.putString(TEST_KEY_2, TEST_VALUE_2);
-            mMediaBrowserService.mCustomActionResult.sendProgressUpdate(bundle1);
-            mWaitLock.wait(WAIT_TIME_FOR_NO_RESPONSE_MS);
-            assertTrue(callback.mOnProgressUpdateCalled);
-            assertNotNull(callback.mExtras);
-            assertEquals(TEST_VALUE_1, callback.mExtras.getString(TEST_KEY_1));
-            assertNotNull(callback.mData);
-            assertEquals(TEST_VALUE_2, callback.mData.getString(TEST_KEY_2));
-
-            callback.reset();
-            Bundle bundle2 = new Bundle();
-            bundle2.putString(TEST_KEY_3, TEST_VALUE_3);
-            mMediaBrowserService.mCustomActionResult.sendError(bundle2);
-            mWaitLock.wait(WAIT_TIME_FOR_NO_RESPONSE_MS);
-            assertTrue(callback.mOnErrorCalled);
-            assertNotNull(callback.mExtras);
-            assertEquals(TEST_VALUE_1, callback.mExtras.getString(TEST_KEY_1));
-            assertNotNull(callback.mData);
-            assertEquals(TEST_VALUE_3, callback.mData.getString(TEST_KEY_3));
-        }
-    }
-
-    @Test
-    @SmallTest
-    public void testSendCustomActionWithNullCallback() throws Exception {
-        Bundle extras = new Bundle();
-        extras.putString(TEST_KEY_1, TEST_VALUE_1);
-        mMediaBrowser.sendCustomAction(StubMediaBrowserServiceCompat.CUSTOM_ACTION, extras, null);
-        new PollingCheck(TIME_OUT_MS) {
-            @Override
-            protected boolean check() {
-                return mMediaBrowserService.mCustomActionResult != null;
-            }
-        }.run();
-        assertNotNull(mMediaBrowserService.mCustomActionResult);
-        assertNotNull(mMediaBrowserService.mCustomActionExtras);
-        assertEquals(TEST_VALUE_1, mMediaBrowserService.mCustomActionExtras.getString(TEST_KEY_1));
-    }
-
-    @Test
-    @SmallTest
-    public void testSendCustomActionWithError() throws Exception {
-        synchronized (mWaitLock) {
-            CustomActionCallback callback = new CustomActionCallback();
-            mMediaBrowser.sendCustomAction(StubMediaBrowserServiceCompat.CUSTOM_ACTION_FOR_ERROR,
-                    null, callback);
-            new PollingCheck(TIME_OUT_MS) {
-                @Override
-                protected boolean check() {
-                    return mMediaBrowserService.mCustomActionResult != null;
-                }
-            }.run();
-            assertNotNull(mMediaBrowserService.mCustomActionResult);
-            assertNull(mMediaBrowserService.mCustomActionExtras);
-            mWaitLock.wait(WAIT_TIME_FOR_NO_RESPONSE_MS);
-            assertTrue(callback.mOnErrorCalled);
-        }
-    }
-
-    @Test
-    @SmallTest
-    public void testBrowserRoot() {
-        final String id = "test-id";
-        final String key = "test-key";
-        final String val = "test-val";
-        final Bundle extras = new Bundle();
-        extras.putString(key, val);
-
-        MediaBrowserServiceCompat.BrowserRoot browserRoot =
-                new MediaBrowserServiceCompat.BrowserRoot(id, extras);
-        assertEquals(id, browserRoot.getRootId());
-        assertEquals(val, browserRoot.getExtras().getString(key));
-    }
-
-    @Test
-    @SmallTest
-    public void testDelayedSetSessionToken() throws Exception {
-        // This test has no meaning in API 21. The framework MediaBrowserService just connects to
-        // the media browser without waiting setMediaSession() to be called.
-        if (Build.VERSION.SDK_INT == 21) {
-            return;
-        }
-        final ConnectionCallbackForDelayedMediaSession callback =
-                new ConnectionCallbackForDelayedMediaSession();
-
-        getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                mMediaBrowserForDelayedMediaSession =
-                        new MediaBrowserCompat(getInstrumentation().getTargetContext(),
-                                TEST_BROWSER_SERVICE_DELAYED_MEDIA_SESSION, callback, null);
-            }
-        });
-
-        synchronized (mWaitLock) {
-            mMediaBrowserForDelayedMediaSession.connect();
-            mWaitLock.wait(WAIT_TIME_FOR_NO_RESPONSE_MS);
-            assertEquals(0, callback.mConnectedCount);
-
-            StubMediaBrowserServiceCompatWithDelayedMediaSession.sInstance.callSetSessionToken();
-            mWaitLock.wait(TIME_OUT_MS);
-            assertEquals(1, callback.mConnectedCount);
-
-            if (Build.VERSION.SDK_INT >= 21) {
-                assertNotNull(
-                        mMediaBrowserForDelayedMediaSession.getSessionToken().getExtraBinder());
-            }
-        }
-    }
-
-    private void assertRootHints(MediaItem item) {
-        Bundle rootHints = item.getDescription().getExtras();
-        assertNotNull(rootHints);
-        assertEquals(mRootHints.getBoolean(MediaBrowserServiceCompat.BrowserRoot.EXTRA_RECENT),
-                rootHints.getBoolean(MediaBrowserServiceCompat.BrowserRoot.EXTRA_RECENT));
-        assertEquals(mRootHints.getBoolean(MediaBrowserServiceCompat.BrowserRoot.EXTRA_OFFLINE),
-                rootHints.getBoolean(MediaBrowserServiceCompat.BrowserRoot.EXTRA_OFFLINE));
-        assertEquals(mRootHints.getBoolean(MediaBrowserServiceCompat.BrowserRoot.EXTRA_SUGGESTED),
-                rootHints.getBoolean(MediaBrowserServiceCompat.BrowserRoot.EXTRA_SUGGESTED));
-    }
-
-    private class ConnectionCallback extends MediaBrowserCompat.ConnectionCallback {
-        @Override
-        public void onConnected() {
-            synchronized (mWaitLock) {
-                mMediaBrowserService = StubMediaBrowserServiceCompat.sInstance;
-                mWaitLock.notify();
-            }
-        }
-    }
-
-    private class SubscriptionCallback extends MediaBrowserCompat.SubscriptionCallback {
-        boolean mOnChildrenLoaded;
-        boolean mOnChildrenLoadedWithOptions;
-
-        @Override
-        public void onChildrenLoaded(String parentId, List<MediaItem> children) {
-            synchronized (mWaitLock) {
-                mOnChildrenLoaded = true;
-                if (children != null) {
-                    for (MediaItem item : children) {
-                        assertRootHints(item);
-                    }
-                }
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onChildrenLoaded(String parentId, List<MediaItem> children, Bundle options) {
-            synchronized (mWaitLock) {
-                mOnChildrenLoadedWithOptions = true;
-                if (children != null) {
-                    for (MediaItem item : children) {
-                        assertRootHints(item);
-                    }
-                }
-                mWaitLock.notify();
-            }
-        }
-
-        public void reset() {
-            mOnChildrenLoaded = false;
-            mOnChildrenLoadedWithOptions = false;
-        }
-    }
-
-    private class ItemCallback extends MediaBrowserCompat.ItemCallback {
-        boolean mOnItemLoaded;
-
-        @Override
-        public void onItemLoaded(MediaItem item) {
-            synchronized (mWaitLock) {
-                mOnItemLoaded = true;
-                assertRootHints(item);
-                mWaitLock.notify();
-            }
-        }
-
-        public void reset() {
-            mOnItemLoaded = false;
-        }
-    }
-
-    private class SearchCallback extends MediaBrowserCompat.SearchCallback {
-        boolean mOnSearchResult;
-        Bundle mSearchExtras;
-        List<MediaItem> mSearchResults;
-
-        @Override
-        public void onSearchResult(String query, Bundle extras, List<MediaItem> items) {
-            synchronized (mWaitLock) {
-                mOnSearchResult = true;
-                mSearchResults = items;
-                mSearchExtras = extras;
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onError(String query, Bundle extras) {
-            synchronized (mWaitLock) {
-                mOnSearchResult = true;
-                mSearchResults = null;
-                mSearchExtras = extras;
-                mWaitLock.notify();
-            }
-        }
-
-        public void reset() {
-            mOnSearchResult = false;
-            mSearchExtras = null;
-            mSearchResults = null;
-        }
-    }
-
-    private class CustomActionCallback extends MediaBrowserCompat.CustomActionCallback {
-        String mAction;
-        Bundle mExtras;
-        Bundle mData;
-        boolean mOnProgressUpdateCalled;
-        boolean mOnResultCalled;
-        boolean mOnErrorCalled;
-
-        @Override
-        public void onProgressUpdate(String action, Bundle extras, Bundle data) {
-            synchronized (mWaitLock) {
-                mOnProgressUpdateCalled = true;
-                mAction = action;
-                mExtras = extras;
-                mData = data;
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onResult(String action, Bundle extras, Bundle resultData) {
-            synchronized (mWaitLock) {
-                mOnResultCalled = true;
-                mAction = action;
-                mExtras = extras;
-                mData = resultData;
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onError(String action, Bundle extras, Bundle data) {
-            synchronized (mWaitLock) {
-                mOnErrorCalled = true;
-                mAction = action;
-                mExtras = extras;
-                mData = data;
-                mWaitLock.notify();
-            }
-        }
-
-        public void reset() {
-            mOnResultCalled = false;
-            mOnProgressUpdateCalled = false;
-            mOnErrorCalled = false;
-            mAction = null;
-            mExtras = null;
-            mData = null;
-        }
-    }
-
-    private class ConnectionCallbackForDelayedMediaSession extends
-            MediaBrowserCompat.ConnectionCallback {
-        private int mConnectedCount = 0;
-
-        @Override
-        public void onConnected() {
-            synchronized (mWaitLock) {
-                mConnectedCount++;
-                mWaitLock.notify();
-            }
-        }
-    }
-}
diff --git a/media-compat/tests/src/android/support/v4/media/MediaItemTest.java b/media-compat/tests/src/android/support/v4/media/MediaItemTest.java
deleted file mode 100644
index bd2565f..0000000
--- a/media-compat/tests/src/android/support/v4/media/MediaItemTest.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.support.v4.media;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.os.Parcel;
-import android.support.test.filters.SmallTest;
-import android.support.v4.media.MediaBrowserCompat.MediaItem;
-
-import org.junit.Test;
-
-/**
- * Test {@link MediaBrowserCompat.MediaItem}.
- */
-public class MediaItemTest {
-    private static final String DESCRIPTION = "test_description";
-    private static final String MEDIA_ID = "test_media_id";
-    private static final String TITLE = "test_title";
-    private static final String SUBTITLE = "test_subtitle";
-
-    @Test
-    @SmallTest
-    public void testBrowsableMediaItem() {
-        MediaDescriptionCompat description =
-                new MediaDescriptionCompat.Builder()
-                        .setDescription(DESCRIPTION)
-                        .setMediaId(MEDIA_ID)
-                        .setTitle(TITLE)
-                        .setSubtitle(SUBTITLE)
-                        .build();
-        MediaItem mediaItem = new MediaItem(description, MediaItem.FLAG_BROWSABLE);
-
-        assertEquals(description.toString(), mediaItem.getDescription().toString());
-        assertEquals(MEDIA_ID, mediaItem.getMediaId());
-        assertEquals(MediaItem.FLAG_BROWSABLE, mediaItem.getFlags());
-        assertTrue(mediaItem.isBrowsable());
-        assertFalse(mediaItem.isPlayable());
-        assertEquals(0, mediaItem.describeContents());
-
-        // Test writeToParcel
-        Parcel p = Parcel.obtain();
-        mediaItem.writeToParcel(p, 0);
-        p.setDataPosition(0);
-        assertEquals(mediaItem.getFlags(), p.readInt());
-        assertEquals(
-                description.toString(),
-                MediaDescriptionCompat.CREATOR.createFromParcel(p).toString());
-        p.recycle();
-    }
-
-    @Test
-    @SmallTest
-    public void testPlayableMediaItem() {
-        MediaDescriptionCompat description = new MediaDescriptionCompat.Builder()
-                .setDescription(DESCRIPTION)
-                .setMediaId(MEDIA_ID)
-                .setTitle(TITLE)
-                .setSubtitle(SUBTITLE)
-                .build();
-        MediaItem mediaItem = new MediaItem(description, MediaItem.FLAG_PLAYABLE);
-
-        assertEquals(description.toString(), mediaItem.getDescription().toString());
-        assertEquals(MEDIA_ID, mediaItem.getMediaId());
-        assertEquals(MediaItem.FLAG_PLAYABLE, mediaItem.getFlags());
-        assertFalse(mediaItem.isBrowsable());
-        assertTrue(mediaItem.isPlayable());
-        assertEquals(0, mediaItem.describeContents());
-
-        // Test writeToParcel
-        Parcel p = Parcel.obtain();
-        mediaItem.writeToParcel(p, 0);
-        p.setDataPosition(0);
-        assertEquals(mediaItem.getFlags(), p.readInt());
-        assertEquals(
-                description.toString(),
-                MediaDescriptionCompat.CREATOR.createFromParcel(p).toString());
-        p.recycle();
-    }
-}
diff --git a/media-compat/tests/src/android/support/v4/media/StubMediaBrowserServiceCompat.java b/media-compat/tests/src/android/support/v4/media/StubMediaBrowserServiceCompat.java
deleted file mode 100644
index c817dce..0000000
--- a/media-compat/tests/src/android/support/v4/media/StubMediaBrowserServiceCompat.java
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.v4.media;
-
-import android.os.Bundle;
-import android.support.v4.media.MediaBrowserCompat.MediaItem;
-import android.support.v4.media.session.MediaSessionCompat;
-
-import junit.framework.Assert;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Stub implementation of {@link android.support.v4.media.MediaBrowserServiceCompat}.
- */
-public class StubMediaBrowserServiceCompat extends MediaBrowserServiceCompat {
-    static final String EXTRAS_KEY = "test_extras_key";
-    static final String EXTRAS_VALUE = "test_extras_value";
-
-    static final String MEDIA_ID = "test_media_id";
-    static final String MEDIA_ID_INVALID = "test_media_id_invalid";
-    static final String MEDIA_ID_ROOT = "test_media_id_root";
-    static final String MEDIA_ID_CHILDREN_DELAYED = "test_media_id_children_delayed";
-    static final String MEDIA_ID_ON_LOAD_ITEM_NOT_IMPLEMENTED =
-            "test_media_id_on_load_item_not_implemented";
-
-    static final String[] MEDIA_ID_CHILDREN = new String[]{
-            "test_media_id_children_0", "test_media_id_children_1",
-            "test_media_id_children_2", "test_media_id_children_3",
-            MEDIA_ID_CHILDREN_DELAYED
-    };
-
-    static final String SEARCH_QUERY = "children_2";
-    static final String SEARCH_QUERY_FOR_NO_RESULT = "query no result";
-    static final String SEARCH_QUERY_FOR_ERROR = "query for error";
-
-    static final String CUSTOM_ACTION = "CUSTOM_ACTION";
-    static final String CUSTOM_ACTION_FOR_ERROR = "CUSTOM_ACTION_FOR_ERROR";
-
-    static StubMediaBrowserServiceCompat sInstance;
-
-    /* package private */ static MediaSessionCompat sSession;
-    private Bundle mExtras;
-    private Result<List<MediaItem>> mPendingLoadChildrenResult;
-    private Result<MediaItem> mPendingLoadItemResult;
-    private Bundle mPendingRootHints;
-
-    /* package private */ Bundle mCustomActionExtras;
-    /* package private */ Result<Bundle> mCustomActionResult;
-
-    @Override
-    public void onCreate() {
-        super.onCreate();
-        sInstance = this;
-        sSession = new MediaSessionCompat(this, "StubMediaBrowserServiceCompat");
-        setSessionToken(sSession.getSessionToken());
-    }
-
-    @Override
-    public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) {
-        mExtras = new Bundle();
-        mExtras.putString(EXTRAS_KEY, EXTRAS_VALUE);
-        return new BrowserRoot(MEDIA_ID_ROOT, mExtras);
-    }
-
-    @Override
-    public void onLoadChildren(final String parentMediaId, final Result<List<MediaItem>> result) {
-        List<MediaItem> mediaItems = new ArrayList<>();
-        if (MEDIA_ID_ROOT.equals(parentMediaId)) {
-            Bundle rootHints = getBrowserRootHints();
-            for (String id : MEDIA_ID_CHILDREN) {
-                mediaItems.add(createMediaItem(id));
-            }
-            result.sendResult(mediaItems);
-        } else if (MEDIA_ID_CHILDREN_DELAYED.equals(parentMediaId)) {
-            Assert.assertNull(mPendingLoadChildrenResult);
-            mPendingLoadChildrenResult = result;
-            mPendingRootHints = getBrowserRootHints();
-            result.detach();
-        } else if (MEDIA_ID_INVALID.equals(parentMediaId)) {
-            result.sendResult(null);
-        }
-    }
-
-    @Override
-    public void onLoadItem(String itemId, Result<MediaItem> result) {
-        if (MEDIA_ID_CHILDREN_DELAYED.equals(itemId)) {
-            mPendingLoadItemResult = result;
-            mPendingRootHints = getBrowserRootHints();
-            result.detach();
-            return;
-        }
-
-        if (MEDIA_ID_INVALID.equals(itemId)) {
-            result.sendResult(null);
-            return;
-        }
-
-        for (String id : MEDIA_ID_CHILDREN) {
-            if (id.equals(itemId)) {
-                result.sendResult(createMediaItem(id));
-                return;
-            }
-        }
-
-        // Test the case where onLoadItem is not implemented.
-        super.onLoadItem(itemId, result);
-    }
-
-    @Override
-    public void onSearch(String query, Bundle extras, Result<List<MediaItem>> result) {
-        if (SEARCH_QUERY_FOR_NO_RESULT.equals(query)) {
-            result.sendResult(Collections.<MediaItem>emptyList());
-        } else if (SEARCH_QUERY_FOR_ERROR.equals(query)) {
-            result.sendResult(null);
-        } else if (SEARCH_QUERY.equals(query)) {
-            List<MediaItem> items = new ArrayList<>();
-            for (String id : MEDIA_ID_CHILDREN) {
-                if (id.contains(query)) {
-                    items.add(createMediaItem(id));
-                }
-            }
-            result.sendResult(items);
-        }
-    }
-
-    @Override
-    public void onCustomAction(String action, Bundle extras,
-            Result<Bundle> result) {
-        mCustomActionResult = result;
-        mCustomActionExtras = extras;
-        if (CUSTOM_ACTION_FOR_ERROR.equals(action)) {
-            result.sendError(null);
-        } else if (CUSTOM_ACTION.equals(action)) {
-            result.detach();
-        }
-    }
-
-    public void sendDelayedNotifyChildrenChanged() {
-        if (mPendingLoadChildrenResult != null) {
-            mPendingLoadChildrenResult.sendResult(Collections.<MediaItem>emptyList());
-            mPendingRootHints = null;
-            mPendingLoadChildrenResult = null;
-        }
-    }
-
-    public void sendDelayedItemLoaded() {
-        if (mPendingLoadItemResult != null) {
-            mPendingLoadItemResult.sendResult(new MediaItem(new MediaDescriptionCompat.Builder()
-                    .setMediaId(MEDIA_ID_CHILDREN_DELAYED).setExtras(mPendingRootHints).build(),
-                    MediaItem.FLAG_BROWSABLE));
-            mPendingRootHints = null;
-            mPendingLoadItemResult = null;
-        }
-    }
-
-    private MediaItem createMediaItem(String id) {
-        return new MediaItem(new MediaDescriptionCompat.Builder()
-                .setMediaId(id).setExtras(getBrowserRootHints()).build(),
-                MediaItem.FLAG_BROWSABLE);
-    }
-}
diff --git a/media-compat/tests/src/android/support/v4/media/StubMediaBrowserServiceCompatWithDelayedMediaSession.java b/media-compat/tests/src/android/support/v4/media/StubMediaBrowserServiceCompatWithDelayedMediaSession.java
deleted file mode 100644
index e93c940..0000000
--- a/media-compat/tests/src/android/support/v4/media/StubMediaBrowserServiceCompatWithDelayedMediaSession.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.v4.media;
-
-import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.v4.media.session.MediaSessionCompat;
-
-import java.util.List;
-
-/**
- * Stub implementation of {@link MediaBrowserServiceCompat}.
- * This implementation does not call
- * {@link MediaBrowserServiceCompat#setSessionToken(MediaSessionCompat.Token)} in its
- * {@link android.app.Service#onCreate}.
- */
-public class StubMediaBrowserServiceCompatWithDelayedMediaSession extends
-        MediaBrowserServiceCompat {
-
-    static StubMediaBrowserServiceCompatWithDelayedMediaSession sInstance;
-    private MediaSessionCompat mSession;
-
-    @Override
-    public void onCreate() {
-        super.onCreate();
-        sInstance = this;
-        mSession = new MediaSessionCompat(
-                this, "StubMediaBrowserServiceCompatWithDelayedMediaSession");
-    }
-
-    @Nullable
-    @Override
-    public BrowserRoot onGetRoot(@NonNull String clientPackageName,
-            int clientUid, @Nullable Bundle rootHints) {
-        return new BrowserRoot("StubRootId", null);
-    }
-
-    @Override
-    public void onLoadChildren(@NonNull String parentId,
-            @NonNull Result<List<MediaBrowserCompat.MediaItem>> result) {
-        result.detach();
-    }
-
-    void callSetSessionToken() {
-        setSessionToken(mSession.getSessionToken());
-    }
-}
diff --git a/media-compat/tests/src/android/support/v4/media/StubRemoteMediaBrowserServiceCompat.java b/media-compat/tests/src/android/support/v4/media/StubRemoteMediaBrowserServiceCompat.java
deleted file mode 100644
index 8e03ab2..0000000
--- a/media-compat/tests/src/android/support/v4/media/StubRemoteMediaBrowserServiceCompat.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.v4.media;
-
-import android.os.Bundle;
-import android.support.v4.media.MediaBrowserCompat.MediaItem;
-import android.support.v4.media.session.MediaSessionCompat;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Stub implementation of {@link android.support.v4.media.MediaBrowserServiceCompat}.
- */
-public class StubRemoteMediaBrowserServiceCompat extends MediaBrowserServiceCompat {
-    static final String EXTRAS_KEY = "test_extras_key";
-    static final String EXTRAS_VALUE = "test_extras_value";
-
-    static final String MEDIA_ID_ROOT = "test_media_id_root";
-    static final String MEDIA_METADATA = "test_media_metadata";
-
-    static final String[] MEDIA_ID_CHILDREN = new String[]{
-            "test_media_id_children_0", "test_media_id_children_1",
-            "test_media_id_children_2", "test_media_id_children_3"
-    };
-
-    private static MediaSessionCompat mSession;
-    private Bundle mExtras;
-
-    @Override
-    public void onCreate() {
-        super.onCreate();
-        mSession = new MediaSessionCompat(this, "StubRemoteMediaBrowserServiceCompat");
-        setSessionToken(mSession.getSessionToken());
-    }
-
-    @Override
-    public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) {
-        mExtras = new Bundle();
-        mExtras.putString(EXTRAS_KEY, EXTRAS_VALUE);
-        return new BrowserRoot(MEDIA_ID_ROOT, mExtras);
-    }
-
-    @Override
-    public void onLoadChildren(final String parentMediaId, final Result<List<MediaItem>> result) {
-        List<MediaItem> mediaItems = new ArrayList<>();
-        if (MEDIA_ID_ROOT.equals(parentMediaId)) {
-            Bundle rootHints = getBrowserRootHints();
-            for (String id : MEDIA_ID_CHILDREN) {
-                mediaItems.add(createMediaItem(id));
-            }
-            result.sendResult(mediaItems);
-        }
-    }
-
-    @Override
-    public void onLoadChildren(final String parentMediaId, final Result<List<MediaItem>> result,
-            final Bundle options) {
-        MediaMetadataCompat metadata = options.getParcelable(MEDIA_METADATA);
-        if (metadata == null) {
-            super.onLoadChildren(parentMediaId, result, options);
-        } else {
-            List<MediaItem> mediaItems = new ArrayList<>();
-            mediaItems.add(new MediaItem(metadata.getDescription(), MediaItem.FLAG_PLAYABLE));
-            result.sendResult(mediaItems);
-        }
-    }
-
-    private MediaItem createMediaItem(String id) {
-        return new MediaItem(new MediaDescriptionCompat.Builder()
-                .setMediaId(id).setExtras(getBrowserRootHints()).build(),
-                MediaItem.FLAG_BROWSABLE);
-    }
-}
diff --git a/media-compat/tests/src/android/support/v4/media/session/MediaControllerCompatTest.java b/media-compat/tests/src/android/support/v4/media/session/MediaControllerCompatTest.java
deleted file mode 100644
index 53d7e47..0000000
--- a/media-compat/tests/src/android/support/v4/media/session/MediaControllerCompatTest.java
+++ /dev/null
@@ -1,795 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.support.v4.media.session;
-
-import static android.support.test.InstrumentationRegistry.getContext;
-import static android.support.test.InstrumentationRegistry.getInstrumentation;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.media.AudioManager;
-import android.media.session.MediaController;
-import android.media.session.MediaSession;
-import android.media.session.PlaybackState;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.ResultReceiver;
-import android.os.SystemClock;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-import android.support.v4.media.MediaDescriptionCompat;
-import android.support.v4.media.RatingCompat;
-import android.support.v4.media.VolumeProviderCompat;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Test {@link MediaControllerCompat}.
- */
-@RunWith(AndroidJUnit4.class)
-public class MediaControllerCompatTest {
-    // The maximum time to wait for an operation.
-    private static final long TIME_OUT_MS = 3000L;
-    private static final String SESSION_TAG = "test-session";
-    private static final String EXTRAS_KEY = "test-key";
-    private static final String EXTRAS_VALUE = "test-val";
-    private static final float DELTA = 1e-4f;
-    private static final boolean ENABLED = true;
-    private static final boolean DISABLED = false;
-    private static final long TEST_POSITION = 1000000L;
-    private static final float TEST_PLAYBACK_SPEED = 3.0f;
-
-
-    private final Object mWaitLock = new Object();
-    private Handler mHandler = new Handler(Looper.getMainLooper());
-    private MediaSessionCompat mSession;
-    private MediaSessionCallback mCallback = new MediaSessionCallback();
-    private MediaControllerCompat mController;
-
-    @Before
-    public void setUp() throws Exception {
-        getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                mSession = new MediaSessionCompat(getContext(), SESSION_TAG);
-                mSession.setCallback(mCallback, mHandler);
-                mSession.setFlags(MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS);
-                mController = mSession.getController();
-            }
-        });
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        mSession.release();
-    }
-
-    @Test
-    @SmallTest
-    public void testGetPackageName() {
-        assertEquals(getContext().getPackageName(), mController.getPackageName());
-    }
-
-    @Test
-    @SmallTest
-    public void testGetRatingType() {
-        assertEquals("Default rating type of a session must be RatingCompat.RATING_NONE",
-                RatingCompat.RATING_NONE, mController.getRatingType());
-
-        mSession.setRatingType(RatingCompat.RATING_5_STARS);
-        assertEquals(RatingCompat.RATING_5_STARS, mController.getRatingType());
-    }
-
-    @Test
-    @SmallTest
-    public void testGetSessionToken() throws Exception {
-        assertEquals(mSession.getSessionToken(), mController.getSessionToken());
-    }
-
-    @Test
-    @SmallTest
-    public void testIsSessionReady() throws Exception {
-        // mController already has the extra binder since it was created with the session token
-        // which holds the extra binder.
-        assertTrue(mController.isSessionReady());
-    }
-
-    @Test
-    @SmallTest
-    public void testSendCommand() throws Exception {
-        synchronized (mWaitLock) {
-            mCallback.reset();
-            final String command = "test-command";
-            final Bundle extras = new Bundle();
-            extras.putString(EXTRAS_KEY, EXTRAS_VALUE);
-            mController.sendCommand(command, extras, new ResultReceiver(null));
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnCommandCalled);
-            assertNotNull(mCallback.mCommandCallback);
-            assertEquals(command, mCallback.mCommand);
-            assertEquals(EXTRAS_VALUE, mCallback.mExtras.getString(EXTRAS_KEY));
-        }
-    }
-
-    @Test
-    @SmallTest
-    public void testAddRemoveQueueItems() throws Exception {
-        final String mediaId1 = "media_id_1";
-        final String mediaTitle1 = "media_title_1";
-        MediaDescriptionCompat itemDescription1 = new MediaDescriptionCompat.Builder()
-                .setMediaId(mediaId1).setTitle(mediaTitle1).build();
-
-        final String mediaId2 = "media_id_2";
-        final String mediaTitle2 = "media_title_2";
-        MediaDescriptionCompat itemDescription2 = new MediaDescriptionCompat.Builder()
-                .setMediaId(mediaId2).setTitle(mediaTitle2).build();
-
-        synchronized (mWaitLock) {
-            mCallback.reset();
-            mController.addQueueItem(itemDescription1);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnAddQueueItemCalled);
-            assertEquals(-1, mCallback.mQueueIndex);
-            assertEquals(mediaId1, mCallback.mQueueDescription.getMediaId());
-            assertEquals(mediaTitle1, mCallback.mQueueDescription.getTitle());
-
-            mCallback.reset();
-            mController.addQueueItem(itemDescription2, 0);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnAddQueueItemAtCalled);
-            assertEquals(0, mCallback.mQueueIndex);
-            assertEquals(mediaId2, mCallback.mQueueDescription.getMediaId());
-            assertEquals(mediaTitle2, mCallback.mQueueDescription.getTitle());
-
-            mCallback.reset();
-            mController.removeQueueItemAt(0);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnRemoveQueueItemCalled);
-            assertEquals(mediaId2, mCallback.mQueueDescription.getMediaId());
-            assertEquals(mediaTitle2, mCallback.mQueueDescription.getTitle());
-
-            mCallback.reset();
-            mController.removeQueueItem(itemDescription1);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnRemoveQueueItemCalled);
-            assertEquals(mediaId1, mCallback.mQueueDescription.getMediaId());
-            assertEquals(mediaTitle1, mCallback.mQueueDescription.getTitle());
-
-            // Try to modify the queue when the session does not support queue management.
-            mSession.setFlags(0);
-            try {
-                mController.addQueueItem(itemDescription1);
-                fail();
-            } catch (UnsupportedOperationException e) {
-                // Expected.
-            }
-        }
-    }
-
-    // TODO: Uncomment after fixing this test. This test causes an Exception on System UI.
-    // @Test
-    // @SmallTest
-    public void testVolumeControl() throws Exception {
-        VolumeProviderCompat vp =
-                new VolumeProviderCompat(VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE, 11, 5) {
-            @Override
-            public void onSetVolumeTo(int volume) {
-                synchronized (mWaitLock) {
-                    setCurrentVolume(volume);
-                    mWaitLock.notify();
-                }
-            }
-
-            @Override
-            public void onAdjustVolume(int direction) {
-                synchronized (mWaitLock) {
-                    switch (direction) {
-                        case AudioManager.ADJUST_LOWER:
-                            setCurrentVolume(getCurrentVolume() - 1);
-                            break;
-                        case AudioManager.ADJUST_RAISE:
-                            setCurrentVolume(getCurrentVolume() + 1);
-                            break;
-                    }
-                    mWaitLock.notify();
-                }
-            }
-        };
-        mSession.setPlaybackToRemote(vp);
-
-        synchronized (mWaitLock) {
-            // test setVolumeTo
-            mController.setVolumeTo(7, 0);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertEquals(7, vp.getCurrentVolume());
-
-            // test adjustVolume
-            mController.adjustVolume(AudioManager.ADJUST_LOWER, 0);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertEquals(6, vp.getCurrentVolume());
-
-            mController.adjustVolume(AudioManager.ADJUST_RAISE, 0);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertEquals(7, vp.getCurrentVolume());
-        }
-    }
-
-    @Test
-    @SmallTest
-    public void testTransportControlsAndMediaSessionCallback() throws Exception {
-        MediaControllerCompat.TransportControls controls = mController.getTransportControls();
-        synchronized (mWaitLock) {
-            mCallback.reset();
-            controls.play();
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnPlayCalled);
-
-            mCallback.reset();
-            controls.pause();
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnPauseCalled);
-
-            mCallback.reset();
-            controls.stop();
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnStopCalled);
-
-            mCallback.reset();
-            controls.fastForward();
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnFastForwardCalled);
-
-            mCallback.reset();
-            controls.rewind();
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnRewindCalled);
-
-            mCallback.reset();
-            controls.skipToPrevious();
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnSkipToPreviousCalled);
-
-            mCallback.reset();
-            controls.skipToNext();
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnSkipToNextCalled);
-
-            mCallback.reset();
-            final long seekPosition = 1000;
-            controls.seekTo(seekPosition);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnSeekToCalled);
-            assertEquals(seekPosition, mCallback.mSeekPosition);
-
-            mCallback.reset();
-            final RatingCompat rating =
-                    RatingCompat.newStarRating(RatingCompat.RATING_5_STARS, 3f);
-            controls.setRating(rating);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnSetRatingCalled);
-            assertEquals(rating.getRatingStyle(), mCallback.mRating.getRatingStyle());
-            assertEquals(rating.getStarRating(), mCallback.mRating.getStarRating(), DELTA);
-
-            mCallback.reset();
-            final Bundle extras = new Bundle();
-            extras.putString(EXTRAS_KEY, EXTRAS_VALUE);
-            controls.setRating(rating, extras);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnSetRatingCalled);
-            assertEquals(rating.getRatingStyle(), mCallback.mRating.getRatingStyle());
-            assertEquals(rating.getStarRating(), mCallback.mRating.getStarRating(), DELTA);
-            assertEquals(EXTRAS_VALUE, mCallback.mExtras.getString(EXTRAS_KEY));
-
-            mCallback.reset();
-            final String mediaId = "test-media-id";
-            controls.playFromMediaId(mediaId, extras);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnPlayFromMediaIdCalled);
-            assertEquals(mediaId, mCallback.mMediaId);
-            assertEquals(EXTRAS_VALUE, mCallback.mExtras.getString(EXTRAS_KEY));
-
-            mCallback.reset();
-            final String query = "test-query";
-            controls.playFromSearch(query, extras);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnPlayFromSearchCalled);
-            assertEquals(query, mCallback.mQuery);
-            assertEquals(EXTRAS_VALUE, mCallback.mExtras.getString(EXTRAS_KEY));
-
-            mCallback.reset();
-            final Uri uri = Uri.parse("content://test/popcorn.mod");
-            controls.playFromUri(uri, extras);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnPlayFromUriCalled);
-            assertEquals(uri, mCallback.mUri);
-            assertEquals(EXTRAS_VALUE, mCallback.mExtras.getString(EXTRAS_KEY));
-
-            mCallback.reset();
-            final String action = "test-action";
-            controls.sendCustomAction(action, extras);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnCustomActionCalled);
-            assertEquals(action, mCallback.mAction);
-            assertEquals(EXTRAS_VALUE, mCallback.mExtras.getString(EXTRAS_KEY));
-
-            mCallback.reset();
-            mCallback.mOnCustomActionCalled = false;
-            final PlaybackStateCompat.CustomAction customAction =
-                    new PlaybackStateCompat.CustomAction.Builder(action, action, -1)
-                            .setExtras(extras)
-                            .build();
-            controls.sendCustomAction(customAction, extras);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnCustomActionCalled);
-            assertEquals(action, mCallback.mAction);
-            assertEquals(EXTRAS_VALUE, mCallback.mExtras.getString(EXTRAS_KEY));
-
-            mCallback.reset();
-            final long queueItemId = 1000;
-            controls.skipToQueueItem(queueItemId);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnSkipToQueueItemCalled);
-            assertEquals(queueItemId, mCallback.mQueueItemId);
-
-            mCallback.reset();
-            controls.prepare();
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnPrepareCalled);
-
-            mCallback.reset();
-            controls.prepareFromMediaId(mediaId, extras);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnPrepareFromMediaIdCalled);
-            assertEquals(mediaId, mCallback.mMediaId);
-            assertEquals(EXTRAS_VALUE, mCallback.mExtras.getString(EXTRAS_KEY));
-
-            mCallback.reset();
-            controls.prepareFromSearch(query, extras);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnPrepareFromSearchCalled);
-            assertEquals(query, mCallback.mQuery);
-            assertEquals(EXTRAS_VALUE, mCallback.mExtras.getString(EXTRAS_KEY));
-
-            mCallback.reset();
-            controls.prepareFromUri(uri, extras);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnPrepareFromUriCalled);
-            assertEquals(uri, mCallback.mUri);
-            assertEquals(EXTRAS_VALUE, mCallback.mExtras.getString(EXTRAS_KEY));
-
-            mCallback.reset();
-            controls.setCaptioningEnabled(ENABLED);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnSetCaptioningEnabledCalled);
-            assertEquals(ENABLED, mCallback.mCaptioningEnabled);
-
-            mCallback.reset();
-            final int repeatMode = PlaybackStateCompat.REPEAT_MODE_ALL;
-            controls.setRepeatMode(repeatMode);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnSetRepeatModeCalled);
-            assertEquals(repeatMode, mCallback.mRepeatMode);
-
-            mCallback.reset();
-            controls.setShuffleMode(PlaybackStateCompat.SHUFFLE_MODE_ALL);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnSetShuffleModeCalled);
-            assertEquals(PlaybackStateCompat.SHUFFLE_MODE_ALL, mCallback.mShuffleMode);
-        }
-    }
-
-    @Test
-    @SmallTest
-    public void testPlaybackInfo() {
-        final int playbackType = MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_LOCAL;
-        final int volumeControl = VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE;
-        final int maxVolume = 10;
-        final int currentVolume = 3;
-
-        int audioStream = 77;
-        MediaControllerCompat.PlaybackInfo info = new MediaControllerCompat.PlaybackInfo(
-                playbackType, audioStream, volumeControl, maxVolume, currentVolume);
-
-        assertEquals(playbackType, info.getPlaybackType());
-        assertEquals(audioStream, info.getAudioStream());
-        assertEquals(volumeControl, info.getVolumeControl());
-        assertEquals(maxVolume, info.getMaxVolume());
-        assertEquals(currentVolume, info.getCurrentVolume());
-    }
-
-    @Test
-    @SmallTest
-    public void testGetPlaybackStateWithPositionUpdate() throws InterruptedException {
-        final long stateSetTime = SystemClock.elapsedRealtime();
-        PlaybackStateCompat stateIn = new PlaybackStateCompat.Builder()
-                .setState(PlaybackStateCompat.STATE_PLAYING, TEST_POSITION, TEST_PLAYBACK_SPEED,
-                        stateSetTime)
-                .build();
-        mSession.setPlaybackState(stateIn);
-
-        final long waitDuration = 100L;
-        Thread.sleep(waitDuration);
-
-        final long expectedUpdateTime = waitDuration + stateSetTime;
-        final long expectedPosition = (long) (TEST_PLAYBACK_SPEED * waitDuration) + TEST_POSITION;
-
-        final double updateTimeTolerance = 30L;
-        final double positionTolerance = updateTimeTolerance * TEST_PLAYBACK_SPEED;
-
-        PlaybackStateCompat stateOut = mSession.getController().getPlaybackState();
-        assertEquals(expectedUpdateTime, stateOut.getLastPositionUpdateTime(), updateTimeTolerance);
-        assertEquals(expectedPosition, stateOut.getPosition(), positionTolerance);
-
-        // Compare the result with MediaController.getPlaybackState().
-        if (Build.VERSION.SDK_INT >= 21) {
-            MediaController controller = new MediaController(
-                    getContext(), (MediaSession.Token) mSession.getSessionToken().getToken());
-            PlaybackState state = controller.getPlaybackState();
-            assertEquals(state.getLastPositionUpdateTime(), stateOut.getLastPositionUpdateTime(),
-                    updateTimeTolerance);
-            assertEquals(state.getPosition(), stateOut.getPosition(), positionTolerance);
-        }
-    }
-
-    private class MediaSessionCallback extends MediaSessionCompat.Callback {
-        private long mSeekPosition;
-        private long mQueueItemId;
-        private RatingCompat mRating;
-        private String mMediaId;
-        private String mQuery;
-        private Uri mUri;
-        private String mAction;
-        private String mCommand;
-        private Bundle mExtras;
-        private ResultReceiver mCommandCallback;
-        private boolean mCaptioningEnabled;
-        private int mRepeatMode;
-        private int mShuffleMode;
-        private int mQueueIndex;
-        private MediaDescriptionCompat mQueueDescription;
-        private List<MediaSessionCompat.QueueItem> mQueue = new ArrayList<>();
-
-        private boolean mOnPlayCalled;
-        private boolean mOnPauseCalled;
-        private boolean mOnStopCalled;
-        private boolean mOnFastForwardCalled;
-        private boolean mOnRewindCalled;
-        private boolean mOnSkipToPreviousCalled;
-        private boolean mOnSkipToNextCalled;
-        private boolean mOnSeekToCalled;
-        private boolean mOnSkipToQueueItemCalled;
-        private boolean mOnSetRatingCalled;
-        private boolean mOnPlayFromMediaIdCalled;
-        private boolean mOnPlayFromSearchCalled;
-        private boolean mOnPlayFromUriCalled;
-        private boolean mOnCustomActionCalled;
-        private boolean mOnCommandCalled;
-        private boolean mOnPrepareCalled;
-        private boolean mOnPrepareFromMediaIdCalled;
-        private boolean mOnPrepareFromSearchCalled;
-        private boolean mOnPrepareFromUriCalled;
-        private boolean mOnSetCaptioningEnabledCalled;
-        private boolean mOnSetRepeatModeCalled;
-        private boolean mOnSetShuffleModeCalled;
-        private boolean mOnAddQueueItemCalled;
-        private boolean mOnAddQueueItemAtCalled;
-        private boolean mOnRemoveQueueItemCalled;
-
-        public void reset() {
-            mSeekPosition = -1;
-            mQueueItemId = -1;
-            mRating = null;
-            mMediaId = null;
-            mQuery = null;
-            mUri = null;
-            mAction = null;
-            mExtras = null;
-            mCommand = null;
-            mCommandCallback = null;
-            mCaptioningEnabled = false;
-            mRepeatMode = PlaybackStateCompat.REPEAT_MODE_NONE;
-            mShuffleMode = PlaybackStateCompat.SHUFFLE_MODE_NONE;
-            mQueueIndex = -1;
-            mQueueDescription = null;
-
-            mOnPlayCalled = false;
-            mOnPauseCalled = false;
-            mOnStopCalled = false;
-            mOnFastForwardCalled = false;
-            mOnRewindCalled = false;
-            mOnSkipToPreviousCalled = false;
-            mOnSkipToNextCalled = false;
-            mOnSkipToQueueItemCalled = false;
-            mOnSeekToCalled = false;
-            mOnSetRatingCalled = false;
-            mOnPlayFromMediaIdCalled = false;
-            mOnPlayFromSearchCalled = false;
-            mOnPlayFromUriCalled = false;
-            mOnCustomActionCalled = false;
-            mOnCommandCalled = false;
-            mOnPrepareCalled = false;
-            mOnPrepareFromMediaIdCalled = false;
-            mOnPrepareFromSearchCalled = false;
-            mOnPrepareFromUriCalled = false;
-            mOnSetCaptioningEnabledCalled = false;
-            mOnSetRepeatModeCalled = false;
-            mOnSetShuffleModeCalled = false;
-            mOnAddQueueItemCalled = false;
-            mOnAddQueueItemAtCalled = false;
-            mOnRemoveQueueItemCalled = false;
-        }
-
-        @Override
-        public void onPlay() {
-            synchronized (mWaitLock) {
-                mOnPlayCalled = true;
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onPause() {
-            synchronized (mWaitLock) {
-                mOnPauseCalled = true;
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onStop() {
-            synchronized (mWaitLock) {
-                mOnStopCalled = true;
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onFastForward() {
-            synchronized (mWaitLock) {
-                mOnFastForwardCalled = true;
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onRewind() {
-            synchronized (mWaitLock) {
-                mOnRewindCalled = true;
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onSkipToPrevious() {
-            synchronized (mWaitLock) {
-                mOnSkipToPreviousCalled = true;
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onSkipToNext() {
-            synchronized (mWaitLock) {
-                mOnSkipToNextCalled = true;
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onSeekTo(long pos) {
-            synchronized (mWaitLock) {
-                mOnSeekToCalled = true;
-                mSeekPosition = pos;
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onSetRating(RatingCompat rating) {
-            synchronized (mWaitLock) {
-                mOnSetRatingCalled = true;
-                mRating = rating;
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onSetRating(RatingCompat rating, Bundle extras) {
-            synchronized (mWaitLock) {
-                mOnSetRatingCalled = true;
-                mRating = rating;
-                mExtras = extras;
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onPlayFromMediaId(String mediaId, Bundle extras) {
-            synchronized (mWaitLock) {
-                mOnPlayFromMediaIdCalled = true;
-                mMediaId = mediaId;
-                mExtras = extras;
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onPlayFromSearch(String query, Bundle extras) {
-            synchronized (mWaitLock) {
-                mOnPlayFromSearchCalled = true;
-                mQuery = query;
-                mExtras = extras;
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onPlayFromUri(Uri uri, Bundle extras) {
-            synchronized (mWaitLock) {
-                mOnPlayFromUriCalled = true;
-                mUri = uri;
-                mExtras = extras;
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onCustomAction(String action, Bundle extras) {
-            synchronized (mWaitLock) {
-                mOnCustomActionCalled = true;
-                mAction = action;
-                mExtras = extras;
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onSkipToQueueItem(long id) {
-            synchronized (mWaitLock) {
-                mOnSkipToQueueItemCalled = true;
-                mQueueItemId = id;
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onCommand(String command, Bundle extras, ResultReceiver cb) {
-            synchronized (mWaitLock) {
-                mOnCommandCalled = true;
-                mCommand = command;
-                mExtras = extras;
-                mCommandCallback = cb;
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onPrepare() {
-            synchronized (mWaitLock) {
-                mOnPrepareCalled = true;
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onPrepareFromMediaId(String mediaId, Bundle extras) {
-            synchronized (mWaitLock) {
-                mOnPrepareFromMediaIdCalled = true;
-                mMediaId = mediaId;
-                mExtras = extras;
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onPrepareFromSearch(String query, Bundle extras) {
-            synchronized (mWaitLock) {
-                mOnPrepareFromSearchCalled = true;
-                mQuery = query;
-                mExtras = extras;
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onPrepareFromUri(Uri uri, Bundle extras) {
-            synchronized (mWaitLock) {
-                mOnPrepareFromUriCalled = true;
-                mUri = uri;
-                mExtras = extras;
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onSetRepeatMode(int repeatMode) {
-            synchronized (mWaitLock) {
-                mOnSetRepeatModeCalled = true;
-                mRepeatMode = repeatMode;
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onAddQueueItem(MediaDescriptionCompat description) {
-            synchronized (mWaitLock) {
-                mOnAddQueueItemCalled = true;
-                mQueueDescription = description;
-                mQueue.add(new MediaSessionCompat.QueueItem(description, mQueue.size()));
-                mSession.setQueue(mQueue);
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onAddQueueItem(MediaDescriptionCompat description, int index) {
-            synchronized (mWaitLock) {
-                mOnAddQueueItemAtCalled = true;
-                mQueueIndex = index;
-                mQueueDescription = description;
-                mQueue.add(index, new MediaSessionCompat.QueueItem(description, mQueue.size()));
-                mSession.setQueue(mQueue);
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onRemoveQueueItem(MediaDescriptionCompat description) {
-            synchronized (mWaitLock) {
-                mOnRemoveQueueItemCalled = true;
-                String mediaId = description.getMediaId();
-                for (int i = mQueue.size() - 1; i >= 0; --i) {
-                    if (mediaId.equals(mQueue.get(i).getDescription().getMediaId())) {
-                        mQueueDescription = mQueue.remove(i).getDescription();
-                        mSession.setQueue(mQueue);
-                        break;
-                    }
-                }
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onSetCaptioningEnabled(boolean enabled) {
-            synchronized (mWaitLock) {
-                mOnSetCaptioningEnabledCalled = true;
-                mCaptioningEnabled = enabled;
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onSetShuffleMode(int shuffleMode) {
-            synchronized (mWaitLock) {
-                mOnSetShuffleModeCalled = true;
-                mShuffleMode = shuffleMode;
-                mWaitLock.notify();
-            }
-        }
-    }
-}
diff --git a/media-compat/tests/src/android/support/v4/media/session/MediaSessionCompatTest.java b/media-compat/tests/src/android/support/v4/media/session/MediaSessionCompatTest.java
deleted file mode 100644
index 2cda242..0000000
--- a/media-compat/tests/src/android/support/v4/media/session/MediaSessionCompatTest.java
+++ /dev/null
@@ -1,1031 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.v4.media.session;
-
-import static android.support.test.InstrumentationRegistry.getContext;
-import static android.support.test.InstrumentationRegistry.getInstrumentation;
-import static android.support.v4.media.MediaMetadataCompat.METADATA_KEY_RATING;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.app.PendingIntent;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Bitmap;
-import android.media.AudioManager;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Parcel;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-import android.support.v4.media.MediaDescriptionCompat;
-import android.support.v4.media.MediaMetadataCompat;
-import android.support.v4.media.RatingCompat;
-import android.support.v4.media.VolumeProviderCompat;
-import android.view.KeyEvent;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Test {@link MediaSessionCompat}.
- */
-@RunWith(AndroidJUnit4.class)
-public class MediaSessionCompatTest {
-    // The maximum time to wait for an operation, that is expected to happen.
-    private static final long TIME_OUT_MS = 3000L;
-    // The maximum time to wait for an operation, that is expected not to happen.
-    private static final long WAIT_TIME_MS = 30L;
-    private static final int MAX_AUDIO_INFO_CHANGED_CALLBACK_COUNT = 10;
-    private static final String TEST_SESSION_TAG = "test-session-tag";
-    private static final String TEST_KEY = "test-key";
-    private static final String TEST_VALUE = "test-val";
-    private static final Bundle TEST_BUNDLE = createTestBundle();
-    private static final String TEST_SESSION_EVENT = "test-session-event";
-    private static final int TEST_CURRENT_VOLUME = 10;
-    private static final int TEST_MAX_VOLUME = 11;
-    private static final long TEST_QUEUE_ID = 12L;
-    private static final long TEST_ACTION = 55L;
-    private static final int TEST_ERROR_CODE =
-            PlaybackStateCompat.ERROR_CODE_AUTHENTICATION_EXPIRED;
-    private static final String TEST_ERROR_MSG = "test-error-msg";
-
-    private static Bundle createTestBundle() {
-        Bundle bundle = new Bundle();
-        bundle.putString(TEST_KEY, TEST_VALUE);
-        return bundle;
-    }
-
-    private AudioManager mAudioManager;
-    private Handler mHandler = new Handler(Looper.getMainLooper());
-    private Object mWaitLock = new Object();
-    private MediaControllerCallback mCallback = new MediaControllerCallback();
-    private MediaSessionCompat mSession;
-
-    @Before
-    public void setUp() throws Exception {
-        getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
-                mSession = new MediaSessionCompat(getContext(), TEST_SESSION_TAG);
-            }
-        });
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        // It is OK to call release() twice.
-        mSession.release();
-        mSession = null;
-    }
-
-    /**
-     * Tests that a session can be created and that all the fields are
-     * initialized correctly.
-     */
-    @Test
-    @SmallTest
-    public void testCreateSession() throws Exception {
-        assertNotNull(mSession.getSessionToken());
-        assertFalse("New session should not be active", mSession.isActive());
-
-        // Verify by getting the controller and checking all its fields
-        MediaControllerCompat controller = mSession.getController();
-        assertNotNull(controller);
-        verifyNewSession(controller, TEST_SESSION_TAG);
-    }
-
-    /**
-     * Tests that a session can be created from the framework session object and the callback
-     * set on the framework session object before fromSession() is called works properly.
-     */
-    @Test
-    @SmallTest
-    public void testFromSession() throws Exception {
-        if (android.os.Build.VERSION.SDK_INT < 21) {
-            // MediaSession was introduced from API level 21.
-            return;
-        }
-        MediaSessionCallback callback = new MediaSessionCallback();
-        callback.reset(1);
-        mSession.setCallback(callback, new Handler(Looper.getMainLooper()));
-        MediaSessionCompat session = MediaSessionCompat.fromMediaSession(
-                getContext(), mSession.getMediaSession());
-        assertEquals(session.getSessionToken(), mSession.getSessionToken());
-        synchronized (mWaitLock) {
-            session.getController().getTransportControls().play();
-            mWaitLock.wait(TIME_OUT_MS);
-            assertEquals(1, callback.mOnPlayCalledCount);
-        }
-    }
-
-    /**
-     * Tests MediaSessionCompat.Token created in the constructor of MediaSessionCompat.
-     */
-    @Test
-    @SmallTest
-    public void testSessionToken() throws Exception {
-        MediaSessionCompat.Token sessionToken = mSession.getSessionToken();
-
-        assertNotNull(sessionToken);
-        assertEquals(0, sessionToken.describeContents());
-
-        // Test writeToParcel
-        Parcel p = Parcel.obtain();
-        sessionToken.writeToParcel(p, 0);
-        p.setDataPosition(0);
-        MediaSessionCompat.Token token = MediaSessionCompat.Token.CREATOR.createFromParcel(p);
-        assertEquals(token, sessionToken);
-        p.recycle();
-    }
-
-    /**
-     * Tests {@link MediaSessionCompat#setExtras}.
-     */
-    @Test
-    @SmallTest
-    public void testSetExtras() throws Exception {
-        final Bundle extras = new Bundle();
-        MediaControllerCompat controller = mSession.getController();
-        controller.registerCallback(mCallback, mHandler);
-        synchronized (mWaitLock) {
-            mCallback.resetLocked();
-            mSession.setExtras(TEST_BUNDLE);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnExtraChangedCalled);
-
-            Bundle extrasOut = mCallback.mExtras;
-            assertNotNull(extrasOut);
-            assertEquals(TEST_VALUE, extrasOut.get(TEST_KEY));
-
-            extrasOut = controller.getExtras();
-            assertNotNull(extrasOut);
-            assertEquals(TEST_VALUE, extrasOut.get(TEST_KEY));
-        }
-    }
-
-    /**
-     * Tests {@link MediaSessionCompat#setFlags}.
-     */
-    @Test
-    @SmallTest
-    public void testSetFlags() throws Exception {
-        MediaControllerCompat controller = mSession.getController();
-        controller.registerCallback(mCallback, mHandler);
-        synchronized (mWaitLock) {
-            mCallback.resetLocked();
-            mSession.setFlags(5);
-            assertEquals(5, controller.getFlags());
-        }
-    }
-
-    /**
-     * Tests {@link MediaSessionCompat#setMetadata}.
-     */
-    @Test
-    @SmallTest
-    public void testSetMetadata() throws Exception {
-        MediaControllerCompat controller = mSession.getController();
-        controller.registerCallback(mCallback, mHandler);
-        synchronized (mWaitLock) {
-            mCallback.resetLocked();
-            RatingCompat rating = RatingCompat.newHeartRating(true);
-            MediaMetadataCompat metadata = new MediaMetadataCompat.Builder()
-                    .putString(TEST_KEY, TEST_VALUE)
-                    .putRating(METADATA_KEY_RATING, rating)
-                    .build();
-            mSession.setActive(true);
-            mSession.setMetadata(metadata);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnMetadataChangedCalled);
-
-            MediaMetadataCompat metadataOut = mCallback.mMediaMetadata;
-            assertNotNull(metadataOut);
-            assertEquals(TEST_VALUE, metadataOut.getString(TEST_KEY));
-
-            metadataOut = controller.getMetadata();
-            assertNotNull(metadataOut);
-            assertEquals(TEST_VALUE, metadataOut.getString(TEST_KEY));
-
-            assertNotNull(metadataOut.getRating(METADATA_KEY_RATING));
-            RatingCompat ratingOut = metadataOut.getRating(METADATA_KEY_RATING);
-            assertEquals(rating.getRatingStyle(), ratingOut.getRatingStyle());
-            assertEquals(rating.getPercentRating(), ratingOut.getPercentRating(), 0.0f);
-        }
-    }
-
-    /**
-     * Tests {@link MediaSessionCompat#setMetadata} with artwork bitmaps.
-     */
-    @Test
-    @SmallTest
-    public void testSetMetadataWithArtworks() throws Exception {
-        MediaControllerCompat controller = mSession.getController();
-        final Bitmap bitmapSmall = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
-        final Bitmap bitmapLarge = Bitmap.createBitmap(1000, 1000, Bitmap.Config.ALPHA_8);
-
-        controller.registerCallback(mCallback, mHandler);
-        mSession.setActive(true);
-        synchronized (mWaitLock) {
-            mCallback.resetLocked();
-            MediaMetadataCompat metadata = new MediaMetadataCompat.Builder()
-                    .putString(TEST_KEY, TEST_VALUE)
-                    .putBitmap(MediaMetadataCompat.METADATA_KEY_ART, bitmapSmall)
-                    .build();
-            mSession.setMetadata(metadata);
-            mWaitLock.wait(TIME_OUT_MS);
-
-            assertTrue(mCallback.mOnMetadataChangedCalled);
-            MediaMetadataCompat metadataOut = mCallback.mMediaMetadata;
-            assertNotNull(metadataOut);
-            assertEquals(TEST_VALUE, metadataOut.getString(TEST_KEY));
-            Bitmap bitmapSmallOut = metadataOut.getBitmap(MediaMetadataCompat.METADATA_KEY_ART);
-            assertNotNull(bitmapSmallOut);
-            assertEquals(bitmapSmall.getHeight(), bitmapSmallOut.getHeight());
-            assertEquals(bitmapSmall.getWidth(), bitmapSmallOut.getWidth());
-            assertEquals(bitmapSmall.getConfig(), bitmapSmallOut.getConfig());
-
-            metadata = new MediaMetadataCompat.Builder()
-                    .putString(TEST_KEY, TEST_VALUE)
-                    .putBitmap(MediaMetadataCompat.METADATA_KEY_ART, bitmapLarge)
-                    .build();
-            mSession.setMetadata(metadata);
-            mWaitLock.wait(TIME_OUT_MS);
-
-            assertTrue(mCallback.mOnMetadataChangedCalled);
-            metadataOut = mCallback.mMediaMetadata;
-            assertNotNull(metadataOut);
-            assertEquals(TEST_VALUE, metadataOut.getString(TEST_KEY));
-            Bitmap bitmapLargeOut = metadataOut.getBitmap(MediaMetadataCompat.METADATA_KEY_ART);
-            assertNotNull(bitmapLargeOut);
-            // Don't check size here because large bitmaps can be scaled down.
-            assertEquals(bitmapLarge.getConfig(), bitmapLargeOut.getConfig());
-
-            assertFalse(bitmapSmall.isRecycled());
-            assertFalse(bitmapLarge.isRecycled());
-            assertFalse(bitmapSmallOut.isRecycled());
-            assertFalse(bitmapLargeOut.isRecycled());
-            bitmapSmallOut.recycle();
-            bitmapLargeOut.recycle();
-        }
-        bitmapSmall.recycle();
-        bitmapLarge.recycle();
-    }
-
-    /**
-     * Tests {@link MediaSessionCompat#setPlaybackState}.
-     */
-    @Test
-    @SmallTest
-    public void testSetPlaybackState() throws Exception {
-        MediaControllerCompat controller = mSession.getController();
-        controller.registerCallback(mCallback, mHandler);
-        synchronized (mWaitLock) {
-            mCallback.resetLocked();
-            PlaybackStateCompat state =
-                    new PlaybackStateCompat.Builder()
-                            .setActions(TEST_ACTION)
-                            .setErrorMessage(TEST_ERROR_CODE, TEST_ERROR_MSG)
-                            .build();
-            mSession.setPlaybackState(state);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnPlaybackStateChangedCalled);
-
-            PlaybackStateCompat stateOut = mCallback.mPlaybackState;
-            assertNotNull(stateOut);
-            assertEquals(TEST_ACTION, stateOut.getActions());
-            assertEquals(TEST_ERROR_CODE, stateOut.getErrorCode());
-            assertEquals(TEST_ERROR_MSG, stateOut.getErrorMessage().toString());
-
-            stateOut = controller.getPlaybackState();
-            assertNotNull(stateOut);
-            assertEquals(TEST_ACTION, stateOut.getActions());
-            assertEquals(TEST_ERROR_CODE, stateOut.getErrorCode());
-            assertEquals(TEST_ERROR_MSG, stateOut.getErrorMessage().toString());
-        }
-    }
-
-    /**
-     * Tests {@link MediaSessionCompat#setQueue} and {@link MediaSessionCompat#setQueueTitle}.
-     */
-    @Test
-    @SmallTest
-    public void testSetQueueAndSetQueueTitle() throws Exception {
-        MediaControllerCompat controller = mSession.getController();
-        controller.registerCallback(mCallback, mHandler);
-        synchronized (mWaitLock) {
-            mCallback.resetLocked();
-            List<MediaSessionCompat.QueueItem> queue = new ArrayList<>();
-            MediaSessionCompat.QueueItem item = new MediaSessionCompat.QueueItem(
-                    new MediaDescriptionCompat.Builder()
-                            .setMediaId(TEST_VALUE)
-                            .setTitle("title")
-                            .build(),
-                    TEST_QUEUE_ID);
-            queue.add(item);
-            mSession.setQueue(queue);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnQueueChangedCalled);
-
-            mSession.setQueueTitle(TEST_VALUE);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnQueueTitleChangedCalled);
-
-            assertEquals(TEST_VALUE, mCallback.mTitle);
-            assertEquals(queue.size(), mCallback.mQueue.size());
-            assertEquals(TEST_QUEUE_ID, mCallback.mQueue.get(0).getQueueId());
-            assertEquals(TEST_VALUE, mCallback.mQueue.get(0).getDescription().getMediaId());
-
-            assertEquals(TEST_VALUE, controller.getQueueTitle());
-            assertEquals(queue.size(), controller.getQueue().size());
-            assertEquals(TEST_QUEUE_ID, controller.getQueue().get(0).getQueueId());
-            assertEquals(TEST_VALUE, controller.getQueue().get(0).getDescription().getMediaId());
-
-            mCallback.resetLocked();
-            mSession.setQueue(null);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnQueueChangedCalled);
-
-            mSession.setQueueTitle(null);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnQueueTitleChangedCalled);
-
-            assertNull(mCallback.mTitle);
-            assertNull(mCallback.mQueue);
-            assertNull(controller.getQueueTitle());
-            assertNull(controller.getQueue());
-        }
-    }
-
-    /**
-     * Tests {@link MediaSessionCompat#setSessionActivity}.
-     */
-    @Test
-    @SmallTest
-    public void testSessionActivity() throws Exception {
-        MediaControllerCompat controller = mSession.getController();
-        synchronized (mWaitLock) {
-            Intent intent = new Intent("cts.MEDIA_SESSION_ACTION");
-            PendingIntent pi = PendingIntent.getActivity(getContext(), 555, intent, 0);
-            mSession.setSessionActivity(pi);
-            assertEquals(pi, controller.getSessionActivity());
-        }
-    }
-
-    /**
-     * Tests {@link MediaSessionCompat#setCaptioningEnabled}.
-     */
-    @Test
-    @SmallTest
-    public void testSetCaptioningEnabled() throws Exception {
-        MediaControllerCompat controller = mSession.getController();
-        controller.registerCallback(mCallback, mHandler);
-        synchronized (mWaitLock) {
-            mCallback.resetLocked();
-            mSession.setCaptioningEnabled(true);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnCaptioningEnabledChangedCalled);
-            assertEquals(true, mCallback.mCaptioningEnabled);
-            assertEquals(true, controller.isCaptioningEnabled());
-
-            mCallback.resetLocked();
-            mSession.setCaptioningEnabled(false);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnCaptioningEnabledChangedCalled);
-            assertEquals(false, mCallback.mCaptioningEnabled);
-            assertEquals(false, controller.isCaptioningEnabled());
-        }
-    }
-
-    /**
-     * Tests {@link MediaSessionCompat#setRepeatMode}.
-     */
-    @Test
-    @SmallTest
-    public void testSetRepeatMode() throws Exception {
-        MediaControllerCompat controller = mSession.getController();
-        controller.registerCallback(mCallback, mHandler);
-        synchronized (mWaitLock) {
-            mCallback.resetLocked();
-            final int repeatMode = PlaybackStateCompat.REPEAT_MODE_ALL;
-            mSession.setRepeatMode(repeatMode);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnRepeatModeChangedCalled);
-            assertEquals(repeatMode, mCallback.mRepeatMode);
-            assertEquals(repeatMode, controller.getRepeatMode());
-        }
-    }
-
-    /**
-     * Tests {@link MediaSessionCompat#setShuffleMode}.
-     */
-    @Test
-    @SmallTest
-    public void testSetShuffleMode() throws Exception {
-        final int shuffleMode = PlaybackStateCompat.SHUFFLE_MODE_ALL;
-        MediaControllerCompat controller = mSession.getController();
-        controller.registerCallback(mCallback, mHandler);
-        synchronized (mWaitLock) {
-            mCallback.resetLocked();
-            mSession.setShuffleMode(shuffleMode);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnShuffleModeChangedCalled);
-            assertEquals(shuffleMode, mCallback.mShuffleMode);
-            assertEquals(shuffleMode, controller.getShuffleMode());
-        }
-    }
-
-    /**
-     * Tests {@link MediaSessionCompat#sendSessionEvent}.
-     */
-    @Test
-    @SmallTest
-    public void testSendSessionEvent() throws Exception {
-        MediaControllerCompat controller = mSession.getController();
-        controller.registerCallback(mCallback, mHandler);
-        synchronized (mWaitLock) {
-            mCallback.resetLocked();
-            mSession.sendSessionEvent(TEST_SESSION_EVENT, TEST_BUNDLE);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnSessionEventCalled);
-            assertEquals(TEST_SESSION_EVENT, mCallback.mEvent);
-            assertEquals(TEST_VALUE, mCallback.mExtras.getString(TEST_KEY));
-        }
-    }
-
-    /**
-     * Tests {@link MediaSessionCompat#setActive} and {@link MediaSessionCompat#release}.
-     */
-    @Test
-    @SmallTest
-    public void testSetActiveAndRelease() throws Exception {
-        MediaControllerCompat controller = mSession.getController();
-        controller.registerCallback(mCallback, mHandler);
-        synchronized (mWaitLock) {
-            mSession.setActive(true);
-            assertTrue(mSession.isActive());
-
-            mCallback.resetLocked();
-            mSession.release();
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnSessionDestroyedCalled);
-        }
-    }
-
-    /**
-     * Tests {@link MediaSessionCompat#setPlaybackToLocal} and
-     * {@link MediaSessionCompat#setPlaybackToRemote}.
-     */
-    @Test
-    @SmallTest
-    public void testPlaybackToLocalAndRemote() throws Exception {
-        MediaControllerCompat controller = mSession.getController();
-        controller.registerCallback(mCallback, mHandler);
-        synchronized (mWaitLock) {
-            // test setPlaybackToRemote, do this before testing setPlaybackToLocal
-            // to ensure it switches correctly.
-            mCallback.resetLocked();
-            try {
-                mSession.setPlaybackToRemote(null);
-                fail("Expected IAE for setPlaybackToRemote(null)");
-            } catch (IllegalArgumentException e) {
-                // expected
-            }
-            VolumeProviderCompat vp = new VolumeProviderCompat(
-                    VolumeProviderCompat.VOLUME_CONTROL_FIXED,
-                    TEST_MAX_VOLUME,
-                    TEST_CURRENT_VOLUME) {};
-            mSession.setPlaybackToRemote(vp);
-
-            MediaControllerCompat.PlaybackInfo info = null;
-            for (int i = 0; i < MAX_AUDIO_INFO_CHANGED_CALLBACK_COUNT; ++i) {
-                mCallback.mOnAudioInfoChangedCalled = false;
-                mWaitLock.wait(TIME_OUT_MS);
-                assertTrue(mCallback.mOnAudioInfoChangedCalled);
-                info = mCallback.mPlaybackInfo;
-                if (info != null && info.getCurrentVolume() == TEST_CURRENT_VOLUME
-                        && info.getMaxVolume() == TEST_MAX_VOLUME
-                        && info.getVolumeControl() == VolumeProviderCompat.VOLUME_CONTROL_FIXED
-                        && info.getPlaybackType()
-                        == MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE) {
-                    break;
-                }
-            }
-            assertNotNull(info);
-            assertEquals(MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE,
-                    info.getPlaybackType());
-            assertEquals(TEST_MAX_VOLUME, info.getMaxVolume());
-            assertEquals(TEST_CURRENT_VOLUME, info.getCurrentVolume());
-            assertEquals(VolumeProviderCompat.VOLUME_CONTROL_FIXED,
-                    info.getVolumeControl());
-
-            info = controller.getPlaybackInfo();
-            assertNotNull(info);
-            assertEquals(MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE,
-                    info.getPlaybackType());
-            assertEquals(TEST_MAX_VOLUME, info.getMaxVolume());
-            assertEquals(TEST_CURRENT_VOLUME, info.getCurrentVolume());
-            assertEquals(VolumeProviderCompat.VOLUME_CONTROL_FIXED, info.getVolumeControl());
-
-            // test setPlaybackToLocal
-            mSession.setPlaybackToLocal(AudioManager.STREAM_RING);
-            info = controller.getPlaybackInfo();
-            assertNotNull(info);
-            assertEquals(MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_LOCAL,
-                    info.getPlaybackType());
-        }
-    }
-
-    /**
-     * Tests {@link MediaSessionCompat.Callback#onMediaButtonEvent}.
-     */
-    @Test
-    @SmallTest
-    public void testCallbackOnMediaButtonEvent() throws Exception {
-        MediaSessionCallback sessionCallback = new MediaSessionCallback();
-        mSession.setCallback(sessionCallback, new Handler(Looper.getMainLooper()));
-        mSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS);
-        mSession.setActive(true);
-
-        Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON).setComponent(
-                new ComponentName(getContext(), getContext().getClass()));
-        PendingIntent pi = PendingIntent.getBroadcast(getContext(), 0, mediaButtonIntent, 0);
-        mSession.setMediaButtonReceiver(pi);
-
-        // Set state to STATE_PLAYING to get higher priority.
-        setPlaybackState(PlaybackStateCompat.STATE_PLAYING);
-
-        sessionCallback.reset(1);
-        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY);
-        assertTrue(sessionCallback.await(TIME_OUT_MS));
-        assertEquals(1, sessionCallback.mOnPlayCalledCount);
-
-        sessionCallback.reset(1);
-        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PAUSE);
-        assertTrue(sessionCallback.await(TIME_OUT_MS));
-        assertTrue(sessionCallback.mOnPauseCalled);
-
-        sessionCallback.reset(1);
-        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_NEXT);
-        assertTrue(sessionCallback.await(TIME_OUT_MS));
-        assertTrue(sessionCallback.mOnSkipToNextCalled);
-
-        sessionCallback.reset(1);
-        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PREVIOUS);
-        assertTrue(sessionCallback.await(TIME_OUT_MS));
-        assertTrue(sessionCallback.mOnSkipToPreviousCalled);
-
-        sessionCallback.reset(1);
-        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_STOP);
-        assertTrue(sessionCallback.await(TIME_OUT_MS));
-        assertTrue(sessionCallback.mOnStopCalled);
-
-        sessionCallback.reset(1);
-        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_FAST_FORWARD);
-        assertTrue(sessionCallback.await(TIME_OUT_MS));
-        assertTrue(sessionCallback.mOnFastForwardCalled);
-
-        sessionCallback.reset(1);
-        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_REWIND);
-        assertTrue(sessionCallback.await(TIME_OUT_MS));
-        assertTrue(sessionCallback.mOnRewindCalled);
-
-        // Test PLAY_PAUSE button twice.
-        // First, send PLAY_PAUSE button event while in STATE_PAUSED.
-        sessionCallback.reset(1);
-        setPlaybackState(PlaybackStateCompat.STATE_PAUSED);
-        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
-        assertTrue(sessionCallback.await(TIME_OUT_MS));
-        assertEquals(1, sessionCallback.mOnPlayCalledCount);
-
-        // Next, send PLAY_PAUSE button event while in STATE_PLAYING.
-        sessionCallback.reset(1);
-        setPlaybackState(PlaybackStateCompat.STATE_PLAYING);
-        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
-        assertTrue(sessionCallback.await(TIME_OUT_MS));
-        assertTrue(sessionCallback.mOnPauseCalled);
-
-        // Double tap of PLAY_PAUSE is the next track.
-        sessionCallback.reset(2);
-        setPlaybackState(PlaybackStateCompat.STATE_PAUSED);
-        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
-        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
-        assertFalse(sessionCallback.await(WAIT_TIME_MS));
-        assertTrue(sessionCallback.mOnSkipToNextCalled);
-        assertEquals(0, sessionCallback.mOnPlayCalledCount);
-        assertFalse(sessionCallback.mOnPauseCalled);
-
-        // Test PLAY_PAUSE button long-press.
-        // It should be the same as the single short-press.
-        sessionCallback.reset(1);
-        setPlaybackState(PlaybackStateCompat.STATE_PAUSED);
-        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, true);
-        assertTrue(sessionCallback.await(TIME_OUT_MS));
-        assertEquals(1, sessionCallback.mOnPlayCalledCount);
-
-        // Double tap of PLAY_PAUSE should be handled once.
-        // Initial down event from the second press within double tap time-out will make
-        // onSkipToNext() to be called, so further down events shouldn't be handled again.
-        sessionCallback.reset(2);
-        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
-        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, true);
-        assertFalse(sessionCallback.await(WAIT_TIME_MS));
-        assertTrue(sessionCallback.mOnSkipToNextCalled);
-        assertEquals(0, sessionCallback.mOnPlayCalledCount);
-        assertFalse(sessionCallback.mOnPauseCalled);
-
-        // Test PLAY_PAUSE button short-press followed by the long-press.
-        // Initial long-press of the PLAY_PAUSE is considered as the single short-press already,
-        // so it shouldn't be used as the first tap of the double tap.
-        sessionCallback.reset(2);
-        setPlaybackState(PlaybackStateCompat.STATE_PAUSED);
-        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, true);
-        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
-        assertTrue(sessionCallback.await(TIME_OUT_MS));
-        // onMediaButtonEvent() calls either onPlay() or onPause() depending on the playback state,
-        // so onPlay() should be called twice while onPause() isn't called.
-        assertEquals(1, sessionCallback.mOnPlayCalledCount);
-        assertTrue(sessionCallback.mOnPauseCalled);
-        assertFalse(sessionCallback.mOnSkipToNextCalled);
-
-        // If another media key is pressed while the double tap of PLAY_PAUSE,
-        // PLAY_PAUSE should be handles as normal.
-        sessionCallback.reset(3);
-        setPlaybackState(PlaybackStateCompat.STATE_PAUSED);
-        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
-        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_STOP);
-        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
-        assertTrue(sessionCallback.await(TIME_OUT_MS));
-        assertFalse(sessionCallback.mOnSkipToNextCalled);
-        assertTrue(sessionCallback.mOnStopCalled);
-        assertEquals(2, sessionCallback.mOnPlayCalledCount);
-
-        // Test if media keys are handled in order.
-        sessionCallback.reset(2);
-        setPlaybackState(PlaybackStateCompat.STATE_PAUSED);
-        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
-        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_STOP);
-        assertTrue(sessionCallback.await(TIME_OUT_MS));
-        assertEquals(1, sessionCallback.mOnPlayCalledCount);
-        assertTrue(sessionCallback.mOnStopCalled);
-        synchronized (mWaitLock) {
-            assertEquals(PlaybackStateCompat.STATE_STOPPED,
-                    mSession.getController().getPlaybackState().getState());
-        }
-    }
-
-    private void setPlaybackState(int state) {
-        final long allActions = PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PAUSE
-                | PlaybackStateCompat.ACTION_PLAY_PAUSE | PlaybackStateCompat.ACTION_STOP
-                | PlaybackStateCompat.ACTION_SKIP_TO_NEXT
-                | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
-                | PlaybackStateCompat.ACTION_FAST_FORWARD | PlaybackStateCompat.ACTION_REWIND;
-        PlaybackStateCompat playbackState = new PlaybackStateCompat.Builder().setActions(allActions)
-                .setState(state, 0L, 0.0f).build();
-        synchronized (mWaitLock) {
-            mSession.setPlaybackState(playbackState);
-        }
-    }
-
-    @Test
-    @SmallTest
-    public void testSetNullCallback() throws Throwable {
-        getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                try {
-                    MediaSessionCompat session = new MediaSessionCompat(getContext(), "TEST");
-                    session.setCallback(null);
-                } catch (Exception e) {
-                    fail("Fail with an exception: " + e);
-                }
-            }
-        });
-    }
-
-    /**
-     * Tests {@link MediaSessionCompat.QueueItem}.
-     */
-    @Test
-    @SmallTest
-    public void testQueueItem() {
-        MediaSessionCompat.QueueItem item = new MediaSessionCompat.QueueItem(
-                new MediaDescriptionCompat.Builder()
-                        .setMediaId("media-id")
-                        .setTitle("title")
-                        .build(),
-                TEST_QUEUE_ID);
-        assertEquals(TEST_QUEUE_ID, item.getQueueId());
-        assertEquals("media-id", item.getDescription().getMediaId());
-        assertEquals("title", item.getDescription().getTitle());
-        assertEquals(0, item.describeContents());
-
-        Parcel p = Parcel.obtain();
-        item.writeToParcel(p, 0);
-        p.setDataPosition(0);
-        MediaSessionCompat.QueueItem other =
-                MediaSessionCompat.QueueItem.CREATOR.createFromParcel(p);
-        assertEquals(item.toString(), other.toString());
-        p.recycle();
-    }
-
-    /**
-     * Verifies that a new session hasn't had any configuration bits set yet.
-     *
-     * @param controller The controller for the session
-     */
-    private void verifyNewSession(MediaControllerCompat controller, String tag) {
-        assertEquals("New session has unexpected configuration", 0L, controller.getFlags());
-        assertNull("New session has unexpected configuration", controller.getExtras());
-        assertNull("New session has unexpected configuration", controller.getMetadata());
-        assertEquals("New session has unexpected configuration",
-                getContext().getPackageName(), controller.getPackageName());
-        assertNull("New session has unexpected configuration", controller.getPlaybackState());
-        assertNull("New session has unexpected configuration", controller.getQueue());
-        assertNull("New session has unexpected configuration", controller.getQueueTitle());
-        assertEquals("New session has unexpected configuration", RatingCompat.RATING_NONE,
-                controller.getRatingType());
-        assertNull("New session has unexpected configuration", controller.getSessionActivity());
-
-        assertNotNull(controller.getSessionToken());
-        assertNotNull(controller.getTransportControls());
-
-        MediaControllerCompat.PlaybackInfo info = controller.getPlaybackInfo();
-        assertNotNull(info);
-        assertEquals(MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_LOCAL,
-                info.getPlaybackType());
-        assertEquals(mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC),
-                info.getCurrentVolume());
-    }
-
-    private void sendMediaKeyInputToController(int keyCode) {
-        sendMediaKeyInputToController(keyCode, false);
-    }
-
-    private void sendMediaKeyInputToController(int keyCode, boolean isLongPress) {
-        MediaControllerCompat controller = mSession.getController();
-        long currentTimeMs = System.currentTimeMillis();
-        KeyEvent down = new KeyEvent(
-                currentTimeMs, currentTimeMs, KeyEvent.ACTION_DOWN, keyCode, 0);
-        controller.dispatchMediaButtonEvent(down);
-        if (isLongPress) {
-            KeyEvent longPress = new KeyEvent(
-                    currentTimeMs, System.currentTimeMillis(), KeyEvent.ACTION_DOWN, keyCode, 1);
-            controller.dispatchMediaButtonEvent(longPress);
-        }
-        KeyEvent up = new KeyEvent(
-                currentTimeMs, System.currentTimeMillis(), KeyEvent.ACTION_UP, keyCode, 0);
-        controller.dispatchMediaButtonEvent(up);
-    }
-
-    private class MediaControllerCallback extends MediaControllerCompat.Callback {
-        private volatile boolean mOnPlaybackStateChangedCalled;
-        private volatile boolean mOnMetadataChangedCalled;
-        private volatile boolean mOnQueueChangedCalled;
-        private volatile boolean mOnQueueTitleChangedCalled;
-        private volatile boolean mOnExtraChangedCalled;
-        private volatile boolean mOnAudioInfoChangedCalled;
-        private volatile boolean mOnSessionDestroyedCalled;
-        private volatile boolean mOnSessionEventCalled;
-        private volatile boolean mOnCaptioningEnabledChangedCalled;
-        private volatile boolean mOnRepeatModeChangedCalled;
-        private volatile boolean mOnShuffleModeChangedCalled;
-
-        private volatile PlaybackStateCompat mPlaybackState;
-        private volatile MediaMetadataCompat mMediaMetadata;
-        private volatile List<MediaSessionCompat.QueueItem> mQueue;
-        private volatile CharSequence mTitle;
-        private volatile String mEvent;
-        private volatile Bundle mExtras;
-        private volatile MediaControllerCompat.PlaybackInfo mPlaybackInfo;
-        private volatile boolean mCaptioningEnabled;
-        private volatile int mRepeatMode;
-        private volatile int mShuffleMode;
-
-        public void resetLocked() {
-            mOnPlaybackStateChangedCalled = false;
-            mOnMetadataChangedCalled = false;
-            mOnQueueChangedCalled = false;
-            mOnQueueTitleChangedCalled = false;
-            mOnExtraChangedCalled = false;
-            mOnAudioInfoChangedCalled = false;
-            mOnSessionDestroyedCalled = false;
-            mOnSessionEventCalled = false;
-            mOnRepeatModeChangedCalled = false;
-            mOnShuffleModeChangedCalled = false;
-
-            mPlaybackState = null;
-            mMediaMetadata = null;
-            mQueue = null;
-            mTitle = null;
-            mExtras = null;
-            mPlaybackInfo = null;
-            mCaptioningEnabled = false;
-            mRepeatMode = PlaybackStateCompat.REPEAT_MODE_NONE;
-            mShuffleMode = PlaybackStateCompat.SHUFFLE_MODE_NONE;
-        }
-
-        @Override
-        public void onPlaybackStateChanged(PlaybackStateCompat state) {
-            synchronized (mWaitLock) {
-                mOnPlaybackStateChangedCalled = true;
-                mPlaybackState = state;
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onMetadataChanged(MediaMetadataCompat metadata) {
-            synchronized (mWaitLock) {
-                mOnMetadataChangedCalled = true;
-                mMediaMetadata = metadata;
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onQueueChanged(List<MediaSessionCompat.QueueItem> queue) {
-            synchronized (mWaitLock) {
-                mOnQueueChangedCalled = true;
-                mQueue = queue;
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onQueueTitleChanged(CharSequence title) {
-            synchronized (mWaitLock) {
-                mOnQueueTitleChangedCalled = true;
-                mTitle = title;
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onExtrasChanged(Bundle extras) {
-            synchronized (mWaitLock) {
-                mOnExtraChangedCalled = true;
-                mExtras = extras;
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onAudioInfoChanged(MediaControllerCompat.PlaybackInfo info) {
-            synchronized (mWaitLock) {
-                mOnAudioInfoChangedCalled = true;
-                mPlaybackInfo = info;
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onSessionDestroyed() {
-            synchronized (mWaitLock) {
-                mOnSessionDestroyedCalled = true;
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onSessionEvent(String event, Bundle extras) {
-            synchronized (mWaitLock) {
-                mOnSessionEventCalled = true;
-                mEvent = event;
-                mExtras = (Bundle) extras.clone();
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onCaptioningEnabledChanged(boolean enabled) {
-            synchronized (mWaitLock) {
-                mOnCaptioningEnabledChangedCalled = true;
-                mCaptioningEnabled = enabled;
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onRepeatModeChanged(int repeatMode) {
-            synchronized (mWaitLock) {
-                mOnRepeatModeChangedCalled = true;
-                mRepeatMode = repeatMode;
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onShuffleModeChanged(int shuffleMode) {
-            synchronized (mWaitLock) {
-                mOnShuffleModeChangedCalled = true;
-                mShuffleMode = shuffleMode;
-                mWaitLock.notify();
-            }
-        }
-    }
-
-    private class MediaSessionCallback extends MediaSessionCompat.Callback {
-        private CountDownLatch mLatch;
-        private int mOnPlayCalledCount;
-        private boolean mOnPauseCalled;
-        private boolean mOnStopCalled;
-        private boolean mOnFastForwardCalled;
-        private boolean mOnRewindCalled;
-        private boolean mOnSkipToPreviousCalled;
-        private boolean mOnSkipToNextCalled;
-
-        public void reset(int count) {
-            mLatch = new CountDownLatch(count);
-            mOnPlayCalledCount = 0;
-            mOnPauseCalled = false;
-            mOnStopCalled = false;
-            mOnFastForwardCalled = false;
-            mOnRewindCalled = false;
-            mOnSkipToPreviousCalled = false;
-            mOnSkipToNextCalled = false;
-        }
-
-        public boolean await(long timeoutMs) {
-            try {
-                return mLatch.await(timeoutMs, TimeUnit.MILLISECONDS);
-            } catch (InterruptedException e) {
-                return false;
-            }
-        }
-
-        @Override
-        public void onPlay() {
-            mOnPlayCalledCount++;
-            setPlaybackState(PlaybackStateCompat.STATE_PLAYING);
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onPause() {
-            mOnPauseCalled = true;
-            setPlaybackState(PlaybackStateCompat.STATE_PAUSED);
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onStop() {
-            mOnStopCalled = true;
-            setPlaybackState(PlaybackStateCompat.STATE_STOPPED);
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onFastForward() {
-            mOnFastForwardCalled = true;
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onRewind() {
-            mOnRewindCalled = true;
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onSkipToPrevious() {
-            mOnSkipToPreviousCalled = true;
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onSkipToNext() {
-            mOnSkipToNextCalled = true;
-            mLatch.countDown();
-        }
-    }
-}
diff --git a/media-compat/tests/src/android/support/v4/media/session/PlaybackStateCompatTest.java b/media-compat/tests/src/android/support/v4/media/session/PlaybackStateCompatTest.java
deleted file mode 100644
index 9e320cd..0000000
--- a/media-compat/tests/src/android/support/v4/media/session/PlaybackStateCompatTest.java
+++ /dev/null
@@ -1,296 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.v4.media.session;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-
-import android.os.Bundle;
-import android.os.Parcel;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-
-/**
- * Test {@link PlaybackStateCompat}.
- */
-@RunWith(AndroidJUnit4.class)
-public class PlaybackStateCompatTest {
-
-    private static final long TEST_POSITION = 20000L;
-    private static final long TEST_BUFFERED_POSITION = 15000L;
-    private static final long TEST_UPDATE_TIME = 100000L;
-    private static final long TEST_ACTIONS = PlaybackStateCompat.ACTION_PLAY
-            | PlaybackStateCompat.ACTION_STOP | PlaybackStateCompat.ACTION_SEEK_TO;
-    private static final long TEST_QUEUE_ITEM_ID = 23L;
-    private static final float TEST_PLAYBACK_SPEED = 3.0f;
-    private static final float TEST_PLAYBACK_SPEED_ON_REWIND = -2.0f;
-    private static final float DELTA = 1e-7f;
-
-    private static final int TEST_ERROR_CODE =
-            PlaybackStateCompat.ERROR_CODE_AUTHENTICATION_EXPIRED;
-    private static final String TEST_ERROR_MSG = "test-error-msg";
-    private static final String TEST_CUSTOM_ACTION = "test-custom-action";
-    private static final String TEST_CUSTOM_ACTION_NAME = "test-custom-action-name";
-    private static final int TEST_ICON_RESOURCE_ID = android.R.drawable.ic_media_next;
-
-    private static final String EXTRAS_KEY = "test-key";
-    private static final String EXTRAS_VALUE = "test-value";
-
-    /**
-     * Test default values of {@link PlaybackStateCompat}.
-     */
-    @Test
-    @SmallTest
-    public void testBuilder() {
-        PlaybackStateCompat state = new PlaybackStateCompat.Builder().build();
-
-        assertEquals(new ArrayList<PlaybackStateCompat.CustomAction>(), state.getCustomActions());
-        assertEquals(0, state.getState());
-        assertEquals(0L, state.getPosition());
-        assertEquals(0L, state.getBufferedPosition());
-        assertEquals(0.0f, state.getPlaybackSpeed(), DELTA);
-        assertEquals(0L, state.getActions());
-        assertEquals(0, state.getErrorCode());
-        assertNull(state.getErrorMessage());
-        assertEquals(0L, state.getLastPositionUpdateTime());
-        assertEquals(MediaSessionCompat.QueueItem.UNKNOWN_ID, state.getActiveQueueItemId());
-        assertNull(state.getExtras());
-    }
-
-    /**
-     * Test following setter methods of {@link PlaybackStateCompat.Builder}:
-     * {@link PlaybackStateCompat.Builder#setState(int, long, float)}
-     * {@link PlaybackStateCompat.Builder#setActions(long)}
-     * {@link PlaybackStateCompat.Builder#setActiveQueueItemId(long)}
-     * {@link PlaybackStateCompat.Builder#setBufferedPosition(long)}
-     * {@link PlaybackStateCompat.Builder#setErrorMessage(CharSequence)}
-     * {@link PlaybackStateCompat.Builder#setExtras(Bundle)}
-     */
-    @Test
-    @SmallTest
-    public void testBuilder_setterMethods() {
-        Bundle extras = new Bundle();
-        extras.putString(EXTRAS_KEY, EXTRAS_VALUE);
-
-        PlaybackStateCompat state = new PlaybackStateCompat.Builder()
-                .setState(PlaybackStateCompat.STATE_PLAYING, TEST_POSITION, TEST_PLAYBACK_SPEED)
-                .setActions(TEST_ACTIONS)
-                .setActiveQueueItemId(TEST_QUEUE_ITEM_ID)
-                .setBufferedPosition(TEST_BUFFERED_POSITION)
-                .setErrorMessage(TEST_ERROR_CODE, TEST_ERROR_MSG)
-                .setExtras(extras)
-                .build();
-        assertEquals(PlaybackStateCompat.STATE_PLAYING, state.getState());
-        assertEquals(TEST_POSITION, state.getPosition());
-        assertEquals(TEST_PLAYBACK_SPEED, state.getPlaybackSpeed(), DELTA);
-        assertEquals(TEST_ACTIONS, state.getActions());
-        assertEquals(TEST_QUEUE_ITEM_ID, state.getActiveQueueItemId());
-        assertEquals(TEST_BUFFERED_POSITION, state.getBufferedPosition());
-        assertEquals(TEST_ERROR_CODE, state.getErrorCode());
-        assertEquals(TEST_ERROR_MSG, state.getErrorMessage().toString());
-        assertNotNull(state.getExtras());
-        assertEquals(EXTRAS_VALUE, state.getExtras().get(EXTRAS_KEY));
-    }
-
-    /**
-     * Test {@link PlaybackStateCompat.Builder#setState(int, long, float, long)}.
-     */
-    @Test
-    @SmallTest
-    public void testBuilder_setStateWithUpdateTime() {
-        PlaybackStateCompat state = new PlaybackStateCompat.Builder()
-                .setState(
-                        PlaybackStateCompat.STATE_REWINDING,
-                        TEST_POSITION,
-                        TEST_PLAYBACK_SPEED_ON_REWIND,
-                        TEST_UPDATE_TIME)
-                .build();
-        assertEquals(PlaybackStateCompat.STATE_REWINDING, state.getState());
-        assertEquals(TEST_POSITION, state.getPosition());
-        assertEquals(TEST_PLAYBACK_SPEED_ON_REWIND, state.getPlaybackSpeed(), DELTA);
-        assertEquals(TEST_UPDATE_TIME, state.getLastPositionUpdateTime());
-    }
-
-    /**
-     * Test {@link PlaybackStateCompat.Builder#addCustomAction(String, String, int)}.
-     */
-    @Test
-    @SmallTest
-    public void testBuilder_addCustomAction() {
-        ArrayList<PlaybackStateCompat.CustomAction> actions = new ArrayList<>();
-        PlaybackStateCompat.Builder builder = new PlaybackStateCompat.Builder();
-
-        for (int i = 0; i < 5; i++) {
-            actions.add(new PlaybackStateCompat.CustomAction.Builder(
-                    TEST_CUSTOM_ACTION + i, TEST_CUSTOM_ACTION_NAME + i, TEST_ICON_RESOURCE_ID + i)
-                    .build());
-            builder.addCustomAction(
-                    TEST_CUSTOM_ACTION + i, TEST_CUSTOM_ACTION_NAME + i, TEST_ICON_RESOURCE_ID + i);
-        }
-
-        PlaybackStateCompat state = builder.build();
-        assertEquals(actions.size(), state.getCustomActions().size());
-        for (int i = 0; i < actions.size(); i++) {
-            assertCustomActionEquals(actions.get(i), state.getCustomActions().get(i));
-        }
-    }
-
-    /**
-     * Test {@link PlaybackStateCompat.Builder#addCustomAction(PlaybackStateCompat.CustomAction)}.
-     */
-    @Test
-    @SmallTest
-    public void testBuilder_addCustomActionWithCustomActionObject() {
-        Bundle extras = new Bundle();
-        extras.putString(EXTRAS_KEY, EXTRAS_VALUE);
-
-        ArrayList<PlaybackStateCompat.CustomAction> actions = new ArrayList<>();
-        PlaybackStateCompat.Builder builder = new PlaybackStateCompat.Builder();
-
-        for (int i = 0; i < 5; i++) {
-            actions.add(new PlaybackStateCompat.CustomAction.Builder(
-                    TEST_CUSTOM_ACTION + i, TEST_CUSTOM_ACTION_NAME + i, TEST_ICON_RESOURCE_ID + i)
-                    .setExtras(extras)
-                    .build());
-            builder.addCustomAction(new PlaybackStateCompat.CustomAction.Builder(
-                    TEST_CUSTOM_ACTION + i, TEST_CUSTOM_ACTION_NAME + i, TEST_ICON_RESOURCE_ID + i)
-                    .setExtras(extras)
-                    .build());
-        }
-
-        PlaybackStateCompat state = builder.build();
-        assertEquals(actions.size(), state.getCustomActions().size());
-        for (int i = 0; i < actions.size(); i++) {
-            assertCustomActionEquals(actions.get(i), state.getCustomActions().get(i));
-        }
-    }
-
-    /**
-     * Test {@link PlaybackStateCompat#writeToParcel(Parcel, int)}.
-     */
-    @Test
-    @SmallTest
-    public void testWriteToParcel() {
-        Bundle extras = new Bundle();
-        extras.putString(EXTRAS_KEY, EXTRAS_VALUE);
-
-        PlaybackStateCompat.Builder builder =
-                new PlaybackStateCompat.Builder()
-                        .setState(PlaybackStateCompat.STATE_CONNECTING, TEST_POSITION,
-                                TEST_PLAYBACK_SPEED, TEST_UPDATE_TIME)
-                        .setActions(TEST_ACTIONS)
-                        .setActiveQueueItemId(TEST_QUEUE_ITEM_ID)
-                        .setBufferedPosition(TEST_BUFFERED_POSITION)
-                        .setErrorMessage(TEST_ERROR_CODE, TEST_ERROR_MSG)
-                        .setExtras(extras);
-
-        for (int i = 0; i < 5; i++) {
-            builder.addCustomAction(
-                    new PlaybackStateCompat.CustomAction.Builder(
-                            TEST_CUSTOM_ACTION + i,
-                            TEST_CUSTOM_ACTION_NAME + i,
-                            TEST_ICON_RESOURCE_ID + i)
-                            .setExtras(extras)
-                            .build());
-        }
-        PlaybackStateCompat state = builder.build();
-
-        Parcel parcel = Parcel.obtain();
-        state.writeToParcel(parcel, 0);
-        parcel.setDataPosition(0);
-
-        PlaybackStateCompat stateOut = PlaybackStateCompat.CREATOR.createFromParcel(parcel);
-        assertEquals(PlaybackStateCompat.STATE_CONNECTING, stateOut.getState());
-        assertEquals(TEST_POSITION, stateOut.getPosition());
-        assertEquals(TEST_PLAYBACK_SPEED, stateOut.getPlaybackSpeed(), DELTA);
-        assertEquals(TEST_UPDATE_TIME, stateOut.getLastPositionUpdateTime());
-        assertEquals(TEST_BUFFERED_POSITION, stateOut.getBufferedPosition());
-        assertEquals(TEST_ACTIONS, stateOut.getActions());
-        assertEquals(TEST_QUEUE_ITEM_ID, stateOut.getActiveQueueItemId());
-        assertEquals(TEST_ERROR_CODE, stateOut.getErrorCode());
-        assertEquals(TEST_ERROR_MSG, stateOut.getErrorMessage());
-        assertNotNull(stateOut.getExtras());
-        assertEquals(EXTRAS_VALUE, stateOut.getExtras().get(EXTRAS_KEY));
-
-        assertEquals(state.getCustomActions().size(), stateOut.getCustomActions().size());
-        for (int i = 0; i < state.getCustomActions().size(); i++) {
-            assertCustomActionEquals(
-                    state.getCustomActions().get(i), stateOut.getCustomActions().get(i));
-        }
-        parcel.recycle();
-    }
-
-    /**
-     * Test {@link PlaybackStateCompat#describeContents()}.
-     */
-    @Test
-    @SmallTest
-    public void testDescribeContents() {
-        assertEquals(0, new PlaybackStateCompat.Builder().build().describeContents());
-    }
-
-    /**
-     * Test {@link PlaybackStateCompat.CustomAction}.
-     */
-    @Test
-    @SmallTest
-    public void testCustomAction() {
-        Bundle extras = new Bundle();
-        extras.putString(EXTRAS_KEY, EXTRAS_VALUE);
-
-        // Test Builder/Getters
-        PlaybackStateCompat.CustomAction customAction = new PlaybackStateCompat.CustomAction
-                .Builder(TEST_CUSTOM_ACTION, TEST_CUSTOM_ACTION_NAME, TEST_ICON_RESOURCE_ID)
-                .setExtras(extras)
-                .build();
-        assertEquals(TEST_CUSTOM_ACTION, customAction.getAction());
-        assertEquals(TEST_CUSTOM_ACTION_NAME, customAction.getName().toString());
-        assertEquals(TEST_ICON_RESOURCE_ID, customAction.getIcon());
-        assertEquals(EXTRAS_VALUE, customAction.getExtras().get(EXTRAS_KEY));
-
-        // Test describeContents
-        assertEquals(0, customAction.describeContents());
-
-        // Test writeToParcel
-        Parcel parcel = Parcel.obtain();
-        customAction.writeToParcel(parcel, 0);
-        parcel.setDataPosition(0);
-
-        assertCustomActionEquals(
-                customAction, PlaybackStateCompat.CustomAction.CREATOR.createFromParcel(parcel));
-        parcel.recycle();
-    }
-
-    private void assertCustomActionEquals(PlaybackStateCompat.CustomAction action1,
-            PlaybackStateCompat.CustomAction action2) {
-        assertEquals(action1.getAction(), action2.getAction());
-        assertEquals(action1.getName(), action2.getName());
-        assertEquals(action1.getIcon(), action2.getIcon());
-
-        // To be the same, two extras should be both null or both not null.
-        assertEquals(action1.getExtras() != null, action2.getExtras() != null);
-        if (action1.getExtras() != null) {
-            assertEquals(action1.getExtras().get(EXTRAS_KEY), action2.getExtras().get(EXTRAS_KEY));
-        }
-    }
-}
diff --git a/media-compat-test-client/OWNERS b/media-compat/version-compat-tests/OWNERS
similarity index 100%
rename from media-compat-test-client/OWNERS
rename to media-compat/version-compat-tests/OWNERS
diff --git a/media-compat/version-compat-tests/current/client/AndroidManifest.xml b/media-compat/version-compat-tests/current/client/AndroidManifest.xml
new file mode 100644
index 0000000..9724d2b
--- /dev/null
+++ b/media-compat/version-compat-tests/current/client/AndroidManifest.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+   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.
+-->
+<manifest package="android.support.mediacompat.client"/>
diff --git a/media-compat/version-compat-tests/current/client/build.gradle b/media-compat/version-compat-tests/current/client/build.gradle
new file mode 100644
index 0000000..aeb82c1
--- /dev/null
+++ b/media-compat/version-compat-tests/current/client/build.gradle
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+
+import static android.support.dependencies.DependenciesKt.*
+
+plugins {
+    id("SupportAndroidLibraryPlugin")
+}
+
+dependencies {
+    androidTestImplementation(project(":support-media-compat"))
+    androidTestImplementation(project(":support-media-compat-test-lib"))
+
+    androidTestImplementation(TEST_RUNNER)
+}
+
+android {
+    defaultConfig {
+        minSdkVersion 14
+    }
+}
+
+supportLibrary {
+    legacySourceLocation = true
+}
\ No newline at end of file
diff --git a/media-compat/version-compat-tests/current/client/lint-baseline.xml b/media-compat/version-compat-tests/current/client/lint-baseline.xml
new file mode 100644
index 0000000..ed7ade1
--- /dev/null
+++ b/media-compat/version-compat-tests/current/client/lint-baseline.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+   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.
+-->
+<issues format="4" by="lint 3.0.0-alpha9">
+
+</issues>
diff --git a/media-compat/version-compat-tests/current/client/tests/AndroidManifest.xml b/media-compat/version-compat-tests/current/client/tests/AndroidManifest.xml
new file mode 100644
index 0000000..afe1865
--- /dev/null
+++ b/media-compat/version-compat-tests/current/client/tests/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+   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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.support.mediacompat.client.test">
+    <application android:supportsRtl="true">
+        <receiver android:name="android.support.mediacompat.client.ClientBroadcastReceiver">
+            <intent-filter>
+                <action android:name="android.support.mediacompat.service.action.CALL_MEDIA_CONTROLLER_METHOD"/>
+                <action android:name="android.support.mediacompat.service.action.CALL_TRANSPORT_CONTROLS_METHOD"/>
+            </intent-filter>
+        </receiver>
+    </application>
+</manifest>
diff --git a/media-compat/version-compat-tests/current/client/tests/NO_DOCS b/media-compat/version-compat-tests/current/client/tests/NO_DOCS
new file mode 100644
index 0000000..61c9b1a
--- /dev/null
+++ b/media-compat/version-compat-tests/current/client/tests/NO_DOCS
@@ -0,0 +1,17 @@
+# 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.
+
+Having this file, named NO_DOCS, in a directory will prevent
+Android javadocs from being generated for java files under
+the directory. This is especially useful for test projects.
diff --git a/media-compat/version-compat-tests/current/client/tests/src/android/support/mediacompat/client/AudioAttributesCompatTest.java b/media-compat/version-compat-tests/current/client/tests/src/android/support/mediacompat/client/AudioAttributesCompatTest.java
new file mode 100644
index 0000000..675c0fc
--- /dev/null
+++ b/media-compat/version-compat-tests/current/client/tests/src/android/support/mediacompat/client/AudioAttributesCompatTest.java
@@ -0,0 +1,170 @@
+/*
+ * 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.client;
+
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.hamcrest.core.IsNot.not;
+import static org.junit.Assert.assertThat;
+
+import android.media.AudioAttributes;
+import android.media.AudioManager;
+import android.os.Build;
+import android.support.test.filters.SdkSuppress;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v4.media.AudioAttributesCompat;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Test {@link AudioAttributesCompat}. */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class AudioAttributesCompatTest {
+    // some macros for conciseness
+    static final AudioAttributesCompat.Builder mkBuilder(
+            @AudioAttributesCompat.AttributeContentType int type,
+            @AudioAttributesCompat.AttributeUsage int usage) {
+        return new AudioAttributesCompat.Builder().setContentType(type).setUsage(usage);
+    }
+
+    static final AudioAttributesCompat.Builder mkBuilder(int legacyStream) {
+        return new AudioAttributesCompat.Builder().setLegacyStreamType(legacyStream);
+    }
+
+    // some objects we'll toss around
+    Object mMediaAA;
+    AudioAttributesCompat mMediaAAC,
+            mMediaLegacyAAC,
+            mMediaAACFromAA,
+            mNotificationAAC,
+            mNotificationLegacyAAC;
+
+    @Before
+    @SdkSuppress(minSdkVersion = 21)
+    public void setUpApi21() {
+        if (Build.VERSION.SDK_INT < 21) return;
+        mMediaAA =
+                new AudioAttributes.Builder()
+                        .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
+                        .setUsage(AudioAttributes.USAGE_MEDIA)
+                        .build();
+        mMediaAACFromAA = AudioAttributesCompat.wrap((AudioAttributes) mMediaAA);
+    }
+
+    @Before
+    public void setUp() {
+        mMediaAAC =
+                mkBuilder(AudioAttributesCompat.CONTENT_TYPE_MUSIC,
+                        AudioAttributesCompat.USAGE_MEDIA).build();
+        mMediaLegacyAAC = mkBuilder(AudioManager.STREAM_MUSIC).build();
+        mNotificationAAC =
+                mkBuilder(AudioAttributesCompat.CONTENT_TYPE_SONIFICATION,
+                        AudioAttributesCompat.USAGE_NOTIFICATION)
+                        .build();
+        mNotificationLegacyAAC = mkBuilder(AudioManager.STREAM_NOTIFICATION).build();
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 21)
+    public void testCreateWithAudioAttributesApi21() {
+        assertThat(mMediaAACFromAA, not(equalTo(null)));
+        assertThat((AudioAttributes) mMediaAACFromAA.unwrap(), equalTo(mMediaAA));
+        assertThat(
+                (AudioAttributes) mMediaAACFromAA.unwrap(),
+                equalTo(new AudioAttributes.Builder((AudioAttributes) mMediaAA).build()));
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 21)
+    public void testEqualityApi21() {
+        assertThat("self equality", mMediaAACFromAA, equalTo(mMediaAACFromAA));
+        assertThat("different things", mMediaAACFromAA, not(equalTo(mNotificationAAC)));
+    }
+
+    @Test
+    public void testEquality() {
+        assertThat("self equality", mMediaAAC, equalTo(mMediaAAC));
+        assertThat(
+                "equal to clone",
+                mMediaAAC,
+                equalTo(new AudioAttributesCompat.Builder(mMediaAAC).build()));
+        assertThat("different things are different", mMediaAAC, not(equalTo(mNotificationAAC)));
+        assertThat("different things are different 2", mNotificationAAC, not(equalTo(mMediaAAC)));
+        assertThat(
+                "equal to clone 2",
+                mNotificationAAC,
+                equalTo(new AudioAttributesCompat.Builder(mNotificationAAC).build()));
+    }
+
+    @Test
+    public void testGetters() {
+        assertThat(mMediaAAC.getContentType(), equalTo(AudioAttributesCompat.CONTENT_TYPE_MUSIC));
+        assertThat(mMediaAAC.getUsage(), equalTo(AudioAttributesCompat.USAGE_MEDIA));
+        assertThat(mMediaAAC.getFlags(), equalTo(0));
+    }
+
+    @Test
+    public void testLegacyStreamTypeInference() {
+        assertThat(mMediaAAC.getLegacyStreamType(), equalTo(AudioManager.STREAM_MUSIC));
+        assertThat(mMediaLegacyAAC.getLegacyStreamType(), equalTo(AudioManager.STREAM_MUSIC));
+        assertThat(
+                mNotificationAAC.getLegacyStreamType(), equalTo(AudioManager.STREAM_NOTIFICATION));
+        assertThat(
+                mNotificationLegacyAAC.getLegacyStreamType(),
+                equalTo(AudioManager.STREAM_NOTIFICATION));
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 21)
+    public void testLegacyStreamTypeInferenceApi21() {
+        assertThat(mMediaAACFromAA.getLegacyStreamType(), equalTo(AudioManager.STREAM_MUSIC));
+    }
+
+    @Test
+    public void testLegacyStreamTypeInferenceInLegacyMode() {
+        // the builders behave differently based on the value of this only-for-testing global
+        // so we need our very own objects inside this method
+        AudioAttributesCompat.setForceLegacyBehavior(true);
+
+        AudioAttributesCompat mediaAAC =
+                mkBuilder(AudioAttributesCompat.CONTENT_TYPE_MUSIC,
+                        AudioAttributesCompat.USAGE_MEDIA).build();
+        AudioAttributesCompat mediaLegacyAAC = mkBuilder(AudioManager.STREAM_MUSIC).build();
+
+        AudioAttributesCompat notificationAAC =
+                mkBuilder(AudioAttributesCompat.CONTENT_TYPE_SONIFICATION,
+                        AudioAttributesCompat.USAGE_NOTIFICATION)
+                        .build();
+        AudioAttributesCompat notificationLegacyAAC =
+                mkBuilder(AudioManager.STREAM_NOTIFICATION).build();
+
+        assertThat(mediaAAC.getLegacyStreamType(), equalTo(AudioManager.STREAM_MUSIC));
+        assertThat(mediaLegacyAAC.getLegacyStreamType(), equalTo(AudioManager.STREAM_MUSIC));
+        assertThat(
+                notificationAAC.getLegacyStreamType(), equalTo(AudioManager.STREAM_NOTIFICATION));
+        assertThat(
+                notificationLegacyAAC.getLegacyStreamType(),
+                equalTo(AudioManager.STREAM_NOTIFICATION));
+    }
+
+    @After
+    public void cleanUp() {
+        AudioAttributesCompat.setForceLegacyBehavior(false);
+    }
+}
diff --git a/media-compat/version-compat-tests/current/client/tests/src/android/support/mediacompat/client/ClientBroadcastReceiver.java b/media-compat/version-compat-tests/current/client/tests/src/android/support/mediacompat/client/ClientBroadcastReceiver.java
new file mode 100644
index 0000000..3227482
--- /dev/null
+++ b/media-compat/version-compat-tests/current/client/tests/src/android/support/mediacompat/client/ClientBroadcastReceiver.java
@@ -0,0 +1,216 @@
+/*
+ * 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.client;
+
+import static android.support.mediacompat.testlib.MediaControllerConstants.ADD_QUEUE_ITEM;
+import static android.support.mediacompat.testlib.MediaControllerConstants
+        .ADD_QUEUE_ITEM_WITH_INDEX;
+import static android.support.mediacompat.testlib.MediaControllerConstants.ADJUST_VOLUME;
+import static android.support.mediacompat.testlib.MediaControllerConstants.FAST_FORWARD;
+import static android.support.mediacompat.testlib.MediaControllerConstants.PAUSE;
+import static android.support.mediacompat.testlib.MediaControllerConstants.PLAY;
+import static android.support.mediacompat.testlib.MediaControllerConstants.PLAY_FROM_MEDIA_ID;
+import static android.support.mediacompat.testlib.MediaControllerConstants.PLAY_FROM_SEARCH;
+import static android.support.mediacompat.testlib.MediaControllerConstants.PLAY_FROM_URI;
+import static android.support.mediacompat.testlib.MediaControllerConstants.PREPARE;
+import static android.support.mediacompat.testlib.MediaControllerConstants.PREPARE_FROM_MEDIA_ID;
+import static android.support.mediacompat.testlib.MediaControllerConstants.PREPARE_FROM_SEARCH;
+import static android.support.mediacompat.testlib.MediaControllerConstants.PREPARE_FROM_URI;
+import static android.support.mediacompat.testlib.MediaControllerConstants.REMOVE_QUEUE_ITEM;
+import static android.support.mediacompat.testlib.MediaControllerConstants.REWIND;
+import static android.support.mediacompat.testlib.MediaControllerConstants.SEEK_TO;
+import static android.support.mediacompat.testlib.MediaControllerConstants.SEND_COMMAND;
+import static android.support.mediacompat.testlib.MediaControllerConstants.SEND_CUSTOM_ACTION;
+import static android.support.mediacompat.testlib.MediaControllerConstants
+        .SEND_CUSTOM_ACTION_PARCELABLE;
+import static android.support.mediacompat.testlib.MediaControllerConstants.SET_CAPTIONING_ENABLED;
+import static android.support.mediacompat.testlib.MediaControllerConstants.SET_RATING;
+import static android.support.mediacompat.testlib.MediaControllerConstants.SET_REPEAT_MODE;
+import static android.support.mediacompat.testlib.MediaControllerConstants.SET_SHUFFLE_MODE;
+import static android.support.mediacompat.testlib.MediaControllerConstants.SET_VOLUME_TO;
+import static android.support.mediacompat.testlib.MediaControllerConstants.SKIP_TO_NEXT;
+import static android.support.mediacompat.testlib.MediaControllerConstants.SKIP_TO_PREVIOUS;
+import static android.support.mediacompat.testlib.MediaControllerConstants.SKIP_TO_QUEUE_ITEM;
+import static android.support.mediacompat.testlib.MediaControllerConstants.STOP;
+import static android.support.mediacompat.testlib.util.IntentUtil
+        .ACTION_CALL_MEDIA_CONTROLLER_METHOD;
+import static android.support.mediacompat.testlib.util.IntentUtil
+        .ACTION_CALL_TRANSPORT_CONTROLS_METHOD;
+import static android.support.mediacompat.testlib.util.IntentUtil.KEY_ARGUMENT;
+import static android.support.mediacompat.testlib.util.IntentUtil.KEY_METHOD_ID;
+import static android.support.mediacompat.testlib.util.IntentUtil.KEY_SESSION_TOKEN;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.support.v4.media.MediaDescriptionCompat;
+import android.support.v4.media.RatingCompat;
+import android.support.v4.media.session.MediaControllerCompat;
+import android.support.v4.media.session.MediaControllerCompat.TransportControls;
+import android.support.v4.media.session.MediaSessionCompat;
+import android.support.v4.media.session.PlaybackStateCompat;
+
+public class ClientBroadcastReceiver extends BroadcastReceiver {
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        Bundle extras = intent.getExtras();
+        MediaControllerCompat controller;
+        try {
+            controller = new MediaControllerCompat(context,
+                    (MediaSessionCompat.Token) extras.getParcelable(KEY_SESSION_TOKEN));
+        } catch (RemoteException ex) {
+            // Do nothing.
+            return;
+        }
+        int method = extras.getInt(KEY_METHOD_ID, 0);
+
+        if (ACTION_CALL_MEDIA_CONTROLLER_METHOD.equals(intent.getAction()) && extras != null) {
+            Bundle arguments;
+            switch (method) {
+                case SEND_COMMAND:
+                    arguments = extras.getBundle(KEY_ARGUMENT);
+                    controller.sendCommand(
+                            arguments.getString("command"),
+                            arguments.getBundle("extras"),
+                            new ResultReceiver(null));
+                    break;
+                case ADD_QUEUE_ITEM:
+                    controller.addQueueItem(
+                            (MediaDescriptionCompat) extras.getParcelable(KEY_ARGUMENT));
+                    break;
+                case ADD_QUEUE_ITEM_WITH_INDEX:
+                    arguments = extras.getBundle(KEY_ARGUMENT);
+                    controller.addQueueItem(
+                            (MediaDescriptionCompat) arguments.getParcelable("description"),
+                            arguments.getInt("index"));
+                    break;
+                case REMOVE_QUEUE_ITEM:
+                    controller.removeQueueItem(
+                            (MediaDescriptionCompat) extras.getParcelable(KEY_ARGUMENT));
+                    break;
+                case SET_VOLUME_TO:
+                    controller.setVolumeTo(extras.getInt(KEY_ARGUMENT), 0);
+                    break;
+                case ADJUST_VOLUME:
+                    controller.adjustVolume(extras.getInt(KEY_ARGUMENT), 0);
+                    break;
+            }
+        } else if (ACTION_CALL_TRANSPORT_CONTROLS_METHOD.equals(intent.getAction())
+                && extras != null) {
+            TransportControls controls = controller.getTransportControls();
+            Bundle arguments;
+            switch (method) {
+                case PLAY:
+                    controls.play();
+                    break;
+                case PAUSE:
+                    controls.pause();
+                    break;
+                case STOP:
+                    controls.stop();
+                    break;
+                case FAST_FORWARD:
+                    controls.fastForward();
+                    break;
+                case REWIND:
+                    controls.rewind();
+                    break;
+                case SKIP_TO_PREVIOUS:
+                    controls.skipToPrevious();
+                    break;
+                case SKIP_TO_NEXT:
+                    controls.skipToNext();
+                    break;
+                case SEEK_TO:
+                    controls.seekTo(extras.getLong(KEY_ARGUMENT));
+                    break;
+                case SET_RATING:
+                    controls.setRating((RatingCompat) extras.getParcelable(KEY_ARGUMENT));
+                    break;
+                case PLAY_FROM_MEDIA_ID:
+                    arguments = extras.getBundle(KEY_ARGUMENT);
+                    controls.playFromMediaId(
+                            arguments.getString("mediaId"),
+                            arguments.getBundle("extras"));
+                    break;
+                case PLAY_FROM_SEARCH:
+                    arguments = extras.getBundle(KEY_ARGUMENT);
+                    controls.playFromSearch(
+                            arguments.getString("query"),
+                            arguments.getBundle("extras"));
+                    break;
+                case PLAY_FROM_URI:
+                    arguments = extras.getBundle(KEY_ARGUMENT);
+                    controls.playFromUri(
+                            (Uri) arguments.getParcelable("uri"),
+                            arguments.getBundle("extras"));
+                    break;
+                case SEND_CUSTOM_ACTION:
+                    arguments = extras.getBundle(KEY_ARGUMENT);
+                    controls.sendCustomAction(
+                            arguments.getString("action"),
+                            arguments.getBundle("extras"));
+                    break;
+                case SEND_CUSTOM_ACTION_PARCELABLE:
+                    arguments = extras.getBundle(KEY_ARGUMENT);
+                    controls.sendCustomAction(
+                            (PlaybackStateCompat.CustomAction)
+                                    arguments.getParcelable("action"),
+                            arguments.getBundle("extras"));
+                    break;
+                case SKIP_TO_QUEUE_ITEM:
+                    controls.skipToQueueItem(extras.getLong(KEY_ARGUMENT));
+                    break;
+                case PREPARE:
+                    controls.prepare();
+                    break;
+                case PREPARE_FROM_MEDIA_ID:
+                    arguments = extras.getBundle(KEY_ARGUMENT);
+                    controls.prepareFromMediaId(
+                            arguments.getString("mediaId"),
+                            arguments.getBundle("extras"));
+                    break;
+                case PREPARE_FROM_SEARCH:
+                    arguments = extras.getBundle(KEY_ARGUMENT);
+                    controls.prepareFromSearch(
+                            arguments.getString("query"),
+                            arguments.getBundle("extras"));
+                    break;
+                case PREPARE_FROM_URI:
+                    arguments = extras.getBundle(KEY_ARGUMENT);
+                    controls.prepareFromUri(
+                            (Uri) arguments.getParcelable("uri"),
+                            arguments.getBundle("extras"));
+                    break;
+                case SET_CAPTIONING_ENABLED:
+                    controls.setCaptioningEnabled(extras.getBoolean(KEY_ARGUMENT));
+                    break;
+                case SET_REPEAT_MODE:
+                    controls.setRepeatMode(extras.getInt(KEY_ARGUMENT));
+                    break;
+                case SET_SHUFFLE_MODE:
+                    controls.setShuffleMode(extras.getInt(KEY_ARGUMENT));
+                    break;
+            }
+        }
+    }
+}
diff --git a/media-compat/version-compat-tests/current/client/tests/src/android/support/mediacompat/client/MediaBrowserCompatTest.java b/media-compat/version-compat-tests/current/client/tests/src/android/support/mediacompat/client/MediaBrowserCompatTest.java
new file mode 100644
index 0000000..6144ede
--- /dev/null
+++ b/media-compat/version-compat-tests/current/client/tests/src/android/support/mediacompat/client/MediaBrowserCompatTest.java
@@ -0,0 +1,1112 @@
+/*
+ * 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.client;
+
+import static android.support.mediacompat.testlib.MediaBrowserConstants.CUSTOM_ACTION;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.CUSTOM_ACTION_FOR_ERROR;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.CUSTOM_ACTION_SEND_ERROR;
+import static android.support.mediacompat.testlib.MediaBrowserConstants
+        .CUSTOM_ACTION_SEND_PROGRESS_UPDATE;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.CUSTOM_ACTION_SEND_RESULT;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.EXTRAS_KEY;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.EXTRAS_VALUE;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.MEDIA_ID_CHILDREN;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.MEDIA_ID_CHILDREN_DELAYED;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.MEDIA_ID_INCLUDE_METADATA;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.MEDIA_ID_INVALID;
+import static android.support.mediacompat.testlib.MediaBrowserConstants
+        .MEDIA_ID_ON_LOAD_ITEM_NOT_IMPLEMENTED;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.MEDIA_ID_ROOT;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.MEDIA_METADATA;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.NOTIFY_CHILDREN_CHANGED;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.SEARCH_QUERY;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.SEARCH_QUERY_FOR_ERROR;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.SEARCH_QUERY_FOR_NO_RESULT;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.SEND_DELAYED_ITEM_LOADED;
+import static android.support.mediacompat.testlib.MediaBrowserConstants
+        .SEND_DELAYED_NOTIFY_CHILDREN_CHANGED;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.SET_SESSION_TOKEN;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.TEST_KEY_1;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.TEST_KEY_2;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.TEST_KEY_3;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.TEST_KEY_4;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.TEST_VALUE_1;
+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.mediacompat.testlib.VersionConstants.VERSION_TOT;
+import static android.support.mediacompat.testlib.util.IntentUtil.SERVICE_PACKAGE_NAME;
+import static android.support.mediacompat.testlib.util.IntentUtil.callMediaBrowserServiceMethod;
+import static android.support.test.InstrumentationRegistry.getArguments;
+import static android.support.test.InstrumentationRegistry.getContext;
+import static android.support.test.InstrumentationRegistry.getInstrumentation;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertSame;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
+
+import android.content.ComponentName;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.mediacompat.testlib.util.PollingCheck;
+import android.support.test.filters.MediumTest;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v4.media.MediaBrowserCompat;
+import android.support.v4.media.MediaBrowserCompat.MediaItem;
+import android.support.v4.media.MediaBrowserServiceCompat;
+import android.support.v4.media.MediaDescriptionCompat;
+import android.support.v4.media.MediaMetadataCompat;
+import android.support.v4.media.RatingCompat;
+import android.util.Log;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Test {@link android.support.v4.media.MediaBrowserCompat}.
+ */
+@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;
+
+    /**
+     * To check {@link MediaBrowserCompat#unsubscribe} works properly,
+     * we notify to the browser after the unsubscription that the media items have changed.
+     * Then {@link MediaBrowserCompat.SubscriptionCallback#onChildrenLoaded} should not be called.
+     *
+     * The measured time from calling {@link MediaBrowserServiceCompat#notifyChildrenChanged}
+     * to {@link MediaBrowserCompat.SubscriptionCallback#onChildrenLoaded} being called is about
+     * 50ms.
+     * So we make the thread sleep for 100ms to properly check that the callback is not called.
+     */
+    private static final long SLEEP_MS = 100L;
+    private static final ComponentName TEST_BROWSER_SERVICE = new ComponentName(
+            SERVICE_PACKAGE_NAME,
+            "android.support.mediacompat.service.StubMediaBrowserServiceCompat");
+    private static final ComponentName TEST_BROWSER_SERVICE_DELAYED_MEDIA_SESSION =
+            new ComponentName(
+                    SERVICE_PACKAGE_NAME,
+                    "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;
+    private StubItemCallback mItemCallback;
+    private StubSearchCallback mSearchCallback;
+    private CustomActionCallback mCustomActionCallback;
+    private Bundle mRootHints;
+
+    @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();
+        mSearchCallback = new StubSearchCallback();
+        mCustomActionCallback = new CustomActionCallback();
+
+        mRootHints = new Bundle();
+        mRootHints.putBoolean(MediaBrowserServiceCompat.BrowserRoot.EXTRA_RECENT, true);
+        mRootHints.putBoolean(MediaBrowserServiceCompat.BrowserRoot.EXTRA_OFFLINE, true);
+        mRootHints.putBoolean(MediaBrowserServiceCompat.BrowserRoot.EXTRA_SUGGESTED, true);
+
+        getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                mMediaBrowser = new MediaBrowserCompat(getInstrumentation().getTargetContext(),
+                        TEST_BROWSER_SERVICE, mConnectionCallback, mRootHints);
+            }
+        });
+    }
+
+    @After
+    public void tearDown() {
+        if (mMediaBrowser != null && mMediaBrowser.isConnected()) {
+            mMediaBrowser.disconnect();
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void testBrowserRoot() {
+        final String id = "test-id";
+        final String key = "test-key";
+        final String val = "test-val";
+        final Bundle extras = new Bundle();
+        extras.putString(key, val);
+
+        MediaBrowserServiceCompat.BrowserRoot browserRoot =
+                new MediaBrowserServiceCompat.BrowserRoot(id, extras);
+        assertEquals(id, browserRoot.getRootId());
+        assertEquals(val, browserRoot.getExtras().getString(key));
+    }
+
+    @Test
+    @SmallTest
+    public void testMediaBrowser() throws Exception {
+        assertFalse(mMediaBrowser.isConnected());
+
+        connectMediaBrowserService();
+        assertTrue(mMediaBrowser.isConnected());
+
+        assertEquals(TEST_BROWSER_SERVICE, mMediaBrowser.getServiceComponent());
+        assertEquals(MEDIA_ID_ROOT, mMediaBrowser.getRoot());
+        assertEquals(EXTRAS_VALUE, mMediaBrowser.getExtras().getString(EXTRAS_KEY));
+
+        mMediaBrowser.disconnect();
+        new PollingCheck(TIME_OUT_MS) {
+            @Override
+            protected boolean check() {
+                return !mMediaBrowser.isConnected();
+            }
+        }.run();
+    }
+
+    @Test
+    @SmallTest
+    public void testGetServiceComponentBeforeConnection() {
+        try {
+            ComponentName serviceComponent = mMediaBrowser.getServiceComponent();
+            fail();
+        } catch (IllegalStateException e) {
+            // expected
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void testConnectionFailed() throws Exception {
+        getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                mMediaBrowser = new MediaBrowserCompat(getInstrumentation().getTargetContext(),
+                        TEST_INVALID_BROWSER_SERVICE, mConnectionCallback, mRootHints);
+            }
+        });
+
+        synchronized (mConnectionCallback.mWaitLock) {
+            mMediaBrowser.connect();
+            mConnectionCallback.mWaitLock.wait(TIME_OUT_MS);
+        }
+        assertEquals(1, mConnectionCallback.mConnectionFailedCount);
+        assertEquals(0, mConnectionCallback.mConnectedCount);
+        assertEquals(0, mConnectionCallback.mConnectionSuspendedCount);
+    }
+
+    @Test
+    @SmallTest
+    public void testConnectTwice() throws Exception {
+        connectMediaBrowserService();
+        try {
+            mMediaBrowser.connect();
+            fail();
+        } catch (IllegalStateException e) {
+            // expected
+        }
+    }
+
+    @Test
+    @MediumTest
+    public void testReconnection() throws Exception {
+        getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                mMediaBrowser.connect();
+                // Reconnect before the first connection was established.
+                mMediaBrowser.disconnect();
+                mMediaBrowser.connect();
+            }
+        });
+
+        synchronized (mConnectionCallback.mWaitLock) {
+            mConnectionCallback.mWaitLock.wait(TIME_OUT_MS);
+            assertEquals(1, mConnectionCallback.mConnectedCount);
+        }
+
+        // Test subscribe.
+        mSubscriptionCallback.reset(1);
+        mMediaBrowser.subscribe(MEDIA_ID_ROOT, mSubscriptionCallback);
+        mSubscriptionCallback.await(TIME_OUT_MS);
+        assertEquals(1, mSubscriptionCallback.mChildrenLoadedCount);
+        assertEquals(MEDIA_ID_ROOT, mSubscriptionCallback.mLastParentId);
+
+        synchronized (mItemCallback.mWaitLock) {
+            // Test getItem.
+            mItemCallback.reset();
+            mMediaBrowser.getItem(MEDIA_ID_CHILDREN[0], mItemCallback);
+            mItemCallback.mWaitLock.wait(TIME_OUT_MS);
+            assertEquals(MEDIA_ID_CHILDREN[0], mItemCallback.mLastMediaItem.getMediaId());
+        }
+
+        // Reconnect after connection was established.
+        mMediaBrowser.disconnect();
+        connectMediaBrowserService();
+
+        synchronized (mItemCallback.mWaitLock) {
+            // Test getItem.
+            mItemCallback.reset();
+            mMediaBrowser.getItem(MEDIA_ID_CHILDREN[0], mItemCallback);
+            mItemCallback.mWaitLock.wait(TIME_OUT_MS);
+            assertEquals(MEDIA_ID_CHILDREN[0], mItemCallback.mLastMediaItem.getMediaId());
+        }
+    }
+
+    @Test
+    @MediumTest
+    public void testConnectionCallbackNotCalledAfterDisconnect() {
+        getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                mMediaBrowser.connect();
+                mMediaBrowser.disconnect();
+                mConnectionCallback.reset();
+            }
+        });
+
+        try {
+            Thread.sleep(SLEEP_MS);
+        } catch (InterruptedException e) {
+            fail("Unexpected InterruptedException occurred.");
+        }
+        assertEquals(0, mConnectionCallback.mConnectedCount);
+        assertEquals(0, mConnectionCallback.mConnectionFailedCount);
+        assertEquals(0, mConnectionCallback.mConnectionSuspendedCount);
+    }
+
+    @Test
+    @MediumTest
+    public void testSubscribe() throws Exception {
+        connectMediaBrowserService();
+
+        mSubscriptionCallback.reset(1);
+        mMediaBrowser.subscribe(MEDIA_ID_ROOT, mSubscriptionCallback);
+        mSubscriptionCallback.await(TIME_OUT_MS);
+        assertEquals(1, mSubscriptionCallback.mChildrenLoadedCount);
+        assertEquals(MEDIA_ID_ROOT, mSubscriptionCallback.mLastParentId);
+        assertEquals(MEDIA_ID_CHILDREN.length, mSubscriptionCallback.mLastChildMediaItems.size());
+        for (int i = 0; i < MEDIA_ID_CHILDREN.length; ++i) {
+            assertEquals(MEDIA_ID_CHILDREN[i],
+                    mSubscriptionCallback.mLastChildMediaItems.get(i).getMediaId());
+        }
+
+        // Test MediaBrowserServiceCompat.notifyChildrenChanged()
+        mSubscriptionCallback.reset(1);
+        callMediaBrowserServiceMethod(NOTIFY_CHILDREN_CHANGED, MEDIA_ID_ROOT, getContext());
+        mSubscriptionCallback.await(TIME_OUT_MS);
+        assertEquals(1, mSubscriptionCallback.mChildrenLoadedCount);
+
+        // Test unsubscribe.
+        mSubscriptionCallback.reset(1);
+        mMediaBrowser.unsubscribe(MEDIA_ID_ROOT);
+
+        // After unsubscribing, make StubMediaBrowserServiceCompat notify that the children are
+        // changed.
+        callMediaBrowserServiceMethod(NOTIFY_CHILDREN_CHANGED, MEDIA_ID_ROOT, getContext());
+        mSubscriptionCallback.await(WAIT_TIME_FOR_NO_RESPONSE_MS);
+
+        // onChildrenLoaded should not be called.
+        assertEquals(0, mSubscriptionCallback.mChildrenLoadedCount);
+    }
+
+    @Test
+    @MediumTest
+    public void testSubscribeWithOptions() throws Exception {
+        connectMediaBrowserService();
+        final int pageSize = 3;
+        final int lastPage = (MEDIA_ID_CHILDREN.length - 1) / pageSize;
+        Bundle options = new Bundle();
+        options.putInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, pageSize);
+
+        for (int page = 0; page <= lastPage; ++page) {
+            mSubscriptionCallback.reset(1);
+            options.putInt(MediaBrowserCompat.EXTRA_PAGE, page);
+            mMediaBrowser.subscribe(MEDIA_ID_ROOT, options, mSubscriptionCallback);
+            mSubscriptionCallback.await(TIME_OUT_MS);
+            assertEquals(1, mSubscriptionCallback.mChildrenLoadedWithOptionCount);
+            assertEquals(MEDIA_ID_ROOT, mSubscriptionCallback.mLastParentId);
+            if (page != lastPage) {
+                assertEquals(pageSize, mSubscriptionCallback.mLastChildMediaItems.size());
+            } else {
+                assertEquals((MEDIA_ID_CHILDREN.length - 1) % pageSize + 1,
+                        mSubscriptionCallback.mLastChildMediaItems.size());
+            }
+            // Check whether all the items in the current page are loaded.
+            for (int i = 0; i < mSubscriptionCallback.mLastChildMediaItems.size(); ++i) {
+                assertEquals(MEDIA_ID_CHILDREN[page * pageSize + i],
+                        mSubscriptionCallback.mLastChildMediaItems.get(i).getMediaId());
+            }
+
+            // Test MediaBrowserServiceCompat.notifyChildrenChanged()
+            mSubscriptionCallback.reset(page + 1);
+            callMediaBrowserServiceMethod(NOTIFY_CHILDREN_CHANGED, MEDIA_ID_ROOT, getContext());
+            mSubscriptionCallback.await(TIME_OUT_MS);
+            assertEquals(page + 1, mSubscriptionCallback.mChildrenLoadedWithOptionCount);
+        }
+
+        // Test unsubscribe with callback argument.
+        mSubscriptionCallback.reset(1);
+        mMediaBrowser.unsubscribe(MEDIA_ID_ROOT, mSubscriptionCallback);
+
+        // After unsubscribing, make StubMediaBrowserServiceCompat notify that the children are
+        // changed.
+        callMediaBrowserServiceMethod(NOTIFY_CHILDREN_CHANGED, MEDIA_ID_ROOT, getContext());
+        try {
+            Thread.sleep(SLEEP_MS);
+        } catch (InterruptedException e) {
+            fail("Unexpected InterruptedException occurred.");
+        }
+        // onChildrenLoaded should not be called.
+        assertEquals(0, mSubscriptionCallback.mChildrenLoadedCount);
+    }
+
+    @Test
+    @SmallTest
+    public void testSubscribeWithOptionsIncludingCompatParcelables() throws Exception {
+        if (Build.VERSION.SDK_INT >= 26 && !VERSION_TOT.equals(mServiceVersion)) {
+            // This test will fail on API 26 or newer APIs if the service application uses
+            // support library v27.0.1 or lower versions.
+            return;
+        }
+        connectMediaBrowserService();
+
+        final String mediaId = "1000";
+        final RatingCompat percentageRating = RatingCompat.newPercentageRating(0.5f);
+        final RatingCompat starRating =
+                RatingCompat.newStarRating(RatingCompat.RATING_5_STARS, 4.0f);
+        MediaMetadataCompat mediaMetadataCompat = new MediaMetadataCompat.Builder()
+                .putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, mediaId)
+                .putString(MediaMetadataCompat.METADATA_KEY_TITLE, "title")
+                .putRating(MediaMetadataCompat.METADATA_KEY_RATING, percentageRating)
+                .putRating(MediaMetadataCompat.METADATA_KEY_USER_RATING, starRating)
+                .build();
+        Bundle options = new Bundle();
+        options.putParcelable(MEDIA_METADATA, mediaMetadataCompat);
+
+        // Remote MediaBrowserService will create a media item with the given MediaMetadataCompat.
+        mSubscriptionCallback.reset(1);
+        mMediaBrowser.subscribe(MEDIA_ID_INCLUDE_METADATA, options, mSubscriptionCallback);
+        mSubscriptionCallback.await(TIME_OUT_MS);
+
+        assertEquals(1, mSubscriptionCallback.mChildrenLoadedWithOptionCount);
+        assertEquals(1, mSubscriptionCallback.mLastChildMediaItems.size());
+        assertEquals(mediaId, mSubscriptionCallback.mLastChildMediaItems.get(0).getMediaId());
+
+        MediaMetadataCompat metadataOut = mSubscriptionCallback.mLastOptions
+                .getParcelable(MEDIA_METADATA);
+        assertEquals(mediaId, metadataOut.getString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID));
+        assertEquals("title", metadataOut.getString(MediaMetadataCompat.METADATA_KEY_TITLE));
+        assertRatingEquals(percentageRating,
+                metadataOut.getRating(MediaMetadataCompat.METADATA_KEY_RATING));
+        assertRatingEquals(starRating,
+                metadataOut.getRating(MediaMetadataCompat.METADATA_KEY_USER_RATING));
+    }
+
+    @Test
+    @MediumTest
+    public void testSubscribeDelayedItems() throws Exception {
+        connectMediaBrowserService();
+
+        mSubscriptionCallback.reset(1);
+        mMediaBrowser.subscribe(MEDIA_ID_CHILDREN_DELAYED, mSubscriptionCallback);
+        mSubscriptionCallback.await(WAIT_TIME_FOR_NO_RESPONSE_MS);
+        assertEquals(0, mSubscriptionCallback.mChildrenLoadedCount);
+
+        callMediaBrowserServiceMethod(
+                SEND_DELAYED_NOTIFY_CHILDREN_CHANGED, MEDIA_ID_CHILDREN_DELAYED, getContext());
+        mSubscriptionCallback.await(TIME_OUT_MS);
+        assertEquals(1, mSubscriptionCallback.mChildrenLoadedCount);
+    }
+
+    @Test
+    @SmallTest
+    public void testSubscribeInvalidItem() throws Exception {
+        connectMediaBrowserService();
+
+        mSubscriptionCallback.reset(1);
+        mMediaBrowser.subscribe(MEDIA_ID_INVALID, mSubscriptionCallback);
+        mSubscriptionCallback.await(TIME_OUT_MS);
+        assertEquals(MEDIA_ID_INVALID, mSubscriptionCallback.mLastErrorId);
+    }
+
+    @Test
+    @SmallTest
+    public void testSubscribeInvalidItemWithOptions() throws Exception {
+        connectMediaBrowserService();
+
+        final int pageSize = 5;
+        final int page = 2;
+        Bundle options = new Bundle();
+        options.putInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, pageSize);
+        options.putInt(MediaBrowserCompat.EXTRA_PAGE, page);
+
+        mSubscriptionCallback.reset(1);
+        mMediaBrowser.subscribe(MEDIA_ID_INVALID, options, mSubscriptionCallback);
+        mSubscriptionCallback.await(TIME_OUT_MS);
+        assertEquals(MEDIA_ID_INVALID, mSubscriptionCallback.mLastErrorId);
+        assertNotNull(mSubscriptionCallback.mLastOptions);
+        assertEquals(page,
+                mSubscriptionCallback.mLastOptions.getInt(MediaBrowserCompat.EXTRA_PAGE));
+        assertEquals(pageSize,
+                mSubscriptionCallback.mLastOptions.getInt(MediaBrowserCompat.EXTRA_PAGE_SIZE));
+    }
+
+    @Test
+    @MediumTest
+    public void testUnsubscribeForMultipleSubscriptions() throws Exception {
+        connectMediaBrowserService();
+        final List<StubSubscriptionCallback> subscriptionCallbacks = new ArrayList<>();
+        final int pageSize = 1;
+
+        // Subscribe four pages, one item per page.
+        for (int page = 0; page < 4; page++) {
+            final StubSubscriptionCallback callback = new StubSubscriptionCallback();
+            subscriptionCallbacks.add(callback);
+
+            Bundle options = new Bundle();
+            options.putInt(MediaBrowserCompat.EXTRA_PAGE, page);
+            options.putInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, pageSize);
+            callback.reset(1);
+            mMediaBrowser.subscribe(MEDIA_ID_ROOT, options, callback);
+            callback.await(TIME_OUT_MS);
+
+            // Each onChildrenLoaded() must be called.
+            assertEquals(1, callback.mChildrenLoadedWithOptionCount);
+        }
+
+        // Reset callbacks and unsubscribe.
+        for (StubSubscriptionCallback callback : subscriptionCallbacks) {
+            callback.reset(1);
+        }
+        mMediaBrowser.unsubscribe(MEDIA_ID_ROOT);
+
+        // After unsubscribing, make StubMediaBrowserServiceCompat notify that the children are
+        // changed.
+        callMediaBrowserServiceMethod(NOTIFY_CHILDREN_CHANGED, MEDIA_ID_ROOT, getContext());
+        try {
+            Thread.sleep(SLEEP_MS);
+        } catch (InterruptedException e) {
+            fail("Unexpected InterruptedException occurred.");
+        }
+
+        // onChildrenLoaded should not be called.
+        for (StubSubscriptionCallback callback : subscriptionCallbacks) {
+            assertEquals(0, callback.mChildrenLoadedWithOptionCount);
+        }
+    }
+
+    @Test
+    @MediumTest
+    public void testUnsubscribeWithSubscriptionCallbackForMultipleSubscriptions() throws Exception {
+        connectMediaBrowserService();
+        final List<StubSubscriptionCallback> subscriptionCallbacks = new ArrayList<>();
+        final int pageSize = 1;
+
+        // Subscribe four pages, one item per page.
+        for (int page = 0; page < 4; page++) {
+            final StubSubscriptionCallback callback = new StubSubscriptionCallback();
+            subscriptionCallbacks.add(callback);
+
+            Bundle options = new Bundle();
+            options.putInt(MediaBrowserCompat.EXTRA_PAGE, page);
+            options.putInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, pageSize);
+            callback.reset(1);
+            mMediaBrowser.subscribe(MEDIA_ID_ROOT, options, callback);
+            callback.await(TIME_OUT_MS);
+
+            // Each onChildrenLoaded() must be called.
+            assertEquals(1, callback.mChildrenLoadedWithOptionCount);
+        }
+
+        // Unsubscribe existing subscriptions one-by-one.
+        final int[] orderOfRemovingCallbacks = {2, 0, 3, 1};
+        for (int i = 0; i < orderOfRemovingCallbacks.length; i++) {
+            // Reset callbacks
+            for (StubSubscriptionCallback callback : subscriptionCallbacks) {
+                callback.reset(1);
+            }
+
+            // Remove one subscription
+            mMediaBrowser.unsubscribe(MEDIA_ID_ROOT,
+                    subscriptionCallbacks.get(orderOfRemovingCallbacks[i]));
+
+            // Make StubMediaBrowserServiceCompat notify that the children are changed.
+            callMediaBrowserServiceMethod(NOTIFY_CHILDREN_CHANGED, MEDIA_ID_ROOT, getContext());
+            try {
+                Thread.sleep(SLEEP_MS);
+            } catch (InterruptedException e) {
+                fail("Unexpected InterruptedException occurred.");
+            }
+
+            // Only the remaining subscriptionCallbacks should be called.
+            for (int j = 0; j < 4; j++) {
+                int childrenLoadedWithOptionsCount = subscriptionCallbacks
+                        .get(orderOfRemovingCallbacks[j]).mChildrenLoadedWithOptionCount;
+                if (j <= i) {
+                    assertEquals(0, childrenLoadedWithOptionsCount);
+                } else {
+                    assertEquals(1, childrenLoadedWithOptionsCount);
+                }
+            }
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void testGetItem() throws Exception {
+        connectMediaBrowserService();
+
+        synchronized (mItemCallback.mWaitLock) {
+            mMediaBrowser.getItem(MEDIA_ID_CHILDREN[0], mItemCallback);
+            mItemCallback.mWaitLock.wait(TIME_OUT_MS);
+            assertNotNull(mItemCallback.mLastMediaItem);
+            assertEquals(MEDIA_ID_CHILDREN[0], mItemCallback.mLastMediaItem.getMediaId());
+        }
+    }
+
+    @Test
+    @MediumTest
+    public void testGetItemDelayed() throws Exception {
+        connectMediaBrowserService();
+
+        synchronized (mItemCallback.mWaitLock) {
+            mMediaBrowser.getItem(MEDIA_ID_CHILDREN_DELAYED, mItemCallback);
+            mItemCallback.mWaitLock.wait(WAIT_TIME_FOR_NO_RESPONSE_MS);
+            assertNull(mItemCallback.mLastMediaItem);
+
+            mItemCallback.reset();
+            callMediaBrowserServiceMethod(SEND_DELAYED_ITEM_LOADED, new Bundle(), getContext());
+            mItemCallback.mWaitLock.wait(TIME_OUT_MS);
+            assertNotNull(mItemCallback.mLastMediaItem);
+            assertEquals(MEDIA_ID_CHILDREN_DELAYED, mItemCallback.mLastMediaItem.getMediaId());
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void testGetItemWhenOnLoadItemIsNotImplemented() throws Exception {
+        connectMediaBrowserService();
+        synchronized (mItemCallback.mWaitLock) {
+            mMediaBrowser.getItem(MEDIA_ID_ON_LOAD_ITEM_NOT_IMPLEMENTED, mItemCallback);
+            mItemCallback.mWaitLock.wait(TIME_OUT_MS);
+            assertEquals(MEDIA_ID_ON_LOAD_ITEM_NOT_IMPLEMENTED, mItemCallback.mLastErrorId);
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void testGetItemWhenMediaIdIsInvalid() throws Exception {
+        mItemCallback.mLastMediaItem = new MediaItem(new MediaDescriptionCompat.Builder()
+                .setMediaId("dummy_id").build(), MediaItem.FLAG_BROWSABLE);
+
+        connectMediaBrowserService();
+        synchronized (mItemCallback.mWaitLock) {
+            mMediaBrowser.getItem(MEDIA_ID_INVALID, mItemCallback);
+            mItemCallback.mWaitLock.wait(TIME_OUT_MS);
+            assertNull(mItemCallback.mLastMediaItem);
+            assertNull(mItemCallback.mLastErrorId);
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void testSearch() throws Exception {
+        connectMediaBrowserService();
+
+        final String key = "test-key";
+        final String val = "test-val";
+
+        synchronized (mSearchCallback.mWaitLock) {
+            mSearchCallback.reset();
+            mMediaBrowser.search(SEARCH_QUERY_FOR_NO_RESULT, null, mSearchCallback);
+            mSearchCallback.mWaitLock.wait(WAIT_TIME_FOR_NO_RESPONSE_MS);
+            assertTrue(mSearchCallback.mOnSearchResult);
+            assertTrue(mSearchCallback.mSearchResults != null
+                    && mSearchCallback.mSearchResults.size() == 0);
+            assertEquals(null, mSearchCallback.mSearchExtras);
+
+            mSearchCallback.reset();
+            mMediaBrowser.search(SEARCH_QUERY_FOR_ERROR, null, mSearchCallback);
+            mSearchCallback.mWaitLock.wait(WAIT_TIME_FOR_NO_RESPONSE_MS);
+            assertTrue(mSearchCallback.mOnSearchResult);
+            assertNull(mSearchCallback.mSearchResults);
+            assertEquals(null, mSearchCallback.mSearchExtras);
+
+            mSearchCallback.reset();
+            Bundle extras = new Bundle();
+            extras.putString(key, val);
+            mMediaBrowser.search(SEARCH_QUERY, extras, mSearchCallback);
+            mSearchCallback.mWaitLock.wait(WAIT_TIME_FOR_NO_RESPONSE_MS);
+            assertTrue(mSearchCallback.mOnSearchResult);
+            assertNotNull(mSearchCallback.mSearchResults);
+            for (MediaItem item : mSearchCallback.mSearchResults) {
+                assertNotNull(item.getMediaId());
+                assertTrue(item.getMediaId().contains(SEARCH_QUERY));
+            }
+            assertNotNull(mSearchCallback.mSearchExtras);
+            assertEquals(val, mSearchCallback.mSearchExtras.getString(key));
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void testSendCustomAction() throws Exception {
+        connectMediaBrowserService();
+
+        synchronized (mCustomActionCallback.mWaitLock) {
+            Bundle customActionExtras = new Bundle();
+            customActionExtras.putString(TEST_KEY_1, TEST_VALUE_1);
+            mMediaBrowser.sendCustomAction(
+                    CUSTOM_ACTION, customActionExtras, mCustomActionCallback);
+            mCustomActionCallback.mWaitLock.wait(WAIT_TIME_FOR_NO_RESPONSE_MS);
+
+            mCustomActionCallback.reset();
+            Bundle data1 = new Bundle();
+            data1.putString(TEST_KEY_2, TEST_VALUE_2);
+            callMediaBrowserServiceMethod(CUSTOM_ACTION_SEND_PROGRESS_UPDATE, data1, getContext());
+            mCustomActionCallback.mWaitLock.wait(TIME_OUT_MS);
+
+            assertTrue(mCustomActionCallback.mOnProgressUpdateCalled);
+            assertEquals(CUSTOM_ACTION, mCustomActionCallback.mAction);
+            assertNotNull(mCustomActionCallback.mExtras);
+            assertEquals(TEST_VALUE_1, mCustomActionCallback.mExtras.getString(TEST_KEY_1));
+            assertNotNull(mCustomActionCallback.mData);
+            assertEquals(TEST_VALUE_2, mCustomActionCallback.mData.getString(TEST_KEY_2));
+
+            mCustomActionCallback.reset();
+            Bundle data2 = new Bundle();
+            data2.putString(TEST_KEY_3, TEST_VALUE_3);
+            callMediaBrowserServiceMethod(CUSTOM_ACTION_SEND_PROGRESS_UPDATE, data2, getContext());
+            mCustomActionCallback.mWaitLock.wait(TIME_OUT_MS);
+
+            assertTrue(mCustomActionCallback.mOnProgressUpdateCalled);
+            assertEquals(CUSTOM_ACTION, mCustomActionCallback.mAction);
+            assertNotNull(mCustomActionCallback.mExtras);
+            assertEquals(TEST_VALUE_1, mCustomActionCallback.mExtras.getString(TEST_KEY_1));
+            assertNotNull(mCustomActionCallback.mData);
+            assertEquals(TEST_VALUE_3, mCustomActionCallback.mData.getString(TEST_KEY_3));
+
+            Bundle resultData = new Bundle();
+            resultData.putString(TEST_KEY_4, TEST_VALUE_4);
+            mCustomActionCallback.reset();
+            callMediaBrowserServiceMethod(CUSTOM_ACTION_SEND_RESULT, resultData, getContext());
+            mCustomActionCallback.mWaitLock.wait(TIME_OUT_MS);
+
+            assertTrue(mCustomActionCallback.mOnResultCalled);
+            assertEquals(CUSTOM_ACTION, mCustomActionCallback.mAction);
+            assertNotNull(mCustomActionCallback.mExtras);
+            assertEquals(TEST_VALUE_1, mCustomActionCallback.mExtras.getString(TEST_KEY_1));
+            assertNotNull(mCustomActionCallback.mData);
+            assertEquals(TEST_VALUE_4, mCustomActionCallback.mData.getString(TEST_KEY_4));
+        }
+    }
+
+
+    @Test
+    @MediumTest
+    public void testSendCustomActionWithDetachedError() throws Exception {
+        connectMediaBrowserService();
+
+        synchronized (mCustomActionCallback.mWaitLock) {
+            Bundle customActionExtras = new Bundle();
+            customActionExtras.putString(TEST_KEY_1, TEST_VALUE_1);
+            mMediaBrowser.sendCustomAction(
+                    CUSTOM_ACTION, customActionExtras, mCustomActionCallback);
+            mCustomActionCallback.mWaitLock.wait(WAIT_TIME_FOR_NO_RESPONSE_MS);
+
+            mCustomActionCallback.reset();
+            Bundle progressUpdateData = new Bundle();
+            progressUpdateData.putString(TEST_KEY_2, TEST_VALUE_2);
+            callMediaBrowserServiceMethod(
+                    CUSTOM_ACTION_SEND_PROGRESS_UPDATE, progressUpdateData, getContext());
+            mCustomActionCallback.mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCustomActionCallback.mOnProgressUpdateCalled);
+            assertEquals(CUSTOM_ACTION, mCustomActionCallback.mAction);
+            assertNotNull(mCustomActionCallback.mExtras);
+            assertEquals(TEST_VALUE_1, mCustomActionCallback.mExtras.getString(TEST_KEY_1));
+            assertNotNull(mCustomActionCallback.mData);
+            assertEquals(TEST_VALUE_2, mCustomActionCallback.mData.getString(TEST_KEY_2));
+
+            mCustomActionCallback.reset();
+            Bundle errorData = new Bundle();
+            errorData.putString(TEST_KEY_3, TEST_VALUE_3);
+            callMediaBrowserServiceMethod(CUSTOM_ACTION_SEND_ERROR, errorData, getContext());
+            mCustomActionCallback.mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCustomActionCallback.mOnErrorCalled);
+            assertEquals(CUSTOM_ACTION, mCustomActionCallback.mAction);
+            assertNotNull(mCustomActionCallback.mExtras);
+            assertEquals(TEST_VALUE_1, mCustomActionCallback.mExtras.getString(TEST_KEY_1));
+            assertNotNull(mCustomActionCallback.mData);
+            assertEquals(TEST_VALUE_3, mCustomActionCallback.mData.getString(TEST_KEY_3));
+        }
+    }
+
+    @Test
+    @MediumTest
+    public void testSendCustomActionWithNullCallback() throws Exception {
+        connectMediaBrowserService();
+
+        Bundle customActionExtras = new Bundle();
+        customActionExtras.putString(TEST_KEY_1, TEST_VALUE_1);
+        mMediaBrowser.sendCustomAction(CUSTOM_ACTION, customActionExtras, null);
+        // Wait some time so that the service can get a result receiver for the custom action.
+        Thread.sleep(WAIT_TIME_FOR_NO_RESPONSE_MS);
+
+        // These calls should not make any exceptions.
+        callMediaBrowserServiceMethod(CUSTOM_ACTION_SEND_PROGRESS_UPDATE, new Bundle(),
+                getContext());
+        callMediaBrowserServiceMethod(CUSTOM_ACTION_SEND_RESULT, new Bundle(), getContext());
+        Thread.sleep(WAIT_TIME_FOR_NO_RESPONSE_MS);
+    }
+
+    @Test
+    @SmallTest
+    public void testSendCustomActionWithError() throws Exception {
+        connectMediaBrowserService();
+
+        synchronized (mCustomActionCallback.mWaitLock) {
+            mMediaBrowser.sendCustomAction(CUSTOM_ACTION_FOR_ERROR, null, mCustomActionCallback);
+            mCustomActionCallback.mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCustomActionCallback.mOnErrorCalled);
+        }
+    }
+
+    @Test
+    @MediumTest
+    public void testDelayedSetSessionToken() throws Exception {
+        // This test has no meaning in API 21. The framework MediaBrowserService just connects to
+        // the media browser without waiting setMediaSession() to be called.
+        if (Build.VERSION.SDK_INT == 21) {
+            return;
+        }
+        final ConnectionCallbackForDelayedMediaSession callback =
+                new ConnectionCallbackForDelayedMediaSession();
+
+        getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                mMediaBrowser = new MediaBrowserCompat(
+                        getInstrumentation().getTargetContext(),
+                        TEST_BROWSER_SERVICE_DELAYED_MEDIA_SESSION,
+                        callback,
+                        null);
+            }
+        });
+
+        synchronized (callback.mWaitLock) {
+            mMediaBrowser.connect();
+            callback.mWaitLock.wait(WAIT_TIME_FOR_NO_RESPONSE_MS);
+            assertEquals(0, callback.mConnectedCount);
+
+            callMediaBrowserServiceMethod(SET_SESSION_TOKEN, new Bundle(), getContext());
+            callback.mWaitLock.wait(TIME_OUT_MS);
+            assertEquals(1, callback.mConnectedCount);
+
+            if (Build.VERSION.SDK_INT >= 21) {
+                assertNotNull(mMediaBrowser.getSessionToken().getExtraBinder());
+            }
+        }
+    }
+
+    private void connectMediaBrowserService() throws Exception {
+        synchronized (mConnectionCallback.mWaitLock) {
+            mMediaBrowser.connect();
+            mConnectionCallback.mWaitLock.wait(TIME_OUT_MS);
+            if (!mMediaBrowser.isConnected()) {
+                fail("Browser failed to connect!");
+            }
+        }
+    }
+
+    private void assertRatingEquals(RatingCompat expected, RatingCompat observed) {
+        if (expected == null || observed == null) {
+            assertSame(expected, observed);
+        }
+        assertEquals(expected.getRatingStyle(), observed.getRatingStyle());
+
+        if (expected.getRatingStyle() == RatingCompat.RATING_PERCENTAGE) {
+            assertEquals(expected.getPercentRating(), observed.getPercentRating());
+        } else if (expected.getRatingStyle() == RatingCompat.RATING_5_STARS) {
+            assertEquals(expected.getStarRating(), observed.getStarRating());
+        } else {
+            // Currently, we use only star and percentage rating.
+            fail("Rating style should be either percentage rating or star rating.");
+        }
+    }
+
+    private class StubConnectionCallback extends MediaBrowserCompat.ConnectionCallback {
+        final Object mWaitLock = new Object();
+        volatile int mConnectedCount;
+        volatile int mConnectionFailedCount;
+        volatile int mConnectionSuspendedCount;
+
+        public void reset() {
+            mConnectedCount = 0;
+            mConnectionFailedCount = 0;
+            mConnectionSuspendedCount = 0;
+        }
+
+        @Override
+        public void onConnected() {
+            synchronized (mWaitLock) {
+                mConnectedCount++;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onConnectionFailed() {
+            synchronized (mWaitLock) {
+                mConnectionFailedCount++;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onConnectionSuspended() {
+            synchronized (mWaitLock) {
+                mConnectionSuspendedCount++;
+                mWaitLock.notify();
+            }
+        }
+    }
+
+    private class StubSubscriptionCallback extends MediaBrowserCompat.SubscriptionCallback {
+        private CountDownLatch mLatch;
+        private volatile int mChildrenLoadedCount;
+        private volatile int mChildrenLoadedWithOptionCount;
+        private volatile String mLastErrorId;
+        private volatile String mLastParentId;
+        private volatile Bundle mLastOptions;
+        private volatile List<MediaItem> mLastChildMediaItems;
+
+        public void reset(int count) {
+            mLatch = new CountDownLatch(count);
+            mChildrenLoadedCount = 0;
+            mChildrenLoadedWithOptionCount = 0;
+            mLastErrorId = null;
+            mLastParentId = null;
+            mLastOptions = null;
+            mLastChildMediaItems = null;
+        }
+
+        public boolean await(long timeoutMs) {
+            try {
+                return mLatch.await(timeoutMs, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                return false;
+            }
+        }
+
+        @Override
+        public void onChildrenLoaded(@NonNull String parentId, @NonNull List<MediaItem> children) {
+            mChildrenLoadedCount++;
+            mLastParentId = parentId;
+            mLastChildMediaItems = children;
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onChildrenLoaded(@NonNull String parentId, @NonNull List<MediaItem> children,
+                @NonNull Bundle options) {
+            mChildrenLoadedWithOptionCount++;
+            mLastParentId = parentId;
+            mLastOptions = options;
+            mLastChildMediaItems = children;
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onError(@NonNull String id) {
+            mLastErrorId = id;
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onError(@NonNull String id, @NonNull Bundle options) {
+            mLastErrorId = id;
+            mLastOptions = options;
+            mLatch.countDown();
+        }
+    }
+
+    private class StubItemCallback extends MediaBrowserCompat.ItemCallback {
+        final Object mWaitLock = new Object();
+        private volatile MediaItem mLastMediaItem;
+        private volatile String mLastErrorId;
+
+        public void reset() {
+            mLastMediaItem = null;
+            mLastErrorId = null;
+        }
+
+        @Override
+        public void onItemLoaded(MediaItem item) {
+            synchronized (mWaitLock) {
+                mLastMediaItem = item;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onError(@NonNull String id) {
+            synchronized (mWaitLock) {
+                mLastErrorId = id;
+                mWaitLock.notify();
+            }
+        }
+    }
+
+    private class StubSearchCallback extends MediaBrowserCompat.SearchCallback {
+        final Object mWaitLock = new Object();
+        boolean mOnSearchResult;
+        Bundle mSearchExtras;
+        List<MediaItem> mSearchResults;
+
+        @Override
+        public void onSearchResult(@NonNull String query, Bundle extras,
+                @NonNull List<MediaItem> items) {
+            synchronized (mWaitLock) {
+                mOnSearchResult = true;
+                mSearchResults = items;
+                mSearchExtras = extras;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onError(@NonNull String query, Bundle extras) {
+            synchronized (mWaitLock) {
+                mOnSearchResult = true;
+                mSearchResults = null;
+                mSearchExtras = extras;
+                mWaitLock.notify();
+            }
+        }
+
+        public void reset() {
+            mOnSearchResult = false;
+            mSearchExtras = null;
+            mSearchResults = null;
+        }
+    }
+
+    private class CustomActionCallback extends MediaBrowserCompat.CustomActionCallback {
+        final Object mWaitLock = new Object();
+        String mAction;
+        Bundle mExtras;
+        Bundle mData;
+        boolean mOnProgressUpdateCalled;
+        boolean mOnResultCalled;
+        boolean mOnErrorCalled;
+
+        @Override
+        public void onProgressUpdate(String action, Bundle extras, Bundle data) {
+            synchronized (mWaitLock) {
+                mOnProgressUpdateCalled = true;
+                mAction = action;
+                mExtras = extras;
+                mData = data;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onResult(String action, Bundle extras, Bundle resultData) {
+            synchronized (mWaitLock) {
+                mOnResultCalled = true;
+                mAction = action;
+                mExtras = extras;
+                mData = resultData;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onError(String action, Bundle extras, Bundle data) {
+            synchronized (mWaitLock) {
+                mOnErrorCalled = true;
+                mAction = action;
+                mExtras = extras;
+                mData = data;
+                mWaitLock.notify();
+            }
+        }
+
+        public void reset() {
+            mOnResultCalled = false;
+            mOnProgressUpdateCalled = false;
+            mOnErrorCalled = false;
+            mAction = null;
+            mExtras = null;
+            mData = null;
+        }
+    }
+
+    private class ConnectionCallbackForDelayedMediaSession extends
+            MediaBrowserCompat.ConnectionCallback {
+        final Object mWaitLock = new Object();
+        private int mConnectedCount = 0;
+
+        @Override
+        public void onConnected() {
+            synchronized (mWaitLock) {
+                mConnectedCount++;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onConnectionFailed() {
+            synchronized (mWaitLock) {
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onConnectionSuspended() {
+            synchronized (mWaitLock) {
+                mWaitLock.notify();
+            }
+        }
+    }
+}
diff --git a/media-compat/version-compat-tests/current/client/tests/src/android/support/mediacompat/client/MediaControllerCompatCallbackTest.java b/media-compat/version-compat-tests/current/client/tests/src/android/support/mediacompat/client/MediaControllerCompatCallbackTest.java
new file mode 100644
index 0000000..4b845c1
--- /dev/null
+++ b/media-compat/version-compat-tests/current/client/tests/src/android/support/mediacompat/client/MediaControllerCompatCallbackTest.java
@@ -0,0 +1,773 @@
+/*
+ * 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.client;
+
+import static android.media.AudioManager.STREAM_MUSIC;
+import static android.support.mediacompat.testlib.MediaSessionConstants.RELEASE;
+import static android.support.mediacompat.testlib.MediaSessionConstants.SEND_SESSION_EVENT;
+import static android.support.mediacompat.testlib.MediaSessionConstants.SET_CAPTIONING_ENABLED;
+import static android.support.mediacompat.testlib.MediaSessionConstants.SET_EXTRAS;
+import static android.support.mediacompat.testlib.MediaSessionConstants.SET_FLAGS;
+import static android.support.mediacompat.testlib.MediaSessionConstants.SET_METADATA;
+import static android.support.mediacompat.testlib.MediaSessionConstants.SET_PLAYBACK_STATE;
+import static android.support.mediacompat.testlib.MediaSessionConstants.SET_PLAYBACK_TO_LOCAL;
+import static android.support.mediacompat.testlib.MediaSessionConstants.SET_PLAYBACK_TO_REMOTE;
+import static android.support.mediacompat.testlib.MediaSessionConstants.SET_QUEUE;
+import static android.support.mediacompat.testlib.MediaSessionConstants.SET_QUEUE_TITLE;
+import static android.support.mediacompat.testlib.MediaSessionConstants.SET_RATING_TYPE;
+import static android.support.mediacompat.testlib.MediaSessionConstants.SET_REPEAT_MODE;
+import static android.support.mediacompat.testlib.MediaSessionConstants.SET_SESSION_ACTIVITY;
+import static android.support.mediacompat.testlib.MediaSessionConstants.SET_SHUFFLE_MODE;
+import static android.support.mediacompat.testlib.MediaSessionConstants.TEST_ACTION;
+import static android.support.mediacompat.testlib.MediaSessionConstants.TEST_CURRENT_VOLUME;
+import static android.support.mediacompat.testlib.MediaSessionConstants.TEST_ERROR_CODE;
+import static android.support.mediacompat.testlib.MediaSessionConstants.TEST_ERROR_MSG;
+import static android.support.mediacompat.testlib.MediaSessionConstants.TEST_FLAGS;
+import static android.support.mediacompat.testlib.MediaSessionConstants.TEST_KEY;
+import static android.support.mediacompat.testlib.MediaSessionConstants.TEST_MAX_VOLUME;
+import static android.support.mediacompat.testlib.MediaSessionConstants.TEST_MEDIA_ID_1;
+import static android.support.mediacompat.testlib.MediaSessionConstants.TEST_MEDIA_ID_2;
+import static android.support.mediacompat.testlib.MediaSessionConstants.TEST_QUEUE_ID_1;
+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.mediacompat.testlib.util.IntentUtil.SERVICE_PACKAGE_NAME;
+import static android.support.mediacompat.testlib.util.IntentUtil.callMediaSessionMethod;
+import static android.support.mediacompat.testlib.util.TestUtil.assertBundleEquals;
+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;
+import static android.support.v4.media.MediaMetadataCompat.METADATA_KEY_RATING;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.media.AudioManager;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.support.mediacompat.testlib.util.PollingCheck;
+import android.support.test.filters.LargeTest;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v4.media.MediaBrowserCompat;
+import android.support.v4.media.MediaDescriptionCompat;
+import android.support.v4.media.MediaMetadataCompat;
+import android.support.v4.media.RatingCompat;
+import android.support.v4.media.VolumeProviderCompat;
+import android.support.v4.media.session.MediaControllerCompat;
+import android.support.v4.media.session.MediaSessionCompat;
+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 org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Test {@link MediaControllerCompat.Callback}.
+ */
+@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;
+
+    private static final ComponentName TEST_BROWSER_SERVICE = new ComponentName(
+            SERVICE_PACKAGE_NAME,
+            "android.support.mediacompat.service.StubMediaBrowserServiceCompat");
+
+    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();
+
+    private MediaSessionCompat.Token mSessionToken;
+    private MediaControllerCompat mController;
+    private MediaControllerCallback mMediaControllerCallback = new MediaControllerCallback();
+
+    @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() {
+                mMediaBrowser = new MediaBrowserCompat(getInstrumentation().getTargetContext(),
+                        TEST_BROWSER_SERVICE, mConnectionCallback, new Bundle());
+            }
+        });
+
+        synchronized (mConnectionCallback.mWaitLock) {
+            mMediaBrowser.connect();
+            mConnectionCallback.mWaitLock.wait(TIME_OUT_MS);
+            if (!mMediaBrowser.isConnected()) {
+                fail("Browser failed to connect!");
+            }
+        }
+        mSessionToken = mMediaBrowser.getSessionToken();
+        mController = new MediaControllerCompat(getTargetContext(), mSessionToken);
+        mController.registerCallback(mMediaControllerCallback, mHandler);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (mMediaBrowser != null && mMediaBrowser.isConnected()) {
+            mMediaBrowser.disconnect();
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void testGetPackageName() {
+        assertEquals(SERVICE_PACKAGE_NAME, mController.getPackageName());
+    }
+
+    @Test
+    @SmallTest
+    public void testIsSessionReady() throws Exception {
+        // mController already has the extra binder since it was created with the session token
+        // which holds the extra binder.
+        assertTrue(mController.isSessionReady());
+    }
+
+    /**
+     * Tests {@link MediaSessionCompat#setExtras}.
+     */
+    @Test
+    @SmallTest
+    public void testSetExtras() throws Exception {
+        synchronized (mWaitLock) {
+            mMediaControllerCallback.resetLocked();
+
+            Bundle extras = new Bundle();
+            extras.putString(TEST_KEY, TEST_VALUE);
+            callMediaSessionMethod(SET_EXTRAS, extras, getContext());
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mMediaControllerCallback.mOnExtraChangedCalled);
+
+            assertBundleEquals(extras, mMediaControllerCallback.mExtras);
+            assertBundleEquals(extras, mController.getExtras());
+        }
+    }
+
+    /**
+     * Tests {@link MediaSessionCompat#setFlags}.
+     */
+    @Test
+    @SmallTest
+    public void testSetFlags() throws Exception {
+        synchronized (mWaitLock) {
+            mMediaControllerCallback.resetLocked();
+
+            callMediaSessionMethod(SET_FLAGS, TEST_FLAGS, getContext());
+            new PollingCheck(TIME_OUT_MS) {
+                @Override
+                public boolean check() {
+                    return TEST_FLAGS == mController.getFlags();
+                }
+            }.run();
+        }
+    }
+
+    /**
+     * Tests {@link MediaSessionCompat#setMetadata}.
+     */
+    @Test
+    @SmallTest
+    public void testSetMetadata() throws Exception {
+        synchronized (mWaitLock) {
+            mMediaControllerCallback.resetLocked();
+            RatingCompat rating = RatingCompat.newHeartRating(true);
+            MediaMetadataCompat metadata = new MediaMetadataCompat.Builder()
+                    .putString(TEST_KEY, TEST_VALUE)
+                    .putRating(METADATA_KEY_RATING, rating)
+                    .build();
+
+            callMediaSessionMethod(SET_METADATA, metadata, getContext());
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mMediaControllerCallback.mOnMetadataChangedCalled);
+
+            MediaMetadataCompat metadataOut = mMediaControllerCallback.mMediaMetadata;
+            assertNotNull(metadataOut);
+            assertEquals(TEST_VALUE, metadataOut.getString(TEST_KEY));
+
+            metadataOut = mController.getMetadata();
+            assertNotNull(metadataOut);
+            assertEquals(TEST_VALUE, metadataOut.getString(TEST_KEY));
+
+            assertNotNull(metadataOut.getRating(METADATA_KEY_RATING));
+            RatingCompat ratingOut = metadataOut.getRating(METADATA_KEY_RATING);
+            assertEquals(rating.getRatingStyle(), ratingOut.getRatingStyle());
+            assertEquals(rating.getPercentRating(), ratingOut.getPercentRating(), 0.0f);
+        }
+    }
+
+    /**
+     * Tests {@link MediaSessionCompat#setMetadata} with artwork bitmaps.
+     */
+    @Test
+    @SmallTest
+    public void testSetMetadataWithArtworks() throws Exception {
+        // TODO: Add test with a large bitmap.
+        // Using large bitmap makes other tests that are executed after this fail.
+        final Bitmap bitmapSmall = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
+
+        synchronized (mWaitLock) {
+            mMediaControllerCallback.resetLocked();
+            MediaMetadataCompat metadata = new MediaMetadataCompat.Builder()
+                    .putString(TEST_KEY, TEST_VALUE)
+                    .putBitmap(MediaMetadataCompat.METADATA_KEY_ART, bitmapSmall)
+                    .build();
+
+            callMediaSessionMethod(SET_METADATA, metadata, getContext());
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mMediaControllerCallback.mOnMetadataChangedCalled);
+
+            MediaMetadataCompat metadataOut = mMediaControllerCallback.mMediaMetadata;
+            assertNotNull(metadataOut);
+            assertEquals(TEST_VALUE, metadataOut.getString(TEST_KEY));
+
+            Bitmap bitmapSmallOut = metadataOut.getBitmap(MediaMetadataCompat.METADATA_KEY_ART);
+            assertNotNull(bitmapSmallOut);
+            assertEquals(bitmapSmall.getHeight(), bitmapSmallOut.getHeight());
+            assertEquals(bitmapSmall.getWidth(), bitmapSmallOut.getWidth());
+            assertEquals(bitmapSmall.getConfig(), bitmapSmallOut.getConfig());
+
+            bitmapSmallOut.recycle();
+        }
+        bitmapSmall.recycle();
+    }
+
+    /**
+     * Tests {@link MediaSessionCompat#setPlaybackState}.
+     */
+    @Test
+    @SmallTest
+    public void testSetPlaybackState() throws Exception {
+        synchronized (mWaitLock) {
+            mMediaControllerCallback.resetLocked();
+            PlaybackStateCompat state =
+                    new PlaybackStateCompat.Builder()
+                            .setActions(TEST_ACTION)
+                            .setErrorMessage(TEST_ERROR_CODE, TEST_ERROR_MSG)
+                            .build();
+
+            callMediaSessionMethod(SET_PLAYBACK_STATE, state, getContext());
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mMediaControllerCallback.mOnPlaybackStateChangedCalled);
+
+            PlaybackStateCompat stateOut = mMediaControllerCallback.mPlaybackState;
+            assertNotNull(stateOut);
+            assertEquals(TEST_ACTION, stateOut.getActions());
+            assertEquals(TEST_ERROR_CODE, stateOut.getErrorCode());
+            assertEquals(TEST_ERROR_MSG, stateOut.getErrorMessage().toString());
+
+            stateOut = mController.getPlaybackState();
+            assertNotNull(stateOut);
+            assertEquals(TEST_ACTION, stateOut.getActions());
+            assertEquals(TEST_ERROR_CODE, stateOut.getErrorCode());
+            assertEquals(TEST_ERROR_MSG, stateOut.getErrorMessage().toString());
+        }
+    }
+
+    /**
+     * Tests {@link MediaSessionCompat#setQueue} and {@link MediaSessionCompat#setQueueTitle}.
+     */
+    @Test
+    @SmallTest
+    public void testSetQueueAndSetQueueTitle() throws Exception {
+        synchronized (mWaitLock) {
+            mMediaControllerCallback.resetLocked();
+            List<QueueItem> queue = new ArrayList<>();
+
+            MediaDescriptionCompat description1 =
+                    new MediaDescriptionCompat.Builder().setMediaId(TEST_MEDIA_ID_1).build();
+            MediaDescriptionCompat description2 =
+                    new MediaDescriptionCompat.Builder().setMediaId(TEST_MEDIA_ID_2).build();
+            QueueItem item1 = new MediaSessionCompat.QueueItem(description1, TEST_QUEUE_ID_1);
+            QueueItem item2 = new MediaSessionCompat.QueueItem(description2, TEST_QUEUE_ID_2);
+            queue.add(item1);
+            queue.add(item2);
+
+            callMediaSessionMethod(SET_QUEUE, queue, getContext());
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mMediaControllerCallback.mOnQueueChangedCalled);
+
+            callMediaSessionMethod(SET_QUEUE_TITLE, TEST_VALUE, getContext());
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mMediaControllerCallback.mOnQueueTitleChangedCalled);
+
+            assertEquals(TEST_VALUE, mMediaControllerCallback.mTitle);
+            assertQueueEquals(queue, mMediaControllerCallback.mQueue);
+
+            assertEquals(TEST_VALUE, mController.getQueueTitle());
+            assertQueueEquals(queue, mController.getQueue());
+
+            mMediaControllerCallback.resetLocked();
+            callMediaSessionMethod(SET_QUEUE, null, getContext());
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mMediaControllerCallback.mOnQueueChangedCalled);
+
+            callMediaSessionMethod(SET_QUEUE_TITLE, null, getContext());
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mMediaControllerCallback.mOnQueueTitleChangedCalled);
+
+            assertNull(mMediaControllerCallback.mTitle);
+            assertNull(mMediaControllerCallback.mQueue);
+            assertNull(mController.getQueueTitle());
+            assertNull(mController.getQueue());
+        }
+    }
+
+    /**
+     * Tests {@link MediaSessionCompat#setSessionActivity}.
+     */
+    @Test
+    @SmallTest
+    public void testSessionActivity() throws Exception {
+        synchronized (mWaitLock) {
+            Intent intent = new Intent("MEDIA_SESSION_ACTION");
+            final int requestCode = 555;
+            final PendingIntent pi =
+                    PendingIntent.getActivity(getTargetContext(), requestCode, intent, 0);
+
+            callMediaSessionMethod(SET_SESSION_ACTIVITY, pi, getContext());
+            new PollingCheck(TIME_OUT_MS) {
+                @Override
+                public boolean check() {
+                    return pi.equals(mController.getSessionActivity());
+                }
+            }.run();
+        }
+    }
+
+    /**
+     * Tests {@link MediaSessionCompat#setCaptioningEnabled}.
+     */
+    @Test
+    @SmallTest
+    public void testSetCaptioningEnabled() throws Exception {
+        synchronized (mWaitLock) {
+            mMediaControllerCallback.resetLocked();
+            callMediaSessionMethod(SET_CAPTIONING_ENABLED, true, getContext());
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mMediaControllerCallback.mOnCaptioningEnabledChangedCalled);
+            assertEquals(true, mMediaControllerCallback.mCaptioningEnabled);
+            assertEquals(true, mController.isCaptioningEnabled());
+
+            mMediaControllerCallback.resetLocked();
+            callMediaSessionMethod(SET_CAPTIONING_ENABLED, false, getContext());
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mMediaControllerCallback.mOnCaptioningEnabledChangedCalled);
+            assertEquals(false, mMediaControllerCallback.mCaptioningEnabled);
+            assertEquals(false, mController.isCaptioningEnabled());
+        }
+    }
+
+    /**
+     * Tests {@link MediaSessionCompat#setRepeatMode}.
+     */
+    @Test
+    @SmallTest
+    public void testSetRepeatMode() throws Exception {
+        synchronized (mWaitLock) {
+            mMediaControllerCallback.resetLocked();
+            final int repeatMode = PlaybackStateCompat.REPEAT_MODE_ALL;
+            callMediaSessionMethod(SET_REPEAT_MODE, repeatMode, getContext());
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mMediaControllerCallback.mOnRepeatModeChangedCalled);
+            assertEquals(repeatMode, mMediaControllerCallback.mRepeatMode);
+            assertEquals(repeatMode, mController.getRepeatMode());
+        }
+    }
+
+    /**
+     * Tests {@link MediaSessionCompat#setShuffleMode}.
+     */
+    @Test
+    @SmallTest
+    public void testSetShuffleMode() throws Exception {
+        final int shuffleMode = PlaybackStateCompat.SHUFFLE_MODE_ALL;
+        synchronized (mWaitLock) {
+            mMediaControllerCallback.resetLocked();
+            callMediaSessionMethod(SET_SHUFFLE_MODE, shuffleMode, getContext());
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mMediaControllerCallback.mOnShuffleModeChangedCalled);
+            assertEquals(shuffleMode, mMediaControllerCallback.mShuffleMode);
+            assertEquals(shuffleMode, mController.getShuffleMode());
+        }
+    }
+
+    /**
+     * Tests {@link MediaSessionCompat#sendSessionEvent}.
+     */
+    @Test
+    @SmallTest
+    public void testSendSessionEvent() throws Exception {
+        synchronized (mWaitLock) {
+            mMediaControllerCallback.resetLocked();
+
+            Bundle arguments = new Bundle();
+            arguments.putString("event", TEST_SESSION_EVENT);
+
+            Bundle extras = new Bundle();
+            extras.putString(TEST_KEY, TEST_VALUE);
+            arguments.putBundle("extras", extras);
+            callMediaSessionMethod(SEND_SESSION_EVENT, arguments, getContext());
+
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mMediaControllerCallback.mOnSessionEventCalled);
+            assertEquals(TEST_SESSION_EVENT, mMediaControllerCallback.mEvent);
+            assertBundleEquals(extras, mMediaControllerCallback.mExtras);
+        }
+    }
+
+    /**
+     * Tests {@link MediaSessionCompat#release}.
+     */
+    @Test
+    @SmallTest
+    public void testRelease() throws Exception {
+        synchronized (mWaitLock) {
+            mMediaControllerCallback.resetLocked();
+            callMediaSessionMethod(RELEASE, null, getContext());
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mMediaControllerCallback.mOnSessionDestroyedCalled);
+        }
+    }
+
+    /**
+     * Tests {@link MediaSessionCompat#setPlaybackToLocal} and
+     * {@link MediaSessionCompat#setPlaybackToRemote}.
+     */
+    @LargeTest
+    public void testPlaybackToLocalAndRemote() throws Exception {
+        synchronized (mWaitLock) {
+            mMediaControllerCallback.resetLocked();
+            ParcelableVolumeInfo volumeInfo = new ParcelableVolumeInfo(
+                    MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE,
+                    STREAM_MUSIC,
+                    VolumeProviderCompat.VOLUME_CONTROL_FIXED,
+                    TEST_MAX_VOLUME,
+                    TEST_CURRENT_VOLUME);
+
+            callMediaSessionMethod(SET_PLAYBACK_TO_REMOTE, volumeInfo, getContext());
+            MediaControllerCompat.PlaybackInfo info = null;
+            for (int i = 0; i < MAX_AUDIO_INFO_CHANGED_CALLBACK_COUNT; ++i) {
+                mMediaControllerCallback.mOnAudioInfoChangedCalled = false;
+                mWaitLock.wait(TIME_OUT_MS);
+                assertTrue(mMediaControllerCallback.mOnAudioInfoChangedCalled);
+                info = mMediaControllerCallback.mPlaybackInfo;
+                if (info != null && info.getCurrentVolume() == TEST_CURRENT_VOLUME
+                        && info.getMaxVolume() == TEST_MAX_VOLUME
+                        && info.getVolumeControl() == VolumeProviderCompat.VOLUME_CONTROL_FIXED
+                        && info.getPlaybackType()
+                                == MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE) {
+                    break;
+                }
+            }
+            assertNotNull(info);
+            assertEquals(MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE,
+                    info.getPlaybackType());
+            assertEquals(TEST_MAX_VOLUME, info.getMaxVolume());
+            assertEquals(TEST_CURRENT_VOLUME, info.getCurrentVolume());
+            assertEquals(VolumeProviderCompat.VOLUME_CONTROL_FIXED,
+                    info.getVolumeControl());
+
+            info = mController.getPlaybackInfo();
+            assertNotNull(info);
+            assertEquals(MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE,
+                    info.getPlaybackType());
+            assertEquals(TEST_MAX_VOLUME, info.getMaxVolume());
+            assertEquals(TEST_CURRENT_VOLUME, info.getCurrentVolume());
+            assertEquals(VolumeProviderCompat.VOLUME_CONTROL_FIXED, info.getVolumeControl());
+
+            // test setPlaybackToLocal
+            mMediaControllerCallback.mOnAudioInfoChangedCalled = false;
+            callMediaSessionMethod(SET_PLAYBACK_TO_LOCAL, AudioManager.STREAM_RING, getContext());
+
+            // In API 21 and 22, onAudioInfoChanged is not called.
+            if (Build.VERSION.SDK_INT == 21 || Build.VERSION.SDK_INT == 22) {
+                Thread.sleep(TIME_OUT_MS);
+            } else {
+                mWaitLock.wait(TIME_OUT_MS);
+                assertTrue(mMediaControllerCallback.mOnAudioInfoChangedCalled);
+            }
+
+            info = mController.getPlaybackInfo();
+            assertNotNull(info);
+            assertEquals(MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_LOCAL,
+                    info.getPlaybackType());
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void testGetRatingType() {
+        assertEquals("Default rating type of a session must be RatingCompat.RATING_NONE",
+                RatingCompat.RATING_NONE, mController.getRatingType());
+
+        callMediaSessionMethod(SET_RATING_TYPE, RatingCompat.RATING_5_STARS, getContext());
+        new PollingCheck(TIME_OUT_MS) {
+            @Override
+            public boolean check() {
+                return RatingCompat.RATING_5_STARS == mController.getRatingType();
+            }
+        }.run();
+    }
+
+    @Test
+    @SmallTest
+    public void testSessionReady() throws Exception {
+        if (android.os.Build.VERSION.SDK_INT < 21) {
+            return;
+        }
+
+        final MediaSessionCompat.Token tokenWithoutExtraBinder =
+                MediaSessionCompat.Token.fromToken(mSessionToken.getToken());
+
+        final MediaControllerCallback callback = new MediaControllerCallback();
+        synchronized (mWaitLock) {
+            getInstrumentation().runOnMainSync(new Runnable() {
+                @Override
+                public void run() {
+                    try {
+                        MediaControllerCompat controller = new MediaControllerCompat(
+                                getInstrumentation().getTargetContext(), tokenWithoutExtraBinder);
+                        controller.registerCallback(callback, new Handler());
+                        assertFalse(controller.isSessionReady());
+                    } catch (Exception e) {
+                        fail();
+                    }
+                }
+            });
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(callback.mOnSessionReadyCalled);
+        }
+    }
+
+    private void assertQueueEquals(List<QueueItem> expected, List<QueueItem> observed) {
+        if (expected == null || observed == null) {
+            assertTrue(expected == observed);
+            return;
+        }
+
+        assertEquals(expected.size(), observed.size());
+        for (int i = 0; i < expected.size(); i++) {
+            QueueItem expectedItem = expected.get(i);
+            QueueItem observedItem = observed.get(i);
+
+            assertEquals(expectedItem.getQueueId(), observedItem.getQueueId());
+            assertEquals(expectedItem.getDescription().getMediaId(),
+                    observedItem.getDescription().getMediaId());
+        }
+    }
+
+    private class MediaControllerCallback extends MediaControllerCompat.Callback {
+        private volatile boolean mOnPlaybackStateChangedCalled;
+        private volatile boolean mOnMetadataChangedCalled;
+        private volatile boolean mOnQueueChangedCalled;
+        private volatile boolean mOnQueueTitleChangedCalled;
+        private volatile boolean mOnExtraChangedCalled;
+        private volatile boolean mOnAudioInfoChangedCalled;
+        private volatile boolean mOnSessionDestroyedCalled;
+        private volatile boolean mOnSessionEventCalled;
+        private volatile boolean mOnCaptioningEnabledChangedCalled;
+        private volatile boolean mOnRepeatModeChangedCalled;
+        private volatile boolean mOnShuffleModeChangedCalled;
+        private volatile boolean mOnSessionReadyCalled;
+
+        private volatile PlaybackStateCompat mPlaybackState;
+        private volatile MediaMetadataCompat mMediaMetadata;
+        private volatile List<QueueItem> mQueue;
+        private volatile CharSequence mTitle;
+        private volatile String mEvent;
+        private volatile Bundle mExtras;
+        private volatile MediaControllerCompat.PlaybackInfo mPlaybackInfo;
+        private volatile boolean mCaptioningEnabled;
+        private volatile int mRepeatMode;
+        private volatile int mShuffleMode;
+
+        public void resetLocked() {
+            mOnPlaybackStateChangedCalled = false;
+            mOnMetadataChangedCalled = false;
+            mOnQueueChangedCalled = false;
+            mOnQueueTitleChangedCalled = false;
+            mOnExtraChangedCalled = false;
+            mOnAudioInfoChangedCalled = false;
+            mOnSessionDestroyedCalled = false;
+            mOnSessionEventCalled = false;
+            mOnRepeatModeChangedCalled = false;
+            mOnShuffleModeChangedCalled = false;
+
+            mPlaybackState = null;
+            mMediaMetadata = null;
+            mQueue = null;
+            mTitle = null;
+            mExtras = null;
+            mPlaybackInfo = null;
+            mCaptioningEnabled = false;
+            mRepeatMode = PlaybackStateCompat.REPEAT_MODE_NONE;
+            mShuffleMode = PlaybackStateCompat.SHUFFLE_MODE_NONE;
+        }
+
+        @Override
+        public void onPlaybackStateChanged(PlaybackStateCompat state) {
+            synchronized (mWaitLock) {
+                mOnPlaybackStateChangedCalled = true;
+                mPlaybackState = state;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onMetadataChanged(MediaMetadataCompat metadata) {
+            synchronized (mWaitLock) {
+                mOnMetadataChangedCalled = true;
+                mMediaMetadata = metadata;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onQueueChanged(List<QueueItem> queue) {
+            synchronized (mWaitLock) {
+                mOnQueueChangedCalled = true;
+                mQueue = queue;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onQueueTitleChanged(CharSequence title) {
+            synchronized (mWaitLock) {
+                mOnQueueTitleChangedCalled = true;
+                mTitle = title;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onExtrasChanged(Bundle extras) {
+            synchronized (mWaitLock) {
+                mOnExtraChangedCalled = true;
+                mExtras = extras;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onAudioInfoChanged(MediaControllerCompat.PlaybackInfo info) {
+            synchronized (mWaitLock) {
+                mOnAudioInfoChangedCalled = true;
+                mPlaybackInfo = info;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onSessionDestroyed() {
+            synchronized (mWaitLock) {
+                mOnSessionDestroyedCalled = true;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onSessionEvent(String event, Bundle extras) {
+            synchronized (mWaitLock) {
+                mOnSessionEventCalled = true;
+                mEvent = event;
+                mExtras = (Bundle) extras.clone();
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onCaptioningEnabledChanged(boolean enabled) {
+            synchronized (mWaitLock) {
+                mOnCaptioningEnabledChangedCalled = true;
+                mCaptioningEnabled = enabled;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onRepeatModeChanged(int repeatMode) {
+            synchronized (mWaitLock) {
+                mOnRepeatModeChangedCalled = true;
+                mRepeatMode = repeatMode;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onShuffleModeChanged(int shuffleMode) {
+            synchronized (mWaitLock) {
+                mOnShuffleModeChangedCalled = true;
+                mShuffleMode = shuffleMode;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onSessionReady() {
+            synchronized (mWaitLock) {
+                mOnSessionReadyCalled = true;
+                mWaitLock.notify();
+            }
+        }
+    }
+
+    private class ConnectionCallback extends MediaBrowserCompat.ConnectionCallback {
+        final Object mWaitLock = new Object();
+
+        @Override
+        public void onConnected() {
+            synchronized (mWaitLock) {
+                mWaitLock.notify();
+            }
+        }
+    }
+}
diff --git a/media-compat/version-compat-tests/current/client/tests/src/android/support/mediacompat/client/MediaItemTest.java b/media-compat/version-compat-tests/current/client/tests/src/android/support/mediacompat/client/MediaItemTest.java
new file mode 100644
index 0000000..179a178
--- /dev/null
+++ b/media-compat/version-compat-tests/current/client/tests/src/android/support/mediacompat/client/MediaItemTest.java
@@ -0,0 +1,96 @@
+/*
+ * 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.client;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
+import android.support.test.filters.SmallTest;
+import android.support.v4.media.MediaBrowserCompat.MediaItem;
+import android.support.v4.media.MediaDescriptionCompat;
+
+import org.junit.Test;
+
+/**
+ * Test {@link MediaItem}.
+ */
+public class MediaItemTest {
+    private static final String DESCRIPTION = "test_description";
+    private static final String MEDIA_ID = "test_media_id";
+    private static final String TITLE = "test_title";
+    private static final String SUBTITLE = "test_subtitle";
+
+    @Test
+    @SmallTest
+    public void testBrowsableMediaItem() {
+        MediaDescriptionCompat description =
+                new MediaDescriptionCompat.Builder()
+                        .setDescription(DESCRIPTION)
+                        .setMediaId(MEDIA_ID)
+                        .setTitle(TITLE)
+                        .setSubtitle(SUBTITLE)
+                        .build();
+        MediaItem mediaItem = new MediaItem(description, MediaItem.FLAG_BROWSABLE);
+
+        assertEquals(description.toString(), mediaItem.getDescription().toString());
+        assertEquals(MEDIA_ID, mediaItem.getMediaId());
+        assertEquals(MediaItem.FLAG_BROWSABLE, mediaItem.getFlags());
+        assertTrue(mediaItem.isBrowsable());
+        assertFalse(mediaItem.isPlayable());
+        assertEquals(0, mediaItem.describeContents());
+
+        // Test writeToParcel
+        Parcel p = Parcel.obtain();
+        mediaItem.writeToParcel(p, 0);
+        p.setDataPosition(0);
+        assertEquals(mediaItem.getFlags(), p.readInt());
+        assertEquals(
+                description.toString(),
+                MediaDescriptionCompat.CREATOR.createFromParcel(p).toString());
+        p.recycle();
+    }
+
+    @Test
+    @SmallTest
+    public void testPlayableMediaItem() {
+        MediaDescriptionCompat description = new MediaDescriptionCompat.Builder()
+                .setDescription(DESCRIPTION)
+                .setMediaId(MEDIA_ID)
+                .setTitle(TITLE)
+                .setSubtitle(SUBTITLE)
+                .build();
+        MediaItem mediaItem = new MediaItem(description, MediaItem.FLAG_PLAYABLE);
+
+        assertEquals(description.toString(), mediaItem.getDescription().toString());
+        assertEquals(MEDIA_ID, mediaItem.getMediaId());
+        assertEquals(MediaItem.FLAG_PLAYABLE, mediaItem.getFlags());
+        assertFalse(mediaItem.isBrowsable());
+        assertTrue(mediaItem.isPlayable());
+        assertEquals(0, mediaItem.describeContents());
+
+        // Test writeToParcel
+        Parcel p = Parcel.obtain();
+        mediaItem.writeToParcel(p, 0);
+        p.setDataPosition(0);
+        assertEquals(mediaItem.getFlags(), p.readInt());
+        assertEquals(
+                description.toString(),
+                MediaDescriptionCompat.CREATOR.createFromParcel(p).toString());
+        p.recycle();
+    }
+}
diff --git a/media-compat/version-compat-tests/current/client/tests/src/android/support/mediacompat/client/PlaybackStateCompatTest.java b/media-compat/version-compat-tests/current/client/tests/src/android/support/mediacompat/client/PlaybackStateCompatTest.java
new file mode 100644
index 0000000..7962731
--- /dev/null
+++ b/media-compat/version-compat-tests/current/client/tests/src/android/support/mediacompat/client/PlaybackStateCompatTest.java
@@ -0,0 +1,298 @@
+/*
+ * 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.client;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v4.media.session.MediaSessionCompat;
+import android.support.v4.media.session.PlaybackStateCompat;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+
+/**
+ * Test {@link PlaybackStateCompat}.
+ */
+@RunWith(AndroidJUnit4.class)
+public class PlaybackStateCompatTest {
+
+    private static final long TEST_POSITION = 20000L;
+    private static final long TEST_BUFFERED_POSITION = 15000L;
+    private static final long TEST_UPDATE_TIME = 100000L;
+    private static final long TEST_ACTIONS = PlaybackStateCompat.ACTION_PLAY
+            | PlaybackStateCompat.ACTION_STOP | PlaybackStateCompat.ACTION_SEEK_TO;
+    private static final long TEST_QUEUE_ITEM_ID = 23L;
+    private static final float TEST_PLAYBACK_SPEED = 3.0f;
+    private static final float TEST_PLAYBACK_SPEED_ON_REWIND = -2.0f;
+    private static final float DELTA = 1e-7f;
+
+    private static final int TEST_ERROR_CODE =
+            PlaybackStateCompat.ERROR_CODE_AUTHENTICATION_EXPIRED;
+    private static final String TEST_ERROR_MSG = "test-error-msg";
+    private static final String TEST_CUSTOM_ACTION = "test-custom-action";
+    private static final String TEST_CUSTOM_ACTION_NAME = "test-custom-action-name";
+    private static final int TEST_ICON_RESOURCE_ID = android.R.drawable.ic_media_next;
+
+    private static final String EXTRAS_KEY = "test-key";
+    private static final String EXTRAS_VALUE = "test-value";
+
+    /**
+     * Test default values of {@link PlaybackStateCompat}.
+     */
+    @Test
+    @SmallTest
+    public void testBuilder() {
+        PlaybackStateCompat state = new PlaybackStateCompat.Builder().build();
+
+        assertEquals(new ArrayList<PlaybackStateCompat.CustomAction>(), state.getCustomActions());
+        assertEquals(0, state.getState());
+        assertEquals(0L, state.getPosition());
+        assertEquals(0L, state.getBufferedPosition());
+        assertEquals(0.0f, state.getPlaybackSpeed(), DELTA);
+        assertEquals(0L, state.getActions());
+        assertEquals(0, state.getErrorCode());
+        assertNull(state.getErrorMessage());
+        assertEquals(0L, state.getLastPositionUpdateTime());
+        assertEquals(MediaSessionCompat.QueueItem.UNKNOWN_ID, state.getActiveQueueItemId());
+        assertNull(state.getExtras());
+    }
+
+    /**
+     * Test following setter methods of {@link PlaybackStateCompat.Builder}:
+     * {@link PlaybackStateCompat.Builder#setState(int, long, float)}
+     * {@link PlaybackStateCompat.Builder#setActions(long)}
+     * {@link PlaybackStateCompat.Builder#setActiveQueueItemId(long)}
+     * {@link PlaybackStateCompat.Builder#setBufferedPosition(long)}
+     * {@link PlaybackStateCompat.Builder#setErrorMessage(CharSequence)}
+     * {@link PlaybackStateCompat.Builder#setExtras(Bundle)}
+     */
+    @Test
+    @SmallTest
+    public void testBuilder_setterMethods() {
+        Bundle extras = new Bundle();
+        extras.putString(EXTRAS_KEY, EXTRAS_VALUE);
+
+        PlaybackStateCompat state = new PlaybackStateCompat.Builder()
+                .setState(PlaybackStateCompat.STATE_PLAYING, TEST_POSITION, TEST_PLAYBACK_SPEED)
+                .setActions(TEST_ACTIONS)
+                .setActiveQueueItemId(TEST_QUEUE_ITEM_ID)
+                .setBufferedPosition(TEST_BUFFERED_POSITION)
+                .setErrorMessage(TEST_ERROR_CODE, TEST_ERROR_MSG)
+                .setExtras(extras)
+                .build();
+        assertEquals(PlaybackStateCompat.STATE_PLAYING, state.getState());
+        assertEquals(TEST_POSITION, state.getPosition());
+        assertEquals(TEST_PLAYBACK_SPEED, state.getPlaybackSpeed(), DELTA);
+        assertEquals(TEST_ACTIONS, state.getActions());
+        assertEquals(TEST_QUEUE_ITEM_ID, state.getActiveQueueItemId());
+        assertEquals(TEST_BUFFERED_POSITION, state.getBufferedPosition());
+        assertEquals(TEST_ERROR_CODE, state.getErrorCode());
+        assertEquals(TEST_ERROR_MSG, state.getErrorMessage().toString());
+        assertNotNull(state.getExtras());
+        assertEquals(EXTRAS_VALUE, state.getExtras().get(EXTRAS_KEY));
+    }
+
+    /**
+     * Test {@link PlaybackStateCompat.Builder#setState(int, long, float, long)}.
+     */
+    @Test
+    @SmallTest
+    public void testBuilder_setStateWithUpdateTime() {
+        PlaybackStateCompat state = new PlaybackStateCompat.Builder()
+                .setState(
+                        PlaybackStateCompat.STATE_REWINDING,
+                        TEST_POSITION,
+                        TEST_PLAYBACK_SPEED_ON_REWIND,
+                        TEST_UPDATE_TIME)
+                .build();
+        assertEquals(PlaybackStateCompat.STATE_REWINDING, state.getState());
+        assertEquals(TEST_POSITION, state.getPosition());
+        assertEquals(TEST_PLAYBACK_SPEED_ON_REWIND, state.getPlaybackSpeed(), DELTA);
+        assertEquals(TEST_UPDATE_TIME, state.getLastPositionUpdateTime());
+    }
+
+    /**
+     * Test {@link PlaybackStateCompat.Builder#addCustomAction(String, String, int)}.
+     */
+    @Test
+    @SmallTest
+    public void testBuilder_addCustomAction() {
+        ArrayList<PlaybackStateCompat.CustomAction> actions = new ArrayList<>();
+        PlaybackStateCompat.Builder builder = new PlaybackStateCompat.Builder();
+
+        for (int i = 0; i < 5; i++) {
+            actions.add(new PlaybackStateCompat.CustomAction.Builder(
+                    TEST_CUSTOM_ACTION + i, TEST_CUSTOM_ACTION_NAME + i, TEST_ICON_RESOURCE_ID + i)
+                    .build());
+            builder.addCustomAction(
+                    TEST_CUSTOM_ACTION + i, TEST_CUSTOM_ACTION_NAME + i, TEST_ICON_RESOURCE_ID + i);
+        }
+
+        PlaybackStateCompat state = builder.build();
+        assertEquals(actions.size(), state.getCustomActions().size());
+        for (int i = 0; i < actions.size(); i++) {
+            assertCustomActionEquals(actions.get(i), state.getCustomActions().get(i));
+        }
+    }
+
+    /**
+     * Test {@link PlaybackStateCompat.Builder#addCustomAction(PlaybackStateCompat.CustomAction)}.
+     */
+    @Test
+    @SmallTest
+    public void testBuilder_addCustomActionWithCustomActionObject() {
+        Bundle extras = new Bundle();
+        extras.putString(EXTRAS_KEY, EXTRAS_VALUE);
+
+        ArrayList<PlaybackStateCompat.CustomAction> actions = new ArrayList<>();
+        PlaybackStateCompat.Builder builder = new PlaybackStateCompat.Builder();
+
+        for (int i = 0; i < 5; i++) {
+            actions.add(new PlaybackStateCompat.CustomAction.Builder(
+                    TEST_CUSTOM_ACTION + i, TEST_CUSTOM_ACTION_NAME + i, TEST_ICON_RESOURCE_ID + i)
+                    .setExtras(extras)
+                    .build());
+            builder.addCustomAction(new PlaybackStateCompat.CustomAction.Builder(
+                    TEST_CUSTOM_ACTION + i, TEST_CUSTOM_ACTION_NAME + i, TEST_ICON_RESOURCE_ID + i)
+                    .setExtras(extras)
+                    .build());
+        }
+
+        PlaybackStateCompat state = builder.build();
+        assertEquals(actions.size(), state.getCustomActions().size());
+        for (int i = 0; i < actions.size(); i++) {
+            assertCustomActionEquals(actions.get(i), state.getCustomActions().get(i));
+        }
+    }
+
+    /**
+     * Test {@link PlaybackStateCompat#writeToParcel(Parcel, int)}.
+     */
+    @Test
+    @SmallTest
+    public void testWriteToParcel() {
+        Bundle extras = new Bundle();
+        extras.putString(EXTRAS_KEY, EXTRAS_VALUE);
+
+        PlaybackStateCompat.Builder builder =
+                new PlaybackStateCompat.Builder()
+                        .setState(PlaybackStateCompat.STATE_CONNECTING, TEST_POSITION,
+                                TEST_PLAYBACK_SPEED, TEST_UPDATE_TIME)
+                        .setActions(TEST_ACTIONS)
+                        .setActiveQueueItemId(TEST_QUEUE_ITEM_ID)
+                        .setBufferedPosition(TEST_BUFFERED_POSITION)
+                        .setErrorMessage(TEST_ERROR_CODE, TEST_ERROR_MSG)
+                        .setExtras(extras);
+
+        for (int i = 0; i < 5; i++) {
+            builder.addCustomAction(
+                    new PlaybackStateCompat.CustomAction.Builder(
+                            TEST_CUSTOM_ACTION + i,
+                            TEST_CUSTOM_ACTION_NAME + i,
+                            TEST_ICON_RESOURCE_ID + i)
+                            .setExtras(extras)
+                            .build());
+        }
+        PlaybackStateCompat state = builder.build();
+
+        Parcel parcel = Parcel.obtain();
+        state.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+
+        PlaybackStateCompat stateOut = PlaybackStateCompat.CREATOR.createFromParcel(parcel);
+        assertEquals(PlaybackStateCompat.STATE_CONNECTING, stateOut.getState());
+        assertEquals(TEST_POSITION, stateOut.getPosition());
+        assertEquals(TEST_PLAYBACK_SPEED, stateOut.getPlaybackSpeed(), DELTA);
+        assertEquals(TEST_UPDATE_TIME, stateOut.getLastPositionUpdateTime());
+        assertEquals(TEST_BUFFERED_POSITION, stateOut.getBufferedPosition());
+        assertEquals(TEST_ACTIONS, stateOut.getActions());
+        assertEquals(TEST_QUEUE_ITEM_ID, stateOut.getActiveQueueItemId());
+        assertEquals(TEST_ERROR_CODE, stateOut.getErrorCode());
+        assertEquals(TEST_ERROR_MSG, stateOut.getErrorMessage());
+        assertNotNull(stateOut.getExtras());
+        assertEquals(EXTRAS_VALUE, stateOut.getExtras().get(EXTRAS_KEY));
+
+        assertEquals(state.getCustomActions().size(), stateOut.getCustomActions().size());
+        for (int i = 0; i < state.getCustomActions().size(); i++) {
+            assertCustomActionEquals(
+                    state.getCustomActions().get(i), stateOut.getCustomActions().get(i));
+        }
+        parcel.recycle();
+    }
+
+    /**
+     * Test {@link PlaybackStateCompat#describeContents()}.
+     */
+    @Test
+    @SmallTest
+    public void testDescribeContents() {
+        assertEquals(0, new PlaybackStateCompat.Builder().build().describeContents());
+    }
+
+    /**
+     * Test {@link PlaybackStateCompat.CustomAction}.
+     */
+    @Test
+    @SmallTest
+    public void testCustomAction() {
+        Bundle extras = new Bundle();
+        extras.putString(EXTRAS_KEY, EXTRAS_VALUE);
+
+        // Test Builder/Getters
+        PlaybackStateCompat.CustomAction customAction = new PlaybackStateCompat.CustomAction
+                .Builder(TEST_CUSTOM_ACTION, TEST_CUSTOM_ACTION_NAME, TEST_ICON_RESOURCE_ID)
+                .setExtras(extras)
+                .build();
+        assertEquals(TEST_CUSTOM_ACTION, customAction.getAction());
+        assertEquals(TEST_CUSTOM_ACTION_NAME, customAction.getName().toString());
+        assertEquals(TEST_ICON_RESOURCE_ID, customAction.getIcon());
+        assertEquals(EXTRAS_VALUE, customAction.getExtras().get(EXTRAS_KEY));
+
+        // Test describeContents
+        assertEquals(0, customAction.describeContents());
+
+        // Test writeToParcel
+        Parcel parcel = Parcel.obtain();
+        customAction.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+
+        assertCustomActionEquals(
+                customAction, PlaybackStateCompat.CustomAction.CREATOR.createFromParcel(parcel));
+        parcel.recycle();
+    }
+
+    private void assertCustomActionEquals(PlaybackStateCompat.CustomAction action1,
+            PlaybackStateCompat.CustomAction action2) {
+        assertEquals(action1.getAction(), action2.getAction());
+        assertEquals(action1.getName(), action2.getName());
+        assertEquals(action1.getIcon(), action2.getIcon());
+
+        // To be the same, two extras should be both null or both not null.
+        assertEquals(action1.getExtras() != null, action2.getExtras() != null);
+        if (action1.getExtras() != null) {
+            assertEquals(action1.getExtras().get(EXTRAS_KEY), action2.getExtras().get(EXTRAS_KEY));
+        }
+    }
+}
diff --git a/media-compat/version-compat-tests/current/service/AndroidManifest.xml b/media-compat/version-compat-tests/current/service/AndroidManifest.xml
new file mode 100644
index 0000000..5e25a83
--- /dev/null
+++ b/media-compat/version-compat-tests/current/service/AndroidManifest.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+   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.
+-->
+<manifest package="android.support.mediacompat.service"/>
diff --git a/media-compat/version-compat-tests/current/service/build.gradle b/media-compat/version-compat-tests/current/service/build.gradle
new file mode 100644
index 0000000..cf82918
--- /dev/null
+++ b/media-compat/version-compat-tests/current/service/build.gradle
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+
+import static android.support.dependencies.DependenciesKt.*
+
+plugins {
+    id("SupportAndroidLibraryPlugin")
+}
+
+dependencies {
+    androidTestImplementation(project(":support-media-compat"))
+    androidTestImplementation(project(":support-media-compat-test-lib"))
+
+    androidTestImplementation(TEST_RUNNER)
+}
+
+android {
+    defaultConfig {
+        minSdkVersion 14
+    }
+}
+
+supportLibrary {
+    legacySourceLocation = true
+}
diff --git a/media-compat/version-compat-tests/current/service/lint-baseline.xml b/media-compat/version-compat-tests/current/service/lint-baseline.xml
new file mode 100644
index 0000000..ed7ade1
--- /dev/null
+++ b/media-compat/version-compat-tests/current/service/lint-baseline.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+   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.
+-->
+<issues format="4" by="lint 3.0.0-alpha9">
+
+</issues>
diff --git a/media-compat/version-compat-tests/current/service/tests/AndroidManifest.xml b/media-compat/version-compat-tests/current/service/tests/AndroidManifest.xml
new file mode 100644
index 0000000..b47eecf
--- /dev/null
+++ b/media-compat/version-compat-tests/current/service/tests/AndroidManifest.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+   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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.support.mediacompat.service.test">
+    <application>
+        <receiver android:name="android.support.mediacompat.service.ServiceBroadcastReceiver">
+            <intent-filter>
+                <action android:name="android.support.mediacompat.service.action.CALL_MEDIA_BROWSER_SERVICE_METHOD"/>
+                <action android:name="android.support.mediacompat.service.action.CALL_MEDIA_SESSION_METHOD"/>
+            </intent-filter>
+        </receiver>
+
+        <receiver android:name="android.support.v4.media.session.MediaButtonReceiver" >
+            <intent-filter>
+                <action android:name="android.intent.action.MEDIA_BUTTON" />
+            </intent-filter>
+        </receiver>
+
+        <service android:name="android.support.mediacompat.service.StubMediaBrowserServiceCompat">
+            <intent-filter>
+                <action android:name="android.media.browse.MediaBrowserService"/>
+            </intent-filter>
+        </service>
+
+        <service android:name="android.support.mediacompat.service.StubMediaBrowserServiceCompatWithDelayedMediaSession">
+            <intent-filter>
+                <action android:name="android.media.browse.MediaBrowserService"/>
+            </intent-filter>
+        </service>
+    </application>
+</manifest>
diff --git a/media-compat/version-compat-tests/current/service/tests/NO_DOCS b/media-compat/version-compat-tests/current/service/tests/NO_DOCS
new file mode 100644
index 0000000..61c9b1a
--- /dev/null
+++ b/media-compat/version-compat-tests/current/service/tests/NO_DOCS
@@ -0,0 +1,17 @@
+# 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.
+
+Having this file, named NO_DOCS, in a directory will prevent
+Android javadocs from being generated for java files under
+the directory. This is especially useful for test projects.
diff --git a/media-compat/version-compat-tests/current/service/tests/src/android/support/mediacompat/service/MediaSessionCompatCallbackTest.java b/media-compat/version-compat-tests/current/service/tests/src/android/support/mediacompat/service/MediaSessionCompatCallbackTest.java
new file mode 100644
index 0000000..5c5a432
--- /dev/null
+++ b/media-compat/version-compat-tests/current/service/tests/src/android/support/mediacompat/service/MediaSessionCompatCallbackTest.java
@@ -0,0 +1,1098 @@
+/*
+ * 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.service;
+
+import static android.support.mediacompat.testlib.MediaControllerConstants.ADD_QUEUE_ITEM;
+import static android.support.mediacompat.testlib.MediaControllerConstants
+        .ADD_QUEUE_ITEM_WITH_INDEX;
+import static android.support.mediacompat.testlib.MediaControllerConstants.ADJUST_VOLUME;
+import static android.support.mediacompat.testlib.MediaControllerConstants.FAST_FORWARD;
+import static android.support.mediacompat.testlib.MediaControllerConstants.PAUSE;
+import static android.support.mediacompat.testlib.MediaControllerConstants.PLAY;
+import static android.support.mediacompat.testlib.MediaControllerConstants.PLAY_FROM_MEDIA_ID;
+import static android.support.mediacompat.testlib.MediaControllerConstants.PLAY_FROM_SEARCH;
+import static android.support.mediacompat.testlib.MediaControllerConstants.PLAY_FROM_URI;
+import static android.support.mediacompat.testlib.MediaControllerConstants.PREPARE;
+import static android.support.mediacompat.testlib.MediaControllerConstants.PREPARE_FROM_MEDIA_ID;
+import static android.support.mediacompat.testlib.MediaControllerConstants.PREPARE_FROM_SEARCH;
+import static android.support.mediacompat.testlib.MediaControllerConstants.PREPARE_FROM_URI;
+import static android.support.mediacompat.testlib.MediaControllerConstants.REMOVE_QUEUE_ITEM;
+import static android.support.mediacompat.testlib.MediaControllerConstants.REWIND;
+import static android.support.mediacompat.testlib.MediaControllerConstants.SEEK_TO;
+import static android.support.mediacompat.testlib.MediaControllerConstants.SEND_COMMAND;
+import static android.support.mediacompat.testlib.MediaControllerConstants.SEND_CUSTOM_ACTION;
+import static android.support.mediacompat.testlib.MediaControllerConstants
+        .SEND_CUSTOM_ACTION_PARCELABLE;
+import static android.support.mediacompat.testlib.MediaControllerConstants.SET_CAPTIONING_ENABLED;
+import static android.support.mediacompat.testlib.MediaControllerConstants.SET_RATING;
+import static android.support.mediacompat.testlib.MediaControllerConstants.SET_REPEAT_MODE;
+import static android.support.mediacompat.testlib.MediaControllerConstants.SET_SHUFFLE_MODE;
+import static android.support.mediacompat.testlib.MediaControllerConstants.SET_VOLUME_TO;
+import static android.support.mediacompat.testlib.MediaControllerConstants.SKIP_TO_NEXT;
+import static android.support.mediacompat.testlib.MediaControllerConstants.SKIP_TO_PREVIOUS;
+import static android.support.mediacompat.testlib.MediaControllerConstants.SKIP_TO_QUEUE_ITEM;
+import static android.support.mediacompat.testlib.MediaControllerConstants.STOP;
+import static android.support.mediacompat.testlib.MediaSessionConstants.TEST_COMMAND;
+import static android.support.mediacompat.testlib.MediaSessionConstants.TEST_KEY;
+import static android.support.mediacompat.testlib.MediaSessionConstants.TEST_MEDIA_ID_1;
+import static android.support.mediacompat.testlib.MediaSessionConstants.TEST_MEDIA_ID_2;
+import static android.support.mediacompat.testlib.MediaSessionConstants.TEST_MEDIA_TITLE_1;
+import static android.support.mediacompat.testlib.MediaSessionConstants.TEST_MEDIA_TITLE_2;
+import static android.support.mediacompat.testlib.MediaSessionConstants.TEST_QUEUE_ID_1;
+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.mediacompat.testlib.util.IntentUtil.callMediaControllerMethod;
+import static android.support.mediacompat.testlib.util.IntentUtil.callTransportControlsMethod;
+import static android.support.mediacompat.testlib.util.TestUtil.assertBundleEquals;
+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;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.media.AudioManager;
+import android.media.session.MediaController;
+import android.media.session.MediaSession;
+import android.media.session.PlaybackState;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Parcel;
+import android.os.ResultReceiver;
+import android.os.SystemClock;
+import android.support.test.filters.MediumTest;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v4.media.MediaDescriptionCompat;
+import android.support.v4.media.RatingCompat;
+import android.support.v4.media.VolumeProviderCompat;
+import android.support.v4.media.session.MediaControllerCompat;
+import android.support.v4.media.session.MediaSessionCompat;
+import android.support.v4.media.session.PlaybackStateCompat;
+import android.util.Log;
+import android.view.KeyEvent;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Test {@link MediaSessionCompat.Callback}.
+ */
+@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 long WAIT_TIME_FOR_NO_RESPONSE_MS = 300L;
+
+    private static final long TEST_POSITION = 1000000L;
+    private static final float TEST_PLAYBACK_SPEED = 3.0f;
+    private static final float DELTA = 1e-4f;
+    private static final boolean ENABLED = true;
+
+    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();
+    private AudioManager mAudioManager;
+
+    @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() {
+                mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
+                mSession = new MediaSessionCompat(getTargetContext(), TEST_SESSION_TAG);
+                mSession.setCallback(mCallback, mHandler);
+            }
+        });
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mSession.release();
+    }
+
+    /**
+     * Tests that a session can be created and that all the fields are initialized correctly.
+     */
+    @Test
+    @SmallTest
+    public void testCreateSession() throws Exception {
+        assertNotNull(mSession.getSessionToken());
+        assertFalse("New session should not be active", mSession.isActive());
+
+        // Verify by getting the controller and checking all its fields
+        MediaControllerCompat controller = mSession.getController();
+        assertNotNull(controller);
+
+        final String errorMsg = "New session has unexpected configuration.";
+        assertEquals(errorMsg, 0L, controller.getFlags());
+        assertNull(errorMsg, controller.getExtras());
+        assertNull(errorMsg, controller.getMetadata());
+        assertEquals(errorMsg, getContext().getPackageName(), controller.getPackageName());
+        assertNull(errorMsg, controller.getPlaybackState());
+        assertNull(errorMsg, controller.getQueue());
+        assertNull(errorMsg, controller.getQueueTitle());
+        assertEquals(errorMsg, RatingCompat.RATING_NONE, controller.getRatingType());
+        assertNull(errorMsg, controller.getSessionActivity());
+
+        assertNotNull(controller.getSessionToken());
+        assertNotNull(controller.getTransportControls());
+
+        MediaControllerCompat.PlaybackInfo info = controller.getPlaybackInfo();
+        assertNotNull(info);
+        assertEquals(errorMsg, MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_LOCAL,
+                info.getPlaybackType());
+        assertEquals(errorMsg, mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC),
+                info.getCurrentVolume());
+    }
+
+    @Test
+    @SmallTest
+    public void testGetSessionToken() throws Exception {
+        assertEquals(mSession.getSessionToken(), mSession.getController().getSessionToken());
+    }
+
+    /**
+     * Tests that a session can be created from the framework session object and the callback
+     * set on the framework session object before fromSession() is called works properly.
+     */
+    @Test
+    @SmallTest
+    public void testFromSession() throws Exception {
+        if (android.os.Build.VERSION.SDK_INT < 21) {
+            // MediaSession was introduced from API level 21.
+            return;
+        }
+        mCallback.reset(1);
+        mSession.setCallback(mCallback, new Handler(Looper.getMainLooper()));
+        MediaSessionCompat session = MediaSessionCompat.fromMediaSession(
+                getContext(), mSession.getMediaSession());
+        assertEquals(session.getSessionToken(), mSession.getSessionToken());
+
+        session.getController().getTransportControls().play();
+        mCallback.await(TIME_OUT_MS);
+        assertEquals(1, mCallback.mOnPlayCalledCount);
+    }
+
+    /**
+     * Tests {@link MediaSessionCompat.Token} created in the constructor of MediaSessionCompat.
+     */
+    @Test
+    @SmallTest
+    public void testSessionToken() throws Exception {
+        MediaSessionCompat.Token sessionToken = mSession.getSessionToken();
+
+        assertNotNull(sessionToken);
+        assertEquals(0, sessionToken.describeContents());
+
+        // Test writeToParcel
+        Parcel p = Parcel.obtain();
+        sessionToken.writeToParcel(p, 0);
+        p.setDataPosition(0);
+        MediaSessionCompat.Token token = MediaSessionCompat.Token.CREATOR.createFromParcel(p);
+        assertEquals(token, sessionToken);
+        p.recycle();
+    }
+
+    /**
+     * Tests {@link MediaSessionCompat.QueueItem}.
+     */
+    @Test
+    @SmallTest
+    public void testQueueItem() {
+        MediaSessionCompat.QueueItem item = new MediaSessionCompat.QueueItem(
+                new MediaDescriptionCompat.Builder()
+                        .setMediaId(TEST_MEDIA_ID_1)
+                        .setTitle(TEST_MEDIA_TITLE_1)
+                        .build(),
+                TEST_QUEUE_ID_1);
+        assertEquals(TEST_QUEUE_ID_1, item.getQueueId());
+        assertEquals(TEST_MEDIA_ID_1, item.getDescription().getMediaId());
+        assertEquals(TEST_MEDIA_TITLE_1, item.getDescription().getTitle());
+        assertEquals(0, item.describeContents());
+
+        Parcel p = Parcel.obtain();
+        item.writeToParcel(p, 0);
+        p.setDataPosition(0);
+        MediaSessionCompat.QueueItem other =
+                MediaSessionCompat.QueueItem.CREATOR.createFromParcel(p);
+        assertEquals(item.toString(), other.toString());
+        p.recycle();
+    }
+
+    /**
+     * Tests {@link MediaSessionCompat#setActive}.
+     */
+    @Test
+    @SmallTest
+    public void testSetActive() throws Exception {
+        mSession.setActive(true);
+        assertTrue(mSession.isActive());
+    }
+
+    @Test
+    @SmallTest
+    public void testGetPlaybackStateWithPositionUpdate() throws InterruptedException {
+        final long stateSetTime = SystemClock.elapsedRealtime();
+        PlaybackStateCompat stateIn = new PlaybackStateCompat.Builder()
+                .setState(PlaybackStateCompat.STATE_PLAYING, TEST_POSITION, TEST_PLAYBACK_SPEED,
+                        stateSetTime)
+                .build();
+        mSession.setPlaybackState(stateIn);
+
+        final long waitDuration = 100L;
+        Thread.sleep(waitDuration);
+
+        final long expectedUpdateTime = waitDuration + stateSetTime;
+        final long expectedPosition = (long) (TEST_PLAYBACK_SPEED * waitDuration) + TEST_POSITION;
+
+        final double updateTimeTolerance = 50L;
+        final double positionTolerance = updateTimeTolerance * TEST_PLAYBACK_SPEED;
+
+        PlaybackStateCompat stateOut = mSession.getController().getPlaybackState();
+        assertEquals(expectedUpdateTime, stateOut.getLastPositionUpdateTime(), updateTimeTolerance);
+        assertEquals(expectedPosition, stateOut.getPosition(), positionTolerance);
+
+        // Compare the result with MediaController.getPlaybackState().
+        if (Build.VERSION.SDK_INT >= 21) {
+            MediaController controller = new MediaController(
+                    getContext(), (MediaSession.Token) mSession.getSessionToken().getToken());
+            PlaybackState state = controller.getPlaybackState();
+            assertEquals(state.getLastPositionUpdateTime(), stateOut.getLastPositionUpdateTime(),
+                    updateTimeTolerance);
+            assertEquals(state.getPosition(), stateOut.getPosition(), positionTolerance);
+        }
+    }
+
+    /**
+     * Tests {@link MediaSessionCompat#setCallback} with {@code null}.
+     * No callback should be called once {@code setCallback(null)} is done.
+     */
+    @Test
+    @SmallTest
+    public void testSetCallbackWithNull() throws Exception {
+        mSession.setActive(true);
+        mCallback.reset(1);
+        callTransportControlsMethod(PLAY, null, getContext(), mSession.getSessionToken());
+        mSession.setCallback(null, mHandler);
+        mCallback.await(WAIT_TIME_FOR_NO_RESPONSE_MS);
+        assertEquals("Callback shouldn't be called.", 0, mCallback.mOnPlayCalledCount);
+    }
+
+    @Test
+    @SmallTest
+    public void testSendCommand() throws Exception {
+        mCallback.reset(1);
+
+        Bundle arguments = new Bundle();
+        arguments.putString("command", TEST_COMMAND);
+        Bundle extras = new Bundle();
+        extras.putString(TEST_KEY, TEST_VALUE);
+        arguments.putBundle("extras", extras);
+        callMediaControllerMethod(
+                SEND_COMMAND, arguments, getContext(), mSession.getSessionToken());
+
+        mCallback.await(TIME_OUT_MS);
+        assertTrue(mCallback.mOnCommandCalled);
+        assertNotNull(mCallback.mCommandCallback);
+        assertEquals(TEST_COMMAND, mCallback.mCommand);
+        assertBundleEquals(extras, mCallback.mExtras);
+    }
+
+    @Test
+    @SmallTest
+    public void testAddRemoveQueueItems() throws Exception {
+        mSession.setFlags(MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS);
+
+        MediaDescriptionCompat itemDescription1 = new MediaDescriptionCompat.Builder()
+                .setMediaId(TEST_MEDIA_ID_1).setTitle(TEST_MEDIA_TITLE_1).build();
+
+        MediaDescriptionCompat itemDescription2 = new MediaDescriptionCompat.Builder()
+                .setMediaId(TEST_MEDIA_ID_2).setTitle(TEST_MEDIA_TITLE_2).build();
+
+        mCallback.reset(1);
+        callMediaControllerMethod(
+                ADD_QUEUE_ITEM, itemDescription1, getContext(), mSession.getSessionToken());
+
+        mCallback.await(TIME_OUT_MS);
+        assertTrue(mCallback.mOnAddQueueItemCalled);
+        assertEquals(-1, mCallback.mQueueIndex);
+        assertEquals(TEST_MEDIA_ID_1, mCallback.mQueueDescription.getMediaId());
+        assertEquals(TEST_MEDIA_TITLE_1, mCallback.mQueueDescription.getTitle());
+
+        mCallback.reset(1);
+        Bundle arguments = new Bundle();
+        arguments.putParcelable("description", itemDescription2);
+        arguments.putInt("index", 0);
+        callMediaControllerMethod(
+                ADD_QUEUE_ITEM_WITH_INDEX, arguments, getContext(), mSession.getSessionToken());
+
+        mCallback.await(TIME_OUT_MS);
+        assertTrue(mCallback.mOnAddQueueItemAtCalled);
+        assertEquals(0, mCallback.mQueueIndex);
+        assertEquals(TEST_MEDIA_ID_2, mCallback.mQueueDescription.getMediaId());
+        assertEquals(TEST_MEDIA_TITLE_2, mCallback.mQueueDescription.getTitle());
+
+        mCallback.reset(1);
+        callMediaControllerMethod(
+                REMOVE_QUEUE_ITEM, itemDescription1, getContext(), mSession.getSessionToken());
+        mCallback.await(TIME_OUT_MS);
+        assertTrue(mCallback.mOnRemoveQueueItemCalled);
+        assertEquals(TEST_MEDIA_ID_1, mCallback.mQueueDescription.getMediaId());
+        assertEquals(TEST_MEDIA_TITLE_1, mCallback.mQueueDescription.getTitle());
+    }
+
+    @Test
+    @SmallTest
+    public void testTransportControlsAndMediaSessionCallback() throws Exception {
+        mCallback.reset(1);
+        callTransportControlsMethod(PLAY, null, getContext(), mSession.getSessionToken());
+        mCallback.await(TIME_OUT_MS);
+        assertEquals(1, mCallback.mOnPlayCalledCount);
+
+        mCallback.reset(1);
+        callTransportControlsMethod(PAUSE, null, getContext(), mSession.getSessionToken());
+        mCallback.await(TIME_OUT_MS);
+        assertTrue(mCallback.mOnPauseCalled);
+
+        mCallback.reset(1);
+        callTransportControlsMethod(STOP, null, getContext(), mSession.getSessionToken());
+        mCallback.await(TIME_OUT_MS);
+        assertTrue(mCallback.mOnStopCalled);
+
+        mCallback.reset(1);
+        callTransportControlsMethod(
+                FAST_FORWARD, null, getContext(), mSession.getSessionToken());
+        mCallback.await(TIME_OUT_MS);
+        assertTrue(mCallback.mOnFastForwardCalled);
+
+        mCallback.reset(1);
+        callTransportControlsMethod(REWIND, null, getContext(), mSession.getSessionToken());
+        mCallback.await(TIME_OUT_MS);
+        assertTrue(mCallback.mOnRewindCalled);
+
+        mCallback.reset(1);
+        callTransportControlsMethod(
+                SKIP_TO_PREVIOUS, null, getContext(), mSession.getSessionToken());
+        mCallback.await(TIME_OUT_MS);
+        assertTrue(mCallback.mOnSkipToPreviousCalled);
+
+        mCallback.reset(1);
+        callTransportControlsMethod(
+                SKIP_TO_NEXT, null, getContext(), mSession.getSessionToken());
+        mCallback.await(TIME_OUT_MS);
+        assertTrue(mCallback.mOnSkipToNextCalled);
+
+        mCallback.reset(1);
+        final long seekPosition = 1000;
+        callTransportControlsMethod(
+                SEEK_TO, seekPosition, getContext(), mSession.getSessionToken());
+        mCallback.await(TIME_OUT_MS);
+        assertTrue(mCallback.mOnSeekToCalled);
+        assertEquals(seekPosition, mCallback.mSeekPosition);
+
+        mCallback.reset(1);
+        final RatingCompat rating =
+                RatingCompat.newStarRating(RatingCompat.RATING_5_STARS, 3f);
+        callTransportControlsMethod(
+                SET_RATING, rating, getContext(), mSession.getSessionToken());
+        mCallback.await(TIME_OUT_MS);
+        assertTrue(mCallback.mOnSetRatingCalled);
+        assertEquals(rating.getRatingStyle(), mCallback.mRating.getRatingStyle());
+        assertEquals(rating.getStarRating(), mCallback.mRating.getStarRating(), DELTA);
+
+        mCallback.reset(1);
+        final Bundle extras = new Bundle();
+        extras.putString(TEST_KEY, TEST_VALUE);
+        Bundle arguments = new Bundle();
+        arguments.putString("mediaId", TEST_MEDIA_ID_1);
+        arguments.putBundle("extras", extras);
+        callTransportControlsMethod(
+                PLAY_FROM_MEDIA_ID, arguments, getContext(), mSession.getSessionToken());
+        mCallback.await(TIME_OUT_MS);
+        assertTrue(mCallback.mOnPlayFromMediaIdCalled);
+        assertEquals(TEST_MEDIA_ID_1, mCallback.mMediaId);
+        assertBundleEquals(extras, mCallback.mExtras);
+
+        mCallback.reset(1);
+        final String query = "test-query";
+        arguments = new Bundle();
+        arguments.putString("query", query);
+        arguments.putBundle("extras", extras);
+        callTransportControlsMethod(
+                PLAY_FROM_SEARCH, arguments, getContext(), mSession.getSessionToken());
+        mCallback.await(TIME_OUT_MS);
+        assertTrue(mCallback.mOnPlayFromSearchCalled);
+        assertEquals(query, mCallback.mQuery);
+        assertBundleEquals(extras, mCallback.mExtras);
+
+        mCallback.reset(1);
+        final Uri uri = Uri.parse("content://test/popcorn.mod");
+        arguments = new Bundle();
+        arguments.putParcelable("uri", uri);
+        arguments.putBundle("extras", extras);
+        callTransportControlsMethod(
+                PLAY_FROM_URI, arguments, getContext(), mSession.getSessionToken());
+        mCallback.await(TIME_OUT_MS);
+        assertTrue(mCallback.mOnPlayFromUriCalled);
+        assertEquals(uri, mCallback.mUri);
+        assertBundleEquals(extras, mCallback.mExtras);
+
+        mCallback.reset(1);
+        final String action = "test-action";
+        arguments = new Bundle();
+        arguments.putString("action", action);
+        arguments.putBundle("extras", extras);
+        callTransportControlsMethod(
+                SEND_CUSTOM_ACTION, arguments, getContext(), mSession.getSessionToken());
+        mCallback.await(TIME_OUT_MS);
+        assertTrue(mCallback.mOnCustomActionCalled);
+        assertEquals(action, mCallback.mAction);
+        assertBundleEquals(extras, mCallback.mExtras);
+
+        mCallback.reset(1);
+        mCallback.mOnCustomActionCalled = false;
+        final PlaybackStateCompat.CustomAction customAction =
+                new PlaybackStateCompat.CustomAction.Builder(action, action, -1)
+                        .setExtras(extras)
+                        .build();
+        arguments = new Bundle();
+        arguments.putParcelable("action", customAction);
+        arguments.putBundle("extras", extras);
+        callTransportControlsMethod(
+                SEND_CUSTOM_ACTION_PARCELABLE,
+                arguments,
+                getContext(),
+                mSession.getSessionToken());
+        mCallback.await(TIME_OUT_MS);
+        assertTrue(mCallback.mOnCustomActionCalled);
+        assertEquals(action, mCallback.mAction);
+        assertBundleEquals(extras, mCallback.mExtras);
+
+        mCallback.reset(1);
+        final long queueItemId = 1000;
+        callTransportControlsMethod(
+                SKIP_TO_QUEUE_ITEM, queueItemId, getContext(), mSession.getSessionToken());
+        mCallback.await(TIME_OUT_MS);
+        assertTrue(mCallback.mOnSkipToQueueItemCalled);
+        assertEquals(queueItemId, mCallback.mQueueItemId);
+
+        mCallback.reset(1);
+        callTransportControlsMethod(
+                PREPARE, null, getContext(), mSession.getSessionToken());
+        mCallback.await(TIME_OUT_MS);
+        assertTrue(mCallback.mOnPrepareCalled);
+
+        mCallback.reset(1);
+        arguments = new Bundle();
+        arguments.putString("mediaId", TEST_MEDIA_ID_2);
+        arguments.putBundle("extras", extras);
+        callTransportControlsMethod(
+                PREPARE_FROM_MEDIA_ID, arguments, getContext(), mSession.getSessionToken());
+        mCallback.await(TIME_OUT_MS);
+        assertTrue(mCallback.mOnPrepareFromMediaIdCalled);
+        assertEquals(TEST_MEDIA_ID_2, mCallback.mMediaId);
+        assertBundleEquals(extras, mCallback.mExtras);
+
+        mCallback.reset(1);
+        arguments = new Bundle();
+        arguments.putString("query", query);
+        arguments.putBundle("extras", extras);
+        callTransportControlsMethod(
+                PREPARE_FROM_SEARCH, arguments, getContext(), mSession.getSessionToken());
+        mCallback.await(TIME_OUT_MS);
+        assertTrue(mCallback.mOnPrepareFromSearchCalled);
+        assertEquals(query, mCallback.mQuery);
+        assertBundleEquals(extras, mCallback.mExtras);
+
+        mCallback.reset(1);
+        arguments = new Bundle();
+        arguments.putParcelable("uri", uri);
+        arguments.putBundle("extras", extras);
+        callTransportControlsMethod(
+                PREPARE_FROM_URI, arguments, getContext(), mSession.getSessionToken());
+        mCallback.await(TIME_OUT_MS);
+        assertTrue(mCallback.mOnPrepareFromUriCalled);
+        assertEquals(uri, mCallback.mUri);
+        assertBundleEquals(extras, mCallback.mExtras);
+
+        mCallback.reset(1);
+        callTransportControlsMethod(
+                SET_CAPTIONING_ENABLED, ENABLED, getContext(), mSession.getSessionToken());
+        mCallback.await(TIME_OUT_MS);
+        assertTrue(mCallback.mOnSetCaptioningEnabledCalled);
+        assertEquals(ENABLED, mCallback.mCaptioningEnabled);
+
+        mCallback.reset(1);
+        final int repeatMode = PlaybackStateCompat.REPEAT_MODE_ALL;
+        callTransportControlsMethod(
+                SET_REPEAT_MODE, repeatMode, getContext(), mSession.getSessionToken());
+        mCallback.await(TIME_OUT_MS);
+        assertTrue(mCallback.mOnSetRepeatModeCalled);
+        assertEquals(repeatMode, mCallback.mRepeatMode);
+
+        mCallback.reset(1);
+        final int shuffleMode = PlaybackStateCompat.SHUFFLE_MODE_ALL;
+        callTransportControlsMethod(
+                SET_SHUFFLE_MODE, shuffleMode, getContext(), mSession.getSessionToken());
+        mCallback.await(TIME_OUT_MS);
+        assertTrue(mCallback.mOnSetShuffleModeCalled);
+        assertEquals(shuffleMode, mCallback.mShuffleMode);
+    }
+
+    /**
+     * Tests {@link MediaSessionCompat.Callback#onMediaButtonEvent}.
+     */
+    @Test
+    @MediumTest
+    public void testCallbackOnMediaButtonEvent() throws Exception {
+        mSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS);
+        mSession.setActive(true);
+
+        final long waitTimeForNoResponse = 30L;
+
+        Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON)
+                .setComponent(new ComponentName(getContext(), getContext().getClass()));
+        PendingIntent pi = PendingIntent.getBroadcast(getContext(), 0, mediaButtonIntent, 0);
+        mSession.setMediaButtonReceiver(pi);
+
+        // Set state to STATE_PLAYING to get higher priority.
+        setPlaybackState(PlaybackStateCompat.STATE_PLAYING);
+
+        mCallback.reset(1);
+        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY);
+        assertTrue(mCallback.await(TIME_OUT_MS));
+        assertEquals(1, mCallback.mOnPlayCalledCount);
+
+        mCallback.reset(1);
+        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PAUSE);
+        assertTrue(mCallback.await(TIME_OUT_MS));
+        assertTrue(mCallback.mOnPauseCalled);
+
+        mCallback.reset(1);
+        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_NEXT);
+        assertTrue(mCallback.await(TIME_OUT_MS));
+        assertTrue(mCallback.mOnSkipToNextCalled);
+
+        mCallback.reset(1);
+        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PREVIOUS);
+        assertTrue(mCallback.await(TIME_OUT_MS));
+        assertTrue(mCallback.mOnSkipToPreviousCalled);
+
+        mCallback.reset(1);
+        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_STOP);
+        assertTrue(mCallback.await(TIME_OUT_MS));
+        assertTrue(mCallback.mOnStopCalled);
+
+        mCallback.reset(1);
+        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_FAST_FORWARD);
+        assertTrue(mCallback.await(TIME_OUT_MS));
+        assertTrue(mCallback.mOnFastForwardCalled);
+
+        mCallback.reset(1);
+        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_REWIND);
+        assertTrue(mCallback.await(TIME_OUT_MS));
+        assertTrue(mCallback.mOnRewindCalled);
+
+        // Test PLAY_PAUSE button twice.
+        // First, send PLAY_PAUSE button event while in STATE_PAUSED.
+        mCallback.reset(1);
+        setPlaybackState(PlaybackStateCompat.STATE_PAUSED);
+        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
+        assertTrue(mCallback.await(TIME_OUT_MS));
+        assertEquals(1, mCallback.mOnPlayCalledCount);
+
+        // Next, send PLAY_PAUSE button event while in STATE_PLAYING.
+        mCallback.reset(1);
+        setPlaybackState(PlaybackStateCompat.STATE_PLAYING);
+        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
+        assertTrue(mCallback.await(TIME_OUT_MS));
+        assertTrue(mCallback.mOnPauseCalled);
+
+        // Double tap of PLAY_PAUSE is the next track.
+        mCallback.reset(2);
+        setPlaybackState(PlaybackStateCompat.STATE_PAUSED);
+        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
+        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
+        assertFalse(mCallback.await(waitTimeForNoResponse));
+        assertTrue(mCallback.mOnSkipToNextCalled);
+        assertEquals(0, mCallback.mOnPlayCalledCount);
+        assertFalse(mCallback.mOnPauseCalled);
+
+        // Test PLAY_PAUSE button long-press.
+        // It should be the same as the single short-press.
+        mCallback.reset(1);
+        setPlaybackState(PlaybackStateCompat.STATE_PAUSED);
+        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, true);
+        assertTrue(mCallback.await(TIME_OUT_MS));
+        assertEquals(1, mCallback.mOnPlayCalledCount);
+
+        // Double tap of PLAY_PAUSE should be handled once.
+        // Initial down event from the second press within double tap time-out will make
+        // onSkipToNext() to be called, so further down events shouldn't be handled again.
+        mCallback.reset(2);
+        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
+        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, true);
+        assertFalse(mCallback.await(waitTimeForNoResponse));
+        assertTrue(mCallback.mOnSkipToNextCalled);
+        assertEquals(0, mCallback.mOnPlayCalledCount);
+        assertFalse(mCallback.mOnPauseCalled);
+
+        // Test PLAY_PAUSE button long-press followed by the short-press.
+        // Initial long-press of the PLAY_PAUSE is considered as the single short-press already,
+        // so it shouldn't be used as the first tap of the double tap.
+        mCallback.reset(2);
+        setPlaybackState(PlaybackStateCompat.STATE_PAUSED);
+        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, true);
+        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
+        assertTrue(mCallback.await(TIME_OUT_MS));
+        // onMediaButtonEvent() calls either onPlay() or onPause() depending on the playback state,
+        // so onPlay() should be called once and onPause() also should be called once.
+        assertEquals(1, mCallback.mOnPlayCalledCount);
+        assertTrue(mCallback.mOnPauseCalled);
+        assertFalse(mCallback.mOnSkipToNextCalled);
+
+        // If another media key is pressed while the double tap of PLAY_PAUSE,
+        // PLAY_PAUSE should be handled as normal.
+        mCallback.reset(3);
+        setPlaybackState(PlaybackStateCompat.STATE_PAUSED);
+        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
+        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_STOP);
+        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
+        assertTrue(mCallback.await(TIME_OUT_MS));
+        assertFalse(mCallback.mOnSkipToNextCalled);
+        assertTrue(mCallback.mOnStopCalled);
+        assertEquals(2, mCallback.mOnPlayCalledCount);
+
+        // Test if media keys are handled in order.
+        mCallback.reset(2);
+        setPlaybackState(PlaybackStateCompat.STATE_PAUSED);
+        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
+        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_STOP);
+        assertTrue(mCallback.await(TIME_OUT_MS));
+        assertEquals(1, mCallback.mOnPlayCalledCount);
+        assertTrue(mCallback.mOnStopCalled);
+        synchronized (mWaitLock) {
+            assertEquals(PlaybackStateCompat.STATE_STOPPED,
+                    mSession.getController().getPlaybackState().getState());
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void testVolumeControl() throws Exception {
+        if (android.os.Build.VERSION.SDK_INT < 27) {
+            // This test causes an Exception on System UI in API < 27.
+            return;
+        }
+        VolumeProviderCompat vp =
+                new VolumeProviderCompat(VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE, 11, 5) {
+                    @Override
+                    public void onSetVolumeTo(int volume) {
+                        synchronized (mWaitLock) {
+                            setCurrentVolume(volume);
+                            mWaitLock.notify();
+                        }
+                    }
+
+                    @Override
+                    public void onAdjustVolume(int direction) {
+                        synchronized (mWaitLock) {
+                            switch (direction) {
+                                case AudioManager.ADJUST_LOWER:
+                                    setCurrentVolume(getCurrentVolume() - 1);
+                                    break;
+                                case AudioManager.ADJUST_RAISE:
+                                    setCurrentVolume(getCurrentVolume() + 1);
+                                    break;
+                            }
+                            mWaitLock.notify();
+                        }
+                    }
+                };
+        mSession.setPlaybackToRemote(vp);
+
+        synchronized (mWaitLock) {
+            // test setVolumeTo
+            callMediaControllerMethod(SET_VOLUME_TO,
+                    7 /* Target volume */, getContext(), mSession.getSessionToken());
+            mWaitLock.wait(TIME_OUT_MS);
+            assertEquals(7, vp.getCurrentVolume());
+
+            // test adjustVolume
+            callMediaControllerMethod(ADJUST_VOLUME,
+                    AudioManager.ADJUST_LOWER, getContext(), mSession.getSessionToken());
+            mWaitLock.wait(TIME_OUT_MS);
+            assertEquals(6, vp.getCurrentVolume());
+
+            callMediaControllerMethod(ADJUST_VOLUME,
+                    AudioManager.ADJUST_RAISE, getContext(), mSession.getSessionToken());
+            mWaitLock.wait(TIME_OUT_MS);
+            assertEquals(7, vp.getCurrentVolume());
+        }
+    }
+
+    private void setPlaybackState(int state) {
+        final long allActions = PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PAUSE
+                | PlaybackStateCompat.ACTION_PLAY_PAUSE | PlaybackStateCompat.ACTION_STOP
+                | PlaybackStateCompat.ACTION_SKIP_TO_NEXT
+                | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
+                | PlaybackStateCompat.ACTION_FAST_FORWARD | PlaybackStateCompat.ACTION_REWIND;
+        PlaybackStateCompat playbackState = new PlaybackStateCompat.Builder().setActions(allActions)
+                .setState(state, 0L, 0.0f).build();
+        synchronized (mWaitLock) {
+            mSession.setPlaybackState(playbackState);
+        }
+    }
+
+    private void sendMediaKeyInputToController(int keyCode) {
+        sendMediaKeyInputToController(keyCode, false);
+    }
+
+    private void sendMediaKeyInputToController(int keyCode, boolean isLongPress) {
+        MediaControllerCompat controller = mSession.getController();
+        long currentTimeMs = System.currentTimeMillis();
+        KeyEvent down = new KeyEvent(
+                currentTimeMs, currentTimeMs, KeyEvent.ACTION_DOWN, keyCode, 0);
+        controller.dispatchMediaButtonEvent(down);
+        if (isLongPress) {
+            KeyEvent longPress = new KeyEvent(
+                    currentTimeMs, System.currentTimeMillis(), KeyEvent.ACTION_DOWN, keyCode, 1);
+            controller.dispatchMediaButtonEvent(longPress);
+        }
+        KeyEvent up = new KeyEvent(
+                currentTimeMs, System.currentTimeMillis(), KeyEvent.ACTION_UP, keyCode, 0);
+        controller.dispatchMediaButtonEvent(up);
+    }
+
+    private class MediaSessionCallback extends MediaSessionCompat.Callback {
+        private CountDownLatch mLatch;
+        private long mSeekPosition;
+        private long mQueueItemId;
+        private RatingCompat mRating;
+        private String mMediaId;
+        private String mQuery;
+        private Uri mUri;
+        private String mAction;
+        private String mCommand;
+        private Bundle mExtras;
+        private ResultReceiver mCommandCallback;
+        private boolean mCaptioningEnabled;
+        private int mRepeatMode;
+        private int mShuffleMode;
+        private int mQueueIndex;
+        private MediaDescriptionCompat mQueueDescription;
+        private List<MediaSessionCompat.QueueItem> mQueue = new ArrayList<>();
+
+        private int mOnPlayCalledCount;
+        private boolean mOnPauseCalled;
+        private boolean mOnStopCalled;
+        private boolean mOnFastForwardCalled;
+        private boolean mOnRewindCalled;
+        private boolean mOnSkipToPreviousCalled;
+        private boolean mOnSkipToNextCalled;
+        private boolean mOnSeekToCalled;
+        private boolean mOnSkipToQueueItemCalled;
+        private boolean mOnSetRatingCalled;
+        private boolean mOnPlayFromMediaIdCalled;
+        private boolean mOnPlayFromSearchCalled;
+        private boolean mOnPlayFromUriCalled;
+        private boolean mOnCustomActionCalled;
+        private boolean mOnCommandCalled;
+        private boolean mOnPrepareCalled;
+        private boolean mOnPrepareFromMediaIdCalled;
+        private boolean mOnPrepareFromSearchCalled;
+        private boolean mOnPrepareFromUriCalled;
+        private boolean mOnSetCaptioningEnabledCalled;
+        private boolean mOnSetRepeatModeCalled;
+        private boolean mOnSetShuffleModeCalled;
+        private boolean mOnAddQueueItemCalled;
+        private boolean mOnAddQueueItemAtCalled;
+        private boolean mOnRemoveQueueItemCalled;
+
+        public void reset(int count) {
+            mLatch = new CountDownLatch(count);
+            mSeekPosition = -1;
+            mQueueItemId = -1;
+            mRating = null;
+            mMediaId = null;
+            mQuery = null;
+            mUri = null;
+            mAction = null;
+            mExtras = null;
+            mCommand = null;
+            mCommandCallback = null;
+            mCaptioningEnabled = false;
+            mRepeatMode = PlaybackStateCompat.REPEAT_MODE_NONE;
+            mShuffleMode = PlaybackStateCompat.SHUFFLE_MODE_NONE;
+            mQueueIndex = -1;
+            mQueueDescription = null;
+
+            mOnPlayCalledCount = 0;
+            mOnPauseCalled = false;
+            mOnStopCalled = false;
+            mOnFastForwardCalled = false;
+            mOnRewindCalled = false;
+            mOnSkipToPreviousCalled = false;
+            mOnSkipToNextCalled = false;
+            mOnSkipToQueueItemCalled = false;
+            mOnSeekToCalled = false;
+            mOnSetRatingCalled = false;
+            mOnPlayFromMediaIdCalled = false;
+            mOnPlayFromSearchCalled = false;
+            mOnPlayFromUriCalled = false;
+            mOnCustomActionCalled = false;
+            mOnCommandCalled = false;
+            mOnPrepareCalled = false;
+            mOnPrepareFromMediaIdCalled = false;
+            mOnPrepareFromSearchCalled = false;
+            mOnPrepareFromUriCalled = false;
+            mOnSetCaptioningEnabledCalled = false;
+            mOnSetRepeatModeCalled = false;
+            mOnSetShuffleModeCalled = false;
+            mOnAddQueueItemCalled = false;
+            mOnAddQueueItemAtCalled = false;
+            mOnRemoveQueueItemCalled = false;
+        }
+
+        public boolean await(long timeoutMs) {
+            try {
+                return mLatch.await(timeoutMs, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                return false;
+            }
+        }
+
+        @Override
+        public void onPlay() {
+            mOnPlayCalledCount++;
+            setPlaybackState(PlaybackStateCompat.STATE_PLAYING);
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onPause() {
+            mOnPauseCalled = true;
+            setPlaybackState(PlaybackStateCompat.STATE_PAUSED);
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onStop() {
+            mOnStopCalled = true;
+            setPlaybackState(PlaybackStateCompat.STATE_STOPPED);
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onFastForward() {
+            mOnFastForwardCalled = true;
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onRewind() {
+            mOnRewindCalled = true;
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onSkipToPrevious() {
+            mOnSkipToPreviousCalled = true;
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onSkipToNext() {
+            mOnSkipToNextCalled = true;
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onSeekTo(long pos) {
+            mOnSeekToCalled = true;
+            mSeekPosition = pos;
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onSetRating(RatingCompat rating) {
+            mOnSetRatingCalled = true;
+            mRating = rating;
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onPlayFromMediaId(String mediaId, Bundle extras) {
+            mOnPlayFromMediaIdCalled = true;
+            mMediaId = mediaId;
+            mExtras = extras;
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onPlayFromSearch(String query, Bundle extras) {
+            mOnPlayFromSearchCalled = true;
+            mQuery = query;
+            mExtras = extras;
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onPlayFromUri(Uri uri, Bundle extras) {
+            mOnPlayFromUriCalled = true;
+            mUri = uri;
+            mExtras = extras;
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onCustomAction(String action, Bundle extras) {
+            mOnCustomActionCalled = true;
+            mAction = action;
+            mExtras = extras;
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onSkipToQueueItem(long id) {
+            mOnSkipToQueueItemCalled = true;
+            mQueueItemId = id;
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onCommand(String command, Bundle extras, ResultReceiver cb) {
+            mOnCommandCalled = true;
+            mCommand = command;
+            mExtras = extras;
+            mCommandCallback = cb;
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onPrepare() {
+            mOnPrepareCalled = true;
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onPrepareFromMediaId(String mediaId, Bundle extras) {
+            mOnPrepareFromMediaIdCalled = true;
+            mMediaId = mediaId;
+            mExtras = extras;
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onPrepareFromSearch(String query, Bundle extras) {
+            mOnPrepareFromSearchCalled = true;
+            mQuery = query;
+            mExtras = extras;
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onPrepareFromUri(Uri uri, Bundle extras) {
+            mOnPrepareFromUriCalled = true;
+            mUri = uri;
+            mExtras = extras;
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onSetRepeatMode(int repeatMode) {
+            mOnSetRepeatModeCalled = true;
+            mRepeatMode = repeatMode;
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onAddQueueItem(MediaDescriptionCompat description) {
+            mOnAddQueueItemCalled = true;
+            mQueueDescription = description;
+            mQueue.add(new MediaSessionCompat.QueueItem(description, mQueue.size()));
+            mSession.setQueue(mQueue);
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onAddQueueItem(MediaDescriptionCompat description, int index) {
+            mOnAddQueueItemAtCalled = true;
+            mQueueIndex = index;
+            mQueueDescription = description;
+            mQueue.add(index, new MediaSessionCompat.QueueItem(description, mQueue.size()));
+            mSession.setQueue(mQueue);
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onRemoveQueueItem(MediaDescriptionCompat description) {
+            mOnRemoveQueueItemCalled = true;
+            String mediaId = description.getMediaId();
+            for (int i = mQueue.size() - 1; i >= 0; --i) {
+                if (mediaId.equals(mQueue.get(i).getDescription().getMediaId())) {
+                    mQueueDescription = mQueue.remove(i).getDescription();
+                    mSession.setQueue(mQueue);
+                    break;
+                }
+            }
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onSetCaptioningEnabled(boolean enabled) {
+            mOnSetCaptioningEnabledCalled = true;
+            mCaptioningEnabled = enabled;
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onSetShuffleMode(int shuffleMode) {
+            mOnSetShuffleModeCalled = true;
+            mShuffleMode = shuffleMode;
+            mLatch.countDown();
+        }
+    }
+}
diff --git a/media-compat/version-compat-tests/current/service/tests/src/android/support/mediacompat/service/ServiceBroadcastReceiver.java b/media-compat/version-compat-tests/current/service/tests/src/android/support/mediacompat/service/ServiceBroadcastReceiver.java
new file mode 100644
index 0000000..57364b7
--- /dev/null
+++ b/media-compat/version-compat-tests/current/service/tests/src/android/support/mediacompat/service/ServiceBroadcastReceiver.java
@@ -0,0 +1,163 @@
+/*
+ * 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.service;
+
+
+import static android.support.mediacompat.testlib.MediaBrowserConstants.CUSTOM_ACTION_SEND_ERROR;
+import static android.support.mediacompat.testlib.MediaBrowserConstants
+        .CUSTOM_ACTION_SEND_PROGRESS_UPDATE;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.CUSTOM_ACTION_SEND_RESULT;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.NOTIFY_CHILDREN_CHANGED;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.SEND_DELAYED_ITEM_LOADED;
+import static android.support.mediacompat.testlib.MediaBrowserConstants
+        .SEND_DELAYED_NOTIFY_CHILDREN_CHANGED;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.SET_SESSION_TOKEN;
+import static android.support.mediacompat.testlib.MediaSessionConstants.RELEASE;
+import static android.support.mediacompat.testlib.MediaSessionConstants.SEND_SESSION_EVENT;
+import static android.support.mediacompat.testlib.MediaSessionConstants.SET_ACTIVE;
+import static android.support.mediacompat.testlib.MediaSessionConstants.SET_CAPTIONING_ENABLED;
+import static android.support.mediacompat.testlib.MediaSessionConstants.SET_EXTRAS;
+import static android.support.mediacompat.testlib.MediaSessionConstants.SET_FLAGS;
+import static android.support.mediacompat.testlib.MediaSessionConstants.SET_METADATA;
+import static android.support.mediacompat.testlib.MediaSessionConstants.SET_PLAYBACK_STATE;
+import static android.support.mediacompat.testlib.MediaSessionConstants.SET_PLAYBACK_TO_LOCAL;
+import static android.support.mediacompat.testlib.MediaSessionConstants.SET_PLAYBACK_TO_REMOTE;
+import static android.support.mediacompat.testlib.MediaSessionConstants.SET_QUEUE;
+import static android.support.mediacompat.testlib.MediaSessionConstants.SET_QUEUE_TITLE;
+import static android.support.mediacompat.testlib.MediaSessionConstants.SET_RATING_TYPE;
+import static android.support.mediacompat.testlib.MediaSessionConstants.SET_REPEAT_MODE;
+import static android.support.mediacompat.testlib.MediaSessionConstants.SET_SESSION_ACTIVITY;
+import static android.support.mediacompat.testlib.MediaSessionConstants.SET_SHUFFLE_MODE;
+import static android.support.mediacompat.testlib.util.IntentUtil
+        .ACTION_CALL_MEDIA_BROWSER_SERVICE_METHOD;
+import static android.support.mediacompat.testlib.util.IntentUtil.ACTION_CALL_MEDIA_SESSION_METHOD;
+import static android.support.mediacompat.testlib.util.IntentUtil.KEY_ARGUMENT;
+import static android.support.mediacompat.testlib.util.IntentUtil.KEY_METHOD_ID;
+
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v4.media.MediaMetadataCompat;
+import android.support.v4.media.RatingCompat;
+import android.support.v4.media.VolumeProviderCompat;
+import android.support.v4.media.session.MediaSessionCompat;
+import android.support.v4.media.session.MediaSessionCompat.QueueItem;
+import android.support.v4.media.session.ParcelableVolumeInfo;
+import android.support.v4.media.session.PlaybackStateCompat;
+
+import java.util.List;
+
+public class ServiceBroadcastReceiver extends BroadcastReceiver {
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        Bundle extras = intent.getExtras();
+        if (ACTION_CALL_MEDIA_BROWSER_SERVICE_METHOD.equals(intent.getAction()) && extras != null) {
+            StubMediaBrowserServiceCompat service = StubMediaBrowserServiceCompat.sInstance;
+            int method = extras.getInt(KEY_METHOD_ID, 0);
+
+            switch (method) {
+                case NOTIFY_CHILDREN_CHANGED:
+                    service.notifyChildrenChanged(extras.getString(KEY_ARGUMENT));
+                    break;
+                case SEND_DELAYED_NOTIFY_CHILDREN_CHANGED:
+                    service.sendDelayedNotifyChildrenChanged();
+                    break;
+                case SEND_DELAYED_ITEM_LOADED:
+                    service.sendDelayedItemLoaded();
+                    break;
+                case CUSTOM_ACTION_SEND_PROGRESS_UPDATE:
+                    service.mCustomActionResult.sendProgressUpdate(extras.getBundle(KEY_ARGUMENT));
+                    break;
+                case CUSTOM_ACTION_SEND_ERROR:
+                    service.mCustomActionResult.sendError(extras.getBundle(KEY_ARGUMENT));
+                    break;
+                case CUSTOM_ACTION_SEND_RESULT:
+                    service.mCustomActionResult.sendResult(extras.getBundle(KEY_ARGUMENT));
+                    break;
+                case SET_SESSION_TOKEN:
+                    StubMediaBrowserServiceCompatWithDelayedMediaSession.sInstance
+                            .callSetSessionToken();
+                    break;
+            }
+        } else if (ACTION_CALL_MEDIA_SESSION_METHOD.equals(intent.getAction()) && extras != null) {
+            MediaSessionCompat session = StubMediaBrowserServiceCompat.sSession;
+            int method = extras.getInt(KEY_METHOD_ID, 0);
+
+            switch (method) {
+                case SET_EXTRAS:
+                    session.setExtras(extras.getBundle(KEY_ARGUMENT));
+                    break;
+                case SET_FLAGS:
+                    session.setFlags(extras.getInt(KEY_ARGUMENT));
+                    break;
+                case SET_METADATA:
+                    session.setMetadata((MediaMetadataCompat) extras.getParcelable(KEY_ARGUMENT));
+                    break;
+                case SET_PLAYBACK_STATE:
+                    session.setPlaybackState(
+                            (PlaybackStateCompat) extras.getParcelable(KEY_ARGUMENT));
+                    break;
+                case SET_QUEUE:
+                    List<QueueItem> items = extras.getParcelableArrayList(KEY_ARGUMENT);
+                    session.setQueue(items);
+                    break;
+                case SET_QUEUE_TITLE:
+                    session.setQueueTitle(extras.getCharSequence(KEY_ARGUMENT));
+                    break;
+                case SET_SESSION_ACTIVITY:
+                    session.setSessionActivity((PendingIntent) extras.getParcelable(KEY_ARGUMENT));
+                    break;
+                case SET_CAPTIONING_ENABLED:
+                    session.setCaptioningEnabled(extras.getBoolean(KEY_ARGUMENT));
+                    break;
+                case SET_REPEAT_MODE:
+                    session.setRepeatMode(extras.getInt(KEY_ARGUMENT));
+                    break;
+                case SET_SHUFFLE_MODE:
+                    session.setShuffleMode(extras.getInt(KEY_ARGUMENT));
+                    break;
+                case SEND_SESSION_EVENT:
+                    Bundle arguments = extras.getBundle(KEY_ARGUMENT);
+                    session.sendSessionEvent(
+                            arguments.getString("event"), arguments.getBundle("extras"));
+                    break;
+                case SET_ACTIVE:
+                    session.setActive(extras.getBoolean(KEY_ARGUMENT));
+                    break;
+                case RELEASE:
+                    session.release();
+                    break;
+                case SET_PLAYBACK_TO_LOCAL:
+                    session.setPlaybackToLocal(extras.getInt(KEY_ARGUMENT));
+                    break;
+                case SET_PLAYBACK_TO_REMOTE:
+                    ParcelableVolumeInfo volumeInfo = extras.getParcelable(KEY_ARGUMENT);
+                    session.setPlaybackToRemote(new VolumeProviderCompat(
+                            volumeInfo.controlType,
+                            volumeInfo.maxVolume,
+                            volumeInfo.currentVolume) {});
+                    break;
+                case SET_RATING_TYPE:
+                    session.setRatingType(RatingCompat.RATING_5_STARS);
+                    break;
+            }
+        }
+    }
+}
diff --git a/media-compat/version-compat-tests/current/service/tests/src/android/support/mediacompat/service/StubMediaBrowserServiceCompat.java b/media-compat/version-compat-tests/current/service/tests/src/android/support/mediacompat/service/StubMediaBrowserServiceCompat.java
new file mode 100644
index 0000000..7032a0b
--- /dev/null
+++ b/media-compat/version-compat-tests/current/service/tests/src/android/support/mediacompat/service/StubMediaBrowserServiceCompat.java
@@ -0,0 +1,197 @@
+/*
+ * 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.service;
+
+import static android.support.mediacompat.testlib.MediaBrowserConstants.CUSTOM_ACTION;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.CUSTOM_ACTION_FOR_ERROR;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.EXTRAS_KEY;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.EXTRAS_VALUE;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.MEDIA_ID_CHILDREN;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.MEDIA_ID_CHILDREN_DELAYED;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.MEDIA_ID_INCLUDE_METADATA;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.MEDIA_ID_INVALID;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.MEDIA_ID_ROOT;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.MEDIA_METADATA;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.SEARCH_QUERY;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.SEARCH_QUERY_FOR_ERROR;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.SEARCH_QUERY_FOR_NO_RESULT;
+
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v4.media.MediaBrowserCompat.MediaItem;
+import android.support.v4.media.MediaBrowserServiceCompat;
+import android.support.v4.media.MediaDescriptionCompat;
+import android.support.v4.media.MediaMetadataCompat;
+import android.support.v4.media.session.MediaSessionCompat;
+
+import junit.framework.Assert;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Stub implementation of {@link android.support.v4.media.MediaBrowserServiceCompat}.
+ */
+public class StubMediaBrowserServiceCompat extends MediaBrowserServiceCompat {
+
+    public static StubMediaBrowserServiceCompat sInstance;
+
+    public static MediaSessionCompat sSession;
+    private Bundle mExtras;
+    private Result<List<MediaItem>> mPendingLoadChildrenResult;
+    private Result<MediaItem> mPendingLoadItemResult;
+    private Bundle mPendingRootHints;
+
+    public Bundle mCustomActionExtras;
+    public Result<Bundle> mCustomActionResult;
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        sInstance = this;
+        sSession = new MediaSessionCompat(this, "StubMediaBrowserServiceCompat");
+        setSessionToken(sSession.getSessionToken());
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        sSession.release();
+        sSession = null;
+    }
+
+    @Override
+    public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) {
+        mExtras = new Bundle();
+        mExtras.putString(EXTRAS_KEY, EXTRAS_VALUE);
+        return new BrowserRoot(MEDIA_ID_ROOT, mExtras);
+    }
+
+    @Override
+    public void onLoadChildren(final String parentId, final Result<List<MediaItem>> result) {
+        List<MediaItem> mediaItems = new ArrayList<>();
+        if (MEDIA_ID_ROOT.equals(parentId)) {
+            Bundle rootHints = getBrowserRootHints();
+            for (String id : MEDIA_ID_CHILDREN) {
+                mediaItems.add(createMediaItem(id));
+            }
+            result.sendResult(mediaItems);
+        } else if (MEDIA_ID_CHILDREN_DELAYED.equals(parentId)) {
+            Assert.assertNull(mPendingLoadChildrenResult);
+            mPendingLoadChildrenResult = result;
+            mPendingRootHints = getBrowserRootHints();
+            result.detach();
+        } else if (MEDIA_ID_INVALID.equals(parentId)) {
+            result.sendResult(null);
+        }
+    }
+
+    @Override
+    public void onLoadChildren(@NonNull String parentId, @NonNull Result<List<MediaItem>> result,
+            @NonNull Bundle options) {
+        if (MEDIA_ID_INCLUDE_METADATA.equals(parentId)) {
+            // Test unparcelling the Bundle.
+            MediaMetadataCompat metadata = options.getParcelable(MEDIA_METADATA);
+            if (metadata == null) {
+                super.onLoadChildren(parentId, result, options);
+            } else {
+                List<MediaItem> mediaItems = new ArrayList<>();
+                mediaItems.add(new MediaItem(metadata.getDescription(), MediaItem.FLAG_PLAYABLE));
+                result.sendResult(mediaItems);
+            }
+        } else {
+            super.onLoadChildren(parentId, result, options);
+        }
+    }
+
+    @Override
+    public void onLoadItem(String itemId, Result<MediaItem> result) {
+        if (MEDIA_ID_CHILDREN_DELAYED.equals(itemId)) {
+            mPendingLoadItemResult = result;
+            mPendingRootHints = getBrowserRootHints();
+            result.detach();
+            return;
+        }
+
+        if (MEDIA_ID_INVALID.equals(itemId)) {
+            result.sendResult(null);
+            return;
+        }
+
+        for (String id : MEDIA_ID_CHILDREN) {
+            if (id.equals(itemId)) {
+                result.sendResult(createMediaItem(id));
+                return;
+            }
+        }
+
+        // Test the case where onLoadItem is not implemented.
+        super.onLoadItem(itemId, result);
+    }
+
+    @Override
+    public void onSearch(String query, Bundle extras, Result<List<MediaItem>> result) {
+        if (SEARCH_QUERY_FOR_NO_RESULT.equals(query)) {
+            result.sendResult(Collections.<MediaItem>emptyList());
+        } else if (SEARCH_QUERY_FOR_ERROR.equals(query)) {
+            result.sendResult(null);
+        } else if (SEARCH_QUERY.equals(query)) {
+            List<MediaItem> items = new ArrayList<>();
+            for (String id : MEDIA_ID_CHILDREN) {
+                if (id.contains(query)) {
+                    items.add(createMediaItem(id));
+                }
+            }
+            result.sendResult(items);
+        }
+    }
+
+    @Override
+    public void onCustomAction(String action, Bundle extras, Result<Bundle> result) {
+        mCustomActionResult = result;
+        mCustomActionExtras = extras;
+        if (CUSTOM_ACTION_FOR_ERROR.equals(action)) {
+            result.sendError(null);
+        } else if (CUSTOM_ACTION.equals(action)) {
+            result.detach();
+        }
+    }
+
+    public void sendDelayedNotifyChildrenChanged() {
+        if (mPendingLoadChildrenResult != null) {
+            mPendingLoadChildrenResult.sendResult(Collections.<MediaItem>emptyList());
+            mPendingRootHints = null;
+            mPendingLoadChildrenResult = null;
+        }
+    }
+
+    public void sendDelayedItemLoaded() {
+        if (mPendingLoadItemResult != null) {
+            mPendingLoadItemResult.sendResult(new MediaItem(new MediaDescriptionCompat.Builder()
+                    .setMediaId(MEDIA_ID_CHILDREN_DELAYED).setExtras(mPendingRootHints).build(),
+                    MediaItem.FLAG_BROWSABLE));
+            mPendingRootHints = null;
+            mPendingLoadItemResult = null;
+        }
+    }
+
+    private MediaItem createMediaItem(String id) {
+        return new MediaItem(new MediaDescriptionCompat.Builder().setMediaId(id).build(),
+                MediaItem.FLAG_BROWSABLE);
+    }
+}
diff --git a/media-compat/version-compat-tests/current/service/tests/src/android/support/mediacompat/service/StubMediaBrowserServiceCompatWithDelayedMediaSession.java b/media-compat/version-compat-tests/current/service/tests/src/android/support/mediacompat/service/StubMediaBrowserServiceCompatWithDelayedMediaSession.java
new file mode 100644
index 0000000..509e13f
--- /dev/null
+++ b/media-compat/version-compat-tests/current/service/tests/src/android/support/mediacompat/service/StubMediaBrowserServiceCompatWithDelayedMediaSession.java
@@ -0,0 +1,64 @@
+/*
+ * 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.service;
+
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.media.MediaBrowserCompat;
+import android.support.v4.media.MediaBrowserServiceCompat;
+import android.support.v4.media.session.MediaSessionCompat;
+
+import java.util.List;
+
+/**
+ * Stub implementation of {@link MediaBrowserServiceCompat}.
+ * This implementation does not call
+ * {@link MediaBrowserServiceCompat#setSessionToken(MediaSessionCompat.Token)} in its
+ * {@link android.app.Service#onCreate}.
+ */
+public class StubMediaBrowserServiceCompatWithDelayedMediaSession extends
+        MediaBrowserServiceCompat {
+
+    static StubMediaBrowserServiceCompatWithDelayedMediaSession sInstance;
+    private MediaSessionCompat mSession;
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        sInstance = this;
+        mSession = new MediaSessionCompat(
+                this, "StubMediaBrowserServiceCompatWithDelayedMediaSession");
+    }
+
+    @Nullable
+    @Override
+    public BrowserRoot onGetRoot(@NonNull String clientPackageName,
+            int clientUid, @Nullable Bundle rootHints) {
+        return new BrowserRoot("StubRootId", null);
+    }
+
+    @Override
+    public void onLoadChildren(@NonNull String parentId,
+            @NonNull Result<List<MediaBrowserCompat.MediaItem>> result) {
+        result.detach();
+    }
+
+    public void callSetSessionToken() {
+        setSessionToken(mSession.getSessionToken());
+    }
+}
diff --git a/media-compat/version-compat-tests/lib/AndroidManifest.xml b/media-compat/version-compat-tests/lib/AndroidManifest.xml
new file mode 100644
index 0000000..857e61c
--- /dev/null
+++ b/media-compat/version-compat-tests/lib/AndroidManifest.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+   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.
+-->
+<manifest package="android.support.mediacompat.testlib"/>
diff --git a/media-compat/version-compat-tests/lib/build.gradle b/media-compat/version-compat-tests/lib/build.gradle
new file mode 100644
index 0000000..a9be453
--- /dev/null
+++ b/media-compat/version-compat-tests/lib/build.gradle
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+plugins {
+    id("SupportAndroidLibraryPlugin")
+}
+
+android {
+    defaultConfig {
+        minSdkVersion 14
+    }
+}
+
+supportLibrary {
+    legacySourceLocation = true
+}
diff --git a/media-compat/version-compat-tests/lib/lint-baseline.xml b/media-compat/version-compat-tests/lib/lint-baseline.xml
new file mode 100644
index 0000000..4dd17af
--- /dev/null
+++ b/media-compat/version-compat-tests/lib/lint-baseline.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+   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.
+-->
+<issues format="4" by="lint 3.0.0">
+
+</issues>
diff --git a/media-compat/version-compat-tests/lib/src/main/java/android/support/mediacompat/testlib/MediaBrowserConstants.java b/media-compat/version-compat-tests/lib/src/main/java/android/support/mediacompat/testlib/MediaBrowserConstants.java
new file mode 100644
index 0000000..f961308
--- /dev/null
+++ b/media-compat/version-compat-tests/lib/src/main/java/android/support/mediacompat/testlib/MediaBrowserConstants.java
@@ -0,0 +1,66 @@
+/*
+ * 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 testing the media browser and service.
+ */
+public class MediaBrowserConstants {
+
+    // MediaBrowserServiceCompat methods.
+    public static final int NOTIFY_CHILDREN_CHANGED = 1;
+    public static final int SEND_DELAYED_NOTIFY_CHILDREN_CHANGED = 2;
+    public static final int SEND_DELAYED_ITEM_LOADED = 3;
+    public static final int CUSTOM_ACTION_SEND_PROGRESS_UPDATE = 4;
+    public static final int CUSTOM_ACTION_SEND_ERROR = 5;
+    public static final int CUSTOM_ACTION_SEND_RESULT = 6;
+    public static final int SET_SESSION_TOKEN = 7;
+
+    public static final String MEDIA_ID_ROOT = "test_media_id_root";
+    public static final String MEDIA_ID_INVALID = "test_media_id_invalid";
+    public static final String MEDIA_ID_CHILDREN_DELAYED = "test_media_id_children_delayed";
+    public static final String MEDIA_ID_ON_LOAD_ITEM_NOT_IMPLEMENTED =
+            "test_media_id_on_load_item_not_implemented";
+    public static final String MEDIA_ID_INCLUDE_METADATA = "test_media_id_include_metadata";
+
+    public static final String EXTRAS_KEY = "test_extras_key";
+    public static final String EXTRAS_VALUE = "test_extras_value";
+
+    public static final String MEDIA_METADATA = "test_media_metadata";
+
+    public static final String SEARCH_QUERY = "children_2";
+    public static final String SEARCH_QUERY_FOR_NO_RESULT = "query no result";
+    public static final String SEARCH_QUERY_FOR_ERROR = "query for error";
+
+    public static final String CUSTOM_ACTION = "CUSTOM_ACTION";
+    public static final String CUSTOM_ACTION_FOR_ERROR = "CUSTOM_ACTION_FOR_ERROR";
+
+    public static final String TEST_KEY_1 = "key_1";
+    public static final String TEST_VALUE_1 = "value_1";
+    public static final String TEST_KEY_2 = "key_2";
+    public static final String TEST_VALUE_2 = "value_2";
+    public static final String TEST_KEY_3 = "key_3";
+    public static final String TEST_VALUE_3 = "value_3";
+    public static final String TEST_KEY_4 = "key_4";
+    public static final String TEST_VALUE_4 = "value_4";
+
+    public static final String[] MEDIA_ID_CHILDREN = new String[]{
+            "test_media_id_children_0", "test_media_id_children_1",
+            "test_media_id_children_2", "test_media_id_children_3",
+            MEDIA_ID_CHILDREN_DELAYED
+    };
+}
diff --git a/media-compat/version-compat-tests/lib/src/main/java/android/support/mediacompat/testlib/MediaControllerConstants.java b/media-compat/version-compat-tests/lib/src/main/java/android/support/mediacompat/testlib/MediaControllerConstants.java
new file mode 100644
index 0000000..4978888
--- /dev/null
+++ b/media-compat/version-compat-tests/lib/src/main/java/android/support/mediacompat/testlib/MediaControllerConstants.java
@@ -0,0 +1,55 @@
+/*
+ * 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 testing the media controller.
+ */
+public class MediaControllerConstants {
+
+    // MediaControllerCompat methods.
+    public static final int SEND_COMMAND = 201;
+    public static final int ADD_QUEUE_ITEM = 202;
+    public static final int ADD_QUEUE_ITEM_WITH_INDEX = 203;
+    public static final int REMOVE_QUEUE_ITEM = 204;
+    public static final int SET_VOLUME_TO = 205;
+    public static final int ADJUST_VOLUME = 206;
+
+    // TransportControls methods.
+    public static final int PLAY = 301;
+    public static final int PAUSE = 302;
+    public static final int STOP = 303;
+    public static final int FAST_FORWARD = 304;
+    public static final int REWIND = 305;
+    public static final int SKIP_TO_PREVIOUS = 306;
+    public static final int SKIP_TO_NEXT = 307;
+    public static final int SEEK_TO = 308;
+    public static final int SET_RATING = 309;
+    public static final int PLAY_FROM_MEDIA_ID = 310;
+    public static final int PLAY_FROM_SEARCH = 311;
+    public static final int PLAY_FROM_URI = 312;
+    public static final int SEND_CUSTOM_ACTION = 313;
+    public static final int SEND_CUSTOM_ACTION_PARCELABLE = 314;
+    public static final int SKIP_TO_QUEUE_ITEM = 315;
+    public static final int PREPARE = 316;
+    public static final int PREPARE_FROM_MEDIA_ID = 317;
+    public static final int PREPARE_FROM_SEARCH = 318;
+    public static final int PREPARE_FROM_URI = 319;
+    public static final int SET_CAPTIONING_ENABLED = 320;
+    public static final int SET_REPEAT_MODE = 321;
+    public static final int SET_SHUFFLE_MODE = 322;
+}
diff --git a/media-compat/version-compat-tests/lib/src/main/java/android/support/mediacompat/testlib/MediaSessionConstants.java b/media-compat/version-compat-tests/lib/src/main/java/android/support/mediacompat/testlib/MediaSessionConstants.java
new file mode 100644
index 0000000..c0a64d4
--- /dev/null
+++ b/media-compat/version-compat-tests/lib/src/main/java/android/support/mediacompat/testlib/MediaSessionConstants.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.mediacompat.testlib;
+
+/**
+ * Constants for testing the media session.
+ */
+public class MediaSessionConstants {
+
+    // MediaSessionCompat methods.
+    public static final int SET_EXTRAS = 101;
+    public static final int SET_FLAGS = 102;
+    public static final int SET_METADATA = 103;
+    public static final int SET_PLAYBACK_STATE = 104;
+    public static final int SET_QUEUE = 105;
+    public static final int SET_QUEUE_TITLE = 106;
+    public static final int SET_SESSION_ACTIVITY = 107;
+    public static final int SET_CAPTIONING_ENABLED = 108;
+    public static final int SET_REPEAT_MODE = 109;
+    public static final int SET_SHUFFLE_MODE = 110;
+    public static final int SEND_SESSION_EVENT = 112;
+    public static final int SET_ACTIVE = 113;
+    public static final int RELEASE = 114;
+    public static final int SET_PLAYBACK_TO_LOCAL = 115;
+    public static final int SET_PLAYBACK_TO_REMOTE = 116;
+    public static final int SET_RATING_TYPE = 117;
+
+    public static final String TEST_SESSION_TAG = "test-session-tag";
+    public static final String TEST_KEY = "test-key";
+    public static final String TEST_VALUE = "test-val";
+    public static final String TEST_SESSION_EVENT = "test-session-event";
+    public static final String TEST_COMMAND = "test-command";
+    public static final int TEST_FLAGS = 5;
+    public static final int TEST_CURRENT_VOLUME = 10;
+    public static final int TEST_MAX_VOLUME = 11;
+    public static final long TEST_QUEUE_ID_1 = 10L;
+    public static final long TEST_QUEUE_ID_2 = 20L;
+    public static final String TEST_MEDIA_ID_1 = "media_id_1";
+    public static final String TEST_MEDIA_ID_2 = "media_id_2";
+    public static final String TEST_MEDIA_TITLE_1 = "media_title_1";
+    public static final String TEST_MEDIA_TITLE_2 = "media_title_2";
+    public static final long TEST_ACTION = 55L;
+
+    public static final int TEST_ERROR_CODE = 0x3;
+    public static final String TEST_ERROR_MSG = "test-error-msg";
+}
diff --git a/media-compat/version-compat-tests/lib/src/main/java/android/support/mediacompat/testlib/VersionConstants.java b/media-compat/version-compat-tests/lib/src/main/java/android/support/mediacompat/testlib/VersionConstants.java
new file mode 100644
index 0000000..4b217b1
--- /dev/null
+++ b/media-compat/version-compat-tests/lib/src/main/java/android/support/mediacompat/testlib/VersionConstants.java
@@ -0,0 +1,28 @@
+/*
+ * 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";
+
+    public static final String VERSION_TOT = "tot";
+    public static final String VERSION_PREVIOUS = "previous";
+}
diff --git a/media-compat/version-compat-tests/lib/src/main/java/android/support/mediacompat/testlib/util/IntentUtil.java b/media-compat/version-compat-tests/lib/src/main/java/android/support/mediacompat/testlib/util/IntentUtil.java
new file mode 100644
index 0000000..8d58a6f
--- /dev/null
+++ b/media-compat/version-compat-tests/lib/src/main/java/android/support/mediacompat/testlib/util/IntentUtil.java
@@ -0,0 +1,132 @@
+/*
+ * 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.util;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+
+/**
+ * Methods and constants used for sending intent between client and service apps.
+ */
+public class IntentUtil {
+
+    public static final String SERVICE_PACKAGE_NAME = "android.support.mediacompat.service.test";
+    public static final String CLIENT_PACKAGE_NAME = "android.support.mediacompat.client.test";
+
+    public static final ComponentName SERVICE_RECEIVER_COMPONENT_NAME = new ComponentName(
+            SERVICE_PACKAGE_NAME, "android.support.mediacompat.service.ServiceBroadcastReceiver");
+    public static final ComponentName CLIENT_RECEIVER_COMPONENT_NAME = new ComponentName(
+            CLIENT_PACKAGE_NAME, "android.support.mediacompat.client.ClientBroadcastReceiver");
+
+    public static final String ACTION_CALL_MEDIA_BROWSER_SERVICE_METHOD =
+            "android.support.mediacompat.service.action.CALL_MEDIA_BROWSER_SERVICE_METHOD";
+    public static final String ACTION_CALL_MEDIA_SESSION_METHOD =
+            "android.support.mediacompat.service.action.CALL_MEDIA_SESSION_METHOD";
+    public static final String ACTION_CALL_MEDIA_CONTROLLER_METHOD =
+            "android.support.mediacompat.client.action.CALL_MEDIA_CONTROLLER_METHOD";
+    public static final String ACTION_CALL_TRANSPORT_CONTROLS_METHOD =
+            "android.support.mediacompat.client.action.CALL_TRANSPORT_CONTROLS_METHOD";
+
+    public static final String KEY_METHOD_ID = "method_id";
+    public static final String KEY_ARGUMENT = "argument";
+    public static final String KEY_SESSION_TOKEN = "session_token";
+
+    /**
+     * Calls a method of MediaBrowserService. Used by client app.
+     */
+    public static void callMediaBrowserServiceMethod(int methodId, Object arg, Context context) {
+        Intent intent = createIntent(SERVICE_RECEIVER_COMPONENT_NAME, methodId, arg);
+        intent.setAction(ACTION_CALL_MEDIA_BROWSER_SERVICE_METHOD);
+        if (Build.VERSION.SDK_INT >= 16) {
+            intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        }
+        context.sendBroadcast(intent);
+    }
+
+    /**
+     * Calls a method of MediaSession. Used by client app.
+     */
+    public static void callMediaSessionMethod(int methodId, Object arg, Context context) {
+        Intent intent = createIntent(SERVICE_RECEIVER_COMPONENT_NAME, methodId, arg);
+        intent.setAction(ACTION_CALL_MEDIA_SESSION_METHOD);
+        if (Build.VERSION.SDK_INT >= 16) {
+            intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        }
+        context.sendBroadcast(intent);
+    }
+
+    /**
+     * Calls a method of MediaController. Used by service app.
+     */
+    public static void callMediaControllerMethod(
+            int methodId, Object arg, Context context, Parcelable token) {
+        Intent intent = createIntent(CLIENT_RECEIVER_COMPONENT_NAME, methodId, arg);
+        intent.setAction(ACTION_CALL_MEDIA_CONTROLLER_METHOD);
+        intent.putExtra(KEY_SESSION_TOKEN, token);
+        if (Build.VERSION.SDK_INT >= 16) {
+            intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        }
+        context.sendBroadcast(intent);
+    }
+
+    /**
+     * Calls a method of TransportControls. Used by service app.
+     */
+    public static void callTransportControlsMethod(
+            int methodId, Object arg, Context context, Parcelable token) {
+        Intent intent = createIntent(CLIENT_RECEIVER_COMPONENT_NAME, methodId, arg);
+        intent.setAction(ACTION_CALL_TRANSPORT_CONTROLS_METHOD);
+        intent.putExtra(KEY_SESSION_TOKEN, token);
+        if (Build.VERSION.SDK_INT >= 16) {
+            intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        }
+        context.sendBroadcast(intent);
+    }
+
+    private static Intent createIntent(ComponentName componentName, int methodId, Object arg) {
+        Intent intent = new Intent();
+        intent.setComponent(componentName);
+        intent.putExtra(KEY_METHOD_ID, methodId);
+
+        if (arg instanceof String) {
+            intent.putExtra(KEY_ARGUMENT, (String) arg);
+        } else if (arg instanceof Integer) {
+            intent.putExtra(KEY_ARGUMENT, (int) arg);
+        } else if (arg instanceof Long) {
+            intent.putExtra(KEY_ARGUMENT, (long) arg);
+        } else if (arg instanceof Boolean) {
+            intent.putExtra(KEY_ARGUMENT, (boolean) arg);
+        } else if (arg instanceof Parcelable) {
+            intent.putExtra(KEY_ARGUMENT, (Parcelable) arg);
+        } else if (arg instanceof ArrayList<?>) {
+            Bundle bundle = new Bundle();
+            bundle.putParcelableArrayList(KEY_ARGUMENT, (ArrayList<? extends Parcelable>) arg);
+            intent.putExtras(bundle);
+        } else if (arg instanceof Bundle) {
+            Bundle bundle = new Bundle();
+            bundle.putBundle(KEY_ARGUMENT, (Bundle) arg);
+            intent.putExtras(bundle);
+        }
+        return intent;
+    }
+}
diff --git a/media-compat/version-compat-tests/lib/src/main/java/android/support/mediacompat/testlib/util/PollingCheck.java b/media-compat/version-compat-tests/lib/src/main/java/android/support/mediacompat/testlib/util/PollingCheck.java
new file mode 100644
index 0000000..3412da0
--- /dev/null
+++ b/media-compat/version-compat-tests/lib/src/main/java/android/support/mediacompat/testlib/util/PollingCheck.java
@@ -0,0 +1,98 @@
+/*
+ * 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.util;
+
+import junit.framework.Assert;
+
+/**
+ * Utility used for testing that allows to poll for a certain condition to happen within a timeout.
+ * (Copied from testutils/src/main/java/android/support/testutils/PollingCheck.java.)
+ */
+public abstract class PollingCheck {
+    private static final long DEFAULT_TIMEOUT = 3000;
+    private static final long TIME_SLICE = 50;
+    private final long mTimeout;
+
+    /**
+     * The condition that the PollingCheck should use to proceed successfully.
+     */
+    public interface PollingCheckCondition {
+        /**
+         * @return Whether the polling condition has been met.
+         */
+        boolean canProceed();
+    }
+
+    public PollingCheck(long timeout) {
+        mTimeout = timeout;
+    }
+
+    protected abstract boolean check();
+
+    /**
+     * Start running the polling check.
+     */
+    public void run() {
+        if (check()) {
+            return;
+        }
+
+        long timeout = mTimeout;
+        while (timeout > 0) {
+            try {
+                Thread.sleep(TIME_SLICE);
+            } catch (InterruptedException e) {
+                Assert.fail("unexpected InterruptedException");
+            }
+
+            if (check()) {
+                return;
+            }
+
+            timeout -= TIME_SLICE;
+        }
+
+        Assert.fail("unexpected timeout");
+    }
+
+    /**
+     * Instantiate and start polling for a given condition with a default 3000ms timeout.
+     * @param condition The condition to check for success.
+     */
+    public static void waitFor(final PollingCheckCondition condition) {
+        new PollingCheck(DEFAULT_TIMEOUT) {
+            @Override
+            protected boolean check() {
+                return condition.canProceed();
+            }
+        }.run();
+    }
+
+    /**
+     * Instantiate and start polling for a given condition.
+     * @param timeout Time out in ms
+     * @param condition The condition to check for success.
+     */
+    public static void waitFor(long timeout, final PollingCheckCondition condition) {
+        new PollingCheck(timeout) {
+            @Override
+            protected boolean check() {
+                return condition.canProceed();
+            }
+        }.run();
+    }
+}
diff --git a/media-compat/version-compat-tests/lib/src/main/java/android/support/mediacompat/testlib/util/TestUtil.java b/media-compat/version-compat-tests/lib/src/main/java/android/support/mediacompat/testlib/util/TestUtil.java
new file mode 100644
index 0000000..d105510
--- /dev/null
+++ b/media-compat/version-compat-tests/lib/src/main/java/android/support/mediacompat/testlib/util/TestUtil.java
@@ -0,0 +1,41 @@
+/*
+ * 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.util;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertSame;
+
+import android.os.Bundle;
+
+/**
+ * Utility methods used for testing.
+ */
+public final class TestUtil {
+
+    /**
+     * Asserts that two Bundles are equal.
+     */
+    public static void assertBundleEquals(Bundle expected, Bundle observed) {
+        if (expected == null || observed == null) {
+            assertSame(expected, observed);
+        }
+        assertEquals(expected.size(), observed.size());
+        for (String key : expected.keySet()) {
+            assertEquals(expected.get(key), observed.get(key));
+        }
+    }
+}
diff --git a/media-compat/version-compat-tests/previous/client/AndroidManifest.xml b/media-compat/version-compat-tests/previous/client/AndroidManifest.xml
new file mode 100644
index 0000000..9724d2b
--- /dev/null
+++ b/media-compat/version-compat-tests/previous/client/AndroidManifest.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+   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.
+-->
+<manifest package="android.support.mediacompat.client"/>
diff --git a/media-compat/version-compat-tests/previous/client/build.gradle b/media-compat/version-compat-tests/previous/client/build.gradle
new file mode 100644
index 0000000..2788a1a
--- /dev/null
+++ b/media-compat/version-compat-tests/previous/client/build.gradle
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+
+import static android.support.dependencies.DependenciesKt.*
+
+plugins {
+    id("SupportAndroidLibraryPlugin")
+}
+
+dependencies {
+    androidTestImplementation project(':support-media-compat-test-lib')
+    androidTestImplementation "com.android.support:support-media-compat:27.0.1"
+
+    androidTestImplementation(TEST_RUNNER)
+}
+
+android {
+    defaultConfig {
+        minSdkVersion 14
+    }
+}
+
+supportLibrary {
+    legacySourceLocation = true
+}
\ No newline at end of file
diff --git a/media-compat/version-compat-tests/previous/client/lint-baseline.xml b/media-compat/version-compat-tests/previous/client/lint-baseline.xml
new file mode 100644
index 0000000..ed7ade1
--- /dev/null
+++ b/media-compat/version-compat-tests/previous/client/lint-baseline.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+   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.
+-->
+<issues format="4" by="lint 3.0.0-alpha9">
+
+</issues>
diff --git a/media-compat/version-compat-tests/previous/client/tests/AndroidManifest.xml b/media-compat/version-compat-tests/previous/client/tests/AndroidManifest.xml
new file mode 100644
index 0000000..afe1865
--- /dev/null
+++ b/media-compat/version-compat-tests/previous/client/tests/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+   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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.support.mediacompat.client.test">
+    <application android:supportsRtl="true">
+        <receiver android:name="android.support.mediacompat.client.ClientBroadcastReceiver">
+            <intent-filter>
+                <action android:name="android.support.mediacompat.service.action.CALL_MEDIA_CONTROLLER_METHOD"/>
+                <action android:name="android.support.mediacompat.service.action.CALL_TRANSPORT_CONTROLS_METHOD"/>
+            </intent-filter>
+        </receiver>
+    </application>
+</manifest>
diff --git a/media-compat/version-compat-tests/previous/client/tests/NO_DOCS b/media-compat/version-compat-tests/previous/client/tests/NO_DOCS
new file mode 100644
index 0000000..61c9b1a
--- /dev/null
+++ b/media-compat/version-compat-tests/previous/client/tests/NO_DOCS
@@ -0,0 +1,17 @@
+# 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.
+
+Having this file, named NO_DOCS, in a directory will prevent
+Android javadocs from being generated for java files under
+the directory. This is especially useful for test projects.
diff --git a/media-compat/version-compat-tests/previous/client/tests/src/android/support/mediacompat/client/AudioAttributesCompatTest.java b/media-compat/version-compat-tests/previous/client/tests/src/android/support/mediacompat/client/AudioAttributesCompatTest.java
new file mode 100644
index 0000000..675c0fc
--- /dev/null
+++ b/media-compat/version-compat-tests/previous/client/tests/src/android/support/mediacompat/client/AudioAttributesCompatTest.java
@@ -0,0 +1,170 @@
+/*
+ * 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.client;
+
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.hamcrest.core.IsNot.not;
+import static org.junit.Assert.assertThat;
+
+import android.media.AudioAttributes;
+import android.media.AudioManager;
+import android.os.Build;
+import android.support.test.filters.SdkSuppress;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v4.media.AudioAttributesCompat;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Test {@link AudioAttributesCompat}. */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class AudioAttributesCompatTest {
+    // some macros for conciseness
+    static final AudioAttributesCompat.Builder mkBuilder(
+            @AudioAttributesCompat.AttributeContentType int type,
+            @AudioAttributesCompat.AttributeUsage int usage) {
+        return new AudioAttributesCompat.Builder().setContentType(type).setUsage(usage);
+    }
+
+    static final AudioAttributesCompat.Builder mkBuilder(int legacyStream) {
+        return new AudioAttributesCompat.Builder().setLegacyStreamType(legacyStream);
+    }
+
+    // some objects we'll toss around
+    Object mMediaAA;
+    AudioAttributesCompat mMediaAAC,
+            mMediaLegacyAAC,
+            mMediaAACFromAA,
+            mNotificationAAC,
+            mNotificationLegacyAAC;
+
+    @Before
+    @SdkSuppress(minSdkVersion = 21)
+    public void setUpApi21() {
+        if (Build.VERSION.SDK_INT < 21) return;
+        mMediaAA =
+                new AudioAttributes.Builder()
+                        .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
+                        .setUsage(AudioAttributes.USAGE_MEDIA)
+                        .build();
+        mMediaAACFromAA = AudioAttributesCompat.wrap((AudioAttributes) mMediaAA);
+    }
+
+    @Before
+    public void setUp() {
+        mMediaAAC =
+                mkBuilder(AudioAttributesCompat.CONTENT_TYPE_MUSIC,
+                        AudioAttributesCompat.USAGE_MEDIA).build();
+        mMediaLegacyAAC = mkBuilder(AudioManager.STREAM_MUSIC).build();
+        mNotificationAAC =
+                mkBuilder(AudioAttributesCompat.CONTENT_TYPE_SONIFICATION,
+                        AudioAttributesCompat.USAGE_NOTIFICATION)
+                        .build();
+        mNotificationLegacyAAC = mkBuilder(AudioManager.STREAM_NOTIFICATION).build();
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 21)
+    public void testCreateWithAudioAttributesApi21() {
+        assertThat(mMediaAACFromAA, not(equalTo(null)));
+        assertThat((AudioAttributes) mMediaAACFromAA.unwrap(), equalTo(mMediaAA));
+        assertThat(
+                (AudioAttributes) mMediaAACFromAA.unwrap(),
+                equalTo(new AudioAttributes.Builder((AudioAttributes) mMediaAA).build()));
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 21)
+    public void testEqualityApi21() {
+        assertThat("self equality", mMediaAACFromAA, equalTo(mMediaAACFromAA));
+        assertThat("different things", mMediaAACFromAA, not(equalTo(mNotificationAAC)));
+    }
+
+    @Test
+    public void testEquality() {
+        assertThat("self equality", mMediaAAC, equalTo(mMediaAAC));
+        assertThat(
+                "equal to clone",
+                mMediaAAC,
+                equalTo(new AudioAttributesCompat.Builder(mMediaAAC).build()));
+        assertThat("different things are different", mMediaAAC, not(equalTo(mNotificationAAC)));
+        assertThat("different things are different 2", mNotificationAAC, not(equalTo(mMediaAAC)));
+        assertThat(
+                "equal to clone 2",
+                mNotificationAAC,
+                equalTo(new AudioAttributesCompat.Builder(mNotificationAAC).build()));
+    }
+
+    @Test
+    public void testGetters() {
+        assertThat(mMediaAAC.getContentType(), equalTo(AudioAttributesCompat.CONTENT_TYPE_MUSIC));
+        assertThat(mMediaAAC.getUsage(), equalTo(AudioAttributesCompat.USAGE_MEDIA));
+        assertThat(mMediaAAC.getFlags(), equalTo(0));
+    }
+
+    @Test
+    public void testLegacyStreamTypeInference() {
+        assertThat(mMediaAAC.getLegacyStreamType(), equalTo(AudioManager.STREAM_MUSIC));
+        assertThat(mMediaLegacyAAC.getLegacyStreamType(), equalTo(AudioManager.STREAM_MUSIC));
+        assertThat(
+                mNotificationAAC.getLegacyStreamType(), equalTo(AudioManager.STREAM_NOTIFICATION));
+        assertThat(
+                mNotificationLegacyAAC.getLegacyStreamType(),
+                equalTo(AudioManager.STREAM_NOTIFICATION));
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 21)
+    public void testLegacyStreamTypeInferenceApi21() {
+        assertThat(mMediaAACFromAA.getLegacyStreamType(), equalTo(AudioManager.STREAM_MUSIC));
+    }
+
+    @Test
+    public void testLegacyStreamTypeInferenceInLegacyMode() {
+        // the builders behave differently based on the value of this only-for-testing global
+        // so we need our very own objects inside this method
+        AudioAttributesCompat.setForceLegacyBehavior(true);
+
+        AudioAttributesCompat mediaAAC =
+                mkBuilder(AudioAttributesCompat.CONTENT_TYPE_MUSIC,
+                        AudioAttributesCompat.USAGE_MEDIA).build();
+        AudioAttributesCompat mediaLegacyAAC = mkBuilder(AudioManager.STREAM_MUSIC).build();
+
+        AudioAttributesCompat notificationAAC =
+                mkBuilder(AudioAttributesCompat.CONTENT_TYPE_SONIFICATION,
+                        AudioAttributesCompat.USAGE_NOTIFICATION)
+                        .build();
+        AudioAttributesCompat notificationLegacyAAC =
+                mkBuilder(AudioManager.STREAM_NOTIFICATION).build();
+
+        assertThat(mediaAAC.getLegacyStreamType(), equalTo(AudioManager.STREAM_MUSIC));
+        assertThat(mediaLegacyAAC.getLegacyStreamType(), equalTo(AudioManager.STREAM_MUSIC));
+        assertThat(
+                notificationAAC.getLegacyStreamType(), equalTo(AudioManager.STREAM_NOTIFICATION));
+        assertThat(
+                notificationLegacyAAC.getLegacyStreamType(),
+                equalTo(AudioManager.STREAM_NOTIFICATION));
+    }
+
+    @After
+    public void cleanUp() {
+        AudioAttributesCompat.setForceLegacyBehavior(false);
+    }
+}
diff --git a/media-compat/version-compat-tests/previous/client/tests/src/android/support/mediacompat/client/ClientBroadcastReceiver.java b/media-compat/version-compat-tests/previous/client/tests/src/android/support/mediacompat/client/ClientBroadcastReceiver.java
new file mode 100644
index 0000000..3227482
--- /dev/null
+++ b/media-compat/version-compat-tests/previous/client/tests/src/android/support/mediacompat/client/ClientBroadcastReceiver.java
@@ -0,0 +1,216 @@
+/*
+ * 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.client;
+
+import static android.support.mediacompat.testlib.MediaControllerConstants.ADD_QUEUE_ITEM;
+import static android.support.mediacompat.testlib.MediaControllerConstants
+        .ADD_QUEUE_ITEM_WITH_INDEX;
+import static android.support.mediacompat.testlib.MediaControllerConstants.ADJUST_VOLUME;
+import static android.support.mediacompat.testlib.MediaControllerConstants.FAST_FORWARD;
+import static android.support.mediacompat.testlib.MediaControllerConstants.PAUSE;
+import static android.support.mediacompat.testlib.MediaControllerConstants.PLAY;
+import static android.support.mediacompat.testlib.MediaControllerConstants.PLAY_FROM_MEDIA_ID;
+import static android.support.mediacompat.testlib.MediaControllerConstants.PLAY_FROM_SEARCH;
+import static android.support.mediacompat.testlib.MediaControllerConstants.PLAY_FROM_URI;
+import static android.support.mediacompat.testlib.MediaControllerConstants.PREPARE;
+import static android.support.mediacompat.testlib.MediaControllerConstants.PREPARE_FROM_MEDIA_ID;
+import static android.support.mediacompat.testlib.MediaControllerConstants.PREPARE_FROM_SEARCH;
+import static android.support.mediacompat.testlib.MediaControllerConstants.PREPARE_FROM_URI;
+import static android.support.mediacompat.testlib.MediaControllerConstants.REMOVE_QUEUE_ITEM;
+import static android.support.mediacompat.testlib.MediaControllerConstants.REWIND;
+import static android.support.mediacompat.testlib.MediaControllerConstants.SEEK_TO;
+import static android.support.mediacompat.testlib.MediaControllerConstants.SEND_COMMAND;
+import static android.support.mediacompat.testlib.MediaControllerConstants.SEND_CUSTOM_ACTION;
+import static android.support.mediacompat.testlib.MediaControllerConstants
+        .SEND_CUSTOM_ACTION_PARCELABLE;
+import static android.support.mediacompat.testlib.MediaControllerConstants.SET_CAPTIONING_ENABLED;
+import static android.support.mediacompat.testlib.MediaControllerConstants.SET_RATING;
+import static android.support.mediacompat.testlib.MediaControllerConstants.SET_REPEAT_MODE;
+import static android.support.mediacompat.testlib.MediaControllerConstants.SET_SHUFFLE_MODE;
+import static android.support.mediacompat.testlib.MediaControllerConstants.SET_VOLUME_TO;
+import static android.support.mediacompat.testlib.MediaControllerConstants.SKIP_TO_NEXT;
+import static android.support.mediacompat.testlib.MediaControllerConstants.SKIP_TO_PREVIOUS;
+import static android.support.mediacompat.testlib.MediaControllerConstants.SKIP_TO_QUEUE_ITEM;
+import static android.support.mediacompat.testlib.MediaControllerConstants.STOP;
+import static android.support.mediacompat.testlib.util.IntentUtil
+        .ACTION_CALL_MEDIA_CONTROLLER_METHOD;
+import static android.support.mediacompat.testlib.util.IntentUtil
+        .ACTION_CALL_TRANSPORT_CONTROLS_METHOD;
+import static android.support.mediacompat.testlib.util.IntentUtil.KEY_ARGUMENT;
+import static android.support.mediacompat.testlib.util.IntentUtil.KEY_METHOD_ID;
+import static android.support.mediacompat.testlib.util.IntentUtil.KEY_SESSION_TOKEN;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.support.v4.media.MediaDescriptionCompat;
+import android.support.v4.media.RatingCompat;
+import android.support.v4.media.session.MediaControllerCompat;
+import android.support.v4.media.session.MediaControllerCompat.TransportControls;
+import android.support.v4.media.session.MediaSessionCompat;
+import android.support.v4.media.session.PlaybackStateCompat;
+
+public class ClientBroadcastReceiver extends BroadcastReceiver {
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        Bundle extras = intent.getExtras();
+        MediaControllerCompat controller;
+        try {
+            controller = new MediaControllerCompat(context,
+                    (MediaSessionCompat.Token) extras.getParcelable(KEY_SESSION_TOKEN));
+        } catch (RemoteException ex) {
+            // Do nothing.
+            return;
+        }
+        int method = extras.getInt(KEY_METHOD_ID, 0);
+
+        if (ACTION_CALL_MEDIA_CONTROLLER_METHOD.equals(intent.getAction()) && extras != null) {
+            Bundle arguments;
+            switch (method) {
+                case SEND_COMMAND:
+                    arguments = extras.getBundle(KEY_ARGUMENT);
+                    controller.sendCommand(
+                            arguments.getString("command"),
+                            arguments.getBundle("extras"),
+                            new ResultReceiver(null));
+                    break;
+                case ADD_QUEUE_ITEM:
+                    controller.addQueueItem(
+                            (MediaDescriptionCompat) extras.getParcelable(KEY_ARGUMENT));
+                    break;
+                case ADD_QUEUE_ITEM_WITH_INDEX:
+                    arguments = extras.getBundle(KEY_ARGUMENT);
+                    controller.addQueueItem(
+                            (MediaDescriptionCompat) arguments.getParcelable("description"),
+                            arguments.getInt("index"));
+                    break;
+                case REMOVE_QUEUE_ITEM:
+                    controller.removeQueueItem(
+                            (MediaDescriptionCompat) extras.getParcelable(KEY_ARGUMENT));
+                    break;
+                case SET_VOLUME_TO:
+                    controller.setVolumeTo(extras.getInt(KEY_ARGUMENT), 0);
+                    break;
+                case ADJUST_VOLUME:
+                    controller.adjustVolume(extras.getInt(KEY_ARGUMENT), 0);
+                    break;
+            }
+        } else if (ACTION_CALL_TRANSPORT_CONTROLS_METHOD.equals(intent.getAction())
+                && extras != null) {
+            TransportControls controls = controller.getTransportControls();
+            Bundle arguments;
+            switch (method) {
+                case PLAY:
+                    controls.play();
+                    break;
+                case PAUSE:
+                    controls.pause();
+                    break;
+                case STOP:
+                    controls.stop();
+                    break;
+                case FAST_FORWARD:
+                    controls.fastForward();
+                    break;
+                case REWIND:
+                    controls.rewind();
+                    break;
+                case SKIP_TO_PREVIOUS:
+                    controls.skipToPrevious();
+                    break;
+                case SKIP_TO_NEXT:
+                    controls.skipToNext();
+                    break;
+                case SEEK_TO:
+                    controls.seekTo(extras.getLong(KEY_ARGUMENT));
+                    break;
+                case SET_RATING:
+                    controls.setRating((RatingCompat) extras.getParcelable(KEY_ARGUMENT));
+                    break;
+                case PLAY_FROM_MEDIA_ID:
+                    arguments = extras.getBundle(KEY_ARGUMENT);
+                    controls.playFromMediaId(
+                            arguments.getString("mediaId"),
+                            arguments.getBundle("extras"));
+                    break;
+                case PLAY_FROM_SEARCH:
+                    arguments = extras.getBundle(KEY_ARGUMENT);
+                    controls.playFromSearch(
+                            arguments.getString("query"),
+                            arguments.getBundle("extras"));
+                    break;
+                case PLAY_FROM_URI:
+                    arguments = extras.getBundle(KEY_ARGUMENT);
+                    controls.playFromUri(
+                            (Uri) arguments.getParcelable("uri"),
+                            arguments.getBundle("extras"));
+                    break;
+                case SEND_CUSTOM_ACTION:
+                    arguments = extras.getBundle(KEY_ARGUMENT);
+                    controls.sendCustomAction(
+                            arguments.getString("action"),
+                            arguments.getBundle("extras"));
+                    break;
+                case SEND_CUSTOM_ACTION_PARCELABLE:
+                    arguments = extras.getBundle(KEY_ARGUMENT);
+                    controls.sendCustomAction(
+                            (PlaybackStateCompat.CustomAction)
+                                    arguments.getParcelable("action"),
+                            arguments.getBundle("extras"));
+                    break;
+                case SKIP_TO_QUEUE_ITEM:
+                    controls.skipToQueueItem(extras.getLong(KEY_ARGUMENT));
+                    break;
+                case PREPARE:
+                    controls.prepare();
+                    break;
+                case PREPARE_FROM_MEDIA_ID:
+                    arguments = extras.getBundle(KEY_ARGUMENT);
+                    controls.prepareFromMediaId(
+                            arguments.getString("mediaId"),
+                            arguments.getBundle("extras"));
+                    break;
+                case PREPARE_FROM_SEARCH:
+                    arguments = extras.getBundle(KEY_ARGUMENT);
+                    controls.prepareFromSearch(
+                            arguments.getString("query"),
+                            arguments.getBundle("extras"));
+                    break;
+                case PREPARE_FROM_URI:
+                    arguments = extras.getBundle(KEY_ARGUMENT);
+                    controls.prepareFromUri(
+                            (Uri) arguments.getParcelable("uri"),
+                            arguments.getBundle("extras"));
+                    break;
+                case SET_CAPTIONING_ENABLED:
+                    controls.setCaptioningEnabled(extras.getBoolean(KEY_ARGUMENT));
+                    break;
+                case SET_REPEAT_MODE:
+                    controls.setRepeatMode(extras.getInt(KEY_ARGUMENT));
+                    break;
+                case SET_SHUFFLE_MODE:
+                    controls.setShuffleMode(extras.getInt(KEY_ARGUMENT));
+                    break;
+            }
+        }
+    }
+}
diff --git a/media-compat/version-compat-tests/previous/client/tests/src/android/support/mediacompat/client/MediaBrowserCompatTest.java b/media-compat/version-compat-tests/previous/client/tests/src/android/support/mediacompat/client/MediaBrowserCompatTest.java
new file mode 100644
index 0000000..a2e77ff
--- /dev/null
+++ b/media-compat/version-compat-tests/previous/client/tests/src/android/support/mediacompat/client/MediaBrowserCompatTest.java
@@ -0,0 +1,1048 @@
+/*
+ * 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.client;
+
+import static android.support.mediacompat.testlib.MediaBrowserConstants.CUSTOM_ACTION;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.CUSTOM_ACTION_FOR_ERROR;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.CUSTOM_ACTION_SEND_ERROR;
+import static android.support.mediacompat.testlib.MediaBrowserConstants
+        .CUSTOM_ACTION_SEND_PROGRESS_UPDATE;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.CUSTOM_ACTION_SEND_RESULT;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.EXTRAS_KEY;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.EXTRAS_VALUE;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.MEDIA_ID_CHILDREN;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.MEDIA_ID_CHILDREN_DELAYED;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.MEDIA_ID_INVALID;
+import static android.support.mediacompat.testlib.MediaBrowserConstants
+        .MEDIA_ID_ON_LOAD_ITEM_NOT_IMPLEMENTED;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.MEDIA_ID_ROOT;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.NOTIFY_CHILDREN_CHANGED;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.SEARCH_QUERY;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.SEARCH_QUERY_FOR_ERROR;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.SEARCH_QUERY_FOR_NO_RESULT;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.SEND_DELAYED_ITEM_LOADED;
+import static android.support.mediacompat.testlib.MediaBrowserConstants
+        .SEND_DELAYED_NOTIFY_CHILDREN_CHANGED;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.SET_SESSION_TOKEN;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.TEST_KEY_1;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.TEST_KEY_2;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.TEST_KEY_3;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.TEST_KEY_4;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.TEST_VALUE_1;
+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.mediacompat.testlib.util.IntentUtil.SERVICE_PACKAGE_NAME;
+import static android.support.mediacompat.testlib.util.IntentUtil.callMediaBrowserServiceMethod;
+import static android.support.test.InstrumentationRegistry.getArguments;
+import static android.support.test.InstrumentationRegistry.getContext;
+import static android.support.test.InstrumentationRegistry.getInstrumentation;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
+
+import android.content.ComponentName;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.mediacompat.testlib.util.PollingCheck;
+import android.support.test.filters.MediumTest;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v4.media.MediaBrowserCompat;
+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;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Test {@link android.support.v4.media.MediaBrowserCompat}.
+ */
+@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;
+
+    /**
+     * To check {@link MediaBrowserCompat#unsubscribe} works properly,
+     * we notify to the browser after the unsubscription that the media items have changed.
+     * Then {@link MediaBrowserCompat.SubscriptionCallback#onChildrenLoaded} should not be called.
+     *
+     * The measured time from calling {@link MediaBrowserServiceCompat#notifyChildrenChanged}
+     * to {@link MediaBrowserCompat.SubscriptionCallback#onChildrenLoaded} being called is about
+     * 50ms.
+     * So we make the thread sleep for 100ms to properly check that the callback is not called.
+     */
+    private static final long SLEEP_MS = 100L;
+    private static final ComponentName TEST_BROWSER_SERVICE = new ComponentName(
+            SERVICE_PACKAGE_NAME,
+            "android.support.mediacompat.service.StubMediaBrowserServiceCompat");
+    private static final ComponentName TEST_BROWSER_SERVICE_DELAYED_MEDIA_SESSION =
+            new ComponentName(
+                    SERVICE_PACKAGE_NAME,
+                    "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;
+    private StubItemCallback mItemCallback;
+    private StubSearchCallback mSearchCallback;
+    private CustomActionCallback mCustomActionCallback;
+    private Bundle mRootHints;
+
+    @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();
+        mSearchCallback = new StubSearchCallback();
+        mCustomActionCallback = new CustomActionCallback();
+
+        mRootHints = new Bundle();
+        mRootHints.putBoolean(MediaBrowserServiceCompat.BrowserRoot.EXTRA_RECENT, true);
+        mRootHints.putBoolean(MediaBrowserServiceCompat.BrowserRoot.EXTRA_OFFLINE, true);
+        mRootHints.putBoolean(MediaBrowserServiceCompat.BrowserRoot.EXTRA_SUGGESTED, true);
+
+        getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                mMediaBrowser = new MediaBrowserCompat(getInstrumentation().getTargetContext(),
+                        TEST_BROWSER_SERVICE, mConnectionCallback, mRootHints);
+            }
+        });
+    }
+
+    @After
+    public void tearDown() {
+        if (mMediaBrowser != null && mMediaBrowser.isConnected()) {
+            mMediaBrowser.disconnect();
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void testBrowserRoot() {
+        final String id = "test-id";
+        final String key = "test-key";
+        final String val = "test-val";
+        final Bundle extras = new Bundle();
+        extras.putString(key, val);
+
+        MediaBrowserServiceCompat.BrowserRoot browserRoot =
+                new MediaBrowserServiceCompat.BrowserRoot(id, extras);
+        assertEquals(id, browserRoot.getRootId());
+        assertEquals(val, browserRoot.getExtras().getString(key));
+    }
+
+    @Test
+    @SmallTest
+    public void testMediaBrowser() throws Exception {
+        assertFalse(mMediaBrowser.isConnected());
+
+        connectMediaBrowserService();
+        assertTrue(mMediaBrowser.isConnected());
+
+        assertEquals(TEST_BROWSER_SERVICE, mMediaBrowser.getServiceComponent());
+        assertEquals(MEDIA_ID_ROOT, mMediaBrowser.getRoot());
+        assertEquals(EXTRAS_VALUE, mMediaBrowser.getExtras().getString(EXTRAS_KEY));
+
+        mMediaBrowser.disconnect();
+        new PollingCheck(TIME_OUT_MS) {
+            @Override
+            protected boolean check() {
+                return !mMediaBrowser.isConnected();
+            }
+        }.run();
+    }
+
+    @Test
+    @SmallTest
+    public void testGetServiceComponentBeforeConnection() {
+        try {
+            ComponentName serviceComponent = mMediaBrowser.getServiceComponent();
+            fail();
+        } catch (IllegalStateException e) {
+            // expected
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void testConnectionFailed() throws Exception {
+        getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                mMediaBrowser = new MediaBrowserCompat(getInstrumentation().getTargetContext(),
+                        TEST_INVALID_BROWSER_SERVICE, mConnectionCallback, mRootHints);
+            }
+        });
+
+        synchronized (mConnectionCallback.mWaitLock) {
+            mMediaBrowser.connect();
+            mConnectionCallback.mWaitLock.wait(TIME_OUT_MS);
+        }
+        assertEquals(1, mConnectionCallback.mConnectionFailedCount);
+        assertEquals(0, mConnectionCallback.mConnectedCount);
+        assertEquals(0, mConnectionCallback.mConnectionSuspendedCount);
+    }
+
+    @Test
+    @SmallTest
+    public void testConnectTwice() throws Exception {
+        connectMediaBrowserService();
+        try {
+            mMediaBrowser.connect();
+            fail();
+        } catch (IllegalStateException e) {
+            // expected
+        }
+    }
+
+    @Test
+    @MediumTest
+    public void testReconnection() throws Exception {
+        getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                mMediaBrowser.connect();
+                // Reconnect before the first connection was established.
+                mMediaBrowser.disconnect();
+                mMediaBrowser.connect();
+            }
+        });
+
+        synchronized (mConnectionCallback.mWaitLock) {
+            mConnectionCallback.mWaitLock.wait(TIME_OUT_MS);
+            assertEquals(1, mConnectionCallback.mConnectedCount);
+        }
+
+        // Test subscribe.
+        mSubscriptionCallback.reset(1);
+        mMediaBrowser.subscribe(MEDIA_ID_ROOT, mSubscriptionCallback);
+        mSubscriptionCallback.await(TIME_OUT_MS);
+        assertEquals(1, mSubscriptionCallback.mChildrenLoadedCount);
+        assertEquals(MEDIA_ID_ROOT, mSubscriptionCallback.mLastParentId);
+
+        synchronized (mItemCallback.mWaitLock) {
+            // Test getItem.
+            mItemCallback.reset();
+            mMediaBrowser.getItem(MEDIA_ID_CHILDREN[0], mItemCallback);
+            mItemCallback.mWaitLock.wait(TIME_OUT_MS);
+            assertEquals(MEDIA_ID_CHILDREN[0], mItemCallback.mLastMediaItem.getMediaId());
+        }
+
+        // Reconnect after connection was established.
+        mMediaBrowser.disconnect();
+        connectMediaBrowserService();
+
+        synchronized (mItemCallback.mWaitLock) {
+            // Test getItem.
+            mItemCallback.reset();
+            mMediaBrowser.getItem(MEDIA_ID_CHILDREN[0], mItemCallback);
+            mItemCallback.mWaitLock.wait(TIME_OUT_MS);
+            assertEquals(MEDIA_ID_CHILDREN[0], mItemCallback.mLastMediaItem.getMediaId());
+        }
+    }
+
+    @Test
+    @MediumTest
+    public void testConnectionCallbackNotCalledAfterDisconnect() {
+        getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                mMediaBrowser.connect();
+                mMediaBrowser.disconnect();
+                mConnectionCallback.reset();
+            }
+        });
+
+        try {
+            Thread.sleep(SLEEP_MS);
+        } catch (InterruptedException e) {
+            fail("Unexpected InterruptedException occurred.");
+        }
+        assertEquals(0, mConnectionCallback.mConnectedCount);
+        assertEquals(0, mConnectionCallback.mConnectionFailedCount);
+        assertEquals(0, mConnectionCallback.mConnectionSuspendedCount);
+    }
+
+    @Test
+    @MediumTest
+    public void testSubscribe() throws Exception {
+        connectMediaBrowserService();
+
+        mSubscriptionCallback.reset(1);
+        mMediaBrowser.subscribe(MEDIA_ID_ROOT, mSubscriptionCallback);
+        mSubscriptionCallback.await(TIME_OUT_MS);
+        assertEquals(1, mSubscriptionCallback.mChildrenLoadedCount);
+        assertEquals(MEDIA_ID_ROOT, mSubscriptionCallback.mLastParentId);
+        assertEquals(MEDIA_ID_CHILDREN.length, mSubscriptionCallback.mLastChildMediaItems.size());
+        for (int i = 0; i < MEDIA_ID_CHILDREN.length; ++i) {
+            assertEquals(MEDIA_ID_CHILDREN[i],
+                    mSubscriptionCallback.mLastChildMediaItems.get(i).getMediaId());
+        }
+
+        // Test MediaBrowserServiceCompat.notifyChildrenChanged()
+        mSubscriptionCallback.reset(1);
+        callMediaBrowserServiceMethod(NOTIFY_CHILDREN_CHANGED, MEDIA_ID_ROOT, getContext());
+        mSubscriptionCallback.await(TIME_OUT_MS);
+        assertEquals(1, mSubscriptionCallback.mChildrenLoadedCount);
+
+        // Test unsubscribe.
+        mSubscriptionCallback.reset(1);
+        mMediaBrowser.unsubscribe(MEDIA_ID_ROOT);
+
+        // After unsubscribing, make StubMediaBrowserServiceCompat notify that the children are
+        // changed.
+        callMediaBrowserServiceMethod(NOTIFY_CHILDREN_CHANGED, MEDIA_ID_ROOT, getContext());
+        mSubscriptionCallback.await(WAIT_TIME_FOR_NO_RESPONSE_MS);
+
+        // onChildrenLoaded should not be called.
+        assertEquals(0, mSubscriptionCallback.mChildrenLoadedCount);
+    }
+
+    @Test
+    @MediumTest
+    public void testSubscribeWithOptions() throws Exception {
+        connectMediaBrowserService();
+        final int pageSize = 3;
+        final int lastPage = (MEDIA_ID_CHILDREN.length - 1) / pageSize;
+        Bundle options = new Bundle();
+        options.putInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, pageSize);
+
+        for (int page = 0; page <= lastPage; ++page) {
+            mSubscriptionCallback.reset(1);
+            options.putInt(MediaBrowserCompat.EXTRA_PAGE, page);
+            mMediaBrowser.subscribe(MEDIA_ID_ROOT, options, mSubscriptionCallback);
+            mSubscriptionCallback.await(TIME_OUT_MS);
+            assertEquals(1, mSubscriptionCallback.mChildrenLoadedWithOptionCount);
+            assertEquals(MEDIA_ID_ROOT, mSubscriptionCallback.mLastParentId);
+            if (page != lastPage) {
+                assertEquals(pageSize, mSubscriptionCallback.mLastChildMediaItems.size());
+            } else {
+                assertEquals((MEDIA_ID_CHILDREN.length - 1) % pageSize + 1,
+                        mSubscriptionCallback.mLastChildMediaItems.size());
+            }
+            // Check whether all the items in the current page are loaded.
+            for (int i = 0; i < mSubscriptionCallback.mLastChildMediaItems.size(); ++i) {
+                assertEquals(MEDIA_ID_CHILDREN[page * pageSize + i],
+                        mSubscriptionCallback.mLastChildMediaItems.get(i).getMediaId());
+            }
+
+            // Test MediaBrowserServiceCompat.notifyChildrenChanged()
+            mSubscriptionCallback.reset(page + 1);
+            callMediaBrowserServiceMethod(NOTIFY_CHILDREN_CHANGED, MEDIA_ID_ROOT, getContext());
+            mSubscriptionCallback.await(TIME_OUT_MS);
+            assertEquals(page + 1, mSubscriptionCallback.mChildrenLoadedWithOptionCount);
+        }
+
+        // Test unsubscribe with callback argument.
+        mSubscriptionCallback.reset(1);
+        mMediaBrowser.unsubscribe(MEDIA_ID_ROOT, mSubscriptionCallback);
+
+        // After unsubscribing, make StubMediaBrowserServiceCompat notify that the children are
+        // changed.
+        callMediaBrowserServiceMethod(NOTIFY_CHILDREN_CHANGED, MEDIA_ID_ROOT, getContext());
+        try {
+            Thread.sleep(SLEEP_MS);
+        } catch (InterruptedException e) {
+            fail("Unexpected InterruptedException occurred.");
+        }
+        // onChildrenLoaded should not be called.
+        assertEquals(0, mSubscriptionCallback.mChildrenLoadedCount);
+    }
+
+    @Test
+    @MediumTest
+    public void testSubscribeDelayedItems() throws Exception {
+        connectMediaBrowserService();
+
+        mSubscriptionCallback.reset(1);
+        mMediaBrowser.subscribe(MEDIA_ID_CHILDREN_DELAYED, mSubscriptionCallback);
+        mSubscriptionCallback.await(WAIT_TIME_FOR_NO_RESPONSE_MS);
+        assertEquals(0, mSubscriptionCallback.mChildrenLoadedCount);
+
+        callMediaBrowserServiceMethod(
+                SEND_DELAYED_NOTIFY_CHILDREN_CHANGED, MEDIA_ID_CHILDREN_DELAYED, getContext());
+        mSubscriptionCallback.await(TIME_OUT_MS);
+        assertEquals(1, mSubscriptionCallback.mChildrenLoadedCount);
+    }
+
+    @Test
+    @SmallTest
+    public void testSubscribeInvalidItem() throws Exception {
+        connectMediaBrowserService();
+
+        mSubscriptionCallback.reset(1);
+        mMediaBrowser.subscribe(MEDIA_ID_INVALID, mSubscriptionCallback);
+        mSubscriptionCallback.await(TIME_OUT_MS);
+        assertEquals(MEDIA_ID_INVALID, mSubscriptionCallback.mLastErrorId);
+    }
+
+    @Test
+    @SmallTest
+    public void testSubscribeInvalidItemWithOptions() throws Exception {
+        connectMediaBrowserService();
+
+        final int pageSize = 5;
+        final int page = 2;
+        Bundle options = new Bundle();
+        options.putInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, pageSize);
+        options.putInt(MediaBrowserCompat.EXTRA_PAGE, page);
+
+        mSubscriptionCallback.reset(1);
+        mMediaBrowser.subscribe(MEDIA_ID_INVALID, options, mSubscriptionCallback);
+        mSubscriptionCallback.await(TIME_OUT_MS);
+        assertEquals(MEDIA_ID_INVALID, mSubscriptionCallback.mLastErrorId);
+        assertNotNull(mSubscriptionCallback.mLastOptions);
+        assertEquals(page,
+                mSubscriptionCallback.mLastOptions.getInt(MediaBrowserCompat.EXTRA_PAGE));
+        assertEquals(pageSize,
+                mSubscriptionCallback.mLastOptions.getInt(MediaBrowserCompat.EXTRA_PAGE_SIZE));
+    }
+
+    @Test
+    @MediumTest
+    public void testUnsubscribeForMultipleSubscriptions() throws Exception {
+        connectMediaBrowserService();
+        final List<StubSubscriptionCallback> subscriptionCallbacks = new ArrayList<>();
+        final int pageSize = 1;
+
+        // Subscribe four pages, one item per page.
+        for (int page = 0; page < 4; page++) {
+            final StubSubscriptionCallback callback = new StubSubscriptionCallback();
+            subscriptionCallbacks.add(callback);
+
+            Bundle options = new Bundle();
+            options.putInt(MediaBrowserCompat.EXTRA_PAGE, page);
+            options.putInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, pageSize);
+            callback.reset(1);
+            mMediaBrowser.subscribe(MEDIA_ID_ROOT, options, callback);
+            callback.await(TIME_OUT_MS);
+
+            // Each onChildrenLoaded() must be called.
+            assertEquals(1, callback.mChildrenLoadedWithOptionCount);
+        }
+
+        // Reset callbacks and unsubscribe.
+        for (StubSubscriptionCallback callback : subscriptionCallbacks) {
+            callback.reset(1);
+        }
+        mMediaBrowser.unsubscribe(MEDIA_ID_ROOT);
+
+        // After unsubscribing, make StubMediaBrowserServiceCompat notify that the children are
+        // changed.
+        callMediaBrowserServiceMethod(NOTIFY_CHILDREN_CHANGED, MEDIA_ID_ROOT, getContext());
+        try {
+            Thread.sleep(SLEEP_MS);
+        } catch (InterruptedException e) {
+            fail("Unexpected InterruptedException occurred.");
+        }
+
+        // onChildrenLoaded should not be called.
+        for (StubSubscriptionCallback callback : subscriptionCallbacks) {
+            assertEquals(0, callback.mChildrenLoadedWithOptionCount);
+        }
+    }
+
+    @Test
+    @MediumTest
+    public void testUnsubscribeWithSubscriptionCallbackForMultipleSubscriptions() throws Exception {
+        connectMediaBrowserService();
+        final List<StubSubscriptionCallback> subscriptionCallbacks = new ArrayList<>();
+        final int pageSize = 1;
+
+        // Subscribe four pages, one item per page.
+        for (int page = 0; page < 4; page++) {
+            final StubSubscriptionCallback callback = new StubSubscriptionCallback();
+            subscriptionCallbacks.add(callback);
+
+            Bundle options = new Bundle();
+            options.putInt(MediaBrowserCompat.EXTRA_PAGE, page);
+            options.putInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, pageSize);
+            callback.reset(1);
+            mMediaBrowser.subscribe(MEDIA_ID_ROOT, options, callback);
+            callback.await(TIME_OUT_MS);
+
+            // Each onChildrenLoaded() must be called.
+            assertEquals(1, callback.mChildrenLoadedWithOptionCount);
+        }
+
+        // Unsubscribe existing subscriptions one-by-one.
+        final int[] orderOfRemovingCallbacks = {2, 0, 3, 1};
+        for (int i = 0; i < orderOfRemovingCallbacks.length; i++) {
+            // Reset callbacks
+            for (StubSubscriptionCallback callback : subscriptionCallbacks) {
+                callback.reset(1);
+            }
+
+            // Remove one subscription
+            mMediaBrowser.unsubscribe(MEDIA_ID_ROOT,
+                    subscriptionCallbacks.get(orderOfRemovingCallbacks[i]));
+
+            // Make StubMediaBrowserServiceCompat notify that the children are changed.
+            callMediaBrowserServiceMethod(NOTIFY_CHILDREN_CHANGED, MEDIA_ID_ROOT, getContext());
+            try {
+                Thread.sleep(SLEEP_MS);
+            } catch (InterruptedException e) {
+                fail("Unexpected InterruptedException occurred.");
+            }
+
+            // Only the remaining subscriptionCallbacks should be called.
+            for (int j = 0; j < 4; j++) {
+                int childrenLoadedWithOptionsCount = subscriptionCallbacks
+                        .get(orderOfRemovingCallbacks[j]).mChildrenLoadedWithOptionCount;
+                if (j <= i) {
+                    assertEquals(0, childrenLoadedWithOptionsCount);
+                } else {
+                    assertEquals(1, childrenLoadedWithOptionsCount);
+                }
+            }
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void testGetItem() throws Exception {
+        connectMediaBrowserService();
+
+        synchronized (mItemCallback.mWaitLock) {
+            mMediaBrowser.getItem(MEDIA_ID_CHILDREN[0], mItemCallback);
+            mItemCallback.mWaitLock.wait(TIME_OUT_MS);
+            assertNotNull(mItemCallback.mLastMediaItem);
+            assertEquals(MEDIA_ID_CHILDREN[0], mItemCallback.mLastMediaItem.getMediaId());
+        }
+    }
+
+    @Test
+    @MediumTest
+    public void testGetItemDelayed() throws Exception {
+        connectMediaBrowserService();
+
+        synchronized (mItemCallback.mWaitLock) {
+            mMediaBrowser.getItem(MEDIA_ID_CHILDREN_DELAYED, mItemCallback);
+            mItemCallback.mWaitLock.wait(WAIT_TIME_FOR_NO_RESPONSE_MS);
+            assertNull(mItemCallback.mLastMediaItem);
+
+            mItemCallback.reset();
+            callMediaBrowserServiceMethod(SEND_DELAYED_ITEM_LOADED, new Bundle(), getContext());
+            mItemCallback.mWaitLock.wait(TIME_OUT_MS);
+            assertNotNull(mItemCallback.mLastMediaItem);
+            assertEquals(MEDIA_ID_CHILDREN_DELAYED, mItemCallback.mLastMediaItem.getMediaId());
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void testGetItemWhenOnLoadItemIsNotImplemented() throws Exception {
+        connectMediaBrowserService();
+        synchronized (mItemCallback.mWaitLock) {
+            mMediaBrowser.getItem(MEDIA_ID_ON_LOAD_ITEM_NOT_IMPLEMENTED, mItemCallback);
+            mItemCallback.mWaitLock.wait(TIME_OUT_MS);
+            assertEquals(MEDIA_ID_ON_LOAD_ITEM_NOT_IMPLEMENTED, mItemCallback.mLastErrorId);
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void testGetItemWhenMediaIdIsInvalid() throws Exception {
+        mItemCallback.mLastMediaItem = new MediaItem(new MediaDescriptionCompat.Builder()
+                .setMediaId("dummy_id").build(), MediaItem.FLAG_BROWSABLE);
+
+        connectMediaBrowserService();
+        synchronized (mItemCallback.mWaitLock) {
+            mMediaBrowser.getItem(MEDIA_ID_INVALID, mItemCallback);
+            mItemCallback.mWaitLock.wait(TIME_OUT_MS);
+            assertNull(mItemCallback.mLastMediaItem);
+            assertNull(mItemCallback.mLastErrorId);
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void testSearch() throws Exception {
+        connectMediaBrowserService();
+
+        final String key = "test-key";
+        final String val = "test-val";
+
+        synchronized (mSearchCallback.mWaitLock) {
+            mSearchCallback.reset();
+            mMediaBrowser.search(SEARCH_QUERY_FOR_NO_RESULT, null, mSearchCallback);
+            mSearchCallback.mWaitLock.wait(WAIT_TIME_FOR_NO_RESPONSE_MS);
+            assertTrue(mSearchCallback.mOnSearchResult);
+            assertTrue(mSearchCallback.mSearchResults != null
+                    && mSearchCallback.mSearchResults.size() == 0);
+            assertEquals(null, mSearchCallback.mSearchExtras);
+
+            mSearchCallback.reset();
+            mMediaBrowser.search(SEARCH_QUERY_FOR_ERROR, null, mSearchCallback);
+            mSearchCallback.mWaitLock.wait(WAIT_TIME_FOR_NO_RESPONSE_MS);
+            assertTrue(mSearchCallback.mOnSearchResult);
+            assertNull(mSearchCallback.mSearchResults);
+            assertEquals(null, mSearchCallback.mSearchExtras);
+
+            mSearchCallback.reset();
+            Bundle extras = new Bundle();
+            extras.putString(key, val);
+            mMediaBrowser.search(SEARCH_QUERY, extras, mSearchCallback);
+            mSearchCallback.mWaitLock.wait(WAIT_TIME_FOR_NO_RESPONSE_MS);
+            assertTrue(mSearchCallback.mOnSearchResult);
+            assertNotNull(mSearchCallback.mSearchResults);
+            for (MediaItem item : mSearchCallback.mSearchResults) {
+                assertNotNull(item.getMediaId());
+                assertTrue(item.getMediaId().contains(SEARCH_QUERY));
+            }
+            assertNotNull(mSearchCallback.mSearchExtras);
+            assertEquals(val, mSearchCallback.mSearchExtras.getString(key));
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void testSendCustomAction() throws Exception {
+        connectMediaBrowserService();
+
+        synchronized (mCustomActionCallback.mWaitLock) {
+            Bundle customActionExtras = new Bundle();
+            customActionExtras.putString(TEST_KEY_1, TEST_VALUE_1);
+            mMediaBrowser.sendCustomAction(
+                    CUSTOM_ACTION, customActionExtras, mCustomActionCallback);
+            mCustomActionCallback.mWaitLock.wait(WAIT_TIME_FOR_NO_RESPONSE_MS);
+
+            mCustomActionCallback.reset();
+            Bundle data1 = new Bundle();
+            data1.putString(TEST_KEY_2, TEST_VALUE_2);
+            callMediaBrowserServiceMethod(CUSTOM_ACTION_SEND_PROGRESS_UPDATE, data1, getContext());
+            mCustomActionCallback.mWaitLock.wait(TIME_OUT_MS);
+
+            assertTrue(mCustomActionCallback.mOnProgressUpdateCalled);
+            assertEquals(CUSTOM_ACTION, mCustomActionCallback.mAction);
+            assertNotNull(mCustomActionCallback.mExtras);
+            assertEquals(TEST_VALUE_1, mCustomActionCallback.mExtras.getString(TEST_KEY_1));
+            assertNotNull(mCustomActionCallback.mData);
+            assertEquals(TEST_VALUE_2, mCustomActionCallback.mData.getString(TEST_KEY_2));
+
+            mCustomActionCallback.reset();
+            Bundle data2 = new Bundle();
+            data2.putString(TEST_KEY_3, TEST_VALUE_3);
+            callMediaBrowserServiceMethod(CUSTOM_ACTION_SEND_PROGRESS_UPDATE, data2, getContext());
+            mCustomActionCallback.mWaitLock.wait(TIME_OUT_MS);
+
+            assertTrue(mCustomActionCallback.mOnProgressUpdateCalled);
+            assertEquals(CUSTOM_ACTION, mCustomActionCallback.mAction);
+            assertNotNull(mCustomActionCallback.mExtras);
+            assertEquals(TEST_VALUE_1, mCustomActionCallback.mExtras.getString(TEST_KEY_1));
+            assertNotNull(mCustomActionCallback.mData);
+            assertEquals(TEST_VALUE_3, mCustomActionCallback.mData.getString(TEST_KEY_3));
+
+            Bundle resultData = new Bundle();
+            resultData.putString(TEST_KEY_4, TEST_VALUE_4);
+            mCustomActionCallback.reset();
+            callMediaBrowserServiceMethod(CUSTOM_ACTION_SEND_RESULT, resultData, getContext());
+            mCustomActionCallback.mWaitLock.wait(TIME_OUT_MS);
+
+            assertTrue(mCustomActionCallback.mOnResultCalled);
+            assertEquals(CUSTOM_ACTION, mCustomActionCallback.mAction);
+            assertNotNull(mCustomActionCallback.mExtras);
+            assertEquals(TEST_VALUE_1, mCustomActionCallback.mExtras.getString(TEST_KEY_1));
+            assertNotNull(mCustomActionCallback.mData);
+            assertEquals(TEST_VALUE_4, mCustomActionCallback.mData.getString(TEST_KEY_4));
+        }
+    }
+
+
+    @Test
+    @MediumTest
+    public void testSendCustomActionWithDetachedError() throws Exception {
+        connectMediaBrowserService();
+
+        synchronized (mCustomActionCallback.mWaitLock) {
+            Bundle customActionExtras = new Bundle();
+            customActionExtras.putString(TEST_KEY_1, TEST_VALUE_1);
+            mMediaBrowser.sendCustomAction(
+                    CUSTOM_ACTION, customActionExtras, mCustomActionCallback);
+            mCustomActionCallback.mWaitLock.wait(WAIT_TIME_FOR_NO_RESPONSE_MS);
+
+            mCustomActionCallback.reset();
+            Bundle progressUpdateData = new Bundle();
+            progressUpdateData.putString(TEST_KEY_2, TEST_VALUE_2);
+            callMediaBrowserServiceMethod(
+                    CUSTOM_ACTION_SEND_PROGRESS_UPDATE, progressUpdateData, getContext());
+            mCustomActionCallback.mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCustomActionCallback.mOnProgressUpdateCalled);
+            assertEquals(CUSTOM_ACTION, mCustomActionCallback.mAction);
+            assertNotNull(mCustomActionCallback.mExtras);
+            assertEquals(TEST_VALUE_1, mCustomActionCallback.mExtras.getString(TEST_KEY_1));
+            assertNotNull(mCustomActionCallback.mData);
+            assertEquals(TEST_VALUE_2, mCustomActionCallback.mData.getString(TEST_KEY_2));
+
+            mCustomActionCallback.reset();
+            Bundle errorData = new Bundle();
+            errorData.putString(TEST_KEY_3, TEST_VALUE_3);
+            callMediaBrowserServiceMethod(CUSTOM_ACTION_SEND_ERROR, errorData, getContext());
+            mCustomActionCallback.mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCustomActionCallback.mOnErrorCalled);
+            assertEquals(CUSTOM_ACTION, mCustomActionCallback.mAction);
+            assertNotNull(mCustomActionCallback.mExtras);
+            assertEquals(TEST_VALUE_1, mCustomActionCallback.mExtras.getString(TEST_KEY_1));
+            assertNotNull(mCustomActionCallback.mData);
+            assertEquals(TEST_VALUE_3, mCustomActionCallback.mData.getString(TEST_KEY_3));
+        }
+    }
+
+    @Test
+    @MediumTest
+    public void testSendCustomActionWithNullCallback() throws Exception {
+        connectMediaBrowserService();
+
+        Bundle customActionExtras = new Bundle();
+        customActionExtras.putString(TEST_KEY_1, TEST_VALUE_1);
+        mMediaBrowser.sendCustomAction(CUSTOM_ACTION, customActionExtras, null);
+        // Wait some time so that the service can get a result receiver for the custom action.
+        Thread.sleep(WAIT_TIME_FOR_NO_RESPONSE_MS);
+
+        // These calls should not make any exceptions.
+        callMediaBrowserServiceMethod(CUSTOM_ACTION_SEND_PROGRESS_UPDATE, new Bundle(),
+                getContext());
+        callMediaBrowserServiceMethod(CUSTOM_ACTION_SEND_RESULT, new Bundle(), getContext());
+        Thread.sleep(WAIT_TIME_FOR_NO_RESPONSE_MS);
+    }
+
+    @Test
+    @SmallTest
+    public void testSendCustomActionWithError() throws Exception {
+        connectMediaBrowserService();
+
+        synchronized (mCustomActionCallback.mWaitLock) {
+            mMediaBrowser.sendCustomAction(CUSTOM_ACTION_FOR_ERROR, null, mCustomActionCallback);
+            mCustomActionCallback.mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCustomActionCallback.mOnErrorCalled);
+        }
+    }
+
+    @Test
+    @MediumTest
+    public void testDelayedSetSessionToken() throws Exception {
+        // This test has no meaning in API 21. The framework MediaBrowserService just connects to
+        // the media browser without waiting setMediaSession() to be called.
+        if (Build.VERSION.SDK_INT == 21) {
+            return;
+        }
+        final ConnectionCallbackForDelayedMediaSession callback =
+                new ConnectionCallbackForDelayedMediaSession();
+
+        getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                mMediaBrowser = new MediaBrowserCompat(
+                        getInstrumentation().getTargetContext(),
+                        TEST_BROWSER_SERVICE_DELAYED_MEDIA_SESSION,
+                        callback,
+                        null);
+            }
+        });
+
+        synchronized (callback.mWaitLock) {
+            mMediaBrowser.connect();
+            callback.mWaitLock.wait(WAIT_TIME_FOR_NO_RESPONSE_MS);
+            assertEquals(0, callback.mConnectedCount);
+
+            callMediaBrowserServiceMethod(SET_SESSION_TOKEN, new Bundle(), getContext());
+            callback.mWaitLock.wait(TIME_OUT_MS);
+            assertEquals(1, callback.mConnectedCount);
+
+            if (Build.VERSION.SDK_INT >= 21) {
+                assertNotNull(mMediaBrowser.getSessionToken().getExtraBinder());
+            }
+        }
+    }
+
+    private void connectMediaBrowserService() throws Exception {
+        synchronized (mConnectionCallback.mWaitLock) {
+            mMediaBrowser.connect();
+            mConnectionCallback.mWaitLock.wait(TIME_OUT_MS);
+            if (!mMediaBrowser.isConnected()) {
+                fail("Browser failed to connect!");
+            }
+        }
+    }
+
+    private class StubConnectionCallback extends MediaBrowserCompat.ConnectionCallback {
+        final Object mWaitLock = new Object();
+        volatile int mConnectedCount;
+        volatile int mConnectionFailedCount;
+        volatile int mConnectionSuspendedCount;
+
+        public void reset() {
+            mConnectedCount = 0;
+            mConnectionFailedCount = 0;
+            mConnectionSuspendedCount = 0;
+        }
+
+        @Override
+        public void onConnected() {
+            synchronized (mWaitLock) {
+                mConnectedCount++;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onConnectionFailed() {
+            synchronized (mWaitLock) {
+                mConnectionFailedCount++;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onConnectionSuspended() {
+            synchronized (mWaitLock) {
+                mConnectionSuspendedCount++;
+                mWaitLock.notify();
+            }
+        }
+    }
+
+    private class StubSubscriptionCallback extends MediaBrowserCompat.SubscriptionCallback {
+        private CountDownLatch mLatch;
+        private volatile int mChildrenLoadedCount;
+        private volatile int mChildrenLoadedWithOptionCount;
+        private volatile String mLastErrorId;
+        private volatile String mLastParentId;
+        private volatile Bundle mLastOptions;
+        private volatile List<MediaItem> mLastChildMediaItems;
+
+        public void reset(int count) {
+            mLatch = new CountDownLatch(count);
+            mChildrenLoadedCount = 0;
+            mChildrenLoadedWithOptionCount = 0;
+            mLastErrorId = null;
+            mLastParentId = null;
+            mLastOptions = null;
+            mLastChildMediaItems = null;
+        }
+
+        public boolean await(long timeoutMs) {
+            try {
+                return mLatch.await(timeoutMs, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                return false;
+            }
+        }
+
+        @Override
+        public void onChildrenLoaded(@NonNull String parentId, @NonNull List<MediaItem> children) {
+            mChildrenLoadedCount++;
+            mLastParentId = parentId;
+            mLastChildMediaItems = children;
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onChildrenLoaded(@NonNull String parentId, @NonNull List<MediaItem> children,
+                @NonNull Bundle options) {
+            mChildrenLoadedWithOptionCount++;
+            mLastParentId = parentId;
+            mLastOptions = options;
+            mLastChildMediaItems = children;
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onError(@NonNull String id) {
+            mLastErrorId = id;
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onError(@NonNull String id, @NonNull Bundle options) {
+            mLastErrorId = id;
+            mLastOptions = options;
+            mLatch.countDown();
+        }
+    }
+
+    private class StubItemCallback extends MediaBrowserCompat.ItemCallback {
+        final Object mWaitLock = new Object();
+        private volatile MediaItem mLastMediaItem;
+        private volatile String mLastErrorId;
+
+        public void reset() {
+            mLastMediaItem = null;
+            mLastErrorId = null;
+        }
+
+        @Override
+        public void onItemLoaded(MediaItem item) {
+            synchronized (mWaitLock) {
+                mLastMediaItem = item;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onError(@NonNull String id) {
+            synchronized (mWaitLock) {
+                mLastErrorId = id;
+                mWaitLock.notify();
+            }
+        }
+    }
+
+    private class StubSearchCallback extends MediaBrowserCompat.SearchCallback {
+        final Object mWaitLock = new Object();
+        boolean mOnSearchResult;
+        Bundle mSearchExtras;
+        List<MediaItem> mSearchResults;
+
+        @Override
+        public void onSearchResult(@NonNull String query, Bundle extras,
+                @NonNull List<MediaItem> items) {
+            synchronized (mWaitLock) {
+                mOnSearchResult = true;
+                mSearchResults = items;
+                mSearchExtras = extras;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onError(@NonNull String query, Bundle extras) {
+            synchronized (mWaitLock) {
+                mOnSearchResult = true;
+                mSearchResults = null;
+                mSearchExtras = extras;
+                mWaitLock.notify();
+            }
+        }
+
+        public void reset() {
+            mOnSearchResult = false;
+            mSearchExtras = null;
+            mSearchResults = null;
+        }
+    }
+
+    private class CustomActionCallback extends MediaBrowserCompat.CustomActionCallback {
+        final Object mWaitLock = new Object();
+        String mAction;
+        Bundle mExtras;
+        Bundle mData;
+        boolean mOnProgressUpdateCalled;
+        boolean mOnResultCalled;
+        boolean mOnErrorCalled;
+
+        @Override
+        public void onProgressUpdate(String action, Bundle extras, Bundle data) {
+            synchronized (mWaitLock) {
+                mOnProgressUpdateCalled = true;
+                mAction = action;
+                mExtras = extras;
+                mData = data;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onResult(String action, Bundle extras, Bundle resultData) {
+            synchronized (mWaitLock) {
+                mOnResultCalled = true;
+                mAction = action;
+                mExtras = extras;
+                mData = resultData;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onError(String action, Bundle extras, Bundle data) {
+            synchronized (mWaitLock) {
+                mOnErrorCalled = true;
+                mAction = action;
+                mExtras = extras;
+                mData = data;
+                mWaitLock.notify();
+            }
+        }
+
+        public void reset() {
+            mOnResultCalled = false;
+            mOnProgressUpdateCalled = false;
+            mOnErrorCalled = false;
+            mAction = null;
+            mExtras = null;
+            mData = null;
+        }
+    }
+
+    private class ConnectionCallbackForDelayedMediaSession extends
+            MediaBrowserCompat.ConnectionCallback {
+        final Object mWaitLock = new Object();
+        private int mConnectedCount = 0;
+
+        @Override
+        public void onConnected() {
+            synchronized (mWaitLock) {
+                mConnectedCount++;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onConnectionFailed() {
+            synchronized (mWaitLock) {
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onConnectionSuspended() {
+            synchronized (mWaitLock) {
+                mWaitLock.notify();
+            }
+        }
+    }
+}
diff --git a/media-compat/version-compat-tests/previous/client/tests/src/android/support/mediacompat/client/MediaControllerCompatCallbackTest.java b/media-compat/version-compat-tests/previous/client/tests/src/android/support/mediacompat/client/MediaControllerCompatCallbackTest.java
new file mode 100644
index 0000000..b173a4d
--- /dev/null
+++ b/media-compat/version-compat-tests/previous/client/tests/src/android/support/mediacompat/client/MediaControllerCompatCallbackTest.java
@@ -0,0 +1,773 @@
+/*
+ * 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.client;
+
+import static android.media.AudioManager.STREAM_MUSIC;
+import static android.support.mediacompat.testlib.MediaSessionConstants.RELEASE;
+import static android.support.mediacompat.testlib.MediaSessionConstants.SEND_SESSION_EVENT;
+import static android.support.mediacompat.testlib.MediaSessionConstants.SET_CAPTIONING_ENABLED;
+import static android.support.mediacompat.testlib.MediaSessionConstants.SET_EXTRAS;
+import static android.support.mediacompat.testlib.MediaSessionConstants.SET_FLAGS;
+import static android.support.mediacompat.testlib.MediaSessionConstants.SET_METADATA;
+import static android.support.mediacompat.testlib.MediaSessionConstants.SET_PLAYBACK_STATE;
+import static android.support.mediacompat.testlib.MediaSessionConstants.SET_PLAYBACK_TO_LOCAL;
+import static android.support.mediacompat.testlib.MediaSessionConstants.SET_PLAYBACK_TO_REMOTE;
+import static android.support.mediacompat.testlib.MediaSessionConstants.SET_QUEUE;
+import static android.support.mediacompat.testlib.MediaSessionConstants.SET_QUEUE_TITLE;
+import static android.support.mediacompat.testlib.MediaSessionConstants.SET_RATING_TYPE;
+import static android.support.mediacompat.testlib.MediaSessionConstants.SET_REPEAT_MODE;
+import static android.support.mediacompat.testlib.MediaSessionConstants.SET_SESSION_ACTIVITY;
+import static android.support.mediacompat.testlib.MediaSessionConstants.SET_SHUFFLE_MODE;
+import static android.support.mediacompat.testlib.MediaSessionConstants.TEST_ACTION;
+import static android.support.mediacompat.testlib.MediaSessionConstants.TEST_CURRENT_VOLUME;
+import static android.support.mediacompat.testlib.MediaSessionConstants.TEST_ERROR_CODE;
+import static android.support.mediacompat.testlib.MediaSessionConstants.TEST_ERROR_MSG;
+import static android.support.mediacompat.testlib.MediaSessionConstants.TEST_FLAGS;
+import static android.support.mediacompat.testlib.MediaSessionConstants.TEST_KEY;
+import static android.support.mediacompat.testlib.MediaSessionConstants.TEST_MAX_VOLUME;
+import static android.support.mediacompat.testlib.MediaSessionConstants.TEST_MEDIA_ID_1;
+import static android.support.mediacompat.testlib.MediaSessionConstants.TEST_MEDIA_ID_2;
+import static android.support.mediacompat.testlib.MediaSessionConstants.TEST_QUEUE_ID_1;
+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.mediacompat.testlib.util.IntentUtil.SERVICE_PACKAGE_NAME;
+import static android.support.mediacompat.testlib.util.IntentUtil.callMediaSessionMethod;
+import static android.support.mediacompat.testlib.util.TestUtil.assertBundleEquals;
+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;
+import static android.support.v4.media.MediaMetadataCompat.METADATA_KEY_RATING;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.media.AudioManager;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.support.mediacompat.testlib.util.PollingCheck;
+import android.support.test.filters.LargeTest;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v4.media.MediaBrowserCompat;
+import android.support.v4.media.MediaDescriptionCompat;
+import android.support.v4.media.MediaMetadataCompat;
+import android.support.v4.media.RatingCompat;
+import android.support.v4.media.VolumeProviderCompat;
+import android.support.v4.media.session.MediaControllerCompat;
+import android.support.v4.media.session.MediaSessionCompat;
+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 org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Test {@link MediaControllerCompat.Callback}.
+ */
+@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;
+
+    private static final ComponentName TEST_BROWSER_SERVICE = new ComponentName(
+            SERVICE_PACKAGE_NAME,
+            "android.support.mediacompat.service.StubMediaBrowserServiceCompat");
+
+    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();
+
+    private MediaSessionCompat.Token mSessionToken;
+    private MediaControllerCompat mController;
+    private MediaControllerCallback mMediaControllerCallback = new MediaControllerCallback();
+
+    @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() {
+                mMediaBrowser = new MediaBrowserCompat(getInstrumentation().getTargetContext(),
+                        TEST_BROWSER_SERVICE, mConnectionCallback, new Bundle());
+            }
+        });
+
+        synchronized (mConnectionCallback.mWaitLock) {
+            mMediaBrowser.connect();
+            mConnectionCallback.mWaitLock.wait(TIME_OUT_MS);
+            if (!mMediaBrowser.isConnected()) {
+                fail("Browser failed to connect!");
+            }
+        }
+        mSessionToken = mMediaBrowser.getSessionToken();
+        mController = new MediaControllerCompat(getTargetContext(), mSessionToken);
+        mController.registerCallback(mMediaControllerCallback, mHandler);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (mMediaBrowser != null && mMediaBrowser.isConnected()) {
+            mMediaBrowser.disconnect();
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void testGetPackageName() {
+        assertEquals(SERVICE_PACKAGE_NAME, mController.getPackageName());
+    }
+
+    @Test
+    @SmallTest
+    public void testIsSessionReady() throws Exception {
+        // mController already has the extra binder since it was created with the session token
+        // which holds the extra binder.
+        assertTrue(mController.isSessionReady());
+    }
+
+    /**
+     * Tests {@link MediaSessionCompat#setExtras}.
+     */
+    @Test
+    @SmallTest
+    public void testSetExtras() throws Exception {
+        synchronized (mWaitLock) {
+            mMediaControllerCallback.resetLocked();
+
+            Bundle extras = new Bundle();
+            extras.putString(TEST_KEY, TEST_VALUE);
+            callMediaSessionMethod(SET_EXTRAS, extras, getContext());
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mMediaControllerCallback.mOnExtraChangedCalled);
+
+            assertBundleEquals(extras, mMediaControllerCallback.mExtras);
+            assertBundleEquals(extras, mController.getExtras());
+        }
+    }
+
+    /**
+     * Tests {@link MediaSessionCompat#setFlags}.
+     */
+    @Test
+    @SmallTest
+    public void testSetFlags() throws Exception {
+        synchronized (mWaitLock) {
+            mMediaControllerCallback.resetLocked();
+
+            callMediaSessionMethod(SET_FLAGS, TEST_FLAGS, getContext());
+            new PollingCheck(TIME_OUT_MS) {
+                @Override
+                public boolean check() {
+                    return TEST_FLAGS == mController.getFlags();
+                }
+            }.run();
+        }
+    }
+
+    /**
+     * Tests {@link MediaSessionCompat#setMetadata}.
+     */
+    @Test
+    @SmallTest
+    public void testSetMetadata() throws Exception {
+        synchronized (mWaitLock) {
+            mMediaControllerCallback.resetLocked();
+            RatingCompat rating = RatingCompat.newHeartRating(true);
+            MediaMetadataCompat metadata = new MediaMetadataCompat.Builder()
+                    .putString(TEST_KEY, TEST_VALUE)
+                    .putRating(METADATA_KEY_RATING, rating)
+                    .build();
+
+            callMediaSessionMethod(SET_METADATA, metadata, getContext());
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mMediaControllerCallback.mOnMetadataChangedCalled);
+
+            MediaMetadataCompat metadataOut = mMediaControllerCallback.mMediaMetadata;
+            assertNotNull(metadataOut);
+            assertEquals(TEST_VALUE, metadataOut.getString(TEST_KEY));
+
+            metadataOut = mController.getMetadata();
+            assertNotNull(metadataOut);
+            assertEquals(TEST_VALUE, metadataOut.getString(TEST_KEY));
+
+            assertNotNull(metadataOut.getRating(METADATA_KEY_RATING));
+            RatingCompat ratingOut = metadataOut.getRating(METADATA_KEY_RATING);
+            assertEquals(rating.getRatingStyle(), ratingOut.getRatingStyle());
+            assertEquals(rating.getPercentRating(), ratingOut.getPercentRating(), 0.0f);
+        }
+    }
+
+    /**
+     * Tests {@link MediaSessionCompat#setMetadata} with artwork bitmaps.
+     */
+    @Test
+    @SmallTest
+    public void testSetMetadataWithArtworks() throws Exception {
+        // TODO: Add test with a large bitmap.
+        // Using large bitmap makes other tests that are executed after this fail.
+        final Bitmap bitmapSmall = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
+
+        synchronized (mWaitLock) {
+            mMediaControllerCallback.resetLocked();
+            MediaMetadataCompat metadata = new MediaMetadataCompat.Builder()
+                    .putString(TEST_KEY, TEST_VALUE)
+                    .putBitmap(MediaMetadataCompat.METADATA_KEY_ART, bitmapSmall)
+                    .build();
+
+            callMediaSessionMethod(SET_METADATA, metadata, getContext());
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mMediaControllerCallback.mOnMetadataChangedCalled);
+
+            MediaMetadataCompat metadataOut = mMediaControllerCallback.mMediaMetadata;
+            assertNotNull(metadataOut);
+            assertEquals(TEST_VALUE, metadataOut.getString(TEST_KEY));
+
+            Bitmap bitmapSmallOut = metadataOut.getBitmap(MediaMetadataCompat.METADATA_KEY_ART);
+            assertNotNull(bitmapSmallOut);
+            assertEquals(bitmapSmall.getHeight(), bitmapSmallOut.getHeight());
+            assertEquals(bitmapSmall.getWidth(), bitmapSmallOut.getWidth());
+            assertEquals(bitmapSmall.getConfig(), bitmapSmallOut.getConfig());
+
+            bitmapSmallOut.recycle();
+        }
+        bitmapSmall.recycle();
+    }
+
+    /**
+     * Tests {@link MediaSessionCompat#setPlaybackState}.
+     */
+    @Test
+    @SmallTest
+    public void testSetPlaybackState() throws Exception {
+        synchronized (mWaitLock) {
+            mMediaControllerCallback.resetLocked();
+            PlaybackStateCompat state =
+                    new PlaybackStateCompat.Builder()
+                            .setActions(TEST_ACTION)
+                            .setErrorMessage(TEST_ERROR_CODE, TEST_ERROR_MSG)
+                            .build();
+
+            callMediaSessionMethod(SET_PLAYBACK_STATE, state, getContext());
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mMediaControllerCallback.mOnPlaybackStateChangedCalled);
+
+            PlaybackStateCompat stateOut = mMediaControllerCallback.mPlaybackState;
+            assertNotNull(stateOut);
+            assertEquals(TEST_ACTION, stateOut.getActions());
+            assertEquals(TEST_ERROR_CODE, stateOut.getErrorCode());
+            assertEquals(TEST_ERROR_MSG, stateOut.getErrorMessage().toString());
+
+            stateOut = mController.getPlaybackState();
+            assertNotNull(stateOut);
+            assertEquals(TEST_ACTION, stateOut.getActions());
+            assertEquals(TEST_ERROR_CODE, stateOut.getErrorCode());
+            assertEquals(TEST_ERROR_MSG, stateOut.getErrorMessage().toString());
+        }
+    }
+
+    /**
+     * Tests {@link MediaSessionCompat#setQueue} and {@link MediaSessionCompat#setQueueTitle}.
+     */
+    @Test
+    @SmallTest
+    public void testSetQueueAndSetQueueTitle() throws Exception {
+        synchronized (mWaitLock) {
+            mMediaControllerCallback.resetLocked();
+            List<QueueItem> queue = new ArrayList<>();
+
+            MediaDescriptionCompat description1 =
+                    new MediaDescriptionCompat.Builder().setMediaId(TEST_MEDIA_ID_1).build();
+            MediaDescriptionCompat description2 =
+                    new MediaDescriptionCompat.Builder().setMediaId(TEST_MEDIA_ID_2).build();
+            QueueItem item1 = new MediaSessionCompat.QueueItem(description1, TEST_QUEUE_ID_1);
+            QueueItem item2 = new MediaSessionCompat.QueueItem(description2, TEST_QUEUE_ID_2);
+            queue.add(item1);
+            queue.add(item2);
+
+            callMediaSessionMethod(SET_QUEUE, queue, getContext());
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mMediaControllerCallback.mOnQueueChangedCalled);
+
+            callMediaSessionMethod(SET_QUEUE_TITLE, TEST_VALUE, getContext());
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mMediaControllerCallback.mOnQueueTitleChangedCalled);
+
+            assertEquals(TEST_VALUE, mMediaControllerCallback.mTitle);
+            assertQueueEquals(queue, mMediaControllerCallback.mQueue);
+
+            assertEquals(TEST_VALUE, mController.getQueueTitle());
+            assertQueueEquals(queue, mController.getQueue());
+
+            mMediaControllerCallback.resetLocked();
+            callMediaSessionMethod(SET_QUEUE, null, getContext());
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mMediaControllerCallback.mOnQueueChangedCalled);
+
+            callMediaSessionMethod(SET_QUEUE_TITLE, null, getContext());
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mMediaControllerCallback.mOnQueueTitleChangedCalled);
+
+            assertNull(mMediaControllerCallback.mTitle);
+            assertNull(mMediaControllerCallback.mQueue);
+            assertNull(mController.getQueueTitle());
+            assertNull(mController.getQueue());
+        }
+    }
+
+    /**
+     * Tests {@link MediaSessionCompat#setSessionActivity}.
+     */
+    @Test
+    @SmallTest
+    public void testSessionActivity() throws Exception {
+        synchronized (mWaitLock) {
+            Intent intent = new Intent("MEDIA_SESSION_ACTION");
+            final int requestCode = 555;
+            final PendingIntent pi =
+                    PendingIntent.getActivity(getTargetContext(), requestCode, intent, 0);
+
+            callMediaSessionMethod(SET_SESSION_ACTIVITY, pi, getContext());
+            new PollingCheck(TIME_OUT_MS) {
+                @Override
+                public boolean check() {
+                    return pi.equals(mController.getSessionActivity());
+                }
+            }.run();
+        }
+    }
+
+    /**
+     * Tests {@link MediaSessionCompat#setCaptioningEnabled}.
+     */
+    @Test
+    @SmallTest
+    public void testSetCaptioningEnabled() throws Exception {
+        synchronized (mWaitLock) {
+            mMediaControllerCallback.resetLocked();
+            callMediaSessionMethod(SET_CAPTIONING_ENABLED, true, getContext());
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mMediaControllerCallback.mOnCaptioningEnabledChangedCalled);
+            assertEquals(true, mMediaControllerCallback.mCaptioningEnabled);
+            assertEquals(true, mController.isCaptioningEnabled());
+
+            mMediaControllerCallback.resetLocked();
+            callMediaSessionMethod(SET_CAPTIONING_ENABLED, false, getContext());
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mMediaControllerCallback.mOnCaptioningEnabledChangedCalled);
+            assertEquals(false, mMediaControllerCallback.mCaptioningEnabled);
+            assertEquals(false, mController.isCaptioningEnabled());
+        }
+    }
+
+    /**
+     * Tests {@link MediaSessionCompat#setRepeatMode}.
+     */
+    @Test
+    @SmallTest
+    public void testSetRepeatMode() throws Exception {
+        synchronized (mWaitLock) {
+            mMediaControllerCallback.resetLocked();
+            final int repeatMode = PlaybackStateCompat.REPEAT_MODE_ALL;
+            callMediaSessionMethod(SET_REPEAT_MODE, repeatMode, getContext());
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mMediaControllerCallback.mOnRepeatModeChangedCalled);
+            assertEquals(repeatMode, mMediaControllerCallback.mRepeatMode);
+            assertEquals(repeatMode, mController.getRepeatMode());
+        }
+    }
+
+    /**
+     * Tests {@link MediaSessionCompat#setShuffleMode}.
+     */
+    @Test
+    @SmallTest
+    public void testSetShuffleMode() throws Exception {
+        final int shuffleMode = PlaybackStateCompat.SHUFFLE_MODE_ALL;
+        synchronized (mWaitLock) {
+            mMediaControllerCallback.resetLocked();
+            callMediaSessionMethod(SET_SHUFFLE_MODE, shuffleMode, getContext());
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mMediaControllerCallback.mOnShuffleModeChangedCalled);
+            assertEquals(shuffleMode, mMediaControllerCallback.mShuffleMode);
+            assertEquals(shuffleMode, mController.getShuffleMode());
+        }
+    }
+
+    /**
+     * Tests {@link MediaSessionCompat#sendSessionEvent}.
+     */
+    @Test
+    @SmallTest
+    public void testSendSessionEvent() throws Exception {
+        synchronized (mWaitLock) {
+            mMediaControllerCallback.resetLocked();
+
+            Bundle arguments = new Bundle();
+            arguments.putString("event", TEST_SESSION_EVENT);
+
+            Bundle extras = new Bundle();
+            extras.putString(TEST_KEY, TEST_VALUE);
+            arguments.putBundle("extras", extras);
+            callMediaSessionMethod(SEND_SESSION_EVENT, arguments, getContext());
+
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mMediaControllerCallback.mOnSessionEventCalled);
+            assertEquals(TEST_SESSION_EVENT, mMediaControllerCallback.mEvent);
+            assertBundleEquals(extras, mMediaControllerCallback.mExtras);
+        }
+    }
+
+    /**
+     * Tests {@link MediaSessionCompat#release}.
+     */
+    @Test
+    @SmallTest
+    public void testRelease() throws Exception {
+        synchronized (mWaitLock) {
+            mMediaControllerCallback.resetLocked();
+            callMediaSessionMethod(RELEASE, null, getContext());
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mMediaControllerCallback.mOnSessionDestroyedCalled);
+        }
+    }
+
+    /**
+     * Tests {@link MediaSessionCompat#setPlaybackToLocal} and
+     * {@link MediaSessionCompat#setPlaybackToRemote}.
+     */
+    @LargeTest
+    public void testPlaybackToLocalAndRemote() throws Exception {
+        synchronized (mWaitLock) {
+            mMediaControllerCallback.resetLocked();
+            ParcelableVolumeInfo volumeInfo = new ParcelableVolumeInfo(
+                    MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE,
+                    STREAM_MUSIC,
+                    VolumeProviderCompat.VOLUME_CONTROL_FIXED,
+                    TEST_MAX_VOLUME,
+                    TEST_CURRENT_VOLUME);
+
+            callMediaSessionMethod(SET_PLAYBACK_TO_REMOTE, volumeInfo, getContext());
+            MediaControllerCompat.PlaybackInfo info = null;
+            for (int i = 0; i < MAX_AUDIO_INFO_CHANGED_CALLBACK_COUNT; ++i) {
+                mMediaControllerCallback.mOnAudioInfoChangedCalled = false;
+                mWaitLock.wait(TIME_OUT_MS);
+                assertTrue(mMediaControllerCallback.mOnAudioInfoChangedCalled);
+                info = mMediaControllerCallback.mPlaybackInfo;
+                if (info != null && info.getCurrentVolume() == TEST_CURRENT_VOLUME
+                        && info.getMaxVolume() == TEST_MAX_VOLUME
+                        && info.getVolumeControl() == VolumeProviderCompat.VOLUME_CONTROL_FIXED
+                        && info.getPlaybackType()
+                        == MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE) {
+                    break;
+                }
+            }
+            assertNotNull(info);
+            assertEquals(MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE,
+                    info.getPlaybackType());
+            assertEquals(TEST_MAX_VOLUME, info.getMaxVolume());
+            assertEquals(TEST_CURRENT_VOLUME, info.getCurrentVolume());
+            assertEquals(VolumeProviderCompat.VOLUME_CONTROL_FIXED,
+                    info.getVolumeControl());
+
+            info = mController.getPlaybackInfo();
+            assertNotNull(info);
+            assertEquals(MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE,
+                    info.getPlaybackType());
+            assertEquals(TEST_MAX_VOLUME, info.getMaxVolume());
+            assertEquals(TEST_CURRENT_VOLUME, info.getCurrentVolume());
+            assertEquals(VolumeProviderCompat.VOLUME_CONTROL_FIXED, info.getVolumeControl());
+
+            // test setPlaybackToLocal
+            mMediaControllerCallback.mOnAudioInfoChangedCalled = false;
+            callMediaSessionMethod(SET_PLAYBACK_TO_LOCAL, AudioManager.STREAM_RING, getContext());
+
+            // In API 21 and 22, onAudioInfoChanged is not called.
+            if (Build.VERSION.SDK_INT == 21 || Build.VERSION.SDK_INT == 22) {
+                Thread.sleep(TIME_OUT_MS);
+            } else {
+                mWaitLock.wait(TIME_OUT_MS);
+                assertTrue(mMediaControllerCallback.mOnAudioInfoChangedCalled);
+            }
+
+            info = mController.getPlaybackInfo();
+            assertNotNull(info);
+            assertEquals(MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_LOCAL,
+                    info.getPlaybackType());
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void testGetRatingType() {
+        assertEquals("Default rating type of a session must be RatingCompat.RATING_NONE",
+                RatingCompat.RATING_NONE, mController.getRatingType());
+
+        callMediaSessionMethod(SET_RATING_TYPE, RatingCompat.RATING_5_STARS, getContext());
+        new PollingCheck(TIME_OUT_MS) {
+            @Override
+            public boolean check() {
+                return RatingCompat.RATING_5_STARS == mController.getRatingType();
+            }
+        }.run();
+    }
+
+    @Test
+    @SmallTest
+    public void testSessionReady() throws Exception {
+        if (android.os.Build.VERSION.SDK_INT < 21) {
+            return;
+        }
+
+        final MediaSessionCompat.Token tokenWithoutExtraBinder =
+                MediaSessionCompat.Token.fromToken(mSessionToken.getToken());
+
+        final MediaControllerCallback callback = new MediaControllerCallback();
+        synchronized (mWaitLock) {
+            getInstrumentation().runOnMainSync(new Runnable() {
+                @Override
+                public void run() {
+                    try {
+                        MediaControllerCompat controller = new MediaControllerCompat(
+                                getInstrumentation().getTargetContext(), tokenWithoutExtraBinder);
+                        controller.registerCallback(callback, new Handler());
+                        assertFalse(controller.isSessionReady());
+                    } catch (Exception e) {
+                        fail();
+                    }
+                }
+            });
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(callback.mOnSessionReadyCalled);
+        }
+    }
+
+    private void assertQueueEquals(List<QueueItem> expected, List<QueueItem> observed) {
+        if (expected == null || observed == null) {
+            assertTrue(expected == observed);
+            return;
+        }
+
+        assertEquals(expected.size(), observed.size());
+        for (int i = 0; i < expected.size(); i++) {
+            QueueItem expectedItem = expected.get(i);
+            QueueItem observedItem = observed.get(i);
+
+            assertEquals(expectedItem.getQueueId(), observedItem.getQueueId());
+            assertEquals(expectedItem.getDescription().getMediaId(),
+                    observedItem.getDescription().getMediaId());
+        }
+    }
+
+    private class MediaControllerCallback extends MediaControllerCompat.Callback {
+        private volatile boolean mOnPlaybackStateChangedCalled;
+        private volatile boolean mOnMetadataChangedCalled;
+        private volatile boolean mOnQueueChangedCalled;
+        private volatile boolean mOnQueueTitleChangedCalled;
+        private volatile boolean mOnExtraChangedCalled;
+        private volatile boolean mOnAudioInfoChangedCalled;
+        private volatile boolean mOnSessionDestroyedCalled;
+        private volatile boolean mOnSessionEventCalled;
+        private volatile boolean mOnCaptioningEnabledChangedCalled;
+        private volatile boolean mOnRepeatModeChangedCalled;
+        private volatile boolean mOnShuffleModeChangedCalled;
+        private volatile boolean mOnSessionReadyCalled;
+
+        private volatile PlaybackStateCompat mPlaybackState;
+        private volatile MediaMetadataCompat mMediaMetadata;
+        private volatile List<QueueItem> mQueue;
+        private volatile CharSequence mTitle;
+        private volatile String mEvent;
+        private volatile Bundle mExtras;
+        private volatile MediaControllerCompat.PlaybackInfo mPlaybackInfo;
+        private volatile boolean mCaptioningEnabled;
+        private volatile int mRepeatMode;
+        private volatile int mShuffleMode;
+
+        public void resetLocked() {
+            mOnPlaybackStateChangedCalled = false;
+            mOnMetadataChangedCalled = false;
+            mOnQueueChangedCalled = false;
+            mOnQueueTitleChangedCalled = false;
+            mOnExtraChangedCalled = false;
+            mOnAudioInfoChangedCalled = false;
+            mOnSessionDestroyedCalled = false;
+            mOnSessionEventCalled = false;
+            mOnRepeatModeChangedCalled = false;
+            mOnShuffleModeChangedCalled = false;
+
+            mPlaybackState = null;
+            mMediaMetadata = null;
+            mQueue = null;
+            mTitle = null;
+            mExtras = null;
+            mPlaybackInfo = null;
+            mCaptioningEnabled = false;
+            mRepeatMode = PlaybackStateCompat.REPEAT_MODE_NONE;
+            mShuffleMode = PlaybackStateCompat.SHUFFLE_MODE_NONE;
+        }
+
+        @Override
+        public void onPlaybackStateChanged(PlaybackStateCompat state) {
+            synchronized (mWaitLock) {
+                mOnPlaybackStateChangedCalled = true;
+                mPlaybackState = state;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onMetadataChanged(MediaMetadataCompat metadata) {
+            synchronized (mWaitLock) {
+                mOnMetadataChangedCalled = true;
+                mMediaMetadata = metadata;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onQueueChanged(List<QueueItem> queue) {
+            synchronized (mWaitLock) {
+                mOnQueueChangedCalled = true;
+                mQueue = queue;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onQueueTitleChanged(CharSequence title) {
+            synchronized (mWaitLock) {
+                mOnQueueTitleChangedCalled = true;
+                mTitle = title;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onExtrasChanged(Bundle extras) {
+            synchronized (mWaitLock) {
+                mOnExtraChangedCalled = true;
+                mExtras = extras;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onAudioInfoChanged(MediaControllerCompat.PlaybackInfo info) {
+            synchronized (mWaitLock) {
+                mOnAudioInfoChangedCalled = true;
+                mPlaybackInfo = info;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onSessionDestroyed() {
+            synchronized (mWaitLock) {
+                mOnSessionDestroyedCalled = true;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onSessionEvent(String event, Bundle extras) {
+            synchronized (mWaitLock) {
+                mOnSessionEventCalled = true;
+                mEvent = event;
+                mExtras = (Bundle) extras.clone();
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onCaptioningEnabledChanged(boolean enabled) {
+            synchronized (mWaitLock) {
+                mOnCaptioningEnabledChangedCalled = true;
+                mCaptioningEnabled = enabled;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onRepeatModeChanged(int repeatMode) {
+            synchronized (mWaitLock) {
+                mOnRepeatModeChangedCalled = true;
+                mRepeatMode = repeatMode;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onShuffleModeChanged(int shuffleMode) {
+            synchronized (mWaitLock) {
+                mOnShuffleModeChangedCalled = true;
+                mShuffleMode = shuffleMode;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onSessionReady() {
+            synchronized (mWaitLock) {
+                mOnSessionReadyCalled = true;
+                mWaitLock.notify();
+            }
+        }
+    }
+
+    private class ConnectionCallback extends MediaBrowserCompat.ConnectionCallback {
+        final Object mWaitLock = new Object();
+
+        @Override
+        public void onConnected() {
+            synchronized (mWaitLock) {
+                mWaitLock.notify();
+            }
+        }
+    }
+}
diff --git a/media-compat/version-compat-tests/previous/client/tests/src/android/support/mediacompat/client/MediaItemTest.java b/media-compat/version-compat-tests/previous/client/tests/src/android/support/mediacompat/client/MediaItemTest.java
new file mode 100644
index 0000000..179a178
--- /dev/null
+++ b/media-compat/version-compat-tests/previous/client/tests/src/android/support/mediacompat/client/MediaItemTest.java
@@ -0,0 +1,96 @@
+/*
+ * 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.client;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
+import android.support.test.filters.SmallTest;
+import android.support.v4.media.MediaBrowserCompat.MediaItem;
+import android.support.v4.media.MediaDescriptionCompat;
+
+import org.junit.Test;
+
+/**
+ * Test {@link MediaItem}.
+ */
+public class MediaItemTest {
+    private static final String DESCRIPTION = "test_description";
+    private static final String MEDIA_ID = "test_media_id";
+    private static final String TITLE = "test_title";
+    private static final String SUBTITLE = "test_subtitle";
+
+    @Test
+    @SmallTest
+    public void testBrowsableMediaItem() {
+        MediaDescriptionCompat description =
+                new MediaDescriptionCompat.Builder()
+                        .setDescription(DESCRIPTION)
+                        .setMediaId(MEDIA_ID)
+                        .setTitle(TITLE)
+                        .setSubtitle(SUBTITLE)
+                        .build();
+        MediaItem mediaItem = new MediaItem(description, MediaItem.FLAG_BROWSABLE);
+
+        assertEquals(description.toString(), mediaItem.getDescription().toString());
+        assertEquals(MEDIA_ID, mediaItem.getMediaId());
+        assertEquals(MediaItem.FLAG_BROWSABLE, mediaItem.getFlags());
+        assertTrue(mediaItem.isBrowsable());
+        assertFalse(mediaItem.isPlayable());
+        assertEquals(0, mediaItem.describeContents());
+
+        // Test writeToParcel
+        Parcel p = Parcel.obtain();
+        mediaItem.writeToParcel(p, 0);
+        p.setDataPosition(0);
+        assertEquals(mediaItem.getFlags(), p.readInt());
+        assertEquals(
+                description.toString(),
+                MediaDescriptionCompat.CREATOR.createFromParcel(p).toString());
+        p.recycle();
+    }
+
+    @Test
+    @SmallTest
+    public void testPlayableMediaItem() {
+        MediaDescriptionCompat description = new MediaDescriptionCompat.Builder()
+                .setDescription(DESCRIPTION)
+                .setMediaId(MEDIA_ID)
+                .setTitle(TITLE)
+                .setSubtitle(SUBTITLE)
+                .build();
+        MediaItem mediaItem = new MediaItem(description, MediaItem.FLAG_PLAYABLE);
+
+        assertEquals(description.toString(), mediaItem.getDescription().toString());
+        assertEquals(MEDIA_ID, mediaItem.getMediaId());
+        assertEquals(MediaItem.FLAG_PLAYABLE, mediaItem.getFlags());
+        assertFalse(mediaItem.isBrowsable());
+        assertTrue(mediaItem.isPlayable());
+        assertEquals(0, mediaItem.describeContents());
+
+        // Test writeToParcel
+        Parcel p = Parcel.obtain();
+        mediaItem.writeToParcel(p, 0);
+        p.setDataPosition(0);
+        assertEquals(mediaItem.getFlags(), p.readInt());
+        assertEquals(
+                description.toString(),
+                MediaDescriptionCompat.CREATOR.createFromParcel(p).toString());
+        p.recycle();
+    }
+}
diff --git a/media-compat/version-compat-tests/previous/client/tests/src/android/support/mediacompat/client/PlaybackStateCompatTest.java b/media-compat/version-compat-tests/previous/client/tests/src/android/support/mediacompat/client/PlaybackStateCompatTest.java
new file mode 100644
index 0000000..7962731
--- /dev/null
+++ b/media-compat/version-compat-tests/previous/client/tests/src/android/support/mediacompat/client/PlaybackStateCompatTest.java
@@ -0,0 +1,298 @@
+/*
+ * 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.client;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v4.media.session.MediaSessionCompat;
+import android.support.v4.media.session.PlaybackStateCompat;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+
+/**
+ * Test {@link PlaybackStateCompat}.
+ */
+@RunWith(AndroidJUnit4.class)
+public class PlaybackStateCompatTest {
+
+    private static final long TEST_POSITION = 20000L;
+    private static final long TEST_BUFFERED_POSITION = 15000L;
+    private static final long TEST_UPDATE_TIME = 100000L;
+    private static final long TEST_ACTIONS = PlaybackStateCompat.ACTION_PLAY
+            | PlaybackStateCompat.ACTION_STOP | PlaybackStateCompat.ACTION_SEEK_TO;
+    private static final long TEST_QUEUE_ITEM_ID = 23L;
+    private static final float TEST_PLAYBACK_SPEED = 3.0f;
+    private static final float TEST_PLAYBACK_SPEED_ON_REWIND = -2.0f;
+    private static final float DELTA = 1e-7f;
+
+    private static final int TEST_ERROR_CODE =
+            PlaybackStateCompat.ERROR_CODE_AUTHENTICATION_EXPIRED;
+    private static final String TEST_ERROR_MSG = "test-error-msg";
+    private static final String TEST_CUSTOM_ACTION = "test-custom-action";
+    private static final String TEST_CUSTOM_ACTION_NAME = "test-custom-action-name";
+    private static final int TEST_ICON_RESOURCE_ID = android.R.drawable.ic_media_next;
+
+    private static final String EXTRAS_KEY = "test-key";
+    private static final String EXTRAS_VALUE = "test-value";
+
+    /**
+     * Test default values of {@link PlaybackStateCompat}.
+     */
+    @Test
+    @SmallTest
+    public void testBuilder() {
+        PlaybackStateCompat state = new PlaybackStateCompat.Builder().build();
+
+        assertEquals(new ArrayList<PlaybackStateCompat.CustomAction>(), state.getCustomActions());
+        assertEquals(0, state.getState());
+        assertEquals(0L, state.getPosition());
+        assertEquals(0L, state.getBufferedPosition());
+        assertEquals(0.0f, state.getPlaybackSpeed(), DELTA);
+        assertEquals(0L, state.getActions());
+        assertEquals(0, state.getErrorCode());
+        assertNull(state.getErrorMessage());
+        assertEquals(0L, state.getLastPositionUpdateTime());
+        assertEquals(MediaSessionCompat.QueueItem.UNKNOWN_ID, state.getActiveQueueItemId());
+        assertNull(state.getExtras());
+    }
+
+    /**
+     * Test following setter methods of {@link PlaybackStateCompat.Builder}:
+     * {@link PlaybackStateCompat.Builder#setState(int, long, float)}
+     * {@link PlaybackStateCompat.Builder#setActions(long)}
+     * {@link PlaybackStateCompat.Builder#setActiveQueueItemId(long)}
+     * {@link PlaybackStateCompat.Builder#setBufferedPosition(long)}
+     * {@link PlaybackStateCompat.Builder#setErrorMessage(CharSequence)}
+     * {@link PlaybackStateCompat.Builder#setExtras(Bundle)}
+     */
+    @Test
+    @SmallTest
+    public void testBuilder_setterMethods() {
+        Bundle extras = new Bundle();
+        extras.putString(EXTRAS_KEY, EXTRAS_VALUE);
+
+        PlaybackStateCompat state = new PlaybackStateCompat.Builder()
+                .setState(PlaybackStateCompat.STATE_PLAYING, TEST_POSITION, TEST_PLAYBACK_SPEED)
+                .setActions(TEST_ACTIONS)
+                .setActiveQueueItemId(TEST_QUEUE_ITEM_ID)
+                .setBufferedPosition(TEST_BUFFERED_POSITION)
+                .setErrorMessage(TEST_ERROR_CODE, TEST_ERROR_MSG)
+                .setExtras(extras)
+                .build();
+        assertEquals(PlaybackStateCompat.STATE_PLAYING, state.getState());
+        assertEquals(TEST_POSITION, state.getPosition());
+        assertEquals(TEST_PLAYBACK_SPEED, state.getPlaybackSpeed(), DELTA);
+        assertEquals(TEST_ACTIONS, state.getActions());
+        assertEquals(TEST_QUEUE_ITEM_ID, state.getActiveQueueItemId());
+        assertEquals(TEST_BUFFERED_POSITION, state.getBufferedPosition());
+        assertEquals(TEST_ERROR_CODE, state.getErrorCode());
+        assertEquals(TEST_ERROR_MSG, state.getErrorMessage().toString());
+        assertNotNull(state.getExtras());
+        assertEquals(EXTRAS_VALUE, state.getExtras().get(EXTRAS_KEY));
+    }
+
+    /**
+     * Test {@link PlaybackStateCompat.Builder#setState(int, long, float, long)}.
+     */
+    @Test
+    @SmallTest
+    public void testBuilder_setStateWithUpdateTime() {
+        PlaybackStateCompat state = new PlaybackStateCompat.Builder()
+                .setState(
+                        PlaybackStateCompat.STATE_REWINDING,
+                        TEST_POSITION,
+                        TEST_PLAYBACK_SPEED_ON_REWIND,
+                        TEST_UPDATE_TIME)
+                .build();
+        assertEquals(PlaybackStateCompat.STATE_REWINDING, state.getState());
+        assertEquals(TEST_POSITION, state.getPosition());
+        assertEquals(TEST_PLAYBACK_SPEED_ON_REWIND, state.getPlaybackSpeed(), DELTA);
+        assertEquals(TEST_UPDATE_TIME, state.getLastPositionUpdateTime());
+    }
+
+    /**
+     * Test {@link PlaybackStateCompat.Builder#addCustomAction(String, String, int)}.
+     */
+    @Test
+    @SmallTest
+    public void testBuilder_addCustomAction() {
+        ArrayList<PlaybackStateCompat.CustomAction> actions = new ArrayList<>();
+        PlaybackStateCompat.Builder builder = new PlaybackStateCompat.Builder();
+
+        for (int i = 0; i < 5; i++) {
+            actions.add(new PlaybackStateCompat.CustomAction.Builder(
+                    TEST_CUSTOM_ACTION + i, TEST_CUSTOM_ACTION_NAME + i, TEST_ICON_RESOURCE_ID + i)
+                    .build());
+            builder.addCustomAction(
+                    TEST_CUSTOM_ACTION + i, TEST_CUSTOM_ACTION_NAME + i, TEST_ICON_RESOURCE_ID + i);
+        }
+
+        PlaybackStateCompat state = builder.build();
+        assertEquals(actions.size(), state.getCustomActions().size());
+        for (int i = 0; i < actions.size(); i++) {
+            assertCustomActionEquals(actions.get(i), state.getCustomActions().get(i));
+        }
+    }
+
+    /**
+     * Test {@link PlaybackStateCompat.Builder#addCustomAction(PlaybackStateCompat.CustomAction)}.
+     */
+    @Test
+    @SmallTest
+    public void testBuilder_addCustomActionWithCustomActionObject() {
+        Bundle extras = new Bundle();
+        extras.putString(EXTRAS_KEY, EXTRAS_VALUE);
+
+        ArrayList<PlaybackStateCompat.CustomAction> actions = new ArrayList<>();
+        PlaybackStateCompat.Builder builder = new PlaybackStateCompat.Builder();
+
+        for (int i = 0; i < 5; i++) {
+            actions.add(new PlaybackStateCompat.CustomAction.Builder(
+                    TEST_CUSTOM_ACTION + i, TEST_CUSTOM_ACTION_NAME + i, TEST_ICON_RESOURCE_ID + i)
+                    .setExtras(extras)
+                    .build());
+            builder.addCustomAction(new PlaybackStateCompat.CustomAction.Builder(
+                    TEST_CUSTOM_ACTION + i, TEST_CUSTOM_ACTION_NAME + i, TEST_ICON_RESOURCE_ID + i)
+                    .setExtras(extras)
+                    .build());
+        }
+
+        PlaybackStateCompat state = builder.build();
+        assertEquals(actions.size(), state.getCustomActions().size());
+        for (int i = 0; i < actions.size(); i++) {
+            assertCustomActionEquals(actions.get(i), state.getCustomActions().get(i));
+        }
+    }
+
+    /**
+     * Test {@link PlaybackStateCompat#writeToParcel(Parcel, int)}.
+     */
+    @Test
+    @SmallTest
+    public void testWriteToParcel() {
+        Bundle extras = new Bundle();
+        extras.putString(EXTRAS_KEY, EXTRAS_VALUE);
+
+        PlaybackStateCompat.Builder builder =
+                new PlaybackStateCompat.Builder()
+                        .setState(PlaybackStateCompat.STATE_CONNECTING, TEST_POSITION,
+                                TEST_PLAYBACK_SPEED, TEST_UPDATE_TIME)
+                        .setActions(TEST_ACTIONS)
+                        .setActiveQueueItemId(TEST_QUEUE_ITEM_ID)
+                        .setBufferedPosition(TEST_BUFFERED_POSITION)
+                        .setErrorMessage(TEST_ERROR_CODE, TEST_ERROR_MSG)
+                        .setExtras(extras);
+
+        for (int i = 0; i < 5; i++) {
+            builder.addCustomAction(
+                    new PlaybackStateCompat.CustomAction.Builder(
+                            TEST_CUSTOM_ACTION + i,
+                            TEST_CUSTOM_ACTION_NAME + i,
+                            TEST_ICON_RESOURCE_ID + i)
+                            .setExtras(extras)
+                            .build());
+        }
+        PlaybackStateCompat state = builder.build();
+
+        Parcel parcel = Parcel.obtain();
+        state.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+
+        PlaybackStateCompat stateOut = PlaybackStateCompat.CREATOR.createFromParcel(parcel);
+        assertEquals(PlaybackStateCompat.STATE_CONNECTING, stateOut.getState());
+        assertEquals(TEST_POSITION, stateOut.getPosition());
+        assertEquals(TEST_PLAYBACK_SPEED, stateOut.getPlaybackSpeed(), DELTA);
+        assertEquals(TEST_UPDATE_TIME, stateOut.getLastPositionUpdateTime());
+        assertEquals(TEST_BUFFERED_POSITION, stateOut.getBufferedPosition());
+        assertEquals(TEST_ACTIONS, stateOut.getActions());
+        assertEquals(TEST_QUEUE_ITEM_ID, stateOut.getActiveQueueItemId());
+        assertEquals(TEST_ERROR_CODE, stateOut.getErrorCode());
+        assertEquals(TEST_ERROR_MSG, stateOut.getErrorMessage());
+        assertNotNull(stateOut.getExtras());
+        assertEquals(EXTRAS_VALUE, stateOut.getExtras().get(EXTRAS_KEY));
+
+        assertEquals(state.getCustomActions().size(), stateOut.getCustomActions().size());
+        for (int i = 0; i < state.getCustomActions().size(); i++) {
+            assertCustomActionEquals(
+                    state.getCustomActions().get(i), stateOut.getCustomActions().get(i));
+        }
+        parcel.recycle();
+    }
+
+    /**
+     * Test {@link PlaybackStateCompat#describeContents()}.
+     */
+    @Test
+    @SmallTest
+    public void testDescribeContents() {
+        assertEquals(0, new PlaybackStateCompat.Builder().build().describeContents());
+    }
+
+    /**
+     * Test {@link PlaybackStateCompat.CustomAction}.
+     */
+    @Test
+    @SmallTest
+    public void testCustomAction() {
+        Bundle extras = new Bundle();
+        extras.putString(EXTRAS_KEY, EXTRAS_VALUE);
+
+        // Test Builder/Getters
+        PlaybackStateCompat.CustomAction customAction = new PlaybackStateCompat.CustomAction
+                .Builder(TEST_CUSTOM_ACTION, TEST_CUSTOM_ACTION_NAME, TEST_ICON_RESOURCE_ID)
+                .setExtras(extras)
+                .build();
+        assertEquals(TEST_CUSTOM_ACTION, customAction.getAction());
+        assertEquals(TEST_CUSTOM_ACTION_NAME, customAction.getName().toString());
+        assertEquals(TEST_ICON_RESOURCE_ID, customAction.getIcon());
+        assertEquals(EXTRAS_VALUE, customAction.getExtras().get(EXTRAS_KEY));
+
+        // Test describeContents
+        assertEquals(0, customAction.describeContents());
+
+        // Test writeToParcel
+        Parcel parcel = Parcel.obtain();
+        customAction.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+
+        assertCustomActionEquals(
+                customAction, PlaybackStateCompat.CustomAction.CREATOR.createFromParcel(parcel));
+        parcel.recycle();
+    }
+
+    private void assertCustomActionEquals(PlaybackStateCompat.CustomAction action1,
+            PlaybackStateCompat.CustomAction action2) {
+        assertEquals(action1.getAction(), action2.getAction());
+        assertEquals(action1.getName(), action2.getName());
+        assertEquals(action1.getIcon(), action2.getIcon());
+
+        // To be the same, two extras should be both null or both not null.
+        assertEquals(action1.getExtras() != null, action2.getExtras() != null);
+        if (action1.getExtras() != null) {
+            assertEquals(action1.getExtras().get(EXTRAS_KEY), action2.getExtras().get(EXTRAS_KEY));
+        }
+    }
+}
diff --git a/media-compat/version-compat-tests/previous/service/AndroidManifest.xml b/media-compat/version-compat-tests/previous/service/AndroidManifest.xml
new file mode 100644
index 0000000..5e25a83
--- /dev/null
+++ b/media-compat/version-compat-tests/previous/service/AndroidManifest.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+   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.
+-->
+<manifest package="android.support.mediacompat.service"/>
diff --git a/media-compat/version-compat-tests/previous/service/build.gradle b/media-compat/version-compat-tests/previous/service/build.gradle
new file mode 100644
index 0000000..469f6e4
--- /dev/null
+++ b/media-compat/version-compat-tests/previous/service/build.gradle
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+
+import static android.support.dependencies.DependenciesKt.*
+
+plugins {
+    id("SupportAndroidLibraryPlugin")
+}
+
+dependencies {
+    androidTestImplementation project(':support-media-compat-test-lib')
+    androidTestImplementation "com.android.support:support-media-compat:27.0.1"
+
+    androidTestImplementation(TEST_RUNNER)
+}
+
+android {
+    defaultConfig {
+        minSdkVersion 14
+    }
+}
+
+supportLibrary {
+    legacySourceLocation = true
+}
diff --git a/media-compat/version-compat-tests/previous/service/lint-baseline.xml b/media-compat/version-compat-tests/previous/service/lint-baseline.xml
new file mode 100644
index 0000000..ed7ade1
--- /dev/null
+++ b/media-compat/version-compat-tests/previous/service/lint-baseline.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+   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.
+-->
+<issues format="4" by="lint 3.0.0-alpha9">
+
+</issues>
diff --git a/media-compat/version-compat-tests/previous/service/tests/AndroidManifest.xml b/media-compat/version-compat-tests/previous/service/tests/AndroidManifest.xml
new file mode 100644
index 0000000..b47eecf
--- /dev/null
+++ b/media-compat/version-compat-tests/previous/service/tests/AndroidManifest.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+   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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.support.mediacompat.service.test">
+    <application>
+        <receiver android:name="android.support.mediacompat.service.ServiceBroadcastReceiver">
+            <intent-filter>
+                <action android:name="android.support.mediacompat.service.action.CALL_MEDIA_BROWSER_SERVICE_METHOD"/>
+                <action android:name="android.support.mediacompat.service.action.CALL_MEDIA_SESSION_METHOD"/>
+            </intent-filter>
+        </receiver>
+
+        <receiver android:name="android.support.v4.media.session.MediaButtonReceiver" >
+            <intent-filter>
+                <action android:name="android.intent.action.MEDIA_BUTTON" />
+            </intent-filter>
+        </receiver>
+
+        <service android:name="android.support.mediacompat.service.StubMediaBrowserServiceCompat">
+            <intent-filter>
+                <action android:name="android.media.browse.MediaBrowserService"/>
+            </intent-filter>
+        </service>
+
+        <service android:name="android.support.mediacompat.service.StubMediaBrowserServiceCompatWithDelayedMediaSession">
+            <intent-filter>
+                <action android:name="android.media.browse.MediaBrowserService"/>
+            </intent-filter>
+        </service>
+    </application>
+</manifest>
diff --git a/media-compat/version-compat-tests/previous/service/tests/NO_DOCS b/media-compat/version-compat-tests/previous/service/tests/NO_DOCS
new file mode 100644
index 0000000..61c9b1a
--- /dev/null
+++ b/media-compat/version-compat-tests/previous/service/tests/NO_DOCS
@@ -0,0 +1,17 @@
+# 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.
+
+Having this file, named NO_DOCS, in a directory will prevent
+Android javadocs from being generated for java files under
+the directory. This is especially useful for test projects.
diff --git a/media-compat/version-compat-tests/previous/service/tests/src/android/support/mediacompat/service/MediaSessionCompatCallbackTest.java b/media-compat/version-compat-tests/previous/service/tests/src/android/support/mediacompat/service/MediaSessionCompatCallbackTest.java
new file mode 100644
index 0000000..5c5a432
--- /dev/null
+++ b/media-compat/version-compat-tests/previous/service/tests/src/android/support/mediacompat/service/MediaSessionCompatCallbackTest.java
@@ -0,0 +1,1098 @@
+/*
+ * 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.service;
+
+import static android.support.mediacompat.testlib.MediaControllerConstants.ADD_QUEUE_ITEM;
+import static android.support.mediacompat.testlib.MediaControllerConstants
+        .ADD_QUEUE_ITEM_WITH_INDEX;
+import static android.support.mediacompat.testlib.MediaControllerConstants.ADJUST_VOLUME;
+import static android.support.mediacompat.testlib.MediaControllerConstants.FAST_FORWARD;
+import static android.support.mediacompat.testlib.MediaControllerConstants.PAUSE;
+import static android.support.mediacompat.testlib.MediaControllerConstants.PLAY;
+import static android.support.mediacompat.testlib.MediaControllerConstants.PLAY_FROM_MEDIA_ID;
+import static android.support.mediacompat.testlib.MediaControllerConstants.PLAY_FROM_SEARCH;
+import static android.support.mediacompat.testlib.MediaControllerConstants.PLAY_FROM_URI;
+import static android.support.mediacompat.testlib.MediaControllerConstants.PREPARE;
+import static android.support.mediacompat.testlib.MediaControllerConstants.PREPARE_FROM_MEDIA_ID;
+import static android.support.mediacompat.testlib.MediaControllerConstants.PREPARE_FROM_SEARCH;
+import static android.support.mediacompat.testlib.MediaControllerConstants.PREPARE_FROM_URI;
+import static android.support.mediacompat.testlib.MediaControllerConstants.REMOVE_QUEUE_ITEM;
+import static android.support.mediacompat.testlib.MediaControllerConstants.REWIND;
+import static android.support.mediacompat.testlib.MediaControllerConstants.SEEK_TO;
+import static android.support.mediacompat.testlib.MediaControllerConstants.SEND_COMMAND;
+import static android.support.mediacompat.testlib.MediaControllerConstants.SEND_CUSTOM_ACTION;
+import static android.support.mediacompat.testlib.MediaControllerConstants
+        .SEND_CUSTOM_ACTION_PARCELABLE;
+import static android.support.mediacompat.testlib.MediaControllerConstants.SET_CAPTIONING_ENABLED;
+import static android.support.mediacompat.testlib.MediaControllerConstants.SET_RATING;
+import static android.support.mediacompat.testlib.MediaControllerConstants.SET_REPEAT_MODE;
+import static android.support.mediacompat.testlib.MediaControllerConstants.SET_SHUFFLE_MODE;
+import static android.support.mediacompat.testlib.MediaControllerConstants.SET_VOLUME_TO;
+import static android.support.mediacompat.testlib.MediaControllerConstants.SKIP_TO_NEXT;
+import static android.support.mediacompat.testlib.MediaControllerConstants.SKIP_TO_PREVIOUS;
+import static android.support.mediacompat.testlib.MediaControllerConstants.SKIP_TO_QUEUE_ITEM;
+import static android.support.mediacompat.testlib.MediaControllerConstants.STOP;
+import static android.support.mediacompat.testlib.MediaSessionConstants.TEST_COMMAND;
+import static android.support.mediacompat.testlib.MediaSessionConstants.TEST_KEY;
+import static android.support.mediacompat.testlib.MediaSessionConstants.TEST_MEDIA_ID_1;
+import static android.support.mediacompat.testlib.MediaSessionConstants.TEST_MEDIA_ID_2;
+import static android.support.mediacompat.testlib.MediaSessionConstants.TEST_MEDIA_TITLE_1;
+import static android.support.mediacompat.testlib.MediaSessionConstants.TEST_MEDIA_TITLE_2;
+import static android.support.mediacompat.testlib.MediaSessionConstants.TEST_QUEUE_ID_1;
+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.mediacompat.testlib.util.IntentUtil.callMediaControllerMethod;
+import static android.support.mediacompat.testlib.util.IntentUtil.callTransportControlsMethod;
+import static android.support.mediacompat.testlib.util.TestUtil.assertBundleEquals;
+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;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.media.AudioManager;
+import android.media.session.MediaController;
+import android.media.session.MediaSession;
+import android.media.session.PlaybackState;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Parcel;
+import android.os.ResultReceiver;
+import android.os.SystemClock;
+import android.support.test.filters.MediumTest;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v4.media.MediaDescriptionCompat;
+import android.support.v4.media.RatingCompat;
+import android.support.v4.media.VolumeProviderCompat;
+import android.support.v4.media.session.MediaControllerCompat;
+import android.support.v4.media.session.MediaSessionCompat;
+import android.support.v4.media.session.PlaybackStateCompat;
+import android.util.Log;
+import android.view.KeyEvent;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Test {@link MediaSessionCompat.Callback}.
+ */
+@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 long WAIT_TIME_FOR_NO_RESPONSE_MS = 300L;
+
+    private static final long TEST_POSITION = 1000000L;
+    private static final float TEST_PLAYBACK_SPEED = 3.0f;
+    private static final float DELTA = 1e-4f;
+    private static final boolean ENABLED = true;
+
+    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();
+    private AudioManager mAudioManager;
+
+    @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() {
+                mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
+                mSession = new MediaSessionCompat(getTargetContext(), TEST_SESSION_TAG);
+                mSession.setCallback(mCallback, mHandler);
+            }
+        });
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mSession.release();
+    }
+
+    /**
+     * Tests that a session can be created and that all the fields are initialized correctly.
+     */
+    @Test
+    @SmallTest
+    public void testCreateSession() throws Exception {
+        assertNotNull(mSession.getSessionToken());
+        assertFalse("New session should not be active", mSession.isActive());
+
+        // Verify by getting the controller and checking all its fields
+        MediaControllerCompat controller = mSession.getController();
+        assertNotNull(controller);
+
+        final String errorMsg = "New session has unexpected configuration.";
+        assertEquals(errorMsg, 0L, controller.getFlags());
+        assertNull(errorMsg, controller.getExtras());
+        assertNull(errorMsg, controller.getMetadata());
+        assertEquals(errorMsg, getContext().getPackageName(), controller.getPackageName());
+        assertNull(errorMsg, controller.getPlaybackState());
+        assertNull(errorMsg, controller.getQueue());
+        assertNull(errorMsg, controller.getQueueTitle());
+        assertEquals(errorMsg, RatingCompat.RATING_NONE, controller.getRatingType());
+        assertNull(errorMsg, controller.getSessionActivity());
+
+        assertNotNull(controller.getSessionToken());
+        assertNotNull(controller.getTransportControls());
+
+        MediaControllerCompat.PlaybackInfo info = controller.getPlaybackInfo();
+        assertNotNull(info);
+        assertEquals(errorMsg, MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_LOCAL,
+                info.getPlaybackType());
+        assertEquals(errorMsg, mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC),
+                info.getCurrentVolume());
+    }
+
+    @Test
+    @SmallTest
+    public void testGetSessionToken() throws Exception {
+        assertEquals(mSession.getSessionToken(), mSession.getController().getSessionToken());
+    }
+
+    /**
+     * Tests that a session can be created from the framework session object and the callback
+     * set on the framework session object before fromSession() is called works properly.
+     */
+    @Test
+    @SmallTest
+    public void testFromSession() throws Exception {
+        if (android.os.Build.VERSION.SDK_INT < 21) {
+            // MediaSession was introduced from API level 21.
+            return;
+        }
+        mCallback.reset(1);
+        mSession.setCallback(mCallback, new Handler(Looper.getMainLooper()));
+        MediaSessionCompat session = MediaSessionCompat.fromMediaSession(
+                getContext(), mSession.getMediaSession());
+        assertEquals(session.getSessionToken(), mSession.getSessionToken());
+
+        session.getController().getTransportControls().play();
+        mCallback.await(TIME_OUT_MS);
+        assertEquals(1, mCallback.mOnPlayCalledCount);
+    }
+
+    /**
+     * Tests {@link MediaSessionCompat.Token} created in the constructor of MediaSessionCompat.
+     */
+    @Test
+    @SmallTest
+    public void testSessionToken() throws Exception {
+        MediaSessionCompat.Token sessionToken = mSession.getSessionToken();
+
+        assertNotNull(sessionToken);
+        assertEquals(0, sessionToken.describeContents());
+
+        // Test writeToParcel
+        Parcel p = Parcel.obtain();
+        sessionToken.writeToParcel(p, 0);
+        p.setDataPosition(0);
+        MediaSessionCompat.Token token = MediaSessionCompat.Token.CREATOR.createFromParcel(p);
+        assertEquals(token, sessionToken);
+        p.recycle();
+    }
+
+    /**
+     * Tests {@link MediaSessionCompat.QueueItem}.
+     */
+    @Test
+    @SmallTest
+    public void testQueueItem() {
+        MediaSessionCompat.QueueItem item = new MediaSessionCompat.QueueItem(
+                new MediaDescriptionCompat.Builder()
+                        .setMediaId(TEST_MEDIA_ID_1)
+                        .setTitle(TEST_MEDIA_TITLE_1)
+                        .build(),
+                TEST_QUEUE_ID_1);
+        assertEquals(TEST_QUEUE_ID_1, item.getQueueId());
+        assertEquals(TEST_MEDIA_ID_1, item.getDescription().getMediaId());
+        assertEquals(TEST_MEDIA_TITLE_1, item.getDescription().getTitle());
+        assertEquals(0, item.describeContents());
+
+        Parcel p = Parcel.obtain();
+        item.writeToParcel(p, 0);
+        p.setDataPosition(0);
+        MediaSessionCompat.QueueItem other =
+                MediaSessionCompat.QueueItem.CREATOR.createFromParcel(p);
+        assertEquals(item.toString(), other.toString());
+        p.recycle();
+    }
+
+    /**
+     * Tests {@link MediaSessionCompat#setActive}.
+     */
+    @Test
+    @SmallTest
+    public void testSetActive() throws Exception {
+        mSession.setActive(true);
+        assertTrue(mSession.isActive());
+    }
+
+    @Test
+    @SmallTest
+    public void testGetPlaybackStateWithPositionUpdate() throws InterruptedException {
+        final long stateSetTime = SystemClock.elapsedRealtime();
+        PlaybackStateCompat stateIn = new PlaybackStateCompat.Builder()
+                .setState(PlaybackStateCompat.STATE_PLAYING, TEST_POSITION, TEST_PLAYBACK_SPEED,
+                        stateSetTime)
+                .build();
+        mSession.setPlaybackState(stateIn);
+
+        final long waitDuration = 100L;
+        Thread.sleep(waitDuration);
+
+        final long expectedUpdateTime = waitDuration + stateSetTime;
+        final long expectedPosition = (long) (TEST_PLAYBACK_SPEED * waitDuration) + TEST_POSITION;
+
+        final double updateTimeTolerance = 50L;
+        final double positionTolerance = updateTimeTolerance * TEST_PLAYBACK_SPEED;
+
+        PlaybackStateCompat stateOut = mSession.getController().getPlaybackState();
+        assertEquals(expectedUpdateTime, stateOut.getLastPositionUpdateTime(), updateTimeTolerance);
+        assertEquals(expectedPosition, stateOut.getPosition(), positionTolerance);
+
+        // Compare the result with MediaController.getPlaybackState().
+        if (Build.VERSION.SDK_INT >= 21) {
+            MediaController controller = new MediaController(
+                    getContext(), (MediaSession.Token) mSession.getSessionToken().getToken());
+            PlaybackState state = controller.getPlaybackState();
+            assertEquals(state.getLastPositionUpdateTime(), stateOut.getLastPositionUpdateTime(),
+                    updateTimeTolerance);
+            assertEquals(state.getPosition(), stateOut.getPosition(), positionTolerance);
+        }
+    }
+
+    /**
+     * Tests {@link MediaSessionCompat#setCallback} with {@code null}.
+     * No callback should be called once {@code setCallback(null)} is done.
+     */
+    @Test
+    @SmallTest
+    public void testSetCallbackWithNull() throws Exception {
+        mSession.setActive(true);
+        mCallback.reset(1);
+        callTransportControlsMethod(PLAY, null, getContext(), mSession.getSessionToken());
+        mSession.setCallback(null, mHandler);
+        mCallback.await(WAIT_TIME_FOR_NO_RESPONSE_MS);
+        assertEquals("Callback shouldn't be called.", 0, mCallback.mOnPlayCalledCount);
+    }
+
+    @Test
+    @SmallTest
+    public void testSendCommand() throws Exception {
+        mCallback.reset(1);
+
+        Bundle arguments = new Bundle();
+        arguments.putString("command", TEST_COMMAND);
+        Bundle extras = new Bundle();
+        extras.putString(TEST_KEY, TEST_VALUE);
+        arguments.putBundle("extras", extras);
+        callMediaControllerMethod(
+                SEND_COMMAND, arguments, getContext(), mSession.getSessionToken());
+
+        mCallback.await(TIME_OUT_MS);
+        assertTrue(mCallback.mOnCommandCalled);
+        assertNotNull(mCallback.mCommandCallback);
+        assertEquals(TEST_COMMAND, mCallback.mCommand);
+        assertBundleEquals(extras, mCallback.mExtras);
+    }
+
+    @Test
+    @SmallTest
+    public void testAddRemoveQueueItems() throws Exception {
+        mSession.setFlags(MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS);
+
+        MediaDescriptionCompat itemDescription1 = new MediaDescriptionCompat.Builder()
+                .setMediaId(TEST_MEDIA_ID_1).setTitle(TEST_MEDIA_TITLE_1).build();
+
+        MediaDescriptionCompat itemDescription2 = new MediaDescriptionCompat.Builder()
+                .setMediaId(TEST_MEDIA_ID_2).setTitle(TEST_MEDIA_TITLE_2).build();
+
+        mCallback.reset(1);
+        callMediaControllerMethod(
+                ADD_QUEUE_ITEM, itemDescription1, getContext(), mSession.getSessionToken());
+
+        mCallback.await(TIME_OUT_MS);
+        assertTrue(mCallback.mOnAddQueueItemCalled);
+        assertEquals(-1, mCallback.mQueueIndex);
+        assertEquals(TEST_MEDIA_ID_1, mCallback.mQueueDescription.getMediaId());
+        assertEquals(TEST_MEDIA_TITLE_1, mCallback.mQueueDescription.getTitle());
+
+        mCallback.reset(1);
+        Bundle arguments = new Bundle();
+        arguments.putParcelable("description", itemDescription2);
+        arguments.putInt("index", 0);
+        callMediaControllerMethod(
+                ADD_QUEUE_ITEM_WITH_INDEX, arguments, getContext(), mSession.getSessionToken());
+
+        mCallback.await(TIME_OUT_MS);
+        assertTrue(mCallback.mOnAddQueueItemAtCalled);
+        assertEquals(0, mCallback.mQueueIndex);
+        assertEquals(TEST_MEDIA_ID_2, mCallback.mQueueDescription.getMediaId());
+        assertEquals(TEST_MEDIA_TITLE_2, mCallback.mQueueDescription.getTitle());
+
+        mCallback.reset(1);
+        callMediaControllerMethod(
+                REMOVE_QUEUE_ITEM, itemDescription1, getContext(), mSession.getSessionToken());
+        mCallback.await(TIME_OUT_MS);
+        assertTrue(mCallback.mOnRemoveQueueItemCalled);
+        assertEquals(TEST_MEDIA_ID_1, mCallback.mQueueDescription.getMediaId());
+        assertEquals(TEST_MEDIA_TITLE_1, mCallback.mQueueDescription.getTitle());
+    }
+
+    @Test
+    @SmallTest
+    public void testTransportControlsAndMediaSessionCallback() throws Exception {
+        mCallback.reset(1);
+        callTransportControlsMethod(PLAY, null, getContext(), mSession.getSessionToken());
+        mCallback.await(TIME_OUT_MS);
+        assertEquals(1, mCallback.mOnPlayCalledCount);
+
+        mCallback.reset(1);
+        callTransportControlsMethod(PAUSE, null, getContext(), mSession.getSessionToken());
+        mCallback.await(TIME_OUT_MS);
+        assertTrue(mCallback.mOnPauseCalled);
+
+        mCallback.reset(1);
+        callTransportControlsMethod(STOP, null, getContext(), mSession.getSessionToken());
+        mCallback.await(TIME_OUT_MS);
+        assertTrue(mCallback.mOnStopCalled);
+
+        mCallback.reset(1);
+        callTransportControlsMethod(
+                FAST_FORWARD, null, getContext(), mSession.getSessionToken());
+        mCallback.await(TIME_OUT_MS);
+        assertTrue(mCallback.mOnFastForwardCalled);
+
+        mCallback.reset(1);
+        callTransportControlsMethod(REWIND, null, getContext(), mSession.getSessionToken());
+        mCallback.await(TIME_OUT_MS);
+        assertTrue(mCallback.mOnRewindCalled);
+
+        mCallback.reset(1);
+        callTransportControlsMethod(
+                SKIP_TO_PREVIOUS, null, getContext(), mSession.getSessionToken());
+        mCallback.await(TIME_OUT_MS);
+        assertTrue(mCallback.mOnSkipToPreviousCalled);
+
+        mCallback.reset(1);
+        callTransportControlsMethod(
+                SKIP_TO_NEXT, null, getContext(), mSession.getSessionToken());
+        mCallback.await(TIME_OUT_MS);
+        assertTrue(mCallback.mOnSkipToNextCalled);
+
+        mCallback.reset(1);
+        final long seekPosition = 1000;
+        callTransportControlsMethod(
+                SEEK_TO, seekPosition, getContext(), mSession.getSessionToken());
+        mCallback.await(TIME_OUT_MS);
+        assertTrue(mCallback.mOnSeekToCalled);
+        assertEquals(seekPosition, mCallback.mSeekPosition);
+
+        mCallback.reset(1);
+        final RatingCompat rating =
+                RatingCompat.newStarRating(RatingCompat.RATING_5_STARS, 3f);
+        callTransportControlsMethod(
+                SET_RATING, rating, getContext(), mSession.getSessionToken());
+        mCallback.await(TIME_OUT_MS);
+        assertTrue(mCallback.mOnSetRatingCalled);
+        assertEquals(rating.getRatingStyle(), mCallback.mRating.getRatingStyle());
+        assertEquals(rating.getStarRating(), mCallback.mRating.getStarRating(), DELTA);
+
+        mCallback.reset(1);
+        final Bundle extras = new Bundle();
+        extras.putString(TEST_KEY, TEST_VALUE);
+        Bundle arguments = new Bundle();
+        arguments.putString("mediaId", TEST_MEDIA_ID_1);
+        arguments.putBundle("extras", extras);
+        callTransportControlsMethod(
+                PLAY_FROM_MEDIA_ID, arguments, getContext(), mSession.getSessionToken());
+        mCallback.await(TIME_OUT_MS);
+        assertTrue(mCallback.mOnPlayFromMediaIdCalled);
+        assertEquals(TEST_MEDIA_ID_1, mCallback.mMediaId);
+        assertBundleEquals(extras, mCallback.mExtras);
+
+        mCallback.reset(1);
+        final String query = "test-query";
+        arguments = new Bundle();
+        arguments.putString("query", query);
+        arguments.putBundle("extras", extras);
+        callTransportControlsMethod(
+                PLAY_FROM_SEARCH, arguments, getContext(), mSession.getSessionToken());
+        mCallback.await(TIME_OUT_MS);
+        assertTrue(mCallback.mOnPlayFromSearchCalled);
+        assertEquals(query, mCallback.mQuery);
+        assertBundleEquals(extras, mCallback.mExtras);
+
+        mCallback.reset(1);
+        final Uri uri = Uri.parse("content://test/popcorn.mod");
+        arguments = new Bundle();
+        arguments.putParcelable("uri", uri);
+        arguments.putBundle("extras", extras);
+        callTransportControlsMethod(
+                PLAY_FROM_URI, arguments, getContext(), mSession.getSessionToken());
+        mCallback.await(TIME_OUT_MS);
+        assertTrue(mCallback.mOnPlayFromUriCalled);
+        assertEquals(uri, mCallback.mUri);
+        assertBundleEquals(extras, mCallback.mExtras);
+
+        mCallback.reset(1);
+        final String action = "test-action";
+        arguments = new Bundle();
+        arguments.putString("action", action);
+        arguments.putBundle("extras", extras);
+        callTransportControlsMethod(
+                SEND_CUSTOM_ACTION, arguments, getContext(), mSession.getSessionToken());
+        mCallback.await(TIME_OUT_MS);
+        assertTrue(mCallback.mOnCustomActionCalled);
+        assertEquals(action, mCallback.mAction);
+        assertBundleEquals(extras, mCallback.mExtras);
+
+        mCallback.reset(1);
+        mCallback.mOnCustomActionCalled = false;
+        final PlaybackStateCompat.CustomAction customAction =
+                new PlaybackStateCompat.CustomAction.Builder(action, action, -1)
+                        .setExtras(extras)
+                        .build();
+        arguments = new Bundle();
+        arguments.putParcelable("action", customAction);
+        arguments.putBundle("extras", extras);
+        callTransportControlsMethod(
+                SEND_CUSTOM_ACTION_PARCELABLE,
+                arguments,
+                getContext(),
+                mSession.getSessionToken());
+        mCallback.await(TIME_OUT_MS);
+        assertTrue(mCallback.mOnCustomActionCalled);
+        assertEquals(action, mCallback.mAction);
+        assertBundleEquals(extras, mCallback.mExtras);
+
+        mCallback.reset(1);
+        final long queueItemId = 1000;
+        callTransportControlsMethod(
+                SKIP_TO_QUEUE_ITEM, queueItemId, getContext(), mSession.getSessionToken());
+        mCallback.await(TIME_OUT_MS);
+        assertTrue(mCallback.mOnSkipToQueueItemCalled);
+        assertEquals(queueItemId, mCallback.mQueueItemId);
+
+        mCallback.reset(1);
+        callTransportControlsMethod(
+                PREPARE, null, getContext(), mSession.getSessionToken());
+        mCallback.await(TIME_OUT_MS);
+        assertTrue(mCallback.mOnPrepareCalled);
+
+        mCallback.reset(1);
+        arguments = new Bundle();
+        arguments.putString("mediaId", TEST_MEDIA_ID_2);
+        arguments.putBundle("extras", extras);
+        callTransportControlsMethod(
+                PREPARE_FROM_MEDIA_ID, arguments, getContext(), mSession.getSessionToken());
+        mCallback.await(TIME_OUT_MS);
+        assertTrue(mCallback.mOnPrepareFromMediaIdCalled);
+        assertEquals(TEST_MEDIA_ID_2, mCallback.mMediaId);
+        assertBundleEquals(extras, mCallback.mExtras);
+
+        mCallback.reset(1);
+        arguments = new Bundle();
+        arguments.putString("query", query);
+        arguments.putBundle("extras", extras);
+        callTransportControlsMethod(
+                PREPARE_FROM_SEARCH, arguments, getContext(), mSession.getSessionToken());
+        mCallback.await(TIME_OUT_MS);
+        assertTrue(mCallback.mOnPrepareFromSearchCalled);
+        assertEquals(query, mCallback.mQuery);
+        assertBundleEquals(extras, mCallback.mExtras);
+
+        mCallback.reset(1);
+        arguments = new Bundle();
+        arguments.putParcelable("uri", uri);
+        arguments.putBundle("extras", extras);
+        callTransportControlsMethod(
+                PREPARE_FROM_URI, arguments, getContext(), mSession.getSessionToken());
+        mCallback.await(TIME_OUT_MS);
+        assertTrue(mCallback.mOnPrepareFromUriCalled);
+        assertEquals(uri, mCallback.mUri);
+        assertBundleEquals(extras, mCallback.mExtras);
+
+        mCallback.reset(1);
+        callTransportControlsMethod(
+                SET_CAPTIONING_ENABLED, ENABLED, getContext(), mSession.getSessionToken());
+        mCallback.await(TIME_OUT_MS);
+        assertTrue(mCallback.mOnSetCaptioningEnabledCalled);
+        assertEquals(ENABLED, mCallback.mCaptioningEnabled);
+
+        mCallback.reset(1);
+        final int repeatMode = PlaybackStateCompat.REPEAT_MODE_ALL;
+        callTransportControlsMethod(
+                SET_REPEAT_MODE, repeatMode, getContext(), mSession.getSessionToken());
+        mCallback.await(TIME_OUT_MS);
+        assertTrue(mCallback.mOnSetRepeatModeCalled);
+        assertEquals(repeatMode, mCallback.mRepeatMode);
+
+        mCallback.reset(1);
+        final int shuffleMode = PlaybackStateCompat.SHUFFLE_MODE_ALL;
+        callTransportControlsMethod(
+                SET_SHUFFLE_MODE, shuffleMode, getContext(), mSession.getSessionToken());
+        mCallback.await(TIME_OUT_MS);
+        assertTrue(mCallback.mOnSetShuffleModeCalled);
+        assertEquals(shuffleMode, mCallback.mShuffleMode);
+    }
+
+    /**
+     * Tests {@link MediaSessionCompat.Callback#onMediaButtonEvent}.
+     */
+    @Test
+    @MediumTest
+    public void testCallbackOnMediaButtonEvent() throws Exception {
+        mSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS);
+        mSession.setActive(true);
+
+        final long waitTimeForNoResponse = 30L;
+
+        Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON)
+                .setComponent(new ComponentName(getContext(), getContext().getClass()));
+        PendingIntent pi = PendingIntent.getBroadcast(getContext(), 0, mediaButtonIntent, 0);
+        mSession.setMediaButtonReceiver(pi);
+
+        // Set state to STATE_PLAYING to get higher priority.
+        setPlaybackState(PlaybackStateCompat.STATE_PLAYING);
+
+        mCallback.reset(1);
+        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY);
+        assertTrue(mCallback.await(TIME_OUT_MS));
+        assertEquals(1, mCallback.mOnPlayCalledCount);
+
+        mCallback.reset(1);
+        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PAUSE);
+        assertTrue(mCallback.await(TIME_OUT_MS));
+        assertTrue(mCallback.mOnPauseCalled);
+
+        mCallback.reset(1);
+        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_NEXT);
+        assertTrue(mCallback.await(TIME_OUT_MS));
+        assertTrue(mCallback.mOnSkipToNextCalled);
+
+        mCallback.reset(1);
+        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PREVIOUS);
+        assertTrue(mCallback.await(TIME_OUT_MS));
+        assertTrue(mCallback.mOnSkipToPreviousCalled);
+
+        mCallback.reset(1);
+        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_STOP);
+        assertTrue(mCallback.await(TIME_OUT_MS));
+        assertTrue(mCallback.mOnStopCalled);
+
+        mCallback.reset(1);
+        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_FAST_FORWARD);
+        assertTrue(mCallback.await(TIME_OUT_MS));
+        assertTrue(mCallback.mOnFastForwardCalled);
+
+        mCallback.reset(1);
+        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_REWIND);
+        assertTrue(mCallback.await(TIME_OUT_MS));
+        assertTrue(mCallback.mOnRewindCalled);
+
+        // Test PLAY_PAUSE button twice.
+        // First, send PLAY_PAUSE button event while in STATE_PAUSED.
+        mCallback.reset(1);
+        setPlaybackState(PlaybackStateCompat.STATE_PAUSED);
+        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
+        assertTrue(mCallback.await(TIME_OUT_MS));
+        assertEquals(1, mCallback.mOnPlayCalledCount);
+
+        // Next, send PLAY_PAUSE button event while in STATE_PLAYING.
+        mCallback.reset(1);
+        setPlaybackState(PlaybackStateCompat.STATE_PLAYING);
+        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
+        assertTrue(mCallback.await(TIME_OUT_MS));
+        assertTrue(mCallback.mOnPauseCalled);
+
+        // Double tap of PLAY_PAUSE is the next track.
+        mCallback.reset(2);
+        setPlaybackState(PlaybackStateCompat.STATE_PAUSED);
+        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
+        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
+        assertFalse(mCallback.await(waitTimeForNoResponse));
+        assertTrue(mCallback.mOnSkipToNextCalled);
+        assertEquals(0, mCallback.mOnPlayCalledCount);
+        assertFalse(mCallback.mOnPauseCalled);
+
+        // Test PLAY_PAUSE button long-press.
+        // It should be the same as the single short-press.
+        mCallback.reset(1);
+        setPlaybackState(PlaybackStateCompat.STATE_PAUSED);
+        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, true);
+        assertTrue(mCallback.await(TIME_OUT_MS));
+        assertEquals(1, mCallback.mOnPlayCalledCount);
+
+        // Double tap of PLAY_PAUSE should be handled once.
+        // Initial down event from the second press within double tap time-out will make
+        // onSkipToNext() to be called, so further down events shouldn't be handled again.
+        mCallback.reset(2);
+        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
+        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, true);
+        assertFalse(mCallback.await(waitTimeForNoResponse));
+        assertTrue(mCallback.mOnSkipToNextCalled);
+        assertEquals(0, mCallback.mOnPlayCalledCount);
+        assertFalse(mCallback.mOnPauseCalled);
+
+        // Test PLAY_PAUSE button long-press followed by the short-press.
+        // Initial long-press of the PLAY_PAUSE is considered as the single short-press already,
+        // so it shouldn't be used as the first tap of the double tap.
+        mCallback.reset(2);
+        setPlaybackState(PlaybackStateCompat.STATE_PAUSED);
+        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, true);
+        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
+        assertTrue(mCallback.await(TIME_OUT_MS));
+        // onMediaButtonEvent() calls either onPlay() or onPause() depending on the playback state,
+        // so onPlay() should be called once and onPause() also should be called once.
+        assertEquals(1, mCallback.mOnPlayCalledCount);
+        assertTrue(mCallback.mOnPauseCalled);
+        assertFalse(mCallback.mOnSkipToNextCalled);
+
+        // If another media key is pressed while the double tap of PLAY_PAUSE,
+        // PLAY_PAUSE should be handled as normal.
+        mCallback.reset(3);
+        setPlaybackState(PlaybackStateCompat.STATE_PAUSED);
+        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
+        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_STOP);
+        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
+        assertTrue(mCallback.await(TIME_OUT_MS));
+        assertFalse(mCallback.mOnSkipToNextCalled);
+        assertTrue(mCallback.mOnStopCalled);
+        assertEquals(2, mCallback.mOnPlayCalledCount);
+
+        // Test if media keys are handled in order.
+        mCallback.reset(2);
+        setPlaybackState(PlaybackStateCompat.STATE_PAUSED);
+        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
+        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_STOP);
+        assertTrue(mCallback.await(TIME_OUT_MS));
+        assertEquals(1, mCallback.mOnPlayCalledCount);
+        assertTrue(mCallback.mOnStopCalled);
+        synchronized (mWaitLock) {
+            assertEquals(PlaybackStateCompat.STATE_STOPPED,
+                    mSession.getController().getPlaybackState().getState());
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void testVolumeControl() throws Exception {
+        if (android.os.Build.VERSION.SDK_INT < 27) {
+            // This test causes an Exception on System UI in API < 27.
+            return;
+        }
+        VolumeProviderCompat vp =
+                new VolumeProviderCompat(VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE, 11, 5) {
+                    @Override
+                    public void onSetVolumeTo(int volume) {
+                        synchronized (mWaitLock) {
+                            setCurrentVolume(volume);
+                            mWaitLock.notify();
+                        }
+                    }
+
+                    @Override
+                    public void onAdjustVolume(int direction) {
+                        synchronized (mWaitLock) {
+                            switch (direction) {
+                                case AudioManager.ADJUST_LOWER:
+                                    setCurrentVolume(getCurrentVolume() - 1);
+                                    break;
+                                case AudioManager.ADJUST_RAISE:
+                                    setCurrentVolume(getCurrentVolume() + 1);
+                                    break;
+                            }
+                            mWaitLock.notify();
+                        }
+                    }
+                };
+        mSession.setPlaybackToRemote(vp);
+
+        synchronized (mWaitLock) {
+            // test setVolumeTo
+            callMediaControllerMethod(SET_VOLUME_TO,
+                    7 /* Target volume */, getContext(), mSession.getSessionToken());
+            mWaitLock.wait(TIME_OUT_MS);
+            assertEquals(7, vp.getCurrentVolume());
+
+            // test adjustVolume
+            callMediaControllerMethod(ADJUST_VOLUME,
+                    AudioManager.ADJUST_LOWER, getContext(), mSession.getSessionToken());
+            mWaitLock.wait(TIME_OUT_MS);
+            assertEquals(6, vp.getCurrentVolume());
+
+            callMediaControllerMethod(ADJUST_VOLUME,
+                    AudioManager.ADJUST_RAISE, getContext(), mSession.getSessionToken());
+            mWaitLock.wait(TIME_OUT_MS);
+            assertEquals(7, vp.getCurrentVolume());
+        }
+    }
+
+    private void setPlaybackState(int state) {
+        final long allActions = PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PAUSE
+                | PlaybackStateCompat.ACTION_PLAY_PAUSE | PlaybackStateCompat.ACTION_STOP
+                | PlaybackStateCompat.ACTION_SKIP_TO_NEXT
+                | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
+                | PlaybackStateCompat.ACTION_FAST_FORWARD | PlaybackStateCompat.ACTION_REWIND;
+        PlaybackStateCompat playbackState = new PlaybackStateCompat.Builder().setActions(allActions)
+                .setState(state, 0L, 0.0f).build();
+        synchronized (mWaitLock) {
+            mSession.setPlaybackState(playbackState);
+        }
+    }
+
+    private void sendMediaKeyInputToController(int keyCode) {
+        sendMediaKeyInputToController(keyCode, false);
+    }
+
+    private void sendMediaKeyInputToController(int keyCode, boolean isLongPress) {
+        MediaControllerCompat controller = mSession.getController();
+        long currentTimeMs = System.currentTimeMillis();
+        KeyEvent down = new KeyEvent(
+                currentTimeMs, currentTimeMs, KeyEvent.ACTION_DOWN, keyCode, 0);
+        controller.dispatchMediaButtonEvent(down);
+        if (isLongPress) {
+            KeyEvent longPress = new KeyEvent(
+                    currentTimeMs, System.currentTimeMillis(), KeyEvent.ACTION_DOWN, keyCode, 1);
+            controller.dispatchMediaButtonEvent(longPress);
+        }
+        KeyEvent up = new KeyEvent(
+                currentTimeMs, System.currentTimeMillis(), KeyEvent.ACTION_UP, keyCode, 0);
+        controller.dispatchMediaButtonEvent(up);
+    }
+
+    private class MediaSessionCallback extends MediaSessionCompat.Callback {
+        private CountDownLatch mLatch;
+        private long mSeekPosition;
+        private long mQueueItemId;
+        private RatingCompat mRating;
+        private String mMediaId;
+        private String mQuery;
+        private Uri mUri;
+        private String mAction;
+        private String mCommand;
+        private Bundle mExtras;
+        private ResultReceiver mCommandCallback;
+        private boolean mCaptioningEnabled;
+        private int mRepeatMode;
+        private int mShuffleMode;
+        private int mQueueIndex;
+        private MediaDescriptionCompat mQueueDescription;
+        private List<MediaSessionCompat.QueueItem> mQueue = new ArrayList<>();
+
+        private int mOnPlayCalledCount;
+        private boolean mOnPauseCalled;
+        private boolean mOnStopCalled;
+        private boolean mOnFastForwardCalled;
+        private boolean mOnRewindCalled;
+        private boolean mOnSkipToPreviousCalled;
+        private boolean mOnSkipToNextCalled;
+        private boolean mOnSeekToCalled;
+        private boolean mOnSkipToQueueItemCalled;
+        private boolean mOnSetRatingCalled;
+        private boolean mOnPlayFromMediaIdCalled;
+        private boolean mOnPlayFromSearchCalled;
+        private boolean mOnPlayFromUriCalled;
+        private boolean mOnCustomActionCalled;
+        private boolean mOnCommandCalled;
+        private boolean mOnPrepareCalled;
+        private boolean mOnPrepareFromMediaIdCalled;
+        private boolean mOnPrepareFromSearchCalled;
+        private boolean mOnPrepareFromUriCalled;
+        private boolean mOnSetCaptioningEnabledCalled;
+        private boolean mOnSetRepeatModeCalled;
+        private boolean mOnSetShuffleModeCalled;
+        private boolean mOnAddQueueItemCalled;
+        private boolean mOnAddQueueItemAtCalled;
+        private boolean mOnRemoveQueueItemCalled;
+
+        public void reset(int count) {
+            mLatch = new CountDownLatch(count);
+            mSeekPosition = -1;
+            mQueueItemId = -1;
+            mRating = null;
+            mMediaId = null;
+            mQuery = null;
+            mUri = null;
+            mAction = null;
+            mExtras = null;
+            mCommand = null;
+            mCommandCallback = null;
+            mCaptioningEnabled = false;
+            mRepeatMode = PlaybackStateCompat.REPEAT_MODE_NONE;
+            mShuffleMode = PlaybackStateCompat.SHUFFLE_MODE_NONE;
+            mQueueIndex = -1;
+            mQueueDescription = null;
+
+            mOnPlayCalledCount = 0;
+            mOnPauseCalled = false;
+            mOnStopCalled = false;
+            mOnFastForwardCalled = false;
+            mOnRewindCalled = false;
+            mOnSkipToPreviousCalled = false;
+            mOnSkipToNextCalled = false;
+            mOnSkipToQueueItemCalled = false;
+            mOnSeekToCalled = false;
+            mOnSetRatingCalled = false;
+            mOnPlayFromMediaIdCalled = false;
+            mOnPlayFromSearchCalled = false;
+            mOnPlayFromUriCalled = false;
+            mOnCustomActionCalled = false;
+            mOnCommandCalled = false;
+            mOnPrepareCalled = false;
+            mOnPrepareFromMediaIdCalled = false;
+            mOnPrepareFromSearchCalled = false;
+            mOnPrepareFromUriCalled = false;
+            mOnSetCaptioningEnabledCalled = false;
+            mOnSetRepeatModeCalled = false;
+            mOnSetShuffleModeCalled = false;
+            mOnAddQueueItemCalled = false;
+            mOnAddQueueItemAtCalled = false;
+            mOnRemoveQueueItemCalled = false;
+        }
+
+        public boolean await(long timeoutMs) {
+            try {
+                return mLatch.await(timeoutMs, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                return false;
+            }
+        }
+
+        @Override
+        public void onPlay() {
+            mOnPlayCalledCount++;
+            setPlaybackState(PlaybackStateCompat.STATE_PLAYING);
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onPause() {
+            mOnPauseCalled = true;
+            setPlaybackState(PlaybackStateCompat.STATE_PAUSED);
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onStop() {
+            mOnStopCalled = true;
+            setPlaybackState(PlaybackStateCompat.STATE_STOPPED);
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onFastForward() {
+            mOnFastForwardCalled = true;
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onRewind() {
+            mOnRewindCalled = true;
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onSkipToPrevious() {
+            mOnSkipToPreviousCalled = true;
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onSkipToNext() {
+            mOnSkipToNextCalled = true;
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onSeekTo(long pos) {
+            mOnSeekToCalled = true;
+            mSeekPosition = pos;
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onSetRating(RatingCompat rating) {
+            mOnSetRatingCalled = true;
+            mRating = rating;
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onPlayFromMediaId(String mediaId, Bundle extras) {
+            mOnPlayFromMediaIdCalled = true;
+            mMediaId = mediaId;
+            mExtras = extras;
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onPlayFromSearch(String query, Bundle extras) {
+            mOnPlayFromSearchCalled = true;
+            mQuery = query;
+            mExtras = extras;
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onPlayFromUri(Uri uri, Bundle extras) {
+            mOnPlayFromUriCalled = true;
+            mUri = uri;
+            mExtras = extras;
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onCustomAction(String action, Bundle extras) {
+            mOnCustomActionCalled = true;
+            mAction = action;
+            mExtras = extras;
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onSkipToQueueItem(long id) {
+            mOnSkipToQueueItemCalled = true;
+            mQueueItemId = id;
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onCommand(String command, Bundle extras, ResultReceiver cb) {
+            mOnCommandCalled = true;
+            mCommand = command;
+            mExtras = extras;
+            mCommandCallback = cb;
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onPrepare() {
+            mOnPrepareCalled = true;
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onPrepareFromMediaId(String mediaId, Bundle extras) {
+            mOnPrepareFromMediaIdCalled = true;
+            mMediaId = mediaId;
+            mExtras = extras;
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onPrepareFromSearch(String query, Bundle extras) {
+            mOnPrepareFromSearchCalled = true;
+            mQuery = query;
+            mExtras = extras;
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onPrepareFromUri(Uri uri, Bundle extras) {
+            mOnPrepareFromUriCalled = true;
+            mUri = uri;
+            mExtras = extras;
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onSetRepeatMode(int repeatMode) {
+            mOnSetRepeatModeCalled = true;
+            mRepeatMode = repeatMode;
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onAddQueueItem(MediaDescriptionCompat description) {
+            mOnAddQueueItemCalled = true;
+            mQueueDescription = description;
+            mQueue.add(new MediaSessionCompat.QueueItem(description, mQueue.size()));
+            mSession.setQueue(mQueue);
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onAddQueueItem(MediaDescriptionCompat description, int index) {
+            mOnAddQueueItemAtCalled = true;
+            mQueueIndex = index;
+            mQueueDescription = description;
+            mQueue.add(index, new MediaSessionCompat.QueueItem(description, mQueue.size()));
+            mSession.setQueue(mQueue);
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onRemoveQueueItem(MediaDescriptionCompat description) {
+            mOnRemoveQueueItemCalled = true;
+            String mediaId = description.getMediaId();
+            for (int i = mQueue.size() - 1; i >= 0; --i) {
+                if (mediaId.equals(mQueue.get(i).getDescription().getMediaId())) {
+                    mQueueDescription = mQueue.remove(i).getDescription();
+                    mSession.setQueue(mQueue);
+                    break;
+                }
+            }
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onSetCaptioningEnabled(boolean enabled) {
+            mOnSetCaptioningEnabledCalled = true;
+            mCaptioningEnabled = enabled;
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onSetShuffleMode(int shuffleMode) {
+            mOnSetShuffleModeCalled = true;
+            mShuffleMode = shuffleMode;
+            mLatch.countDown();
+        }
+    }
+}
diff --git a/media-compat/version-compat-tests/previous/service/tests/src/android/support/mediacompat/service/ServiceBroadcastReceiver.java b/media-compat/version-compat-tests/previous/service/tests/src/android/support/mediacompat/service/ServiceBroadcastReceiver.java
new file mode 100644
index 0000000..57364b7
--- /dev/null
+++ b/media-compat/version-compat-tests/previous/service/tests/src/android/support/mediacompat/service/ServiceBroadcastReceiver.java
@@ -0,0 +1,163 @@
+/*
+ * 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.service;
+
+
+import static android.support.mediacompat.testlib.MediaBrowserConstants.CUSTOM_ACTION_SEND_ERROR;
+import static android.support.mediacompat.testlib.MediaBrowserConstants
+        .CUSTOM_ACTION_SEND_PROGRESS_UPDATE;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.CUSTOM_ACTION_SEND_RESULT;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.NOTIFY_CHILDREN_CHANGED;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.SEND_DELAYED_ITEM_LOADED;
+import static android.support.mediacompat.testlib.MediaBrowserConstants
+        .SEND_DELAYED_NOTIFY_CHILDREN_CHANGED;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.SET_SESSION_TOKEN;
+import static android.support.mediacompat.testlib.MediaSessionConstants.RELEASE;
+import static android.support.mediacompat.testlib.MediaSessionConstants.SEND_SESSION_EVENT;
+import static android.support.mediacompat.testlib.MediaSessionConstants.SET_ACTIVE;
+import static android.support.mediacompat.testlib.MediaSessionConstants.SET_CAPTIONING_ENABLED;
+import static android.support.mediacompat.testlib.MediaSessionConstants.SET_EXTRAS;
+import static android.support.mediacompat.testlib.MediaSessionConstants.SET_FLAGS;
+import static android.support.mediacompat.testlib.MediaSessionConstants.SET_METADATA;
+import static android.support.mediacompat.testlib.MediaSessionConstants.SET_PLAYBACK_STATE;
+import static android.support.mediacompat.testlib.MediaSessionConstants.SET_PLAYBACK_TO_LOCAL;
+import static android.support.mediacompat.testlib.MediaSessionConstants.SET_PLAYBACK_TO_REMOTE;
+import static android.support.mediacompat.testlib.MediaSessionConstants.SET_QUEUE;
+import static android.support.mediacompat.testlib.MediaSessionConstants.SET_QUEUE_TITLE;
+import static android.support.mediacompat.testlib.MediaSessionConstants.SET_RATING_TYPE;
+import static android.support.mediacompat.testlib.MediaSessionConstants.SET_REPEAT_MODE;
+import static android.support.mediacompat.testlib.MediaSessionConstants.SET_SESSION_ACTIVITY;
+import static android.support.mediacompat.testlib.MediaSessionConstants.SET_SHUFFLE_MODE;
+import static android.support.mediacompat.testlib.util.IntentUtil
+        .ACTION_CALL_MEDIA_BROWSER_SERVICE_METHOD;
+import static android.support.mediacompat.testlib.util.IntentUtil.ACTION_CALL_MEDIA_SESSION_METHOD;
+import static android.support.mediacompat.testlib.util.IntentUtil.KEY_ARGUMENT;
+import static android.support.mediacompat.testlib.util.IntentUtil.KEY_METHOD_ID;
+
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v4.media.MediaMetadataCompat;
+import android.support.v4.media.RatingCompat;
+import android.support.v4.media.VolumeProviderCompat;
+import android.support.v4.media.session.MediaSessionCompat;
+import android.support.v4.media.session.MediaSessionCompat.QueueItem;
+import android.support.v4.media.session.ParcelableVolumeInfo;
+import android.support.v4.media.session.PlaybackStateCompat;
+
+import java.util.List;
+
+public class ServiceBroadcastReceiver extends BroadcastReceiver {
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        Bundle extras = intent.getExtras();
+        if (ACTION_CALL_MEDIA_BROWSER_SERVICE_METHOD.equals(intent.getAction()) && extras != null) {
+            StubMediaBrowserServiceCompat service = StubMediaBrowserServiceCompat.sInstance;
+            int method = extras.getInt(KEY_METHOD_ID, 0);
+
+            switch (method) {
+                case NOTIFY_CHILDREN_CHANGED:
+                    service.notifyChildrenChanged(extras.getString(KEY_ARGUMENT));
+                    break;
+                case SEND_DELAYED_NOTIFY_CHILDREN_CHANGED:
+                    service.sendDelayedNotifyChildrenChanged();
+                    break;
+                case SEND_DELAYED_ITEM_LOADED:
+                    service.sendDelayedItemLoaded();
+                    break;
+                case CUSTOM_ACTION_SEND_PROGRESS_UPDATE:
+                    service.mCustomActionResult.sendProgressUpdate(extras.getBundle(KEY_ARGUMENT));
+                    break;
+                case CUSTOM_ACTION_SEND_ERROR:
+                    service.mCustomActionResult.sendError(extras.getBundle(KEY_ARGUMENT));
+                    break;
+                case CUSTOM_ACTION_SEND_RESULT:
+                    service.mCustomActionResult.sendResult(extras.getBundle(KEY_ARGUMENT));
+                    break;
+                case SET_SESSION_TOKEN:
+                    StubMediaBrowserServiceCompatWithDelayedMediaSession.sInstance
+                            .callSetSessionToken();
+                    break;
+            }
+        } else if (ACTION_CALL_MEDIA_SESSION_METHOD.equals(intent.getAction()) && extras != null) {
+            MediaSessionCompat session = StubMediaBrowserServiceCompat.sSession;
+            int method = extras.getInt(KEY_METHOD_ID, 0);
+
+            switch (method) {
+                case SET_EXTRAS:
+                    session.setExtras(extras.getBundle(KEY_ARGUMENT));
+                    break;
+                case SET_FLAGS:
+                    session.setFlags(extras.getInt(KEY_ARGUMENT));
+                    break;
+                case SET_METADATA:
+                    session.setMetadata((MediaMetadataCompat) extras.getParcelable(KEY_ARGUMENT));
+                    break;
+                case SET_PLAYBACK_STATE:
+                    session.setPlaybackState(
+                            (PlaybackStateCompat) extras.getParcelable(KEY_ARGUMENT));
+                    break;
+                case SET_QUEUE:
+                    List<QueueItem> items = extras.getParcelableArrayList(KEY_ARGUMENT);
+                    session.setQueue(items);
+                    break;
+                case SET_QUEUE_TITLE:
+                    session.setQueueTitle(extras.getCharSequence(KEY_ARGUMENT));
+                    break;
+                case SET_SESSION_ACTIVITY:
+                    session.setSessionActivity((PendingIntent) extras.getParcelable(KEY_ARGUMENT));
+                    break;
+                case SET_CAPTIONING_ENABLED:
+                    session.setCaptioningEnabled(extras.getBoolean(KEY_ARGUMENT));
+                    break;
+                case SET_REPEAT_MODE:
+                    session.setRepeatMode(extras.getInt(KEY_ARGUMENT));
+                    break;
+                case SET_SHUFFLE_MODE:
+                    session.setShuffleMode(extras.getInt(KEY_ARGUMENT));
+                    break;
+                case SEND_SESSION_EVENT:
+                    Bundle arguments = extras.getBundle(KEY_ARGUMENT);
+                    session.sendSessionEvent(
+                            arguments.getString("event"), arguments.getBundle("extras"));
+                    break;
+                case SET_ACTIVE:
+                    session.setActive(extras.getBoolean(KEY_ARGUMENT));
+                    break;
+                case RELEASE:
+                    session.release();
+                    break;
+                case SET_PLAYBACK_TO_LOCAL:
+                    session.setPlaybackToLocal(extras.getInt(KEY_ARGUMENT));
+                    break;
+                case SET_PLAYBACK_TO_REMOTE:
+                    ParcelableVolumeInfo volumeInfo = extras.getParcelable(KEY_ARGUMENT);
+                    session.setPlaybackToRemote(new VolumeProviderCompat(
+                            volumeInfo.controlType,
+                            volumeInfo.maxVolume,
+                            volumeInfo.currentVolume) {});
+                    break;
+                case SET_RATING_TYPE:
+                    session.setRatingType(RatingCompat.RATING_5_STARS);
+                    break;
+            }
+        }
+    }
+}
diff --git a/media-compat/version-compat-tests/previous/service/tests/src/android/support/mediacompat/service/StubMediaBrowserServiceCompat.java b/media-compat/version-compat-tests/previous/service/tests/src/android/support/mediacompat/service/StubMediaBrowserServiceCompat.java
new file mode 100644
index 0000000..7032a0b
--- /dev/null
+++ b/media-compat/version-compat-tests/previous/service/tests/src/android/support/mediacompat/service/StubMediaBrowserServiceCompat.java
@@ -0,0 +1,197 @@
+/*
+ * 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.service;
+
+import static android.support.mediacompat.testlib.MediaBrowserConstants.CUSTOM_ACTION;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.CUSTOM_ACTION_FOR_ERROR;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.EXTRAS_KEY;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.EXTRAS_VALUE;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.MEDIA_ID_CHILDREN;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.MEDIA_ID_CHILDREN_DELAYED;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.MEDIA_ID_INCLUDE_METADATA;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.MEDIA_ID_INVALID;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.MEDIA_ID_ROOT;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.MEDIA_METADATA;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.SEARCH_QUERY;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.SEARCH_QUERY_FOR_ERROR;
+import static android.support.mediacompat.testlib.MediaBrowserConstants.SEARCH_QUERY_FOR_NO_RESULT;
+
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v4.media.MediaBrowserCompat.MediaItem;
+import android.support.v4.media.MediaBrowserServiceCompat;
+import android.support.v4.media.MediaDescriptionCompat;
+import android.support.v4.media.MediaMetadataCompat;
+import android.support.v4.media.session.MediaSessionCompat;
+
+import junit.framework.Assert;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Stub implementation of {@link android.support.v4.media.MediaBrowserServiceCompat}.
+ */
+public class StubMediaBrowserServiceCompat extends MediaBrowserServiceCompat {
+
+    public static StubMediaBrowserServiceCompat sInstance;
+
+    public static MediaSessionCompat sSession;
+    private Bundle mExtras;
+    private Result<List<MediaItem>> mPendingLoadChildrenResult;
+    private Result<MediaItem> mPendingLoadItemResult;
+    private Bundle mPendingRootHints;
+
+    public Bundle mCustomActionExtras;
+    public Result<Bundle> mCustomActionResult;
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        sInstance = this;
+        sSession = new MediaSessionCompat(this, "StubMediaBrowserServiceCompat");
+        setSessionToken(sSession.getSessionToken());
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        sSession.release();
+        sSession = null;
+    }
+
+    @Override
+    public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) {
+        mExtras = new Bundle();
+        mExtras.putString(EXTRAS_KEY, EXTRAS_VALUE);
+        return new BrowserRoot(MEDIA_ID_ROOT, mExtras);
+    }
+
+    @Override
+    public void onLoadChildren(final String parentId, final Result<List<MediaItem>> result) {
+        List<MediaItem> mediaItems = new ArrayList<>();
+        if (MEDIA_ID_ROOT.equals(parentId)) {
+            Bundle rootHints = getBrowserRootHints();
+            for (String id : MEDIA_ID_CHILDREN) {
+                mediaItems.add(createMediaItem(id));
+            }
+            result.sendResult(mediaItems);
+        } else if (MEDIA_ID_CHILDREN_DELAYED.equals(parentId)) {
+            Assert.assertNull(mPendingLoadChildrenResult);
+            mPendingLoadChildrenResult = result;
+            mPendingRootHints = getBrowserRootHints();
+            result.detach();
+        } else if (MEDIA_ID_INVALID.equals(parentId)) {
+            result.sendResult(null);
+        }
+    }
+
+    @Override
+    public void onLoadChildren(@NonNull String parentId, @NonNull Result<List<MediaItem>> result,
+            @NonNull Bundle options) {
+        if (MEDIA_ID_INCLUDE_METADATA.equals(parentId)) {
+            // Test unparcelling the Bundle.
+            MediaMetadataCompat metadata = options.getParcelable(MEDIA_METADATA);
+            if (metadata == null) {
+                super.onLoadChildren(parentId, result, options);
+            } else {
+                List<MediaItem> mediaItems = new ArrayList<>();
+                mediaItems.add(new MediaItem(metadata.getDescription(), MediaItem.FLAG_PLAYABLE));
+                result.sendResult(mediaItems);
+            }
+        } else {
+            super.onLoadChildren(parentId, result, options);
+        }
+    }
+
+    @Override
+    public void onLoadItem(String itemId, Result<MediaItem> result) {
+        if (MEDIA_ID_CHILDREN_DELAYED.equals(itemId)) {
+            mPendingLoadItemResult = result;
+            mPendingRootHints = getBrowserRootHints();
+            result.detach();
+            return;
+        }
+
+        if (MEDIA_ID_INVALID.equals(itemId)) {
+            result.sendResult(null);
+            return;
+        }
+
+        for (String id : MEDIA_ID_CHILDREN) {
+            if (id.equals(itemId)) {
+                result.sendResult(createMediaItem(id));
+                return;
+            }
+        }
+
+        // Test the case where onLoadItem is not implemented.
+        super.onLoadItem(itemId, result);
+    }
+
+    @Override
+    public void onSearch(String query, Bundle extras, Result<List<MediaItem>> result) {
+        if (SEARCH_QUERY_FOR_NO_RESULT.equals(query)) {
+            result.sendResult(Collections.<MediaItem>emptyList());
+        } else if (SEARCH_QUERY_FOR_ERROR.equals(query)) {
+            result.sendResult(null);
+        } else if (SEARCH_QUERY.equals(query)) {
+            List<MediaItem> items = new ArrayList<>();
+            for (String id : MEDIA_ID_CHILDREN) {
+                if (id.contains(query)) {
+                    items.add(createMediaItem(id));
+                }
+            }
+            result.sendResult(items);
+        }
+    }
+
+    @Override
+    public void onCustomAction(String action, Bundle extras, Result<Bundle> result) {
+        mCustomActionResult = result;
+        mCustomActionExtras = extras;
+        if (CUSTOM_ACTION_FOR_ERROR.equals(action)) {
+            result.sendError(null);
+        } else if (CUSTOM_ACTION.equals(action)) {
+            result.detach();
+        }
+    }
+
+    public void sendDelayedNotifyChildrenChanged() {
+        if (mPendingLoadChildrenResult != null) {
+            mPendingLoadChildrenResult.sendResult(Collections.<MediaItem>emptyList());
+            mPendingRootHints = null;
+            mPendingLoadChildrenResult = null;
+        }
+    }
+
+    public void sendDelayedItemLoaded() {
+        if (mPendingLoadItemResult != null) {
+            mPendingLoadItemResult.sendResult(new MediaItem(new MediaDescriptionCompat.Builder()
+                    .setMediaId(MEDIA_ID_CHILDREN_DELAYED).setExtras(mPendingRootHints).build(),
+                    MediaItem.FLAG_BROWSABLE));
+            mPendingRootHints = null;
+            mPendingLoadItemResult = null;
+        }
+    }
+
+    private MediaItem createMediaItem(String id) {
+        return new MediaItem(new MediaDescriptionCompat.Builder().setMediaId(id).build(),
+                MediaItem.FLAG_BROWSABLE);
+    }
+}
diff --git a/media-compat/version-compat-tests/previous/service/tests/src/android/support/mediacompat/service/StubMediaBrowserServiceCompatWithDelayedMediaSession.java b/media-compat/version-compat-tests/previous/service/tests/src/android/support/mediacompat/service/StubMediaBrowserServiceCompatWithDelayedMediaSession.java
new file mode 100644
index 0000000..509e13f
--- /dev/null
+++ b/media-compat/version-compat-tests/previous/service/tests/src/android/support/mediacompat/service/StubMediaBrowserServiceCompatWithDelayedMediaSession.java
@@ -0,0 +1,64 @@
+/*
+ * 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.service;
+
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.media.MediaBrowserCompat;
+import android.support.v4.media.MediaBrowserServiceCompat;
+import android.support.v4.media.session.MediaSessionCompat;
+
+import java.util.List;
+
+/**
+ * Stub implementation of {@link MediaBrowserServiceCompat}.
+ * This implementation does not call
+ * {@link MediaBrowserServiceCompat#setSessionToken(MediaSessionCompat.Token)} in its
+ * {@link android.app.Service#onCreate}.
+ */
+public class StubMediaBrowserServiceCompatWithDelayedMediaSession extends
+        MediaBrowserServiceCompat {
+
+    static StubMediaBrowserServiceCompatWithDelayedMediaSession sInstance;
+    private MediaSessionCompat mSession;
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        sInstance = this;
+        mSession = new MediaSessionCompat(
+                this, "StubMediaBrowserServiceCompatWithDelayedMediaSession");
+    }
+
+    @Nullable
+    @Override
+    public BrowserRoot onGetRoot(@NonNull String clientPackageName,
+            int clientUid, @Nullable Bundle rootHints) {
+        return new BrowserRoot("StubRootId", null);
+    }
+
+    @Override
+    public void onLoadChildren(@NonNull String parentId,
+            @NonNull Result<List<MediaBrowserCompat.MediaItem>> result) {
+        result.detach();
+    }
+
+    public void callSetSessionToken() {
+        setSessionToken(mSession.getSessionToken());
+    }
+}
diff --git a/media-compat/version-compat-tests/runtest.sh b/media-compat/version-compat-tests/runtest.sh
new file mode 100755
index 0000000..817cd33
--- /dev/null
+++ b/media-compat/version-compat-tests/runtest.sh
@@ -0,0 +1,133 @@
+#!/bin/bash
+# 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.
+
+# A script that runs media-compat-test between different versions.
+#
+# Preconditions:
+#  - Exactly one test device should be connected.
+#
+# TODO:
+#  - Support simultaneous multiple device connection
+
+# Usage './runtest.sh <version_combination_number> [option]'
+
+CLIENT_MODULE_NAME_BASE="support-media-compat-test-client"
+SERVICE_MODULE_NAME_BASE="support-media-compat-test-service"
+CLIENT_VERSION=""
+SERVICE_VERSION=""
+OPTION_TEST_TARGET=""
+
+function printRunTestUsage() {
+  echo "Usage: ./runtest.sh <version_combination_number> [option]"
+  echo ""
+  echo "Version combination number:"
+  echo "    1. Client-ToT             / Service-ToT"
+  echo "    2. Client-ToT             / Service-Latest release"
+  echo "    3. Client-Latest release  / Service-ToT"
+  echo "    4. Run all of the above"
+  echo ""
+  echo "Option:"
+  echo "    -t <class/method>: Only run the specific test class/method."
+}
+
+function runTest() {
+  echo "Running test: Client-$CLIENT_VERSION / Service-$SERVICE_VERSION"
+
+  local CLIENT_MODULE_NAME="$CLIENT_MODULE_NAME_BASE$([ "$CLIENT_VERSION" = "tot" ] || echo "-previous")"
+  local SERVICE_MODULE_NAME="$SERVICE_MODULE_NAME_BASE$([ "$SERVICE_VERSION" = "tot" ] || echo "-previous")"
+
+  # Build test apks
+  ./gradlew $CLIENT_MODULE_NAME:assembleDebugAndroidTest || { echo "Build failed. Aborting."; exit 1; }
+  ./gradlew $SERVICE_MODULE_NAME:assembleDebugAndroidTest || { echo "Build failed. Aborting."; exit 1; }
+
+  # Install the apks
+  adb install -r -d "../../out/dist/$CLIENT_MODULE_NAME.apk" || { echo "Apk installation failed. Aborting."; exit 1; }
+  adb install -r -d "../../out/dist/$SERVICE_MODULE_NAME.apk" || { echo "Apk installation failed. Aborting."; exit 1; }
+
+  # Run the tests
+  local test_command="adb shell am instrument -w -e debug false -e client_version $CLIENT_VERSION -e service_version $SERVICE_VERSION"
+  local client_test_runner="android.support.mediacompat.client.test/android.support.test.runner.AndroidJUnitRunner"
+  local service_test_runner="android.support.mediacompat.service.test/android.support.test.runner.AndroidJUnitRunner"
+
+  echo ">>>>>>>>>>>>>>>>>>>>>>>> Test Started: Client-$CLIENT_VERSION & Service-$SERVICE_VERSION <<<<<<<<<<<<<<<<<<<<<<<<"
+
+  if [[ $OPTION_TEST_TARGET == *"client"* ]]; then
+    ${test_command} $OPTION_TEST_TARGET ${client_test_runner}
+  elif [[ $OPTION_TEST_TARGET == *"service"* ]]; then
+    ${test_command} $OPTION_TEST_TARGET ${service_test_runner}
+  else
+    ${test_command} ${client_test_runner}
+    ${test_command} ${service_test_runner}
+  fi
+
+  echo ">>>>>>>>>>>>>>>>>>>>>>>> Test Ended: Client-$CLIENT_VERSION & Service-$SERVICE_VERSION <<<<<<<<<<<<<<<<<<<<<<<<<<"
+}
+
+
+OLD_PWD=$(pwd)
+
+if ! cd "$(echo $OLD_PWD | awk -F'frameworks/support' '{print $1}')"/frameworks/support &> /dev/null
+then
+  echo "Current working directory is $OLD_PWD"
+  echo "Please re-run this script in any folder under frameworks/support."
+  exit 1;
+fi
+
+if [[ $# -eq 0 || $1 -le 0 || $1 -gt 4 ]]
+then
+  printRunTestUsage
+  exit 1;
+fi
+
+if [[ ${2} == "-t" ]]; then
+  if [[ ${3} == *"client"* || ${3} == *"service"* ]]; then
+    OPTION_TEST_TARGET="-e class ${3}"
+  else
+    echo "Wrong test class/method name. Aborting."
+    echo "It should be in the form of \"<FULL_CLASS_NAME>[#METHOD_NAME]\"."
+    exit 1;
+  fi
+fi
+
+case ${1} in
+  1)
+     CLIENT_VERSION="tot"
+     SERVICE_VERSION="tot"
+     runTest
+     ;;
+  2)
+     CLIENT_VERSION="tot"
+     SERVICE_VERSION="previous"
+     runTest
+     ;;
+  3)
+     CLIENT_VERSION="previous"
+     SERVICE_VERSION="tot"
+     runTest
+     ;;
+  4)
+     CLIENT_VERSION="tot"
+     SERVICE_VERSION="tot"
+     runTest
+
+     CLIENT_VERSION="tot"
+     SERVICE_VERSION="previous"
+     runTest
+
+     CLIENT_VERSION="previous"
+     SERVICE_VERSION="tot"
+     runTest
+     ;;
+esac
diff --git a/percent/build.gradle b/percent/build.gradle
index da5ccc9..7d5a651 100644
--- a/percent/build.gradle
+++ b/percent/build.gradle
@@ -9,8 +9,8 @@
 dependencies {
     api(project(":support-compat"))
 
-    androidTestImplementation(TEST_RUNNER, libs.exclude_annotations)
-    androidTestImplementation(ESPRESSO_CORE, libs.exclude_annotations)
+    androidTestImplementation(TEST_RUNNER)
+    androidTestImplementation(ESPRESSO_CORE)
 }
 
 android {
diff --git a/persistence/db-framework/api/1.0.0.txt b/persistence/db-framework/api/1.0.0.txt
index f460993..7051765 100644
--- a/persistence/db-framework/api/1.0.0.txt
+++ b/persistence/db-framework/api/1.0.0.txt
@@ -1,6 +1,6 @@
 package android.arch.persistence.db.framework {
 
-  public final class FrameworkSQLiteOpenHelperFactory {
+  public final class FrameworkSQLiteOpenHelperFactory implements android.arch.persistence.db.SupportSQLiteOpenHelper.Factory {
     ctor public FrameworkSQLiteOpenHelperFactory();
     method public android.arch.persistence.db.SupportSQLiteOpenHelper create(android.arch.persistence.db.SupportSQLiteOpenHelper.Configuration);
   }
diff --git a/persistence/db/api/1.0.0.txt b/persistence/db/api/1.0.0.txt
index 0e7aea9..f96f17a 100644
--- a/persistence/db/api/1.0.0.txt
+++ b/persistence/db/api/1.0.0.txt
@@ -8,7 +8,7 @@
     method public java.lang.String getSql();
   }
 
-  public abstract interface SupportSQLiteDatabase {
+  public abstract interface SupportSQLiteDatabase implements java.io.Closeable {
     method public abstract void beginTransaction();
     method public abstract void beginTransactionNonExclusive();
     method public abstract void beginTransactionWithListener(android.database.sqlite.SQLiteTransactionListener);
@@ -85,7 +85,7 @@
     method public abstract android.arch.persistence.db.SupportSQLiteOpenHelper create(android.arch.persistence.db.SupportSQLiteOpenHelper.Configuration);
   }
 
-  public abstract interface SupportSQLiteProgram {
+  public abstract interface SupportSQLiteProgram implements java.io.Closeable {
     method public abstract void bindBlob(int, byte[]);
     method public abstract void bindDouble(int, double);
     method public abstract void bindLong(int, long);
diff --git a/v17/preference-leanback/Android.mk b/preference-leanback/Android.mk
similarity index 100%
rename from v17/preference-leanback/Android.mk
rename to preference-leanback/Android.mk
diff --git a/v17/preference-leanback/AndroidManifest.xml b/preference-leanback/AndroidManifest.xml
similarity index 100%
rename from v17/preference-leanback/AndroidManifest.xml
rename to preference-leanback/AndroidManifest.xml
diff --git a/v17/preference-leanback/OWNERS b/preference-leanback/OWNERS
similarity index 100%
rename from v17/preference-leanback/OWNERS
rename to preference-leanback/OWNERS
diff --git a/v17/preference-leanback/api/26.0.0.txt b/preference-leanback/api/26.0.0.txt
similarity index 100%
rename from v17/preference-leanback/api/26.0.0.txt
rename to preference-leanback/api/26.0.0.txt
diff --git a/v17/preference-leanback/api/26.1.0.txt b/preference-leanback/api/26.1.0.txt
similarity index 100%
rename from v17/preference-leanback/api/26.1.0.txt
rename to preference-leanback/api/26.1.0.txt
diff --git a/v17/preference-leanback/api/27.0.0.txt b/preference-leanback/api/27.0.0.txt
similarity index 100%
rename from v17/preference-leanback/api/27.0.0.txt
rename to preference-leanback/api/27.0.0.txt
diff --git a/preference-leanback/api/current.txt b/preference-leanback/api/current.txt
new file mode 100644
index 0000000..4703ae3
--- /dev/null
+++ b/preference-leanback/api/current.txt
@@ -0,0 +1,62 @@
+package android.support.v17.preference {
+
+  public abstract class BaseLeanbackPreferenceFragment extends android.support.v14.preference.PreferenceFragment {
+    ctor public BaseLeanbackPreferenceFragment();
+  }
+
+  public class LeanbackListPreferenceDialogFragment extends android.support.v17.preference.LeanbackPreferenceDialogFragment {
+    ctor public LeanbackListPreferenceDialogFragment();
+    method public static android.support.v17.preference.LeanbackListPreferenceDialogFragment newInstanceMulti(java.lang.String);
+    method public static android.support.v17.preference.LeanbackListPreferenceDialogFragment newInstanceSingle(java.lang.String);
+    method public android.support.v7.widget.RecyclerView.Adapter onCreateAdapter();
+  }
+
+  public class LeanbackListPreferenceDialogFragment.AdapterMulti extends android.support.v7.widget.RecyclerView.Adapter implements android.support.v17.preference.LeanbackListPreferenceDialogFragment.ViewHolder.OnItemClickListener {
+    ctor public LeanbackListPreferenceDialogFragment.AdapterMulti(java.lang.CharSequence[], java.lang.CharSequence[], java.util.Set<java.lang.String>);
+    method public int getItemCount();
+    method public void onBindViewHolder(android.support.v17.preference.LeanbackListPreferenceDialogFragment.ViewHolder, int);
+    method public android.support.v17.preference.LeanbackListPreferenceDialogFragment.ViewHolder onCreateViewHolder(android.view.ViewGroup, int);
+    method public void onItemClick(android.support.v17.preference.LeanbackListPreferenceDialogFragment.ViewHolder);
+  }
+
+  public class LeanbackListPreferenceDialogFragment.AdapterSingle extends android.support.v7.widget.RecyclerView.Adapter implements android.support.v17.preference.LeanbackListPreferenceDialogFragment.ViewHolder.OnItemClickListener {
+    ctor public LeanbackListPreferenceDialogFragment.AdapterSingle(java.lang.CharSequence[], java.lang.CharSequence[], java.lang.CharSequence);
+    method public int getItemCount();
+    method public void onBindViewHolder(android.support.v17.preference.LeanbackListPreferenceDialogFragment.ViewHolder, int);
+    method public android.support.v17.preference.LeanbackListPreferenceDialogFragment.ViewHolder onCreateViewHolder(android.view.ViewGroup, int);
+    method public void onItemClick(android.support.v17.preference.LeanbackListPreferenceDialogFragment.ViewHolder);
+  }
+
+  public static class LeanbackListPreferenceDialogFragment.ViewHolder extends android.support.v7.widget.RecyclerView.ViewHolder implements android.view.View.OnClickListener {
+    ctor public LeanbackListPreferenceDialogFragment.ViewHolder(android.view.View, android.support.v17.preference.LeanbackListPreferenceDialogFragment.ViewHolder.OnItemClickListener);
+    method public android.view.ViewGroup getContainer();
+    method public android.widget.TextView getTitleView();
+    method public android.widget.Checkable getWidgetView();
+    method public void onClick(android.view.View);
+  }
+
+  public static abstract interface LeanbackListPreferenceDialogFragment.ViewHolder.OnItemClickListener {
+    method public abstract void onItemClick(android.support.v17.preference.LeanbackListPreferenceDialogFragment.ViewHolder);
+  }
+
+  public class LeanbackPreferenceDialogFragment extends android.app.Fragment {
+    ctor public LeanbackPreferenceDialogFragment();
+    method public android.support.v7.preference.DialogPreference getPreference();
+    field public static final java.lang.String ARG_KEY = "key";
+  }
+
+  public abstract class LeanbackPreferenceFragment extends android.support.v17.preference.BaseLeanbackPreferenceFragment {
+    ctor public LeanbackPreferenceFragment();
+    method public void setTitle(java.lang.CharSequence);
+  }
+
+  public abstract class LeanbackSettingsFragment extends android.app.Fragment implements android.support.v14.preference.PreferenceFragment.OnPreferenceDisplayDialogCallback android.support.v14.preference.PreferenceFragment.OnPreferenceStartFragmentCallback android.support.v14.preference.PreferenceFragment.OnPreferenceStartScreenCallback {
+    ctor public LeanbackSettingsFragment();
+    method public boolean onPreferenceDisplayDialog(android.support.v14.preference.PreferenceFragment, android.support.v7.preference.Preference);
+    method public abstract void onPreferenceStartInitialScreen();
+    method public void startImmersiveFragment(android.app.Fragment);
+    method public void startPreferenceFragment(android.app.Fragment);
+  }
+
+}
+
diff --git a/v17/preference-leanback/api21/android/support/v17/internal/widget/OutlineOnlyWithChildrenFrameLayout.java b/preference-leanback/api21/android/support/v17/internal/widget/OutlineOnlyWithChildrenFrameLayout.java
similarity index 100%
rename from v17/preference-leanback/api21/android/support/v17/internal/widget/OutlineOnlyWithChildrenFrameLayout.java
rename to preference-leanback/api21/android/support/v17/internal/widget/OutlineOnlyWithChildrenFrameLayout.java
diff --git a/v17/preference-leanback/api21/android/support/v17/preference/LeanbackPreferenceFragmentTransitionHelperApi21.java b/preference-leanback/api21/android/support/v17/preference/LeanbackPreferenceFragmentTransitionHelperApi21.java
similarity index 100%
rename from v17/preference-leanback/api21/android/support/v17/preference/LeanbackPreferenceFragmentTransitionHelperApi21.java
rename to preference-leanback/api21/android/support/v17/preference/LeanbackPreferenceFragmentTransitionHelperApi21.java
diff --git a/v17/preference-leanback/build.gradle b/preference-leanback/build.gradle
similarity index 100%
rename from v17/preference-leanback/build.gradle
rename to preference-leanback/build.gradle
diff --git a/v17/preference-leanback/lint-baseline.xml b/preference-leanback/lint-baseline.xml
similarity index 100%
rename from v17/preference-leanback/lint-baseline.xml
rename to preference-leanback/lint-baseline.xml
diff --git a/v17/preference-leanback/res/color/lb_preference_item_primary_text_color.xml b/preference-leanback/res/color/lb_preference_item_primary_text_color.xml
similarity index 100%
rename from v17/preference-leanback/res/color/lb_preference_item_primary_text_color.xml
rename to preference-leanback/res/color/lb_preference_item_primary_text_color.xml
diff --git a/v17/preference-leanback/res/color/lb_preference_item_secondary_text_color.xml b/preference-leanback/res/color/lb_preference_item_secondary_text_color.xml
similarity index 100%
rename from v17/preference-leanback/res/color/lb_preference_item_secondary_text_color.xml
rename to preference-leanback/res/color/lb_preference_item_secondary_text_color.xml
diff --git a/v17/preference-leanback/res/layout-v21/leanback_preference_category.xml b/preference-leanback/res/layout-v21/leanback_preference_category.xml
similarity index 100%
rename from v17/preference-leanback/res/layout-v21/leanback_preference_category.xml
rename to preference-leanback/res/layout-v21/leanback_preference_category.xml
diff --git a/v17/preference-leanback/res/layout-v21/leanback_settings_fragment.xml b/preference-leanback/res/layout-v21/leanback_settings_fragment.xml
similarity index 100%
rename from v17/preference-leanback/res/layout-v21/leanback_settings_fragment.xml
rename to preference-leanback/res/layout-v21/leanback_settings_fragment.xml
diff --git a/v17/preference-leanback/res/layout/leanback_list_preference_fragment.xml b/preference-leanback/res/layout/leanback_list_preference_fragment.xml
similarity index 100%
rename from v17/preference-leanback/res/layout/leanback_list_preference_fragment.xml
rename to preference-leanback/res/layout/leanback_list_preference_fragment.xml
diff --git a/v17/preference-leanback/res/layout/leanback_list_preference_item_multi.xml b/preference-leanback/res/layout/leanback_list_preference_item_multi.xml
similarity index 100%
rename from v17/preference-leanback/res/layout/leanback_list_preference_item_multi.xml
rename to preference-leanback/res/layout/leanback_list_preference_item_multi.xml
diff --git a/v17/preference-leanback/res/layout/leanback_list_preference_item_single.xml b/preference-leanback/res/layout/leanback_list_preference_item_single.xml
similarity index 100%
rename from v17/preference-leanback/res/layout/leanback_list_preference_item_single.xml
rename to preference-leanback/res/layout/leanback_list_preference_item_single.xml
diff --git a/v17/preference-leanback/res/layout/leanback_preference.xml b/preference-leanback/res/layout/leanback_preference.xml
similarity index 100%
rename from v17/preference-leanback/res/layout/leanback_preference.xml
rename to preference-leanback/res/layout/leanback_preference.xml
diff --git a/v17/preference-leanback/res/layout/leanback_preference_category.xml b/preference-leanback/res/layout/leanback_preference_category.xml
similarity index 100%
rename from v17/preference-leanback/res/layout/leanback_preference_category.xml
rename to preference-leanback/res/layout/leanback_preference_category.xml
diff --git a/v17/preference-leanback/res/layout/leanback_preference_fragment.xml b/preference-leanback/res/layout/leanback_preference_fragment.xml
similarity index 100%
rename from v17/preference-leanback/res/layout/leanback_preference_fragment.xml
rename to preference-leanback/res/layout/leanback_preference_fragment.xml
diff --git a/v17/preference-leanback/res/layout/leanback_preference_information.xml b/preference-leanback/res/layout/leanback_preference_information.xml
similarity index 100%
rename from v17/preference-leanback/res/layout/leanback_preference_information.xml
rename to preference-leanback/res/layout/leanback_preference_information.xml
diff --git a/v17/preference-leanback/res/layout/leanback_preference_widget_seekbar.xml b/preference-leanback/res/layout/leanback_preference_widget_seekbar.xml
similarity index 100%
rename from v17/preference-leanback/res/layout/leanback_preference_widget_seekbar.xml
rename to preference-leanback/res/layout/leanback_preference_widget_seekbar.xml
diff --git a/v17/preference-leanback/res/layout/leanback_preferences_list.xml b/preference-leanback/res/layout/leanback_preferences_list.xml
similarity index 100%
rename from v17/preference-leanback/res/layout/leanback_preferences_list.xml
rename to preference-leanback/res/layout/leanback_preferences_list.xml
diff --git a/v17/preference-leanback/res/layout/leanback_settings_fragment.xml b/preference-leanback/res/layout/leanback_settings_fragment.xml
similarity index 100%
rename from v17/preference-leanback/res/layout/leanback_settings_fragment.xml
rename to preference-leanback/res/layout/leanback_settings_fragment.xml
diff --git a/v17/preference-leanback/res/values/colors.xml b/preference-leanback/res/values/colors.xml
similarity index 100%
rename from v17/preference-leanback/res/values/colors.xml
rename to preference-leanback/res/values/colors.xml
diff --git a/v17/preference-leanback/res/values/dimens.xml b/preference-leanback/res/values/dimens.xml
similarity index 100%
rename from v17/preference-leanback/res/values/dimens.xml
rename to preference-leanback/res/values/dimens.xml
diff --git a/v17/preference-leanback/res/values/styles.xml b/preference-leanback/res/values/styles.xml
similarity index 100%
rename from v17/preference-leanback/res/values/styles.xml
rename to preference-leanback/res/values/styles.xml
diff --git a/v17/preference-leanback/res/values/themes.xml b/preference-leanback/res/values/themes.xml
similarity index 100%
rename from v17/preference-leanback/res/values/themes.xml
rename to preference-leanback/res/values/themes.xml
diff --git a/v17/preference-leanback/src/android/support/v17/preference/BaseLeanbackPreferenceFragment.java b/preference-leanback/src/android/support/v17/preference/BaseLeanbackPreferenceFragment.java
similarity index 100%
rename from v17/preference-leanback/src/android/support/v17/preference/BaseLeanbackPreferenceFragment.java
rename to preference-leanback/src/android/support/v17/preference/BaseLeanbackPreferenceFragment.java
diff --git a/v17/preference-leanback/src/android/support/v17/preference/LeanbackListPreferenceDialogFragment.java b/preference-leanback/src/android/support/v17/preference/LeanbackListPreferenceDialogFragment.java
similarity index 100%
rename from v17/preference-leanback/src/android/support/v17/preference/LeanbackListPreferenceDialogFragment.java
rename to preference-leanback/src/android/support/v17/preference/LeanbackListPreferenceDialogFragment.java
diff --git a/v17/preference-leanback/src/android/support/v17/preference/LeanbackPreferenceDialogFragment.java b/preference-leanback/src/android/support/v17/preference/LeanbackPreferenceDialogFragment.java
similarity index 100%
rename from v17/preference-leanback/src/android/support/v17/preference/LeanbackPreferenceDialogFragment.java
rename to preference-leanback/src/android/support/v17/preference/LeanbackPreferenceDialogFragment.java
diff --git a/v17/preference-leanback/src/android/support/v17/preference/LeanbackPreferenceFragment.java b/preference-leanback/src/android/support/v17/preference/LeanbackPreferenceFragment.java
similarity index 100%
rename from v17/preference-leanback/src/android/support/v17/preference/LeanbackPreferenceFragment.java
rename to preference-leanback/src/android/support/v17/preference/LeanbackPreferenceFragment.java
diff --git a/v17/preference-leanback/src/android/support/v17/preference/LeanbackSettingsFragment.java b/preference-leanback/src/android/support/v17/preference/LeanbackSettingsFragment.java
similarity index 100%
rename from v17/preference-leanback/src/android/support/v17/preference/LeanbackSettingsFragment.java
rename to preference-leanback/src/android/support/v17/preference/LeanbackSettingsFragment.java
diff --git a/v17/preference-leanback/src/android/support/v17/preference/LeanbackSettingsRootView.java b/preference-leanback/src/android/support/v17/preference/LeanbackSettingsRootView.java
similarity index 100%
rename from v17/preference-leanback/src/android/support/v17/preference/LeanbackSettingsRootView.java
rename to preference-leanback/src/android/support/v17/preference/LeanbackSettingsRootView.java
diff --git a/recyclerview-selection/Android.mk b/recyclerview-selection/Android.mk
new file mode 100644
index 0000000..ed93fa2
--- /dev/null
+++ b/recyclerview-selection/Android.mk
@@ -0,0 +1,30 @@
+# 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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
+LOCAL_MODULE := android-support-recyclerview-selection
+LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
+LOCAL_SRC_FILES := $(call all-java-files-under, src/main/java)
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+LOCAL_SHARED_ANDROID_LIBRARIES := \
+    android-support-v7-recyclerview \
+    android-support-compat \
+    android-support-annotations
+LOCAL_JAR_EXCLUDE_FILES := none
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
+LOCAL_AAPT_FLAGS := --add-javadoc-annotation doconly
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/recyclerview-selection/AndroidManifest.xml b/recyclerview-selection/AndroidManifest.xml
new file mode 100644
index 0000000..320ae3a
--- /dev/null
+++ b/recyclerview-selection/AndroidManifest.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="androidx.recyclerview.selection">
+    <uses-sdk android:minSdkVersion="14" />
+</manifest>
diff --git a/recyclerview-selection/build.gradle b/recyclerview-selection/build.gradle
new file mode 100644
index 0000000..06dc730
--- /dev/null
+++ b/recyclerview-selection/build.gradle
@@ -0,0 +1,50 @@
+/*
+ * 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.
+ */
+
+import static android.support.dependencies.DependenciesKt.*
+
+plugins {
+    id("SupportAndroidLibraryPlugin")
+}
+
+dependencies {
+    api project(':recyclerview-v7')
+    api project(':support-annotations')
+    api project(':support-compat')
+
+    androidTestImplementation(TEST_RUNNER)
+    androidTestImplementation(ESPRESSO_CORE)
+    androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
+    androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
+    androidTestImplementation(JUNIT)
+}
+
+android {
+    defaultConfig {
+        minSdkVersion 14
+    }
+    sourceSets {
+        main.res.srcDirs 'res', 'res-public'
+    }
+}
+
+supportLibrary {
+    name 'Android RecyclerView Selection'
+    publish false
+    legacySourceLocation true
+    inceptionYear '2017'
+    description 'Library providing item selection framework for RecyclerView. Support for single and multi selection is provided.'
+}
diff --git a/recyclerview-selection/res/drawable/selection_band_overlay.xml b/recyclerview-selection/res/drawable/selection_band_overlay.xml
new file mode 100644
index 0000000..f780178
--- /dev/null
+++ b/recyclerview-selection/res/drawable/selection_band_overlay.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:shape="rectangle">
+    <solid android:color="#339999ff" />
+    <stroke android:width="1dp" android:color="#44000000" />
+</shape>
diff --git a/recyclerview-selection/src/main/java/androidx/recyclerview/selection/ActivationCallbacks.java b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/ActivationCallbacks.java
new file mode 100644
index 0000000..606f35a
--- /dev/null
+++ b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/ActivationCallbacks.java
@@ -0,0 +1,55 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.support.annotation.RestrictTo;
+import android.view.MotionEvent;
+
+import androidx.recyclerview.selection.ItemDetailsLookup.ItemDetails;
+
+/**
+ * Override methods in this class to connect specialized behaviors of the selection
+ * code to the application environment.
+ *
+ * @param <K> Selection key type. Usually String or Long.
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public abstract class ActivationCallbacks<K> {
+
+    static <K> ActivationCallbacks<K> dummy() {
+        return new ActivationCallbacks<K>() {
+            @Override
+            public boolean onItemActivated(ItemDetails item, MotionEvent e) {
+                return false;
+            }
+        };
+    }
+
+    /**
+     * Called when an item is activated. An item is activitated, for example, when
+     * there is no active selection and the user double clicks an item with a
+     * pointing device like a Mouse.
+     *
+     * @param item details of the item.
+     * @param e the event associated with item.
+     * @return true if the event was handled.
+     */
+    public abstract boolean onItemActivated(ItemDetails<K> item, MotionEvent e);
+}
diff --git a/recyclerview-selection/src/main/java/androidx/recyclerview/selection/AutoScroller.java b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/AutoScroller.java
new file mode 100644
index 0000000..13e87bd
--- /dev/null
+++ b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/AutoScroller.java
@@ -0,0 +1,42 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.graphics.Point;
+import android.support.annotation.RestrictTo;
+
+/**
+ * Provides support for auto-scrolling a view.
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public abstract class AutoScroller {
+
+    /**
+     * Resets state of the scroller. Call this when the user activity that is driving
+     * auto-scrolling is done.
+     */
+    protected abstract void reset();
+
+    /**
+     * Processes a new input location.
+     * @param location
+     */
+    protected abstract void scroll(Point location);
+}
diff --git a/recyclerview-selection/src/main/java/androidx/recyclerview/selection/BandPredicate.java b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/BandPredicate.java
new file mode 100644
index 0000000..9a5ae47
--- /dev/null
+++ b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/BandPredicate.java
@@ -0,0 +1,131 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+import static android.support.v4.util.Preconditions.checkArgument;
+
+import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+import android.support.v7.widget.GridLayoutManager;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.view.MotionEvent;
+import android.view.View;
+
+/**
+ * Provides a means of controlling when and where band selection can be initiated.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public abstract class BandPredicate {
+
+    /** @return true if band selection can be initiated in response to the {@link MotionEvent}. */
+    public abstract boolean canInitiate(MotionEvent e);
+
+    private static boolean hasSupportedLayoutManager(RecyclerView recView) {
+        RecyclerView.LayoutManager lm = recView.getLayoutManager();
+        return lm instanceof GridLayoutManager
+                || lm instanceof LinearLayoutManager;
+    }
+
+    /**
+     * Creates a new band predicate that permits initiation of band on areas
+     * of a RecyclerView that map to RecyclerView.NO_POSITION.
+     *
+     * @param recView
+     * @return
+     */
+    @SuppressWarnings("unused")
+    public static BandPredicate noPosition(RecyclerView recView) {
+        return new NoPosition(recView);
+    }
+
+    /**
+     * Creates a new band predicate that permits initiation of band
+     * anywhere doesn't correspond to a draggable region of a item.
+     *
+     * @param detailsLookup
+     * @return
+     */
+    public static BandPredicate notDraggable(
+            RecyclerView recView, ItemDetailsLookup detailsLookup) {
+        return new NotDraggable(recView, detailsLookup);
+    }
+
+    /**
+     * A BandPredicate that allows initiation of band selection only in areas of RecyclerView
+     * that have {@link RecyclerView#NO_POSITION}. In most cases, this will be the empty areas
+     * between views.
+     */
+    private static final class NoPosition extends BandPredicate {
+
+        private final RecyclerView mRecView;
+
+        NoPosition(RecyclerView recView) {
+            checkArgument(recView != null);
+
+            mRecView = recView;
+        }
+
+        @Override
+        public boolean canInitiate(MotionEvent e) {
+            if (!hasSupportedLayoutManager(mRecView)
+                    || mRecView.hasPendingAdapterUpdates()) {
+                return false;
+            }
+
+            View itemView = mRecView.findChildViewUnder(e.getX(), e.getY());
+            int position = itemView != null
+                    ? mRecView.getChildAdapterPosition(itemView)
+                    : RecyclerView.NO_POSITION;
+
+            return position == RecyclerView.NO_POSITION;
+        }
+    }
+
+    /**
+     * A BandPredicate that allows initiation of band selection in any area that is not
+     * draggable as determined by consulting
+     * {@link ItemDetailsLookup#inItemDragRegion(MotionEvent)}.
+     */
+    private static final class NotDraggable extends BandPredicate {
+
+        private final RecyclerView mRecView;
+        private final ItemDetailsLookup mDetailsLookup;
+
+        NotDraggable(RecyclerView recView, ItemDetailsLookup detailsLookup) {
+            checkArgument(recView != null);
+            checkArgument(detailsLookup != null);
+
+            mRecView = recView;
+            mDetailsLookup = detailsLookup;
+        }
+
+        @Override
+        public boolean canInitiate(MotionEvent e) {
+            if (!hasSupportedLayoutManager(mRecView)
+                    || mRecView.hasPendingAdapterUpdates()) {
+                return false;
+            }
+
+            @Nullable ItemDetailsLookup.ItemDetails details = mDetailsLookup.getItemDetails(e);
+            return (details == null) || !details.inDragRegion(e);
+        }
+    }
+}
diff --git a/recyclerview-selection/src/main/java/androidx/recyclerview/selection/BandSelectionHelper.java b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/BandSelectionHelper.java
new file mode 100644
index 0000000..5362e2b
--- /dev/null
+++ b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/BandSelectionHelper.java
@@ -0,0 +1,355 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static android.support.v4.util.Preconditions.checkArgument;
+import static android.support.v4.util.Preconditions.checkState;
+
+import static androidx.recyclerview.selection.Shared.VERBOSE;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.support.annotation.DrawableRes;
+import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerView.OnItemTouchListener;
+import android.support.v7.widget.RecyclerView.OnScrollListener;
+import android.util.Log;
+import android.view.MotionEvent;
+
+import java.util.Set;
+
+import androidx.recyclerview.selection.SelectionHelper.SelectionPredicate;
+
+/**
+ * Provides mouse driven band-selection support when used in conjunction with a {@link RecyclerView}
+ * instance. This class is responsible for rendering a band overlay and manipulating selection
+ * status of the items it intersects with.
+ *
+ * <p> Given the recycling nature of RecyclerView items that have scrolled off-screen would not
+ * be selectable with a band that itself was partially rendered off-screen. To address this,
+ * BandSelectionController builds a model of the list/grid information presented by RecyclerView as
+ * the user interacts with items using their pointer (and the band). Selectable items that intersect
+ * with the band, both on and off screen, are selected on pointer up.
+ *
+ * @param <K> Selection key type. Usually String or Long.
+ */
+class BandSelectionHelper<K> implements OnItemTouchListener {
+
+    static final String TAG = "BandSelectionHelper";
+    static final boolean DEBUG = false;
+
+    private final BandHost mHost;
+    private final ItemKeyProvider<K> mKeyProvider;
+    private final SelectionHelper<K> mSelectionHelper;
+    private final SelectionPredicate<K> mSelectionPredicate;
+    private final BandPredicate mBandPredicate;
+    private final FocusCallbacks<K> mFocusCallbacks;
+    private final ContentLock mLock;
+    private final AutoScroller mScroller;
+    private final GridModel.SelectionObserver mGridObserver;
+
+    private @Nullable Point mCurrentPosition;
+    private @Nullable Point mOrigin;
+    private @Nullable GridModel mModel;
+
+    /**
+     * See {@link BandSelectionHelper#create}.
+     */
+    BandSelectionHelper(
+            BandHost host,
+            AutoScroller scroller,
+            ItemKeyProvider<K> keyProvider,
+            SelectionHelper<K> selectionHelper,
+            SelectionPredicate<K> selectionPredicate,
+            BandPredicate bandPredicate,
+            FocusCallbacks<K> focusCallbacks,
+            ContentLock lock) {
+
+        checkArgument(host != null);
+        checkArgument(scroller != null);
+        checkArgument(keyProvider != null);
+        checkArgument(selectionHelper != null);
+        checkArgument(selectionPredicate != null);
+        checkArgument(bandPredicate != null);
+        checkArgument(focusCallbacks != null);
+        checkArgument(lock != null);
+
+        mHost = host;
+        mKeyProvider = keyProvider;
+        mSelectionHelper = selectionHelper;
+        mSelectionPredicate = selectionPredicate;
+        mBandPredicate = bandPredicate;
+        mFocusCallbacks = focusCallbacks;
+        mLock = lock;
+
+        mHost.addOnScrollListener(
+                new OnScrollListener() {
+                    @Override
+                    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+                        BandSelectionHelper.this.onScrolled(recyclerView, dx, dy);
+                    }
+                });
+
+        mScroller = scroller;
+
+        mGridObserver = new GridModel.SelectionObserver<K>() {
+            @Override
+            public void onSelectionChanged(Set<K> updatedSelection) {
+                mSelectionHelper.setProvisionalSelection(updatedSelection);
+            }
+        };
+    }
+
+    /**
+     * Creates a new instance.
+     *
+     * @return new BandSelectionHelper instance.
+     */
+    static <K> BandSelectionHelper create(
+            RecyclerView recView,
+            AutoScroller scroller,
+            @DrawableRes int bandOverlayId,
+            ItemKeyProvider<K> keyProvider,
+            SelectionHelper<K> selectionHelper,
+            SelectionPredicate<K> selectionPredicate,
+            BandPredicate bandPredicate,
+            FocusCallbacks<K> focusCallbacks,
+            ContentLock lock) {
+
+        return new BandSelectionHelper<>(
+                new DefaultBandHost<>(recView, bandOverlayId, keyProvider, selectionPredicate),
+                scroller,
+                keyProvider,
+                selectionHelper,
+                selectionPredicate,
+                bandPredicate,
+                focusCallbacks,
+                lock);
+    }
+
+    @VisibleForTesting
+    boolean isActive() {
+        boolean active = mModel != null;
+        if (DEBUG && active) {
+            mLock.checkLocked();
+        }
+        return active;
+    }
+
+    /**
+     * Clients must call reset when there are any material changes to the layout of items
+     * in RecyclerView.
+     */
+    void reset() {
+        if (!isActive()) {
+            return;
+        }
+
+        mHost.hideBand();
+        if (mModel != null) {
+            mModel.stopCapturing();
+            mModel.onDestroy();
+        }
+
+        mModel = null;
+        mOrigin = null;
+
+        mScroller.reset();
+        mLock.unblock();
+    }
+
+    @VisibleForTesting
+    boolean shouldStart(MotionEvent e) {
+        // b/30146357 && b/23793622. onInterceptTouchEvent does not dispatch events to onTouchEvent
+        // unless the event is != ACTION_DOWN. Thus, we need to actually start band selection when
+        // mouse moves.
+        return MotionEvents.isPrimaryButtonPressed(e)
+                && MotionEvents.isActionMove(e)
+                && mBandPredicate.canInitiate(e)
+                && !isActive();
+    }
+
+    @VisibleForTesting
+    boolean shouldStop(MotionEvent e) {
+        return isActive()
+                && (MotionEvents.isActionUp(e)
+                || MotionEvents.isActionPointerUp(e)
+                || MotionEvents.isActionCancel(e));
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(RecyclerView unused, MotionEvent e) {
+        if (shouldStart(e)) {
+            startBandSelect(e);
+        } else if (shouldStop(e)) {
+            endBandSelect();
+        }
+
+        return isActive();
+    }
+
+    /**
+     * Processes a MotionEvent by starting, ending, or resizing the band select overlay.
+     */
+    @Override
+    public void onTouchEvent(RecyclerView unused, MotionEvent e) {
+        if (shouldStop(e)) {
+            endBandSelect();
+            return;
+        }
+
+        // We shouldn't get any events in this method when band select is not active,
+        // but it turns some guests show up late to the party.
+        // Probably happening when a re-layout is happening to the ReyclerView (ie. Pull-To-Refresh)
+        if (!isActive()) {
+            return;
+        }
+
+        if (DEBUG) {
+            checkArgument(MotionEvents.isActionMove(e));
+            checkState(mModel != null);
+        }
+
+        mCurrentPosition = MotionEvents.getOrigin(e);
+
+        mModel.resizeSelection(mCurrentPosition);
+
+        resizeBand();
+        mScroller.scroll(mCurrentPosition);
+    }
+
+    @Override
+    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
+    }
+
+    /**
+     * Starts band select by adding the drawable to the RecyclerView's overlay.
+     */
+    private void startBandSelect(MotionEvent e) {
+        checkState(!isActive());
+
+        if (!MotionEvents.isCtrlKeyPressed(e)) {
+            mSelectionHelper.clearSelection();
+        }
+
+        Point origin = MotionEvents.getOrigin(e);
+        if (DEBUG) Log.d(TAG, "Starting band select @ " + origin);
+
+        mModel = mHost.createGridModel();
+        mModel.addOnSelectionChangedListener(mGridObserver);
+
+        mLock.block();
+        mFocusCallbacks.clearFocus();
+        mOrigin = origin;
+        // NOTE: Pay heed that resizeBand modifies the y coordinates
+        // in onScrolled. Not sure if model expects this. If not
+        // it should be defending against this.
+        mModel.startCapturing(mOrigin);
+    }
+
+    /**
+     * Resizes the band select rectangle by using the origin and the current pointer position as
+     * two opposite corners of the selection.
+     */
+    private void resizeBand() {
+        Rect bounds = new Rect(Math.min(mOrigin.x, mCurrentPosition.x),
+                Math.min(mOrigin.y, mCurrentPosition.y),
+                Math.max(mOrigin.x, mCurrentPosition.x),
+                Math.max(mOrigin.y, mCurrentPosition.y));
+
+        if (VERBOSE) Log.v(TAG, "Resizing band! " + bounds);
+        mHost.showBand(bounds);
+    }
+
+    /**
+     * Ends band select by removing the overlay.
+     */
+    private void endBandSelect() {
+        if (DEBUG) {
+            Log.d(TAG, "Ending band select.");
+            checkState(mModel != null);
+        }
+
+        // TODO: Currently when a band select operation ends outside
+        // of an item (e.g. in the empty area between items),
+        // getPositionNearestOrigin may return an unselected item.
+        // Since the point of this code is to establish the
+        // anchor point for subsequent range operations (SHIFT+CLICK)
+        // we really want to do a better job figuring out the last
+        // item selected (and nearest to the cursor).
+        int firstSelected = mModel.getPositionNearestOrigin();
+        if (firstSelected != GridModel.NOT_SET
+                && mSelectionHelper.isSelected(mKeyProvider.getKey(firstSelected))) {
+            // Establish the band selection point as range anchor. This
+            // allows touch and keyboard based selection activities
+            // to be based on the band selection anchor point.
+            mSelectionHelper.anchorRange(firstSelected);
+        }
+
+        mSelectionHelper.mergeProvisionalSelection();
+        reset();
+    }
+
+    /**
+     * @see RecyclerView.OnScrollListener
+     */
+    private void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+        if (!isActive()) {
+            return;
+        }
+
+        // Adjust the y-coordinate of the origin the opposite number of pixels so that the
+        // origin remains in the same place relative to the view's items.
+        mOrigin.y -= dy;
+        resizeBand();
+    }
+
+    /**
+     * Provides functionality for BandController. Exists primarily to tests that are
+     * fully isolated from RecyclerView.
+     *
+     * @param <K> Selection key type. Usually String or Long.
+     */
+    abstract static class BandHost<K> {
+
+        /**
+         * Returns a new GridModel instance.
+         */
+        abstract GridModel<K> createGridModel();
+
+        /**
+         * Show the band covering the bounds.
+         *
+         * @param bounds The boundaries of the band to show.
+         */
+        abstract void showBand(Rect bounds);
+
+        /**
+         * Hide the band.
+         */
+        abstract void hideBand();
+
+        /**
+         * Add a listener to be notified on scroll events.
+         *
+         * @param listener
+         */
+        abstract void addOnScrollListener(RecyclerView.OnScrollListener listener);
+    }
+}
diff --git a/recyclerview-selection/src/main/java/androidx/recyclerview/selection/ContentLock.java b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/ContentLock.java
new file mode 100644
index 0000000..6891eab
--- /dev/null
+++ b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/ContentLock.java
@@ -0,0 +1,98 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+import static android.support.v4.util.Preconditions.checkState;
+
+import static androidx.recyclerview.selection.Shared.DEBUG;
+
+import android.content.Loader;
+import android.support.annotation.MainThread;
+import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+import android.util.Log;
+
+/**
+ * ContentLock provides a mechanism to block content from reloading while selection
+ * activities like gesture and band selection are active. Clients using live data
+ * (data loaded, for example by a {@link Loader}), should route calls to load
+ * content through this lock using {@link ContentLock#runWhenUnlocked(Runnable)}.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public final class ContentLock {
+
+    private static final String TAG = "ContentLock";
+
+    private int mLocks = 0;
+    private @Nullable Runnable mCallback;
+
+    /**
+     * Increment the block count by 1
+     */
+    @MainThread
+    synchronized void block() {
+        mLocks++;
+        if (DEBUG) Log.v(TAG, "Incremented content lock count to " + mLocks + ".");
+    }
+
+    /**
+     * Decrement the block count by 1; If no other object is trying to block and there exists some
+     * callback, that callback will be run
+     */
+    @MainThread
+    synchronized void unblock() {
+        checkState(mLocks > 0);
+
+        mLocks--;
+        if (DEBUG) Log.v(TAG, "Decremented content lock count to " + mLocks + ".");
+
+        if (mLocks == 0 && mCallback != null) {
+            mCallback.run();
+            mCallback = null;
+        }
+    }
+
+    /**
+     * Attempts to run the given Runnable if not-locked, or else the Runnable is set to be ran next
+     * (replacing any previous set Runnables).
+     */
+    @SuppressWarnings("unused")
+    public synchronized void runWhenUnlocked(Runnable runnable) {
+        if (mLocks == 0) {
+            runnable.run();
+        } else {
+            mCallback = runnable;
+        }
+    }
+
+    /**
+     * Allows other selection code to perform a precondition check asserting the state is locked.
+     */
+    void checkLocked() {
+        checkState(mLocks > 0);
+    }
+
+    /**
+     * Allows other selection code to perform a precondition check asserting the state is unlocked.
+     */
+    void checkUnlocked() {
+        checkState(mLocks == 0);
+    }
+}
diff --git a/recyclerview-selection/src/main/java/androidx/recyclerview/selection/DefaultBandHost.java b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/DefaultBandHost.java
new file mode 100644
index 0000000..f0fd4fe
--- /dev/null
+++ b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/DefaultBandHost.java
@@ -0,0 +1,153 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static android.support.v4.util.Preconditions.checkArgument;
+
+import android.graphics.Canvas;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.DrawableRes;
+import android.support.v7.widget.GridLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerView.ItemDecoration;
+import android.view.View;
+
+import androidx.recyclerview.selection.SelectionHelper.SelectionPredicate;
+
+/**
+ * RecyclerView backed {@link BandSelectionHelper.BandHost}.
+ */
+final class DefaultBandHost<K> extends GridModel.GridHost<K> {
+
+    private static final Rect NILL_RECT = new Rect(0, 0, 0, 0);
+
+    private final RecyclerView mRecView;
+    private final Drawable mBand;
+    private final ItemKeyProvider<K> mKeyProvider;
+    private final SelectionPredicate<K> mSelectionPredicate;
+
+    DefaultBandHost(
+            RecyclerView recView,
+            @DrawableRes int bandOverlayId,
+            ItemKeyProvider<K> keyProvider,
+            SelectionPredicate<K> selectionPredicate) {
+
+        checkArgument(recView != null);
+
+        mRecView = recView;
+        mBand = mRecView.getContext().getResources().getDrawable(bandOverlayId);
+
+        checkArgument(mBand != null);
+        checkArgument(keyProvider != null);
+        checkArgument(selectionPredicate != null);
+
+        mKeyProvider = keyProvider;
+        mSelectionPredicate = selectionPredicate;
+
+        mRecView.addItemDecoration(
+                new ItemDecoration() {
+                    @Override
+                    public void onDrawOver(
+                            Canvas canvas,
+                            RecyclerView unusedParent,
+                            RecyclerView.State unusedState) {
+                        DefaultBandHost.this.onDrawBand(canvas);
+                    }
+                });
+    }
+
+    @Override
+    GridModel<K> createGridModel() {
+        return new GridModel<>(this, mKeyProvider, mSelectionPredicate);
+    }
+
+    @Override
+    int getAdapterPositionAt(int index) {
+        return mRecView.getChildAdapterPosition(mRecView.getChildAt(index));
+    }
+
+    @Override
+    void addOnScrollListener(RecyclerView.OnScrollListener listener) {
+        mRecView.addOnScrollListener(listener);
+    }
+
+    @Override
+    void removeOnScrollListener(RecyclerView.OnScrollListener listener) {
+        mRecView.removeOnScrollListener(listener);
+    }
+
+    @Override
+    Point createAbsolutePoint(Point relativePoint) {
+        return new Point(relativePoint.x + mRecView.computeHorizontalScrollOffset(),
+                relativePoint.y + mRecView.computeVerticalScrollOffset());
+    }
+
+    @Override
+    Rect getAbsoluteRectForChildViewAt(int index) {
+        final View child = mRecView.getChildAt(index);
+        final Rect childRect = new Rect();
+        child.getHitRect(childRect);
+        childRect.left += mRecView.computeHorizontalScrollOffset();
+        childRect.right += mRecView.computeHorizontalScrollOffset();
+        childRect.top += mRecView.computeVerticalScrollOffset();
+        childRect.bottom += mRecView.computeVerticalScrollOffset();
+        return childRect;
+    }
+
+    @Override
+    int getVisibleChildCount() {
+        return mRecView.getChildCount();
+    }
+
+    @Override
+    int getColumnCount() {
+        RecyclerView.LayoutManager layoutManager = mRecView.getLayoutManager();
+        if (layoutManager instanceof GridLayoutManager) {
+            return ((GridLayoutManager) layoutManager).getSpanCount();
+        }
+
+        // Otherwise, it is a list with 1 column.
+        return 1;
+    }
+
+    @Override
+    void showBand(Rect rect) {
+        mBand.setBounds(rect);
+        // TODO: mRecView.invalidateItemDecorations() should work, but it isn't currently.
+        // NOTE: That without invalidating rv, the band only gets updated
+        // when the pointer moves off a the item view into "NO_POSITION" territory.
+        mRecView.invalidate();
+    }
+
+    @Override
+    void hideBand() {
+        mBand.setBounds(NILL_RECT);
+        // TODO: mRecView.invalidateItemDecorations() should work, but it isn't currently.
+        mRecView.invalidate();
+    }
+
+    private void onDrawBand(Canvas c) {
+        mBand.draw(c);
+    }
+
+    @Override
+    boolean hasView(int pos) {
+        return mRecView.findViewHolderForAdapterPosition(pos) != null;
+    }
+}
diff --git a/recyclerview-selection/src/main/java/androidx/recyclerview/selection/DefaultSelectionHelper.java b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/DefaultSelectionHelper.java
new file mode 100644
index 0000000..5625e3d
--- /dev/null
+++ b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/DefaultSelectionHelper.java
@@ -0,0 +1,475 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+import static android.support.v4.util.Preconditions.checkArgument;
+import static android.support.v4.util.Preconditions.checkState;
+
+import static androidx.recyclerview.selection.Shared.DEBUG;
+
+import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import androidx.recyclerview.selection.Range.RangeType;
+
+/**
+ * {@link SelectionHelper} providing support for traditional multi-item selection on top
+ * of {@link RecyclerView}.
+ *
+ * <p>The class supports running in a single-select mode, which can be enabled
+ * by passing {@code #MODE_SINGLE} to the constructor.
+ *
+ * @param <K> Selection key type. Usually String or Long.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public class DefaultSelectionHelper<K> extends SelectionHelper<K> {
+
+    private static final String TAG = "DefaultSelectionHelper";
+
+    private final Selection<K> mSelection = new Selection<>();
+    private final List<SelectionObserver> mObservers = new ArrayList<>(1);
+    private final ItemKeyProvider<K> mKeyProvider;
+    private final SelectionPredicate<K> mSelectionPredicate;
+    private final RangeCallbacks mRangeCallbacks;
+    private final boolean mSingleSelect;
+
+    private @Nullable Range mRange;
+
+    /**
+     * Creates a new instance.
+     *
+     * @param keyProvider client supplied class providing access to stable ids.
+     * @param selectionPredicate A predicate allowing the client to disallow selection
+     *     of individual elements.
+     */
+    public DefaultSelectionHelper(
+            ItemKeyProvider keyProvider,
+            SelectionPredicate selectionPredicate) {
+
+        checkArgument(keyProvider != null);
+        checkArgument(selectionPredicate != null);
+
+        mKeyProvider = keyProvider;
+        mSelectionPredicate = selectionPredicate;
+        mRangeCallbacks = new RangeCallbacks();
+
+        mSingleSelect = !selectionPredicate.canSelectMultiple();
+    }
+
+    @Override
+    public void addObserver(SelectionObserver callback) {
+        checkArgument(callback != null);
+        mObservers.add(callback);
+    }
+
+    @Override
+    public boolean hasSelection() {
+        return !mSelection.isEmpty();
+    }
+
+    @Override
+    public Selection getSelection() {
+        return mSelection;
+    }
+
+    @Override
+    public void copySelection(Selection dest) {
+        dest.copyFrom(mSelection);
+    }
+
+    @Override
+    public boolean isSelected(@Nullable K key) {
+        return mSelection.contains(key);
+    }
+
+    @Override
+    public void restoreSelection(Selection other) {
+        checkArgument(other != null);
+        setItemsSelectedQuietly(other.mSelection, true);
+        // NOTE: We intentionally don't restore provisional selection. It's provisional.
+        notifySelectionRestored();
+    }
+
+    @Override
+    public boolean setItemsSelected(Iterable<K> keys, boolean selected) {
+        boolean changed = setItemsSelectedQuietly(keys, selected);
+        notifySelectionChanged();
+        return changed;
+    }
+
+    private boolean setItemsSelectedQuietly(Iterable<K> keys, boolean selected) {
+        boolean changed = false;
+        for (K key: keys) {
+            boolean itemChanged = selected
+                    ? canSetState(key, true) && mSelection.add(key)
+                    : canSetState(key, false) && mSelection.remove(key);
+            if (itemChanged) {
+                notifyItemStateChanged(key, selected);
+            }
+            changed |= itemChanged;
+        }
+        return changed;
+    }
+
+    @Override
+    public void clearSelection() {
+        if (!hasSelection()) {
+            return;
+        }
+
+        Selection prev = clearSelectionQuietly();
+        notifySelectionCleared(prev);
+        notifySelectionChanged();
+    }
+
+    @Override
+    public boolean clear() {
+        boolean somethingChanged = hasSelection();
+        clearProvisionalSelection();
+        clearSelection();
+        return somethingChanged;
+    }
+
+    /**
+     * Clears the selection, without notifying selection listeners.
+     * Returns items in previous selection. Callers are responsible for notifying
+     * listeners about changes.
+     */
+    private Selection clearSelectionQuietly() {
+        mRange = null;
+
+        Selection prevSelection = new Selection();
+        if (hasSelection()) {
+            copySelection(prevSelection);
+            mSelection.clear();
+        }
+
+        return prevSelection;
+    }
+
+    @Override
+    public boolean select(K key) {
+        checkArgument(key != null);
+
+        if (!mSelection.contains(key)) {
+            if (!canSetState(key, true)) {
+                if (DEBUG) Log.d(TAG, "Select cancelled by selection predicate test.");
+                return false;
+            }
+
+            // Enforce single selection policy.
+            if (mSingleSelect && hasSelection()) {
+                Selection prev = clearSelectionQuietly();
+                notifySelectionCleared(prev);
+            }
+
+            mSelection.add(key);
+            notifyItemStateChanged(key, true);
+            notifySelectionChanged();
+
+            return true;
+        }
+
+        return false;
+    }
+
+    @Override
+    public boolean deselect(K key) {
+        checkArgument(key != null);
+
+        if (mSelection.contains(key)) {
+            if (!canSetState(key, false)) {
+                if (DEBUG) Log.d(TAG, "Deselect cancelled by selection predicate test.");
+                return false;
+            }
+            mSelection.remove(key);
+            notifyItemStateChanged(key, false);
+            notifySelectionChanged();
+            if (mSelection.isEmpty() && isRangeActive()) {
+                // if there's nothing in the selection and there is an active ranger it results
+                // in unexpected behavior when the user tries to start range selection: the item
+                // which the ranger 'thinks' is the already selected anchor becomes unselectable
+                endRange();
+            }
+            return true;
+        }
+
+        return false;
+    }
+
+    @Override
+    public void startRange(int position) {
+        select(mKeyProvider.getKey(position));
+        anchorRange(position);
+    }
+
+    @Override
+    public void extendRange(int position) {
+        extendRange(position, Range.TYPE_PRIMARY);
+    }
+
+    @Override
+    public void endRange() {
+        mRange = null;
+        // Clean up in case there was any leftover provisional selection
+        clearProvisionalSelection();
+    }
+
+    @Override
+    public void anchorRange(int position) {
+        checkArgument(position != RecyclerView.NO_POSITION);
+        checkArgument(mSelection.contains(mKeyProvider.getKey(position)));
+
+        mRange = new Range(position, mRangeCallbacks);
+    }
+
+    @Override
+    public void extendProvisionalRange(int position) {
+        if (mSingleSelect) {
+            return;
+        }
+
+        if (DEBUG) Log.i(TAG, "Extending provision range to position: " + position);
+        checkState(isRangeActive(), "Range start point not set.");
+        extendRange(position, Range.TYPE_PROVISIONAL);
+    }
+
+    /**
+     * Sets the end point for the current range selection, started by a call to
+     * {@link #startRange(int)}. This function should only be called when a range selection
+     * is active (see {@link #isRangeActive()}. Items in the range [anchor, end] will be
+     * selected or in provisional select, depending on the type supplied. Note that if the type is
+     * provisional selection, one should do {@link #mergeProvisionalSelection()} at some
+     * point before calling on {@link #endRange()}.
+     *
+     * @param position The new end position for the selection range.
+     * @param type The type of selection the range should utilize.
+     */
+    private void extendRange(int position, @RangeType int type) {
+        checkState(isRangeActive(), "Range start point not set.");
+
+        mRange.extendRange(position, type);
+
+        // We're being lazy here notifying even when something might not have changed.
+        // To make this more correct, we'd need to update the Ranger class to return
+        // information about what has changed.
+        notifySelectionChanged();
+    }
+
+    @Override
+    public void setProvisionalSelection(Set<K> newSelection) {
+        if (mSingleSelect) {
+            return;
+        }
+
+        Map<K, Boolean> delta = mSelection.setProvisionalSelection(newSelection);
+        for (Map.Entry<K, Boolean> entry: delta.entrySet()) {
+            notifyItemStateChanged(entry.getKey(), entry.getValue());
+        }
+
+        notifySelectionChanged();
+    }
+
+    @Override
+    public void mergeProvisionalSelection() {
+        mSelection.mergeProvisionalSelection();
+
+        // Note, that for almost all functional purposes, merging a provisional selection
+        // into a the primary selection doesn't change the selection, just an internal
+        // representation of it. But there are some nuanced areas cases where
+        // that isn't true. equality for 1. So, we notify regardless.
+
+        notifySelectionChanged();
+    }
+
+    @Override
+    public void clearProvisionalSelection() {
+        for (K key : mSelection.mProvisionalSelection) {
+            notifyItemStateChanged(key, false);
+        }
+        mSelection.clearProvisionalSelection();
+    }
+
+    @Override
+    public boolean isRangeActive() {
+        return mRange != null;
+    }
+
+    private boolean canSetState(K key, boolean nextState) {
+        return mSelectionPredicate.canSetStateForKey(key, nextState);
+    }
+
+    @Override
+    void onDataSetChanged() {
+        mSelection.clearProvisionalSelection();
+
+        notifySelectionReset();
+
+        for (K key : mSelection) {
+            // If the underlying data set has changed, before restoring
+            // selection we must re-verify that it can be selected.
+            // Why? Because if the dataset has changed, then maybe the
+            // selectability of an item has changed.
+            if (!canSetState(key, true)) {
+                deselect(key);
+            } else {
+                int lastListener = mObservers.size() - 1;
+                for (int i = lastListener; i >= 0; i--) {
+                    mObservers.get(i).onItemStateChanged(key, true);
+                }
+            }
+        }
+
+        notifySelectionChanged();
+    }
+
+    /**
+     * Notifies registered listeners when the selection status of a single item
+     * (identified by {@code position}) changes.
+     */
+    private void notifyItemStateChanged(K key, boolean selected) {
+        checkArgument(key != null);
+
+        int lastListenerIndex = mObservers.size() - 1;
+        for (int i = lastListenerIndex; i >= 0; i--) {
+            mObservers.get(i).onItemStateChanged(key, selected);
+        }
+    }
+
+    private void notifySelectionCleared(Selection<K> selection) {
+        for (K key: selection.mSelection) {
+            notifyItemStateChanged(key, false);
+        }
+        for (K key: selection.mProvisionalSelection) {
+            notifyItemStateChanged(key, false);
+        }
+    }
+
+    /**
+     * Notifies registered listeners when the selection has changed. This
+     * notification should be sent only once a full series of changes
+     * is complete, e.g. clearingSelection, or updating the single
+     * selection from one item to another.
+     */
+    private void notifySelectionChanged() {
+        int lastListenerIndex = mObservers.size() - 1;
+        for (int i = lastListenerIndex; i >= 0; i--) {
+            mObservers.get(i).onSelectionChanged();
+        }
+    }
+
+    private void notifySelectionRestored() {
+        int lastListenerIndex = mObservers.size() - 1;
+        for (int i = lastListenerIndex; i >= 0; i--) {
+            mObservers.get(i).onSelectionRestored();
+        }
+    }
+
+    private void notifySelectionReset() {
+        int lastListenerIndex = mObservers.size() - 1;
+        for (int i = lastListenerIndex; i >= 0; i--) {
+            mObservers.get(i).onSelectionReset();
+        }
+    }
+
+    private void updateForRange(int begin, int end, boolean selected, @RangeType int type) {
+        switch (type) {
+            case Range.TYPE_PRIMARY:
+                updateForRegularRange(begin, end, selected);
+                break;
+            case Range.TYPE_PROVISIONAL:
+                updateForProvisionalRange(begin, end, selected);
+                break;
+            default:
+                throw new IllegalArgumentException("Invalid range type: " + type);
+        }
+    }
+
+    private void updateForRegularRange(int begin, int end, boolean selected) {
+        checkArgument(end >= begin);
+
+        for (int i = begin; i <= end; i++) {
+            K key = mKeyProvider.getKey(i);
+            if (key == null) {
+                continue;
+            }
+
+            if (selected) {
+                select(key);
+            } else {
+                deselect(key);
+            }
+        }
+    }
+
+    private void updateForProvisionalRange(int begin, int end, boolean selected) {
+        checkArgument(end >= begin);
+
+        for (int i = begin; i <= end; i++) {
+            K key = mKeyProvider.getKey(i);
+            if (key == null) {
+                continue;
+            }
+
+            boolean changedState = false;
+            if (selected) {
+                boolean canSelect = canSetState(key, true);
+                if (canSelect && !mSelection.mSelection.contains(key)) {
+                    mSelection.mProvisionalSelection.add(key);
+                    changedState = true;
+                }
+            } else {
+                mSelection.mProvisionalSelection.remove(key);
+                changedState = true;
+            }
+
+            // Only notify item callbacks when something's state is actually changed in provisional
+            // selection.
+            if (changedState) {
+                notifyItemStateChanged(key, selected);
+            }
+        }
+
+        notifySelectionChanged();
+    }
+
+    private final class RangeCallbacks extends Range.Callbacks {
+        @Override
+        void updateForRange(int begin, int end, boolean selected, int type) {
+            switch (type) {
+                case Range.TYPE_PRIMARY:
+                    updateForRegularRange(begin, end, selected);
+                    break;
+                case Range.TYPE_PROVISIONAL:
+                    updateForProvisionalRange(begin, end, selected);
+                    break;
+                default:
+                    throw new IllegalArgumentException("Invalid range type: " + type);
+            }
+        }
+    }
+}
diff --git a/recyclerview-selection/src/main/java/androidx/recyclerview/selection/EventBridge.java b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/EventBridge.java
new file mode 100644
index 0000000..b418ad4
--- /dev/null
+++ b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/EventBridge.java
@@ -0,0 +1,137 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+import static android.support.v4.util.Preconditions.checkArgument;
+
+import static androidx.recyclerview.selection.Shared.VERBOSE;
+
+import android.support.annotation.RestrictTo;
+import android.support.annotation.VisibleForTesting;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+
+/**
+ * Provides the necessary glue to notify RecyclerView when selection data changes,
+ * and to notify SelectionHelper when the underlying RecyclerView.Adapter data changes.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+@VisibleForTesting
+public class EventBridge {
+
+    private static final String TAG = "EventsRelays";
+
+    /**
+     * Installs the event bridge for on the supplied adapter/helper.
+     *
+     * @param adapter
+     * @param selectionHelper
+     * @param keyProvider
+     * @param <K>
+     */
+    @VisibleForTesting
+    public static <K> void install(
+            RecyclerView.Adapter<?> adapter,
+            SelectionHelper<K> selectionHelper,
+            ItemKeyProvider<K> keyProvider) {
+        new AdapterToSelectionHelper(adapter, selectionHelper);
+        new SelectionHelperToAdapter<>(selectionHelper, keyProvider, adapter);
+    }
+
+    private static final class AdapterToSelectionHelper extends RecyclerView.AdapterDataObserver {
+
+        private final SelectionHelper<?> mSelectionHelper;
+
+        AdapterToSelectionHelper(
+                RecyclerView.Adapter<?> adapter,
+                SelectionHelper<?> selectionHelper) {
+            adapter.registerAdapterDataObserver(this);
+
+            checkArgument(selectionHelper != null);
+            mSelectionHelper = selectionHelper;
+        }
+
+        @Override
+        public void onChanged() {
+            mSelectionHelper.onDataSetChanged();
+        }
+
+        @Override
+        public void onItemRangeChanged(int startPosition, int itemCount, Object payload) {
+            // No change in position. Ignore, since we assume
+            // selection is a user driven activity. So changes
+            // in properties of items shouldn't result in a
+            // change of selection.
+            // TODO: It is possible properties of items chould change to make them unselectable.
+        }
+
+        @Override
+        public void onItemRangeInserted(int startPosition, int itemCount) {
+            // Uninteresting to us since selection is stable ID based.
+        }
+
+        @Override
+        public void onItemRangeRemoved(int startPosition, int itemCount) {
+            // Uninteresting to us since selection is stable ID based.
+        }
+
+        @Override
+        public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
+            // Uninteresting to us since selection is stable ID based.
+        }
+    }
+
+    private static final class SelectionHelperToAdapter<K>
+            extends SelectionHelper.SelectionObserver<K> {
+
+        private final ItemKeyProvider<K> mKeyProvider;
+        private final RecyclerView.Adapter<?> mAdapter;
+
+        SelectionHelperToAdapter(
+                SelectionHelper<K> selectionHelper,
+                ItemKeyProvider<K> keyProvider,
+                RecyclerView.Adapter<?> adapter) {
+
+            selectionHelper.addObserver(this);
+
+            checkArgument(keyProvider != null);
+            checkArgument(adapter != null);
+
+            mKeyProvider = keyProvider;
+            mAdapter = adapter;
+        }
+
+        /**
+         * Called when state of an item has been changed.
+         */
+        @Override
+        public void onItemStateChanged(K key, boolean selected) {
+            int position = mKeyProvider.getPosition(key);
+            if (VERBOSE) Log.v(TAG, "ITEM " + key + " CHANGED at pos: " + position);
+
+            if (position < 0) {
+                Log.w(TAG, "Item change notification received for unknown item: " + key);
+                return;
+            }
+
+            mAdapter.notifyItemChanged(position, SelectionHelper.SELECTION_CHANGED_MARKER);
+        }
+    }
+}
diff --git a/recyclerview-selection/src/main/java/androidx/recyclerview/selection/FocusCallbacks.java b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/FocusCallbacks.java
new file mode 100644
index 0000000..4c1c12e
--- /dev/null
+++ b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/FocusCallbacks.java
@@ -0,0 +1,78 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.support.annotation.RestrictTo;
+import android.support.v7.widget.RecyclerView;
+
+import androidx.recyclerview.selection.ItemDetailsLookup.ItemDetails;
+
+/**
+ * Override methods in this class to connect specialized behaviors of the selection
+ * code to the application environment.
+ *
+ * @param <K> Selection key type. Usually String or Long.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public abstract class FocusCallbacks<K> {
+
+    static final <K> FocusCallbacks<K> dummy() {
+        return new FocusCallbacks<K>() {
+            @Override
+            public void focusItem(ItemDetails<K> item) {
+            }
+
+            @Override
+            public boolean hasFocusedItem() {
+                return false;
+            }
+
+            @Override
+            public int getFocusedPosition() {
+                return RecyclerView.NO_POSITION;
+            }
+
+            @Override
+            public void clearFocus() {
+            }
+        };
+    }
+
+    /**
+     * If environment supports focus, focus {@code item}.
+     */
+    public abstract void focusItem(ItemDetails<K> item);
+
+    /**
+     * @return true if there is a focused item.
+     */
+    public abstract boolean hasFocusedItem();
+
+    /**
+     * @return the position of the currently focused item, if any.
+     */
+    public abstract int getFocusedPosition();
+
+    /**
+     * If the environment supports focus and something is focused, unfocus it.
+     */
+    public abstract void clearFocus();
+}
diff --git a/recyclerview-selection/src/main/java/androidx/recyclerview/selection/GestureRouter.java b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/GestureRouter.java
new file mode 100644
index 0000000..82fab87
--- /dev/null
+++ b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/GestureRouter.java
@@ -0,0 +1,100 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static android.support.v4.util.Preconditions.checkArgument;
+
+import android.support.annotation.Nullable;
+import android.view.GestureDetector.OnDoubleTapListener;
+import android.view.GestureDetector.OnGestureListener;
+import android.view.GestureDetector.SimpleOnGestureListener;
+import android.view.MotionEvent;
+
+/**
+ * GestureRouter is responsible for routing gestures detected by a GestureDetector
+ * to registered handlers. The primary function is to divide events by tool-type
+ * allowing handlers to cleanly implement tool-type specific policies.
+ *
+ * @param <T> listener type. Must extend OnGestureListener & OnDoubleTapListener.
+ */
+final class GestureRouter<T extends OnGestureListener & OnDoubleTapListener>
+        implements OnGestureListener, OnDoubleTapListener {
+
+    private final ToolHandlerRegistry<T> mDelegates;
+
+    GestureRouter(T defaultDelegate) {
+        checkArgument(defaultDelegate != null);
+        mDelegates = new ToolHandlerRegistry<>(defaultDelegate);
+    }
+
+    GestureRouter() {
+        this((T) new SimpleOnGestureListener());
+    }
+
+    /**
+     * @param toolType
+     * @param delegate the delegate, or null to unregister.
+     */
+    public void register(int toolType, @Nullable T delegate) {
+        mDelegates.set(toolType, delegate);
+    }
+
+    @Override
+    public boolean onSingleTapConfirmed(MotionEvent e) {
+        return mDelegates.get(e).onSingleTapConfirmed(e);
+    }
+
+    @Override
+    public boolean onDoubleTap(MotionEvent e) {
+        return mDelegates.get(e).onDoubleTap(e);
+    }
+
+    @Override
+    public boolean onDoubleTapEvent(MotionEvent e) {
+        return mDelegates.get(e).onDoubleTapEvent(e);
+    }
+
+    @Override
+    public boolean onDown(MotionEvent e) {
+        return mDelegates.get(e).onDown(e);
+    }
+
+    @Override
+    public void onShowPress(MotionEvent e) {
+        mDelegates.get(e).onShowPress(e);
+    }
+
+    @Override
+    public boolean onSingleTapUp(MotionEvent e) {
+        return mDelegates.get(e).onSingleTapUp(e);
+    }
+
+    @Override
+    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
+        return mDelegates.get(e2).onScroll(e1, e2, distanceX, distanceY);
+    }
+
+    @Override
+    public void onLongPress(MotionEvent e) {
+        mDelegates.get(e).onLongPress(e);
+    }
+
+    @Override
+    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
+        return mDelegates.get(e2).onFling(e1, e2, velocityX, velocityY);
+    }
+}
diff --git a/recyclerview-selection/src/main/java/androidx/recyclerview/selection/GestureSelectionHelper.java b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/GestureSelectionHelper.java
new file mode 100644
index 0000000..2a28fc5
--- /dev/null
+++ b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/GestureSelectionHelper.java
@@ -0,0 +1,287 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static android.support.v4.util.Preconditions.checkArgument;
+import static android.support.v4.util.Preconditions.checkState;
+
+import android.graphics.Point;
+import android.support.annotation.VisibleForTesting;
+import android.support.v4.view.ViewCompat;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerView.OnItemTouchListener;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+
+/**
+ * GestureSelectionHelper provides logic that interprets a combination
+ * of motions and gestures in order to provide gesture driven selection support
+ * when used in conjunction with RecyclerView and other classes in the ReyclerView
+ * selection support package.
+ */
+final class GestureSelectionHelper implements OnItemTouchListener {
+
+    private static final String TAG = "GestureSelectionHelper";
+
+    private final SelectionHelper<?> mSelectionMgr;
+    private final AutoScroller mScroller;
+    private final ViewDelegate mView;
+    private final ContentLock mLock;
+
+    private int mLastStartedItemPos = -1;
+    private boolean mStarted = false;
+    private Point mLastInterceptedPoint;
+
+    /**
+     * See {@link #create(SelectionHelper, RecyclerView, AutoScroller, ContentLock)} for convenience
+     * method.
+     */
+    GestureSelectionHelper(
+            SelectionHelper<?> selectionHelper,
+            ViewDelegate view,
+            AutoScroller scroller,
+            ContentLock lock) {
+
+        checkArgument(selectionHelper != null);
+        checkArgument(view != null);
+        checkArgument(scroller != null);
+        checkArgument(lock != null);
+
+        mSelectionMgr = selectionHelper;
+        mView = view;
+        mScroller = scroller;
+        mLock = lock;
+    }
+
+    /**
+     * Explicitly kicks off a gesture multi-select.
+     */
+    void start() {
+        checkState(!mStarted);
+        checkState(mLastStartedItemPos > -1);
+
+        // Partner code in MotionInputHandler ensures items
+        // are selected and range established prior to
+        // start being called.
+        // Verify the truth of that statement here
+        // to make the implicit coupling less of a time bomb.
+        checkState(mSelectionMgr.isRangeActive());
+
+        mLock.checkUnlocked();
+
+        mStarted = true;
+        mLock.block();
+    }
+
+    @Override
+    /** @hide */
+    public boolean onInterceptTouchEvent(RecyclerView unused, MotionEvent e) {
+        if (MotionEvents.isMouseEvent(e)) {
+            if (Shared.DEBUG) Log.w(TAG, "Unexpected Mouse event. Check configuration.");
+        }
+
+        switch (e.getActionMasked()) {
+            case MotionEvent.ACTION_DOWN:
+                // NOTE: Unlike events with other actions, RecyclerView eats
+                // "DOWN" events. So even if we return true here we'll
+                // never see an event w/ ACTION_DOWN passed to onTouchEvent.
+                return handleInterceptedDownEvent(e);
+            case MotionEvent.ACTION_MOVE:
+                return mStarted;
+        }
+
+        return false;
+    }
+
+    @Override
+    /** @hide */
+    public void onTouchEvent(RecyclerView unused, MotionEvent e) {
+        checkState(mStarted);
+
+        switch (e.getActionMasked()) {
+            case MotionEvent.ACTION_MOVE:
+                handleMoveEvent(e);
+                break;
+            case MotionEvent.ACTION_UP:
+                handleUpEvent(e);
+                break;
+            case MotionEvent.ACTION_CANCEL:
+                handleCancelEvent(e);
+                break;
+        }
+    }
+
+    @Override
+    /** @hide */
+    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
+    }
+
+    // Called when an ACTION_DOWN event is intercepted.
+    // If down event happens on an item, we mark that item's position as last started.
+    private boolean handleInterceptedDownEvent(MotionEvent e) {
+        mLastStartedItemPos = mView.getItemUnder(e);
+        return mLastStartedItemPos != RecyclerView.NO_POSITION;
+    }
+
+    // Called when ACTION_UP event is to be handled.
+    // Essentially, since this means all gesture movement is over, reset everything and apply
+    // provisional selection.
+    private void handleUpEvent(MotionEvent e) {
+        mSelectionMgr.mergeProvisionalSelection();
+        endSelection();
+        if (mLastStartedItemPos > -1) {
+            mSelectionMgr.startRange(mLastStartedItemPos);
+        }
+    }
+
+    // Called when ACTION_CANCEL event is to be handled.
+    // This means this gesture selection is aborted, so reset everything and abandon provisional
+    // selection.
+    private void handleCancelEvent(MotionEvent unused) {
+        mSelectionMgr.clearProvisionalSelection();
+        endSelection();
+    }
+
+    private void endSelection() {
+        checkState(mStarted);
+
+        mLastStartedItemPos = -1;
+        mStarted = false;
+        mScroller.reset();
+        mLock.unblock();
+    }
+
+    // Call when an intercepted ACTION_MOVE event is passed down.
+    // At this point, we are sure user wants to gesture multi-select.
+    private void handleMoveEvent(MotionEvent e) {
+        mLastInterceptedPoint = MotionEvents.getOrigin(e);
+
+        int lastGlidedItemPos = mView.getLastGlidedItemPosition(e);
+        if (lastGlidedItemPos != RecyclerView.NO_POSITION) {
+            extendSelection(lastGlidedItemPos);
+        }
+
+        mScroller.scroll(mLastInterceptedPoint);
+    }
+
+    // It's possible for events to go over the top/bottom of the RecyclerView.
+    // We want to get a Y-coordinate within the RecyclerView so we can find the childView underneath
+    // correctly.
+    private static float getInboundY(float max, float y) {
+        if (y < 0f) {
+            return 0f;
+        } else if (y > max) {
+            return max;
+        }
+        return y;
+    }
+
+    /* Given the end position, select everything in-between.
+     * @param endPos  The adapter position of the end item.
+     */
+    private void extendSelection(int endPos) {
+        mSelectionMgr.extendProvisionalRange(endPos);
+    }
+
+    /**
+     * Returns a new instance of GestureSelectionHelper.
+     */
+    static GestureSelectionHelper create(
+            SelectionHelper selectionMgr,
+            RecyclerView recView,
+            AutoScroller scroller,
+            ContentLock lock) {
+
+        return new GestureSelectionHelper(
+                selectionMgr,
+                new RecyclerViewDelegate(recView),
+                scroller,
+                lock);
+    }
+
+    @VisibleForTesting
+    abstract static class ViewDelegate {
+        abstract int getHeight();
+
+        abstract int getItemUnder(MotionEvent e);
+
+        abstract int getLastGlidedItemPosition(MotionEvent e);
+    }
+
+    @VisibleForTesting
+    static final class RecyclerViewDelegate extends ViewDelegate {
+
+        private final RecyclerView mRecView;
+
+        RecyclerViewDelegate(RecyclerView view) {
+            checkArgument(view != null);
+            mRecView = view;
+        }
+
+        @Override
+        int getHeight() {
+            return mRecView.getHeight();
+        }
+
+        @Override
+        int getItemUnder(MotionEvent e) {
+            View child = mRecView.findChildViewUnder(e.getX(), e.getY());
+            return child != null
+                    ? mRecView.getChildAdapterPosition(child)
+                    : RecyclerView.NO_POSITION;
+        }
+
+        @Override
+        int getLastGlidedItemPosition(MotionEvent e) {
+            // If user has moved his pointer to the bottom-right empty pane (ie. to the right of the
+            // last item of the recycler view), we would want to set that as the currentItemPos
+            View lastItem = mRecView.getLayoutManager()
+                    .getChildAt(mRecView.getLayoutManager().getChildCount() - 1);
+            int direction = ViewCompat.getLayoutDirection(mRecView);
+            final boolean pastLastItem = isPastLastItem(lastItem.getTop(),
+                    lastItem.getLeft(),
+                    lastItem.getRight(),
+                    e,
+                    direction);
+
+            // Since views get attached & detached from RecyclerView,
+            // {@link LayoutManager#getChildCount} can return a different number from the actual
+            // number
+            // of items in the adapter. Using the adapter is the for sure way to get the actual last
+            // item position.
+            final float inboundY = getInboundY(mRecView.getHeight(), e.getY());
+            return (pastLastItem) ? mRecView.getAdapter().getItemCount() - 1
+                    : mRecView.getChildAdapterPosition(
+                            mRecView.findChildViewUnder(e.getX(), inboundY));
+        }
+
+        /*
+         * Check to see if MotionEvent if past a particular item, i.e. to the right or to the bottom
+         * of the item.
+         * For RTL, it would to be to the left or to the bottom of the item.
+         */
+        @VisibleForTesting
+        static boolean isPastLastItem(int top, int left, int right, MotionEvent e, int direction) {
+            if (direction == View.LAYOUT_DIRECTION_LTR) {
+                return e.getX() > right && e.getY() > top;
+            } else {
+                return e.getX() < left && e.getY() > top;
+            }
+        }
+    }
+}
diff --git a/recyclerview-selection/src/main/java/androidx/recyclerview/selection/GridModel.java b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/GridModel.java
new file mode 100644
index 0000000..4358958
--- /dev/null
+++ b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/GridModel.java
@@ -0,0 +1,786 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static android.support.v4.util.Preconditions.checkArgument;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.support.annotation.VisibleForTesting;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerView.OnScrollListener;
+import android.util.Log;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import androidx.recyclerview.selection.SelectionHelper.SelectionPredicate;
+
+/**
+ * Provides a band selection item model for views within a RecyclerView. This class queries the
+ * RecyclerView to determine where its items are placed; then, once band selection is underway,
+ * it alerts listeners of which items are covered by the selections.
+ *
+ * @param <K> Selection key type. Usually String or Long.
+ */
+final class GridModel<K> {
+
+    // Magical value indicating that a value has not been previously set. primitive null :)
+    static final int NOT_SET = -1;
+
+    // Enum values used to determine the corner at which the origin is located within the
+    private static final int UPPER = 0x00;
+    private static final int LOWER = 0x01;
+    private static final int LEFT = 0x00;
+    private static final int RIGHT = 0x02;
+    private static final int UPPER_LEFT = UPPER | LEFT;
+    private static final int UPPER_RIGHT = UPPER | RIGHT;
+    private static final int LOWER_LEFT = LOWER | LEFT;
+    private static final int LOWER_RIGHT = LOWER | RIGHT;
+
+    private final GridHost<K> mHost;
+    private final ItemKeyProvider<K> mKeyProvider;
+    private final SelectionPredicate<K> mSelectionPredicate;
+
+    private final List<SelectionObserver> mOnSelectionChangedListeners = new ArrayList<>();
+
+    // Map from the x-value of the left side of a SparseBooleanArray of adapter positions, keyed
+    // by their y-offset. For example, if the first column of the view starts at an x-value of 5,
+    // mColumns.get(5) would return an array of positions in that column. Within that array, the
+    // value for key y is the adapter position for the item whose y-offset is y.
+    private final SparseArray<SparseIntArray> mColumns = new SparseArray<>();
+
+    // List of limits along the x-axis (columns).
+    // This list is sorted from furthest left to furthest right.
+    private final List<Limits> mColumnBounds = new ArrayList<>();
+
+    // List of limits along the y-axis (rows). Note that this list only contains items which
+    // have been in the viewport.
+    private final List<Limits> mRowBounds = new ArrayList<>();
+
+    // The adapter positions which have been recorded so far.
+    private final SparseBooleanArray mKnownPositions = new SparseBooleanArray();
+
+    // Array passed to registered OnSelectionChangedListeners. One array is created and reused
+    // throughout the lifetime of the object.
+    private final Set<K> mSelection = new HashSet<>();
+
+    // The current pointer (in absolute positioning from the top of the view).
+    private Point mPointer;
+
+    // The bounds of the band selection.
+    private RelativePoint mRelOrigin;
+    private RelativePoint mRelPointer;
+
+    private boolean mIsActive;
+
+    // Tracks where the band select originated from. This is used to determine where selections
+    // should expand from when Shift+click is used.
+    private int mPositionNearestOrigin = NOT_SET;
+
+    private final OnScrollListener mScrollListener;
+
+    GridModel(
+            GridHost host,
+            ItemKeyProvider<K> keyProvider,
+            SelectionPredicate<K> selectionPredicate) {
+
+        checkArgument(host != null);
+        checkArgument(keyProvider != null);
+        checkArgument(selectionPredicate != null);
+
+        mHost = host;
+        mKeyProvider = keyProvider;
+        mSelectionPredicate = selectionPredicate;
+
+        mScrollListener = new OnScrollListener() {
+            @Override
+            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+                GridModel.this.onScrolled(recyclerView, dx, dy);
+            }
+        };
+
+        mHost.addOnScrollListener(mScrollListener);
+    }
+
+    /**
+     * Start a band select operation at the given point.
+     *
+     * @param relativeOrigin The origin of the band select operation, relative to the viewport.
+     *                       For example, if the view is scrolled to the bottom, the top-left of
+     *                       the
+     *                       viewport
+     *                       would have a relative origin of (0, 0), even though its absolute point
+     *                       has a higher
+     *                       y-value.
+     */
+    void startCapturing(Point relativeOrigin) {
+        recordVisibleChildren();
+        if (isEmpty()) {
+            // The selection band logic works only if there is at least one visible child.
+            return;
+        }
+
+        mIsActive = true;
+        mPointer = mHost.createAbsolutePoint(relativeOrigin);
+        mRelOrigin = createRelativePoint(mPointer);
+        mRelPointer = createRelativePoint(mPointer);
+        computeCurrentSelection();
+        notifySelectionChanged();
+    }
+
+    /**
+     * Ends the band selection.
+     */
+    void stopCapturing() {
+        mIsActive = false;
+    }
+
+    /**
+     * Resizes the selection by adjusting the pointer (i.e., the corner of the selection
+     * opposite the origin.
+     *
+     * @param relativePointer The pointer (opposite of the origin) of the band select operation,
+     *                        relative to the viewport. For example, if the view is scrolled to the
+     *                        bottom, the
+     *                        top-left of the viewport would have a relative origin of (0, 0), even
+     *                        though its
+     *                        absolute point has a higher y-value.
+     */
+    @VisibleForTesting
+    void resizeSelection(Point relativePointer) {
+        mPointer = mHost.createAbsolutePoint(relativePointer);
+        updateModel();
+    }
+
+    /**
+     * @return The adapter position for the item nearest the origin corresponding to the latest
+     * band select operation, or NOT_SET if the selection did not cover any items.
+     */
+    int getPositionNearestOrigin() {
+        return mPositionNearestOrigin;
+    }
+
+    private void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+        if (!mIsActive) {
+            return;
+        }
+
+        mPointer.x += dx;
+        mPointer.y += dy;
+        recordVisibleChildren();
+        updateModel();
+    }
+
+    /**
+     * Queries the view for all children and records their location metadata.
+     */
+    private void recordVisibleChildren() {
+        for (int i = 0; i < mHost.getVisibleChildCount(); i++) {
+            int adapterPosition = mHost.getAdapterPositionAt(i);
+            // Sometimes the view is not attached, as we notify the multi selection manager
+            // synchronously, while views are attached asynchronously. As a result items which
+            // are in the adapter may not actually have a corresponding view (yet).
+            if (mHost.hasView(adapterPosition)
+                    && mSelectionPredicate.canSetStateAtPosition(adapterPosition, true)
+                    && !mKnownPositions.get(adapterPosition)) {
+                mKnownPositions.put(adapterPosition, true);
+                recordItemData(mHost.getAbsoluteRectForChildViewAt(i), adapterPosition);
+            }
+        }
+    }
+
+    /**
+     * Checks if there are any recorded children.
+     */
+    private boolean isEmpty() {
+        return mColumnBounds.size() == 0 || mRowBounds.size() == 0;
+    }
+
+    /**
+     * Updates the limits lists and column map with the given item metadata.
+     *
+     * @param absoluteChildRect The absolute rectangle for the child view being processed.
+     * @param adapterPosition   The position of the child view being processed.
+     */
+    private void recordItemData(Rect absoluteChildRect, int adapterPosition) {
+        if (mColumnBounds.size() != mHost.getColumnCount()) {
+            // If not all x-limits have been recorded, record this one.
+            recordLimits(
+                    mColumnBounds, new Limits(absoluteChildRect.left, absoluteChildRect.right));
+        }
+
+        recordLimits(mRowBounds, new Limits(absoluteChildRect.top, absoluteChildRect.bottom));
+
+        SparseIntArray columnList = mColumns.get(absoluteChildRect.left);
+        if (columnList == null) {
+            columnList = new SparseIntArray();
+            mColumns.put(absoluteChildRect.left, columnList);
+        }
+        columnList.put(absoluteChildRect.top, adapterPosition);
+    }
+
+    /**
+     * Ensures limits exists within the sorted list limitsList, and adds it to the list if it
+     * does not exist.
+     */
+    private void recordLimits(List<Limits> limitsList, Limits limits) {
+        int index = Collections.binarySearch(limitsList, limits);
+        if (index < 0) {
+            limitsList.add(~index, limits);
+        }
+    }
+
+    /**
+     * Handles a moved pointer; this function determines whether the pointer movement resulted
+     * in a selection change and, if it has, notifies listeners of this change.
+     */
+    private void updateModel() {
+        RelativePoint old = mRelPointer;
+        mRelPointer = createRelativePoint(mPointer);
+        if (old != null && mRelPointer.equals(old)) {
+            return;
+        }
+
+        computeCurrentSelection();
+        notifySelectionChanged();
+    }
+
+    /**
+     * Computes the currently-selected items.
+     */
+    private void computeCurrentSelection() {
+        if (areItemsCoveredByBand(mRelPointer, mRelOrigin)) {
+            updateSelection(computeBounds());
+        } else {
+            mSelection.clear();
+            mPositionNearestOrigin = NOT_SET;
+        }
+    }
+
+    /**
+     * Notifies all listeners of a selection change. Note that this function simply passes
+     * mSelection, so computeCurrentSelection() should be called before this
+     * function.
+     */
+    private void notifySelectionChanged() {
+        for (SelectionObserver listener : mOnSelectionChangedListeners) {
+            listener.onSelectionChanged(mSelection);
+        }
+    }
+
+    /**
+     * @param rect Rectangle including all covered items.
+     */
+    private void updateSelection(Rect rect) {
+        int columnStart =
+                Collections.binarySearch(mColumnBounds, new Limits(rect.left, rect.left));
+
+        checkArgument(columnStart >= 0, "Rect doesn't intesect any known column.");
+
+        int columnEnd = columnStart;
+
+        for (int i = columnStart; i < mColumnBounds.size()
+                && mColumnBounds.get(i).lowerLimit <= rect.right; i++) {
+            columnEnd = i;
+        }
+
+        int rowStart = Collections.binarySearch(mRowBounds, new Limits(rect.top, rect.top));
+        if (rowStart < 0) {
+            mPositionNearestOrigin = NOT_SET;
+            return;
+        }
+
+        int rowEnd = rowStart;
+        for (int i = rowStart; i < mRowBounds.size()
+                && mRowBounds.get(i).lowerLimit <= rect.bottom; i++) {
+            rowEnd = i;
+        }
+
+        updateSelection(columnStart, columnEnd, rowStart, rowEnd);
+    }
+
+    /**
+     * Computes the selection given the previously-computed start- and end-indices for each
+     * row and column.
+     */
+    private void updateSelection(
+            int columnStartIndex, int columnEndIndex, int rowStartIndex, int rowEndIndex) {
+
+        if (BandSelectionHelper.DEBUG) {
+            Log.d(BandSelectionHelper.TAG, String.format(
+                    "updateSelection: %d, %d, %d, %d",
+                    columnStartIndex, columnEndIndex, rowStartIndex, rowEndIndex));
+        }
+
+        mSelection.clear();
+        for (int column = columnStartIndex; column <= columnEndIndex; column++) {
+            SparseIntArray items = mColumns.get(mColumnBounds.get(column).lowerLimit);
+            for (int row = rowStartIndex; row <= rowEndIndex; row++) {
+                // The default return value for SparseIntArray.get is 0, which is a valid
+                // position. Use a sentry value to prevent erroneously selecting item 0.
+                final int rowKey = mRowBounds.get(row).lowerLimit;
+                int position = items.get(rowKey, NOT_SET);
+                if (position != NOT_SET) {
+                    K key = mKeyProvider.getKey(position);
+                    if (key != null) {
+                        // The adapter inserts items for UI layout purposes that aren't
+                        // associated with files. Those will have a null model ID.
+                        // Don't select them.
+                        if (canSelect(key)) {
+                            mSelection.add(key);
+                        }
+                    }
+                    if (isPossiblePositionNearestOrigin(column, columnStartIndex, columnEndIndex,
+                            row, rowStartIndex, rowEndIndex)) {
+                        // If this is the position nearest the origin, record it now so that it
+                        // can be returned by endSelection() later.
+                        mPositionNearestOrigin = position;
+                    }
+                }
+            }
+        }
+    }
+
+    private boolean canSelect(K key) {
+        return mSelectionPredicate.canSetStateForKey(key, true);
+    }
+
+    /**
+     * @return Returns true if the position is the nearest to the origin, or, in the case of the
+     * lower-right corner, whether it is possible that the position is the nearest to the
+     * origin. See comment below for reasoning for this special case.
+     */
+    private boolean isPossiblePositionNearestOrigin(int columnIndex, int columnStartIndex,
+            int columnEndIndex, int rowIndex, int rowStartIndex, int rowEndIndex) {
+        int corner = computeCornerNearestOrigin();
+        switch (corner) {
+            case UPPER_LEFT:
+                return columnIndex == columnStartIndex && rowIndex == rowStartIndex;
+            case UPPER_RIGHT:
+                return columnIndex == columnEndIndex && rowIndex == rowStartIndex;
+            case LOWER_LEFT:
+                return columnIndex == columnStartIndex && rowIndex == rowEndIndex;
+            case LOWER_RIGHT:
+                // Note that in some cases, the last row will not have as many items as there
+                // are columns (e.g., if there are 4 items and 3 columns, the second row will
+                // only have one item in the first column). This function is invoked for each
+                // position from left to right, so return true for any position in the bottom
+                // row and only the right-most position in the bottom row will be recorded.
+                return rowIndex == rowEndIndex;
+            default:
+                throw new RuntimeException("Invalid corner type.");
+        }
+    }
+
+    /**
+     * Listener for changes in which items have been band selected.
+     */
+    public abstract static class SelectionObserver<K> {
+        abstract void onSelectionChanged(Set<K> updatedSelection);
+    }
+
+    void addOnSelectionChangedListener(SelectionObserver listener) {
+        mOnSelectionChangedListeners.add(listener);
+    }
+
+    /**
+     * Called when {@link BandSelectionHelper} is finished with a GridModel.
+     */
+    void onDestroy() {
+        mOnSelectionChangedListeners.clear();
+        // Cleanup listeners to prevent memory leaks.
+        mHost.removeOnScrollListener(mScrollListener);
+    }
+
+    /**
+     * Limits of a view item. For example, if an item's left side is at x-value 5 and its right side
+     * is at x-value 10, the limits would be from 5 to 10. Used to record the left- and right sides
+     * of item columns and the top- and bottom sides of item rows so that it can be determined
+     * whether the pointer is located within the bounds of an item.
+     */
+    private static class Limits implements Comparable<Limits> {
+        public int lowerLimit;
+        public int upperLimit;
+
+        Limits(int lowerLimit, int upperLimit) {
+            this.lowerLimit = lowerLimit;
+            this.upperLimit = upperLimit;
+        }
+
+        @Override
+        public int compareTo(Limits other) {
+            return lowerLimit - other.lowerLimit;
+        }
+
+        @Override
+        public int hashCode() {
+            return lowerLimit ^ upperLimit;
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if (!(other instanceof Limits)) {
+                return false;
+            }
+
+            return ((Limits) other).lowerLimit == lowerLimit
+                    && ((Limits) other).upperLimit == upperLimit;
+        }
+
+        @Override
+        public String toString() {
+            return "(" + lowerLimit + ", " + upperLimit + ")";
+        }
+    }
+
+    /**
+     * The location of a coordinate relative to items. This class represents a general area of the
+     * view as it relates to band selection rather than an explicit point. For example, two
+     * different points within an item are considered to have the same "location" because band
+     * selection originating within the item would select the same items no matter which point
+     * was used. Same goes for points between items as well as those at the very beginning or end
+     * of the view.
+     *
+     * Tracking a coordinate (e.g., an x-value) as a CoordinateLocation instead of as an int has the
+     * advantage of tying the value to the Limits of items along that axis. This allows easy
+     * selection of items within those Limits as opposed to a search through every item to see if a
+     * given coordinate value falls within those Limits.
+     */
+    private static class RelativeCoordinate
+            implements Comparable<RelativeCoordinate> {
+        /**
+         * Location describing points after the last known item.
+         */
+        static final int AFTER_LAST_ITEM = 0;
+
+        /**
+         * Location describing points before the first known item.
+         */
+        static final int BEFORE_FIRST_ITEM = 1;
+
+        /**
+         * Location describing points between two items.
+         */
+        static final int BETWEEN_TWO_ITEMS = 2;
+
+        /**
+         * Location describing points within the limits of one item.
+         */
+        static final int WITHIN_LIMITS = 3;
+
+        /**
+         * The type of this coordinate, which is one of AFTER_LAST_ITEM, BEFORE_FIRST_ITEM,
+         * BETWEEN_TWO_ITEMS, or WITHIN_LIMITS.
+         */
+        public final int type;
+
+        /**
+         * The limits before the coordinate; only populated when type == WITHIN_LIMITS or type ==
+         * BETWEEN_TWO_ITEMS.
+         */
+        public Limits limitsBeforeCoordinate;
+
+        /**
+         * The limits after the coordinate; only populated when type == BETWEEN_TWO_ITEMS.
+         */
+        public Limits limitsAfterCoordinate;
+
+        // Limits of the first known item; only populated when type == BEFORE_FIRST_ITEM.
+        public Limits mFirstKnownItem;
+        // Limits of the last known item; only populated when type == AFTER_LAST_ITEM.
+        public Limits mLastKnownItem;
+
+        /**
+         * @param limitsList The sorted limits list for the coordinate type. If this
+         *                   CoordinateLocation is an x-value, mXLimitsList should be passed;
+         *                   otherwise,
+         *                   mYLimitsList should be pased.
+         * @param value      The coordinate value.
+         */
+        RelativeCoordinate(List<Limits> limitsList, int value) {
+            int index = Collections.binarySearch(limitsList, new Limits(value, value));
+
+            if (index >= 0) {
+                this.type = WITHIN_LIMITS;
+                this.limitsBeforeCoordinate = limitsList.get(index);
+            } else if (~index == 0) {
+                this.type = BEFORE_FIRST_ITEM;
+                this.mFirstKnownItem = limitsList.get(0);
+            } else if (~index == limitsList.size()) {
+                Limits lastLimits = limitsList.get(limitsList.size() - 1);
+                if (lastLimits.lowerLimit <= value && value <= lastLimits.upperLimit) {
+                    this.type = WITHIN_LIMITS;
+                    this.limitsBeforeCoordinate = lastLimits;
+                } else {
+                    this.type = AFTER_LAST_ITEM;
+                    this.mLastKnownItem = lastLimits;
+                }
+            } else {
+                Limits limitsBeforeIndex = limitsList.get(~index - 1);
+                if (limitsBeforeIndex.lowerLimit <= value
+                        && value <= limitsBeforeIndex.upperLimit) {
+                    this.type = WITHIN_LIMITS;
+                    this.limitsBeforeCoordinate = limitsList.get(~index - 1);
+                } else {
+                    this.type = BETWEEN_TWO_ITEMS;
+                    this.limitsBeforeCoordinate = limitsList.get(~index - 1);
+                    this.limitsAfterCoordinate = limitsList.get(~index);
+                }
+            }
+        }
+
+        int toComparisonValue() {
+            if (type == BEFORE_FIRST_ITEM) {
+                return mFirstKnownItem.lowerLimit - 1;
+            } else if (type == AFTER_LAST_ITEM) {
+                return mLastKnownItem.upperLimit + 1;
+            } else if (type == BETWEEN_TWO_ITEMS) {
+                return limitsBeforeCoordinate.upperLimit + 1;
+            } else {
+                return limitsBeforeCoordinate.lowerLimit;
+            }
+        }
+
+        @Override
+        public int hashCode() {
+            return mFirstKnownItem.lowerLimit
+                    ^ mLastKnownItem.upperLimit
+                    ^ limitsBeforeCoordinate.upperLimit
+                    ^ limitsBeforeCoordinate.lowerLimit;
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if (!(other instanceof RelativeCoordinate)) {
+                return false;
+            }
+
+            RelativeCoordinate otherCoordinate = (RelativeCoordinate) other;
+            return toComparisonValue() == otherCoordinate.toComparisonValue();
+        }
+
+        @Override
+        public int compareTo(RelativeCoordinate other) {
+            return toComparisonValue() - other.toComparisonValue();
+        }
+    }
+
+    RelativePoint createRelativePoint(Point point) {
+        return new RelativePoint(
+                new RelativeCoordinate(mColumnBounds, point.x),
+                new RelativeCoordinate(mRowBounds, point.y));
+    }
+
+    /**
+     * The location of a point relative to the Limits of nearby items; consists of both an x- and
+     * y-RelativeCoordinateLocation.
+     */
+    private static class RelativePoint {
+
+        final RelativeCoordinate mX;
+        final RelativeCoordinate mY;
+
+        RelativePoint(List<Limits> columnLimits, List<Limits> rowLimits, Point point) {
+            this.mX = new RelativeCoordinate(columnLimits, point.x);
+            this.mY = new RelativeCoordinate(rowLimits, point.y);
+        }
+
+        RelativePoint(RelativeCoordinate x, RelativeCoordinate y) {
+            this.mX = x;
+            this.mY = y;
+        }
+
+        @Override
+        public int hashCode() {
+            return mX.toComparisonValue() ^ mY.toComparisonValue();
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if (!(other instanceof RelativePoint)) {
+                return false;
+            }
+
+            RelativePoint otherPoint = (RelativePoint) other;
+            return mX.equals(otherPoint.mX) && mY.equals(otherPoint.mY);
+        }
+    }
+
+    /**
+     * Generates a rectangle which contains the items selected by the pointer and origin.
+     *
+     * @return The rectangle, or null if no items were selected.
+     */
+    private Rect computeBounds() {
+        Rect rect = new Rect();
+        rect.left = getCoordinateValue(
+                min(mRelOrigin.mX, mRelPointer.mX),
+                mColumnBounds,
+                true);
+        rect.right = getCoordinateValue(
+                max(mRelOrigin.mX, mRelPointer.mX),
+                mColumnBounds,
+                false);
+        rect.top = getCoordinateValue(
+                min(mRelOrigin.mY, mRelPointer.mY),
+                mRowBounds,
+                true);
+        rect.bottom = getCoordinateValue(
+                max(mRelOrigin.mY, mRelPointer.mY),
+                mRowBounds,
+                false);
+        return rect;
+    }
+
+    /**
+     * Computes the corner of the selection nearest the origin.
+     */
+    private int computeCornerNearestOrigin() {
+        int cornerValue = 0;
+
+        if (mRelOrigin.mY.equals(min(mRelOrigin.mY, mRelPointer.mY))) {
+            cornerValue |= UPPER;
+        } else {
+            cornerValue |= LOWER;
+        }
+
+        if (mRelOrigin.mX.equals(min(mRelOrigin.mX, mRelPointer.mX))) {
+            cornerValue |= LEFT;
+        } else {
+            cornerValue |= RIGHT;
+        }
+
+        return cornerValue;
+    }
+
+    private RelativeCoordinate min(RelativeCoordinate first, RelativeCoordinate second) {
+        return first.compareTo(second) < 0 ? first : second;
+    }
+
+    private RelativeCoordinate max(RelativeCoordinate first, RelativeCoordinate second) {
+        return first.compareTo(second) > 0 ? first : second;
+    }
+
+    /**
+     * @return The absolute coordinate (i.e., the x- or y-value) of the given relative
+     * coordinate.
+     */
+    private int getCoordinateValue(
+            RelativeCoordinate coordinate, List<Limits> limitsList, boolean isStartOfRange) {
+
+        switch (coordinate.type) {
+            case RelativeCoordinate.BEFORE_FIRST_ITEM:
+                return limitsList.get(0).lowerLimit;
+            case RelativeCoordinate.AFTER_LAST_ITEM:
+                return limitsList.get(limitsList.size() - 1).upperLimit;
+            case RelativeCoordinate.BETWEEN_TWO_ITEMS:
+                if (isStartOfRange) {
+                    return coordinate.limitsAfterCoordinate.lowerLimit;
+                } else {
+                    return coordinate.limitsBeforeCoordinate.upperLimit;
+                }
+            case RelativeCoordinate.WITHIN_LIMITS:
+                return coordinate.limitsBeforeCoordinate.lowerLimit;
+        }
+
+        throw new RuntimeException("Invalid coordinate value.");
+    }
+
+    private boolean areItemsCoveredByBand(
+            RelativePoint first, RelativePoint second) {
+
+        return doesCoordinateLocationCoverItems(first.mX, second.mX)
+                && doesCoordinateLocationCoverItems(first.mY, second.mY);
+    }
+
+    private boolean doesCoordinateLocationCoverItems(
+            RelativeCoordinate pointerCoordinate, RelativeCoordinate originCoordinate) {
+
+        if (pointerCoordinate.type == RelativeCoordinate.BEFORE_FIRST_ITEM
+                && originCoordinate.type == RelativeCoordinate.BEFORE_FIRST_ITEM) {
+            return false;
+        }
+
+        if (pointerCoordinate.type == RelativeCoordinate.AFTER_LAST_ITEM
+                && originCoordinate.type == RelativeCoordinate.AFTER_LAST_ITEM) {
+            return false;
+        }
+
+        if (pointerCoordinate.type == RelativeCoordinate.BETWEEN_TWO_ITEMS
+                && originCoordinate.type == RelativeCoordinate.BETWEEN_TWO_ITEMS
+                && pointerCoordinate.limitsBeforeCoordinate.equals(
+                originCoordinate.limitsBeforeCoordinate)
+                && pointerCoordinate.limitsAfterCoordinate.equals(
+                originCoordinate.limitsAfterCoordinate)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Provides functionality for BandController. Exists primarily to tests that are
+     * fully isolated from RecyclerView.
+     *
+     * @param <K> Selection key type. Usually String or Long.
+     */
+    abstract static class GridHost<K> extends BandSelectionHelper.BandHost<K> {
+
+        /**
+         * Remove the listener.
+         *
+         * @param listener
+         */
+        abstract void removeOnScrollListener(RecyclerView.OnScrollListener listener);
+
+        /**
+         * @param relativePoint for which to create absolute point.
+         * @return absolute point.
+         */
+        abstract Point createAbsolutePoint(Point relativePoint);
+
+        /**
+         * @param index index of child.
+         * @return rectangle describing child at {@code index}.
+         */
+        abstract Rect getAbsoluteRectForChildViewAt(int index);
+
+        /**
+         * @param index index of child.
+         * @return child adapter position for the child at {@code index}
+         */
+        abstract int getAdapterPositionAt(int index);
+
+        /** @return column count. */
+        abstract int getColumnCount();
+
+        /** @return number of children visible in the view. */
+        abstract int getVisibleChildCount();
+
+        /**
+         * @return true if the item at adapter position is attached to a view.
+         */
+        abstract boolean hasView(int adapterPosition);
+    }
+}
diff --git a/recyclerview-selection/src/main/java/androidx/recyclerview/selection/ItemDetailsLookup.java b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/ItemDetailsLookup.java
new file mode 100644
index 0000000..da30c97
--- /dev/null
+++ b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/ItemDetailsLookup.java
@@ -0,0 +1,149 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+import android.support.v7.widget.RecyclerView;
+import android.view.MotionEvent;
+
+/**
+ * Provides event handlers w/ access to details about documents details
+ * view items Documents in the UI (RecyclerView).
+ *
+ * @param <K> Selection key type. Usually String or Long.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public abstract class ItemDetailsLookup<K> {
+
+    /** @return true if there is an item under the finger/cursor. */
+    public boolean overItem(MotionEvent e) {
+        return getItemPosition(e) != RecyclerView.NO_POSITION;
+    }
+
+    /** @return true if there is an item w/ a stable ID under the finger/cursor. */
+    public boolean overItemWithSelectionKey(MotionEvent e) {
+        return overItem(e) && hasSelectionKey(getItemDetails(e));
+    }
+
+    /**
+     * @return true if the event is over an area that can be dragged via touch
+     * or via mouse. List items have a white area that is not draggable.
+     */
+    public boolean inItemDragRegion(MotionEvent e) {
+        return overItem(e) && getItemDetails(e).inDragRegion(e);
+    }
+
+    /**
+     * @return true if the event is in the "selection hot spot" region.
+     * The hot spot region instantly selects in touch mode, vs launches.
+     */
+    public boolean inItemSelectRegion(MotionEvent e) {
+        return overItem(e) && getItemDetails(e).inSelectionHotspot(e);
+    }
+
+    /**
+     * @return the adapter position of the item under the finger/cursor.
+     */
+    public int getItemPosition(MotionEvent e) {
+        @Nullable ItemDetails<?> item = getItemDetails(e);
+        return item != null
+                ? item.getPosition()
+                : RecyclerView.NO_POSITION;
+    }
+
+    private static boolean hasSelectionKey(@Nullable ItemDetails<?> item) {
+        return item != null && item.getSelectionKey() != null;
+    }
+
+    private static boolean hasPosition(@Nullable ItemDetails<?> item) {
+        return item != null && item.getPosition() != RecyclerView.NO_POSITION;
+    }
+
+    /**
+     * @return the DocumentDetails for the item under the event, or null.
+     */
+    public abstract @Nullable ItemDetails<K> getItemDetails(MotionEvent e);
+
+    /**
+     * Abstract class providing helper classes with access to information about
+     * RecyclerView item associated with a MotionEvent.
+     *
+     * @param <K> Selection key type. Usually String or Long.
+     */
+    // TODO: Can this be merged with ViewHolder?
+    public abstract static class ItemDetails<K> {
+
+        /** @return the position of an item. */
+        public abstract int getPosition();
+
+        /** @return true if the item has a stable id. */
+        public boolean hasSelectionKey() {
+            return getSelectionKey() != null;
+        }
+
+        /** @return the stable id of an item. */
+        public abstract @Nullable K getSelectionKey();
+
+        /**
+         * @return true if the event is in an area of the item that should be
+         * directly interpreted as a user wishing to select the item. This
+         * is useful for checkboxes and other UI affordances focused on enabling
+         * selection.
+         */
+        public boolean inSelectionHotspot(MotionEvent e) {
+            return false;
+        }
+
+        /**
+         * Events in the drag region will dealt with differently that events outside
+         * of the drag region. This allows the client to implement custom handling
+         * for events related to drag and drop.
+         */
+        public boolean inDragRegion(MotionEvent e) {
+            return false;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj instanceof ItemDetails) {
+                return isEqualTo((ItemDetails) obj);
+            }
+            return false;
+        }
+
+        private boolean isEqualTo(ItemDetails other) {
+            K key = getSelectionKey();
+            boolean sameKeys = false;
+            if (key == null) {
+                sameKeys = other.getSelectionKey() == null;
+            } else {
+                sameKeys = key.equals(other.getSelectionKey());
+            }
+            return sameKeys && this.getPosition() == other.getPosition();
+        }
+
+        @Override
+        public int hashCode() {
+            return getPosition() >>> 8;
+        }
+    }
+}
diff --git a/recyclerview-selection/src/main/java/androidx/recyclerview/selection/ItemKeyProvider.java b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/ItemKeyProvider.java
new file mode 100644
index 0000000..134c442
--- /dev/null
+++ b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/ItemKeyProvider.java
@@ -0,0 +1,92 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+import static android.support.v4.util.Preconditions.checkArgument;
+
+import android.support.annotation.IntDef;
+import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+import android.support.annotation.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Provides support for sting based stable ids in the RecyclerView selection helper.
+ * Client code can use this to look up stable ids when working with selection
+ * in application code.
+ *
+ * @param <K> Selection key type. Usually String or Long.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public abstract class ItemKeyProvider<K> {
+
+    /**
+     * Provides access to all data, regardless of whether it is bound to a view or not.
+     * Key providers with this access type enjoy support for enhanced features like:
+     * SHIFT+click range selection, and band selection.
+     */
+    @VisibleForTesting  // otherwise protected would do nicely.
+    public static final int SCOPE_MAPPED = 0;
+
+    /**
+     * Provides access cached data based on what was recently bound in the view.
+     * Employing this provider will result in a reduced feature-set, as some
+     * featuers like SHIFT+click range selection and band selection are dependent
+     * on mapped access.
+     */
+    @VisibleForTesting  // otherwise protected would do nicely.
+    public static final int SCOPE_CACHED = 1;
+
+    @IntDef({
+            SCOPE_MAPPED,
+            SCOPE_CACHED
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    protected @interface Scope {}
+
+    private final @Scope int mScope;
+
+    /**
+     * Creates a new provider with the given scope.
+     * @param scope Scope can't change at runtime (at least code won't adapt)
+     *         so it must be specified in the constructor.
+     */
+    protected ItemKeyProvider(@Scope int scope) {
+        checkArgument(scope == SCOPE_MAPPED || scope == SCOPE_CACHED);
+
+        mScope = scope;
+    }
+
+    final boolean hasAccess(@Scope int scope) {
+        return scope == mScope;
+    }
+
+    /**
+     * @return The selection key of the item at the given adapter position.
+     */
+    public abstract @Nullable K getKey(int position);
+
+    /**
+     * @return the position of a stable ID, or RecyclerView.NO_POSITION.
+     */
+    public abstract int getPosition(K key);
+}
diff --git a/recyclerview-selection/src/main/java/androidx/recyclerview/selection/MotionEvents.java b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/MotionEvents.java
new file mode 100644
index 0000000..dd9e54f
--- /dev/null
+++ b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/MotionEvents.java
@@ -0,0 +1,108 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import android.graphics.Point;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+
+/**
+ * Utility methods for working with {@link MotionEvent} instances.
+ */
+final class MotionEvents {
+
+    private MotionEvents() {}
+
+    static boolean isMouseEvent(MotionEvent e) {
+        return e.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE;
+    }
+
+    static boolean isTouchEvent(MotionEvent e) {
+        return e.getToolType(0) == MotionEvent.TOOL_TYPE_FINGER;
+    }
+
+    static boolean isActionMove(MotionEvent e) {
+        return e.getActionMasked() == MotionEvent.ACTION_MOVE;
+    }
+
+    static boolean isActionDown(MotionEvent e) {
+        return e.getActionMasked() == MotionEvent.ACTION_DOWN;
+    }
+
+    static boolean isActionUp(MotionEvent e) {
+        return e.getActionMasked() == MotionEvent.ACTION_UP;
+    }
+
+    static boolean isActionPointerUp(MotionEvent e) {
+        return e.getActionMasked() == MotionEvent.ACTION_POINTER_UP;
+    }
+
+    @SuppressWarnings("unused")
+    static boolean isActionPointerDown(MotionEvent e) {
+        return e.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN;
+    }
+
+    static boolean isActionCancel(MotionEvent e) {
+        return e.getActionMasked() == MotionEvent.ACTION_CANCEL;
+    }
+
+    static Point getOrigin(MotionEvent e) {
+        return new Point((int) e.getX(), (int) e.getY());
+    }
+
+    static boolean isPrimaryButtonPressed(MotionEvent e) {
+        return isButtonPressed(e, MotionEvent.BUTTON_PRIMARY);
+    }
+
+    static boolean isSecondaryButtonPressed(MotionEvent e) {
+        return isButtonPressed(e, MotionEvent.BUTTON_SECONDARY);
+    }
+
+    static boolean isTertiaryButtonPressed(MotionEvent e) {
+        return isButtonPressed(e, MotionEvent.BUTTON_TERTIARY);
+    }
+
+    // TODO: Replace with MotionEvent.isButtonPressed once targeting 21 or higher.
+    private static boolean isButtonPressed(MotionEvent e, int button) {
+        if (button == 0) {
+            return false;
+        }
+        return (e.getButtonState() & button) == button;
+    }
+
+    static boolean isShiftKeyPressed(MotionEvent e) {
+        return hasBit(e.getMetaState(), KeyEvent.META_SHIFT_ON);
+    }
+
+    static boolean isCtrlKeyPressed(MotionEvent e) {
+        return hasBit(e.getMetaState(), KeyEvent.META_CTRL_ON);
+    }
+
+    static boolean isAltKeyPressed(MotionEvent e) {
+        return hasBit(e.getMetaState(), KeyEvent.META_ALT_ON);
+    }
+
+    static boolean isTouchpadScroll(MotionEvent e) {
+        // Touchpad inputs are treated as mouse inputs, and when scrolling, there are no buttons
+        // returned.
+        return isMouseEvent(e) && isActionMove(e) && e.getButtonState() == 0;
+    }
+
+    private static boolean hasBit(int metaState, int bit) {
+        return (metaState & bit) != 0;
+    }
+}
diff --git a/recyclerview-selection/src/main/java/androidx/recyclerview/selection/MotionInputHandler.java b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/MotionInputHandler.java
new file mode 100644
index 0000000..1c06302
--- /dev/null
+++ b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/MotionInputHandler.java
@@ -0,0 +1,111 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static android.support.v4.util.Preconditions.checkArgument;
+import static android.support.v4.util.Preconditions.checkState;
+
+import android.support.annotation.Nullable;
+import android.support.v7.widget.RecyclerView;
+import android.view.GestureDetector.SimpleOnGestureListener;
+import android.view.MotionEvent;
+
+import androidx.recyclerview.selection.ItemDetailsLookup.ItemDetails;
+
+/**
+ * Base class for handlers that can be registered w/ {@link GestureRouter}.
+ */
+abstract class MotionInputHandler<K> extends SimpleOnGestureListener {
+
+    protected final SelectionHelper<K> mSelectionHelper;
+
+    private final ItemKeyProvider<K> mKeyProvider;
+    private final FocusCallbacks<K> mFocusCallbacks;
+
+    MotionInputHandler(
+            SelectionHelper<K> selectionHelper,
+            ItemKeyProvider<K> keyProvider,
+            FocusCallbacks<K> focusCallbacks) {
+
+        checkArgument(selectionHelper != null);
+        checkArgument(keyProvider != null);
+        checkArgument(focusCallbacks != null);
+
+        mSelectionHelper = selectionHelper;
+        mKeyProvider = keyProvider;
+        mFocusCallbacks = focusCallbacks;
+    }
+
+    final boolean selectItem(ItemDetails<K> details) {
+        checkArgument(details != null);
+        checkArgument(hasPosition(details));
+        checkArgument(hasSelectionKey(details));
+
+        if (mSelectionHelper.select(details.getSelectionKey())) {
+            mSelectionHelper.anchorRange(details.getPosition());
+        }
+
+        // we set the focus on this doc so it will be the origin for keyboard events or shift+clicks
+        // if there is only a single item selected, otherwise clear focus
+        if (mSelectionHelper.getSelection().size() == 1) {
+            mFocusCallbacks.focusItem(details);
+        } else {
+            mFocusCallbacks.clearFocus();
+        }
+        return true;
+    }
+
+    protected final boolean focusItem(ItemDetails<K> details) {
+        checkArgument(details != null);
+        checkArgument(hasSelectionKey(details));
+
+        mSelectionHelper.clearSelection();
+        mFocusCallbacks.focusItem(details);
+        return true;
+    }
+
+    protected final void extendSelectionRange(ItemDetails<K> details) {
+        checkState(mKeyProvider.hasAccess(ItemKeyProvider.SCOPE_MAPPED));
+        checkArgument(hasPosition(details));
+        checkArgument(hasSelectionKey(details));
+
+        mSelectionHelper.extendRange(details.getPosition());
+        mFocusCallbacks.focusItem(details);
+    }
+
+    final boolean isRangeExtension(MotionEvent e) {
+        return MotionEvents.isShiftKeyPressed(e)
+                && mSelectionHelper.isRangeActive()
+                // Without full corpus access we can't reliably implement range
+                // as a user can scroll *anywhere* then SHIFT+click.
+                && mKeyProvider.hasAccess(ItemKeyProvider.SCOPE_MAPPED);
+    }
+
+    boolean shouldClearSelection(MotionEvent e, ItemDetails<K> item) {
+        return !MotionEvents.isCtrlKeyPressed(e)
+                && !item.inSelectionHotspot(e)
+                && !mSelectionHelper.isSelected(item.getSelectionKey());
+    }
+
+    static boolean hasSelectionKey(@Nullable ItemDetails<?> item) {
+        return item != null && item.getSelectionKey() != null;
+    }
+
+    static boolean hasPosition(@Nullable ItemDetails<?> item) {
+        return item != null && item.getPosition() != RecyclerView.NO_POSITION;
+    }
+}
diff --git a/recyclerview-selection/src/main/java/androidx/recyclerview/selection/MouseCallbacks.java b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/MouseCallbacks.java
new file mode 100644
index 0000000..05c47c1
--- /dev/null
+++ b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/MouseCallbacks.java
@@ -0,0 +1,48 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.support.annotation.RestrictTo;
+import android.view.MotionEvent;
+
+/**
+ * Override methods in this class to connect specialized behaviors of the selection
+ * code to the application environment.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public abstract class MouseCallbacks {
+
+    static final MouseCallbacks DUMMY = new MouseCallbacks() {
+        @Override
+        public boolean onContextClick(MotionEvent e) {
+            return false;
+        }
+    };
+
+    /**
+     * Called when user performs a context click, usually via mouse pointer
+     * right-click.
+     *
+     * @param e the event associated with the click.
+     * @return true if the event was handled.
+     */
+    public abstract boolean onContextClick(MotionEvent e);
+}
diff --git a/recyclerview-selection/src/main/java/androidx/recyclerview/selection/MouseInputHandler.java b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/MouseInputHandler.java
new file mode 100644
index 0000000..b6fe36b
--- /dev/null
+++ b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/MouseInputHandler.java
@@ -0,0 +1,223 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static android.support.v4.util.Preconditions.checkArgument;
+import static android.support.v4.util.Preconditions.checkState;
+
+import static androidx.recyclerview.selection.Shared.DEBUG;
+import static androidx.recyclerview.selection.Shared.VERBOSE;
+
+import android.support.annotation.Nullable;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+import android.view.MotionEvent;
+
+import androidx.recyclerview.selection.ItemDetailsLookup.ItemDetails;
+
+/**
+ * A MotionInputHandler that provides the high-level glue for mouse driven selection. This
+ * class works with {@link RecyclerView}, {@link GestureRouter}, and {@link GestureSelectionHelper}
+ * to provide robust user driven selection support.
+ */
+final class MouseInputHandler<K> extends MotionInputHandler<K> {
+
+    private static final String TAG = "MouseInputDelegate";
+
+    private final ItemDetailsLookup<K> mDetailsLookup;
+    private final MouseCallbacks mMouseCallbacks;
+    private final ActivationCallbacks<K> mActivationCallbacks;
+    private final FocusCallbacks<K> mFocusCallbacks;
+
+    // The event has been handled in onSingleTapUp
+    private boolean mHandledTapUp;
+    // true when the previous event has consumed a right click motion event
+    private boolean mHandledOnDown;
+
+    MouseInputHandler(
+            SelectionHelper<K> selectionHelper,
+            ItemKeyProvider<K> keyProvider,
+            ItemDetailsLookup<K> detailsLookup,
+            MouseCallbacks mouseCallbacks,
+            ActivationCallbacks<K> activationCallbacks,
+            FocusCallbacks<K> focusCallbacks) {
+
+        super(selectionHelper, keyProvider, focusCallbacks);
+
+        checkArgument(detailsLookup != null);
+        checkArgument(mouseCallbacks != null);
+        checkArgument(activationCallbacks != null);
+
+        mDetailsLookup = detailsLookup;
+        mMouseCallbacks = mouseCallbacks;
+        mActivationCallbacks = activationCallbacks;
+        mFocusCallbacks = focusCallbacks;
+    }
+
+    @Override
+    public boolean onDown(MotionEvent e) {
+        if (VERBOSE) Log.v(TAG, "Delegated onDown event.");
+        if ((MotionEvents.isAltKeyPressed(e) && MotionEvents.isPrimaryButtonPressed(e))
+                || MotionEvents.isSecondaryButtonPressed(e)) {
+            mHandledOnDown = true;
+            return onRightClick(e);
+        }
+
+        return false;
+    }
+
+    @Override
+    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
+        // Don't scroll content window in response to mouse drag
+        // If it's two-finger trackpad scrolling, we want to scroll
+        return !MotionEvents.isTouchpadScroll(e2);
+    }
+
+    @Override
+    public boolean onSingleTapUp(MotionEvent e) {
+        // See b/27377794. Since we don't get a button state back from UP events, we have to
+        // explicitly save this state to know whether something was previously handled by
+        // DOWN events or not.
+        if (mHandledOnDown) {
+            if (VERBOSE) Log.v(TAG, "Ignoring onSingleTapUp, previously handled in onDown.");
+            mHandledOnDown = false;
+            return false;
+        }
+
+        if (!mDetailsLookup.overItemWithSelectionKey(e)) {
+            if (DEBUG) Log.d(TAG, "Tap not associated w/ model item. Clearing selection.");
+            mSelectionHelper.clearSelection();
+            mFocusCallbacks.clearFocus();
+            return false;
+        }
+
+        if (MotionEvents.isTertiaryButtonPressed(e)) {
+            if (DEBUG) Log.d(TAG, "Ignoring middle click");
+            return false;
+        }
+
+        if (mSelectionHelper.hasSelection()) {
+            onItemClick(e, mDetailsLookup.getItemDetails(e));
+            mHandledTapUp = true;
+            return true;
+        }
+
+        return false;
+    }
+
+    // tap on an item when there is an existing selection. We could extend
+    // a selection, we could clear selection (then launch)
+    private void onItemClick(MotionEvent e, ItemDetails<K> item) {
+        checkState(mSelectionHelper.hasSelection());
+        checkArgument(item != null);
+
+        if (isRangeExtension(e)) {
+            extendSelectionRange(item);
+        } else {
+            if (shouldClearSelection(e, item)) {
+                mSelectionHelper.clearSelection();
+            }
+            if (mSelectionHelper.isSelected(item.getSelectionKey())) {
+                if (mSelectionHelper.deselect(item.getSelectionKey())) {
+                    mFocusCallbacks.clearFocus();
+                }
+            } else {
+                selectOrFocusItem(item, e);
+            }
+        }
+    }
+
+    @Override
+    public boolean onSingleTapConfirmed(MotionEvent e) {
+        if (mHandledTapUp) {
+            if (VERBOSE) {
+                Log.v(TAG,
+                        "Ignoring onSingleTapConfirmed, previously handled in onSingleTapUp.");
+            }
+            mHandledTapUp = false;
+            return false;
+        }
+
+        if (mSelectionHelper.hasSelection()) {
+            return false;  // should have been handled by onSingleTapUp.
+        }
+
+        if (!mDetailsLookup.overItem(e)) {
+            if (DEBUG) Log.d(TAG, "Ignoring Confirmed Tap on non-item.");
+            return false;
+        }
+
+        if (MotionEvents.isTertiaryButtonPressed(e)) {
+            if (DEBUG) Log.d(TAG, "Ignoring middle click");
+            return false;
+        }
+
+        @Nullable ItemDetails<K> item = mDetailsLookup.getItemDetails(e);
+        if (item == null || !item.hasSelectionKey()) {
+            return false;
+        }
+
+        if (mFocusCallbacks.hasFocusedItem() && MotionEvents.isShiftKeyPressed(e)) {
+            mSelectionHelper.startRange(mFocusCallbacks.getFocusedPosition());
+            mSelectionHelper.extendRange(item.getPosition());
+        } else {
+            selectOrFocusItem(item, e);
+        }
+        return true;
+    }
+
+    @Override
+    public boolean onDoubleTap(MotionEvent e) {
+        mHandledTapUp = false;
+
+        if (!mDetailsLookup.overItemWithSelectionKey(e)) {
+            if (DEBUG) Log.d(TAG, "Ignoring DoubleTap on non-model-backed item.");
+            return false;
+        }
+
+        if (MotionEvents.isTertiaryButtonPressed(e)) {
+            if (DEBUG) Log.d(TAG, "Ignoring middle click");
+            return false;
+        }
+
+        ItemDetails<K> item = mDetailsLookup.getItemDetails(e);
+        return (item != null) && mActivationCallbacks.onItemActivated(item, e);
+    }
+
+    private boolean onRightClick(MotionEvent e) {
+        if (mDetailsLookup.overItemWithSelectionKey(e)) {
+            @Nullable ItemDetails<K> item = mDetailsLookup.getItemDetails(e);
+            if (item != null && !mSelectionHelper.isSelected(item.getSelectionKey())) {
+                mSelectionHelper.clearSelection();
+                selectItem(item);
+            }
+        }
+
+        // We always delegate final handling of the event,
+        // since the handler might want to show a context menu
+        // in an empty area or some other weirdo view.
+        return mMouseCallbacks.onContextClick(e);
+    }
+
+    private void selectOrFocusItem(ItemDetails<K> item, MotionEvent e) {
+        if (item.inSelectionHotspot(e) || MotionEvents.isCtrlKeyPressed(e)) {
+            selectItem(item);
+        } else {
+            focusItem(item);
+        }
+    }
+}
diff --git a/recyclerview-selection/src/main/java/androidx/recyclerview/selection/MutableSelection.java b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/MutableSelection.java
new file mode 100644
index 0000000..6e11698
--- /dev/null
+++ b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/MutableSelection.java
@@ -0,0 +1,54 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.support.annotation.RestrictTo;
+
+/**
+ * Subclass of Selection exposing public support for mutating the underlying selection data.
+ * This is useful for clients of {@link SelectionHelper} that wish to manipulate
+ * a copy of selection data obtained via {@link SelectionHelper#copySelection(Selection)}.
+ *
+ * @param <K> Selection key type. Usually String or Long.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public final class MutableSelection<K> extends Selection<K> {
+
+    @Override
+    public boolean add(K key) {
+        return super.add(key);
+    }
+
+    @Override
+    public boolean remove(K key) {
+        return super.remove(key);
+    }
+
+    @Override
+    public void copyFrom(Selection<K> source) {
+        super.copyFrom(source);
+    }
+
+    @Override
+    public void clear() {
+        super.clear();
+    }
+}
diff --git a/recyclerview-selection/src/main/java/androidx/recyclerview/selection/Range.java b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/Range.java
new file mode 100644
index 0000000..632e436
--- /dev/null
+++ b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/Range.java
@@ -0,0 +1,186 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static android.support.v4.util.Preconditions.checkArgument;
+import static android.support.v7.widget.RecyclerView.NO_POSITION;
+
+import static androidx.recyclerview.selection.Shared.DEBUG;
+
+import android.support.annotation.IntDef;
+import android.util.Log;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Class providing support for managing range selections.
+ */
+final class Range {
+
+    static final int TYPE_PRIMARY = 0;
+
+    /**
+     * "Provisional" selection represents a overlay on the primary selection. A provisional
+     * selection maybe be eventually added to the primary selection, or it may be abandoned.
+     *
+     * <p>E.g. BandSelectionHelper creates a provisional selection while a user is actively
+     * selecting items with a band. GestureSelectionHelper creates a provisional selection
+     * while a user is active selecting via gesture.
+     *
+     * <p>Provisionally selected items are considered to be selected in
+     * {@link Selection#contains(String)} and related methods. A provisional may be abandoned or
+     * merged into the promary selection.
+     *
+     * <p>A provisional selection may intersect with the primary selection, however clearing the
+     * provisional selection will not affect the primary selection where the two may intersect.
+     */
+    static final int TYPE_PROVISIONAL = 1;
+    @IntDef({
+            TYPE_PRIMARY,
+            TYPE_PROVISIONAL
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface RangeType {}
+
+    private static final String TAG = "Range";
+
+    private final Callbacks mCallbacks;
+    private final int mBegin;
+    private int mEnd = NO_POSITION;
+
+    /**
+     * Creates a new range anchored at {@code position}.
+     *
+     * @param position
+     * @param callbacks
+     */
+    Range(int position, Callbacks callbacks) {
+        mBegin = position;
+        mCallbacks = callbacks;
+        if (DEBUG) Log.d(TAG, "Creating new Range anchored @ " + position);
+    }
+
+    void extendRange(int position, @RangeType int type) {
+        checkArgument(position != NO_POSITION, "Position cannot be NO_POSITION.");
+
+        if (mEnd == NO_POSITION || mEnd == mBegin) {
+            // Reset mEnd so it can be established in establishRange.
+            mEnd = NO_POSITION;
+            establishRange(position, type);
+        } else {
+            reviseRange(position, type);
+        }
+    }
+
+    private void establishRange(int position, @RangeType int type) {
+        checkArgument(mEnd == NO_POSITION, "End has already been set.");
+
+        mEnd = position;
+
+        if (position > mBegin) {
+            if (DEBUG) log(type, "Establishing initial range at @ " + position);
+            updateRange(mBegin + 1, position, true, type);
+        } else if (position < mBegin) {
+            if (DEBUG) log(type, "Establishing initial range at @ " + position);
+            updateRange(position, mBegin - 1, true, type);
+        }
+    }
+
+    private void reviseRange(int position, @RangeType int type) {
+        checkArgument(mEnd != NO_POSITION, "End must already be set.");
+        checkArgument(mBegin != mEnd, "Beging and end point to same position.");
+
+        if (position == mEnd) {
+            if (DEBUG) log(type, "Ignoring no-op revision for range @ " + position);
+        }
+
+        if (mEnd > mBegin) {
+            reviseAscending(position, type);
+        } else if (mEnd < mBegin) {
+            reviseDescending(position, type);
+        }
+        // the "else" case is covered by checkState at beginning of method.
+
+        mEnd = position;
+    }
+
+    /**
+     * Updates an existing ascending selection.
+     */
+    private void reviseAscending(int position, @RangeType int type) {
+        if (DEBUG) log(type, "*ascending* Revising range @ " + position);
+
+        if (position < mEnd) {
+            if (position < mBegin) {
+                updateRange(mBegin + 1, mEnd, false, type);
+                updateRange(position, mBegin - 1, true, type);
+            } else {
+                updateRange(position + 1, mEnd, false, type);
+            }
+        } else if (position > mEnd) {   // Extending the range...
+            updateRange(mEnd + 1, position, true, type);
+        }
+    }
+
+    private void reviseDescending(int position, @RangeType int type) {
+        if (DEBUG) log(type, "*descending* Revising range @ " + position);
+
+        if (position > mEnd) {
+            if (position > mBegin) {
+                updateRange(mEnd, mBegin - 1, false, type);
+                updateRange(mBegin + 1, position, true, type);
+            } else {
+                updateRange(mEnd, position - 1, false, type);
+            }
+        } else if (position < mEnd) {   // Extending the range...
+            updateRange(position, mEnd - 1, true, type);
+        }
+    }
+
+    /**
+     * Try to set selection state for all elements in range. Not that callbacks can cancel
+     * selection of specific items, so some or even all items may not reflect the desired state
+     * after the update is complete.
+     *
+     * @param begin    Adapter position for range start (inclusive).
+     * @param end      Adapter position for range end (inclusive).
+     * @param selected New selection state.
+     */
+    private void updateRange(
+            int begin, int end, boolean selected, @RangeType int type) {
+        mCallbacks.updateForRange(begin, end, selected, type);
+    }
+
+    @Override
+    public String toString() {
+        return "Range{begin=" + mBegin + ", end=" + mEnd + "}";
+    }
+
+    private void log(@RangeType int type, String message) {
+        String opType = type == TYPE_PRIMARY ? "PRIMARY" : "PROVISIONAL";
+        Log.d(TAG, String.valueOf(this) + ": " + message + " (" + opType + ")");
+    }
+
+    /*
+     * @see {@link DefaultSelectionHelper#updateForRange(int, int , boolean, int)}.
+     */
+    abstract static class Callbacks {
+        abstract void updateForRange(
+                int begin, int end, boolean selected, @RangeType int type);
+    }
+}
diff --git a/recyclerview-selection/src/main/java/androidx/recyclerview/selection/Selection.java b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/Selection.java
new file mode 100644
index 0000000..a622530
--- /dev/null
+++ b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/Selection.java
@@ -0,0 +1,259 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+import android.support.annotation.VisibleForTesting;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Object representing the current selection and provisional selection. Provides read only public
+ * access, and private write access.
+ * <p>
+ * This class tracks selected items by managing two sets:
+ *
+ * <li>primary selection
+ *
+ * Primary selection consists of items tapped by a user or by lassoed by band select operation.
+ *
+ * <li>provisional selection
+ *
+ * Provisional selections are selections which have been temporarily created
+ * by an in-progress band select or gesture selection. Once the user releases the mouse button
+ * or lifts their finger the corresponding provisional selection should be converted into
+ * primary selection.
+ *
+ * <p>The total selection is the combination of
+ * both the core selection and the provisional selection. Tracking both separately is necessary to
+ * ensure that items in the core selection are not "erased" from the core selection when they
+ * are temporarily included in a secondary selection (like band selection).
+ *
+ * @param <K> Selection key type. Usually String or Long.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public class Selection<K> implements Iterable<K> {
+
+    // NOTE: Not currently private as DefaultSelectionHelper directly manipulates values.
+    final Set<K> mSelection;
+    final Set<K> mProvisionalSelection;
+
+    Selection() {
+        mSelection = new HashSet<>();
+        mProvisionalSelection = new HashSet<>();
+    }
+
+    /**
+     * Used by {@link SelectionStorage} when restoring selection.
+     */
+    Selection(Set<K> selection) {
+        mSelection = selection;
+        mProvisionalSelection = new HashSet<>();
+    }
+
+    /**
+     * @param key
+     * @return true if the position is currently selected.
+     */
+    public boolean contains(@Nullable K key) {
+        return mSelection.contains(key) || mProvisionalSelection.contains(key);
+    }
+
+    /**
+     * Returns an {@link Iterator} that iterators over the selection, *excluding*
+     * any provisional selection.
+     *
+     * {@inheritDoc}
+     */
+    @Override
+    public Iterator<K> iterator() {
+        return mSelection.iterator();
+    }
+
+    /**
+     * @return size of the selection including both final and provisional selected items.
+     */
+    public int size() {
+        return mSelection.size() + mProvisionalSelection.size();
+    }
+
+    /**
+     * @return true if the selection is empty.
+     */
+    public boolean isEmpty() {
+        return mSelection.isEmpty() && mProvisionalSelection.isEmpty();
+    }
+
+    /**
+     * Sets the provisional selection, which is a temporary selection that can be saved,
+     * canceled, or adjusted at a later time. When a new provision selection is applied, the old
+     * one (if it exists) is abandoned.
+     * @return Map of ids added or removed. Added ids have a value of true, removed are false.
+     */
+    Map<K, Boolean> setProvisionalSelection(Set<K> newSelection) {
+        Map<K, Boolean> delta = new HashMap<>();
+
+        for (K key: mProvisionalSelection) {
+            // Mark each item that used to be in the provisional selection
+            // but is not in the new provisional selection.
+            if (!newSelection.contains(key) && !mSelection.contains(key)) {
+                delta.put(key, false);
+            }
+        }
+
+        for (K key: mSelection) {
+            // Mark each item that used to be in the selection but is unsaved and not in the new
+            // provisional selection.
+            if (!newSelection.contains(key)) {
+                delta.put(key, false);
+            }
+        }
+
+        for (K key: newSelection) {
+            // Mark each item that was not previously in the selection but is in the new
+            // provisional selection.
+            if (!mSelection.contains(key) && !mProvisionalSelection.contains(key)) {
+                delta.put(key, true);
+            }
+        }
+
+        // Now, iterate through the changes and actually add/remove them to/from the current
+        // selection. This could not be done in the previous loops because changing the size of
+        // the selection mid-iteration changes iteration order erroneously.
+        for (Map.Entry<K, Boolean> entry: delta.entrySet()) {
+            K key = entry.getKey();
+            if (entry.getValue()) {
+                mProvisionalSelection.add(key);
+            } else {
+                mProvisionalSelection.remove(key);
+            }
+        }
+
+        return delta;
+    }
+
+    /**
+     * Saves the existing provisional selection. Once the provisional selection is saved,
+     * subsequent provisional selections which are different from this existing one cannot
+     * cause items in this existing provisional selection to become deselected.
+     */
+    @VisibleForTesting
+    protected void mergeProvisionalSelection() {
+        mSelection.addAll(mProvisionalSelection);
+        mProvisionalSelection.clear();
+    }
+
+    /**
+     * Abandons the existing provisional selection so that all items provisionally selected are
+     * now deselected.
+     */
+    @VisibleForTesting
+    void clearProvisionalSelection() {
+        mProvisionalSelection.clear();
+    }
+
+    /**
+     * Adds a new item to the primary selection.
+     *
+     * @return true if the operation resulted in a modification to the selection.
+     */
+    boolean add(K key) {
+        if (mSelection.contains(key)) {
+            return false;
+        }
+
+        mSelection.add(key);
+        return true;
+    }
+
+    /**
+     * Removes an item from the primary selection.
+     *
+     * @return true if the operation resulted in a modification to the selection.
+     */
+    boolean remove(K key) {
+        if (!mSelection.contains(key)) {
+            return false;
+        }
+
+        mSelection.remove(key);
+        return true;
+    }
+
+    /**
+     * Clears the primary selection. The provisional selection, if any, is unaffected.
+     */
+    void clear() {
+        mSelection.clear();
+    }
+
+    /**
+     * Clones primary and provisional selection from supplied {@link Selection}.
+     * Does not copy active range data.
+     */
+    void copyFrom(Selection<K> source) {
+        mSelection.clear();
+        mSelection.addAll(source.mSelection);
+
+        mProvisionalSelection.clear();
+        mProvisionalSelection.addAll(source.mProvisionalSelection);
+    }
+
+    @Override
+    public String toString() {
+        if (size() <= 0) {
+            return "size=0, items=[]";
+        }
+
+        StringBuilder buffer = new StringBuilder(size() * 28);
+        buffer.append("Selection{")
+            .append("primary{size=" + mSelection.size())
+            .append(", entries=" + mSelection)
+            .append("}, provisional{size=" + mProvisionalSelection.size())
+            .append(", entries=" + mProvisionalSelection)
+            .append("}}");
+        return buffer.toString();
+    }
+
+    @Override
+    public int hashCode() {
+        return mSelection.hashCode() ^ mProvisionalSelection.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        }
+
+        return other instanceof Selection && isEqualTo((Selection) other);
+    }
+
+    private boolean isEqualTo(Selection other) {
+        return mSelection.equals(other.mSelection)
+                && mProvisionalSelection.equals(other.mProvisionalSelection);
+    }
+}
diff --git a/recyclerview-selection/src/main/java/androidx/recyclerview/selection/SelectionHelper.java b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/SelectionHelper.java
new file mode 100644
index 0000000..276f903
--- /dev/null
+++ b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/SelectionHelper.java
@@ -0,0 +1,251 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+
+import java.util.Set;
+
+/**
+ * SelectionManager provides support for managing selection within a RecyclerView instance.
+ *
+ * @see DefaultSelectionHelper for details on instantiation.
+ *
+ * @param <K> Selection key type. Usually String or Long.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public abstract class SelectionHelper<K> {
+
+    /**
+     * This value is included in the payload when SelectionHelper implementations
+     * notify RecyclerView of changes. Clients can look for this in
+     * {@code onBindViewHolder} to know if the bind event is occurring in response
+     * to a selection state change.
+     */
+    public static final String SELECTION_CHANGED_MARKER = "Selection-Changed";
+
+    /**
+     * Adds {@code observer} to be notified when changes to selection occur.
+     * This method allows observers to closely track changes to selection
+     * avoiding the need to poll selection at performance critical points.
+     */
+    public abstract void addObserver(SelectionObserver observer);
+
+    /** @return true if has a selection */
+    public abstract boolean hasSelection();
+
+    /**
+     * Returns a Selection object that provides a live view on the current selection.
+     *
+     * @return The current selection.
+     * @see #copySelection(Selection) on how to get a snapshot
+     * of the selection that will not reflect future changes
+     * to selection.
+     */
+    public abstract Selection getSelection();
+
+    /**
+     * Updates {@code dest} to reflect the current selection.
+     */
+    public abstract void copySelection(Selection dest);
+
+    /**
+     * @return true if the item specified by its id is selected. Shorthand for
+     * {@code getSelection().contains(K)}.
+     */
+    public abstract boolean isSelected(@Nullable K key);
+
+    /**
+     * Restores the selected state of specified items. Used in cases such as restore the selection
+     * after rotation etc. Provisional selection, being provisional 'n all, isn't restored.
+     *
+     * <p>This affords clients the ability to restore selection from selection saved
+     * in Activity state. See {@link android.app.Activity#onCreate(Bundle)}.
+     *
+     * @param savedSelection selection being restored.
+     */
+    public abstract void restoreSelection(Selection savedSelection);
+
+    abstract void onDataSetChanged();
+
+    /**
+     * Clears both primary selection and provisional selection.
+     *
+     * @return true if anything changed.
+     */
+    public abstract boolean clear();
+
+    /**
+     * Clears the selection and notifies (if something changes).
+     */
+    public abstract void clearSelection();
+
+    /**
+     * Sets the selected state of the specified items. Note that the callback will NOT
+     * be consulted to see if an item can be selected.
+     */
+    public abstract boolean setItemsSelected(Iterable<K> keys, boolean selected);
+
+    /**
+     * Attempts to select an item.
+     *
+     * @return true if the item was selected. False if the item was not selected, or was
+     * was already selected prior to the method being called.
+     */
+    public abstract boolean select(K key);
+
+    /**
+     * Attempts to deselect an item.
+     *
+     * @return true if the item was deselected. False if the item was not deselected, or was
+     * was already deselected prior to the method being called.
+     */
+    public abstract boolean deselect(K key);
+
+    /**
+     * Selects the item at position and establishes the "anchor" for a range selection,
+     * replacing any existing range anchor.
+     *
+     * @param position The anchor position for the selection range.
+     */
+    public abstract void startRange(int position);
+
+    /**
+     * Sets the end point for the active range selection.
+     *
+     * <p>This function should only be called when a range selection is active
+     * (see {@link #isRangeActive()}. Items in the range [anchor, end] will be
+     * selected.
+     *
+     * @param position  The new end position for the selection range.
+     * @throws IllegalStateException if a range selection is not active. Range selection
+     *         must have been started by a call to {@link #startRange(int)}.
+     */
+    public abstract void extendRange(int position);
+
+    /**
+     * Stops an in-progress range selection. All selection done with
+     * {@link #extendProvisionalRange(int)} will be lost if
+     * {@link Selection#mergeProvisionalSelection()} is not called beforehand.
+     */
+    public abstract void endRange();
+
+    /**
+     * @return Whether or not there is a current range selection active.
+     */
+    public abstract boolean isRangeActive();
+
+    /**
+     * Establishes the "anchor" at which a selection range begins. This "anchor" is consulted
+     * when determining how to extend, and modify selection ranges. Calling this when a
+     * range selection is active will reset the range selection.
+     *
+     * @param position the anchor position. Must already be selected.
+     */
+    protected abstract void anchorRange(int position);
+
+    /**
+     * @param position
+     */
+    // TODO: This is smelly. Maybe this type of logic needs to move into range selection,
+    // then selection manager can have a startProvisionalRange and startRange. Or
+    // maybe ranges always start life as provisional.
+    protected abstract void extendProvisionalRange(int position);
+
+    /**
+     * Sets the provisional selection, replacing any existing selection.
+     * @param newSelection
+     */
+    public abstract void setProvisionalSelection(Set<K> newSelection);
+
+    /** Clears any existing provisional selection */
+    public abstract void clearProvisionalSelection();
+
+    /**
+     * Converts the provisional selection into primary selection, then clears
+     * provisional selection.
+     */
+    public abstract void mergeProvisionalSelection();
+
+    /**
+     * Observer interface providing access to information about Selection state changes.
+     *
+     * @param <K> Selection key type. Usually String or Long.
+     *
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    public abstract static class SelectionObserver<K> {
+
+        /**
+         * Called when state of an item has been changed.
+         */
+        public void onItemStateChanged(K key, boolean selected) {
+        }
+
+        /**
+         * Called when the underlying data set has change. After this method is called
+         * the selection manager will attempt traverse the existing selection,
+         * calling {@link #onItemStateChanged(K, boolean)} for each selected item,
+         * and deselecting any items that cannot be selected given the updated dataset.
+         */
+        public void onSelectionReset() {
+        }
+
+        /**
+         * Called immediately after completion of any set of changes, excluding
+         * those resulting in calls to {@link #onSelectionReset()} and
+         * {@link #onSelectionRestored()}.
+         */
+        public void onSelectionChanged() {
+        }
+
+        /**
+         * Called immediately after selection is restored.
+         * {@link #onItemStateChanged(K, boolean)} will not be called
+         * for individual items in the selection.
+         */
+        public void onSelectionRestored() {
+        }
+    }
+
+    /**
+     * Implement SelectionPredicate to control when items can be selected or unselected.
+     *
+     * @param <K> Selection key type. Usually String or Long.
+     *
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    public abstract static class SelectionPredicate<K> {
+
+        /** @return true if the item at {@code id} can be set to {@code nextState}. */
+        public abstract boolean canSetStateForKey(K key, boolean nextState);
+
+        /** @return true if the item at {@code id} can be set to {@code nextState}. */
+        public abstract boolean canSetStateAtPosition(int position, boolean nextState);
+
+        /** @return true if more than a single item can be selected. */
+        public abstract boolean canSelectMultiple();
+    }
+}
diff --git a/recyclerview-selection/src/main/java/androidx/recyclerview/selection/SelectionHelperBuilder.java b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/SelectionHelperBuilder.java
new file mode 100644
index 0000000..127a511
--- /dev/null
+++ b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/SelectionHelperBuilder.java
@@ -0,0 +1,341 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+import static android.support.v4.util.Preconditions.checkArgument;
+
+import android.content.Context;
+import android.support.annotation.DrawableRes;
+import android.support.annotation.RestrictTo;
+import android.support.v7.widget.RecyclerView;
+import android.view.GestureDetector;
+import android.view.HapticFeedbackConstants;
+import android.view.MotionEvent;
+
+import androidx.recyclerview.selection.SelectionHelper.SelectionPredicate;
+
+/**
+ * Builder class for assembling selection support. Example usage:
+ *
+ * <p><pre>SelectionHelperBuilder selSupport = new SelectionHelperBuilder(
+        mRecView, new DemoStableIdProvider(mAdapter), detailsLookup);
+
+ // By default multi-select is supported.
+ SelectionHelper selHelper = selSupport
+       .build();
+
+ // This configuration support single selection for any element.
+ SelectionHelper selHelper = selSupport
+       .withSelectionPredicate(SelectionHelper.SelectionPredicate.SINGLE_ANYTHING)
+       .build();
+
+ // Lazily bind SelectionHelper. Allows us to defer initialization of the
+ // SelectionHelper dependency until after the adapter is created.
+ mAdapter.bindSelectionHelper(selHelper);
+
+ * </pre></p>
+ *
+ * @see SelectionStorage for important deatils on retaining selection across Activity
+ * lifecycle events.
+ *
+ * @param <K> Selection key type. Usually String or Long.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public final class SelectionHelperBuilder<K> {
+
+    private final RecyclerView mRecView;
+    private final RecyclerView.Adapter<?> mAdapter;
+    private final Context mContext;
+
+    // Content lock provides a mechanism to block content reload while selection
+    // activities are active. If using a loader to load content, route
+    // the call through the content lock using ContentLock#runWhenUnlocked.
+    // This is especially useful when listening on content change notification.
+    private final ContentLock mLock = new ContentLock();
+
+    private SelectionPredicate<K> mSelectionPredicate = SelectionPredicates.selectAnything();
+    private ItemKeyProvider<K> mKeyProvider;
+    private ItemDetailsLookup<K> mDetailsLookup;
+
+    private ActivationCallbacks<K> mActivationCallbacks = ActivationCallbacks.dummy();
+    private FocusCallbacks<K> mFocusCallbacks = FocusCallbacks.dummy();
+    private TouchCallbacks mTouchCallbacks = TouchCallbacks.DUMMY;
+    private MouseCallbacks mMouseCallbacks = MouseCallbacks.DUMMY;
+
+    private BandPredicate mBandPredicate;
+    private int mBandOverlayId = R.drawable.selection_band_overlay;
+
+    private int[] mGestureToolTypes = new int[] {
+        MotionEvent.TOOL_TYPE_FINGER,
+        MotionEvent.TOOL_TYPE_UNKNOWN
+    };
+
+    private int[] mBandToolTypes = new int[] {
+        MotionEvent.TOOL_TYPE_MOUSE
+    };
+
+    public SelectionHelperBuilder(
+            RecyclerView recView,
+            ItemKeyProvider<K> keyProvider,
+            ItemDetailsLookup<K> detailsLookup) {
+
+        checkArgument(recView != null);
+
+        mRecView = recView;
+        mContext = recView.getContext();
+        mAdapter = recView.getAdapter();
+
+        checkArgument(mAdapter != null);
+        checkArgument(keyProvider != null);
+        checkArgument(detailsLookup != null);
+
+        mDetailsLookup = detailsLookup;
+        mKeyProvider = keyProvider;
+
+        mBandPredicate = BandPredicate.notDraggable(mRecView, detailsLookup);
+    }
+
+    /**
+     * Install seleciton predicate.
+     * @param predicate
+     * @return
+     */
+    public SelectionHelperBuilder<K> withSelectionPredicate(SelectionPredicate<K> predicate) {
+        checkArgument(predicate != null);
+        mSelectionPredicate = predicate;
+        return this;
+    }
+
+    /**
+     * Add activation callbacks to respond to taps/enter/double-click on items.
+     *
+     * @param callbacks
+     * @return
+     */
+    public SelectionHelperBuilder<K> withActivationCallbacks(ActivationCallbacks<K> callbacks) {
+        checkArgument(callbacks != null);
+        mActivationCallbacks = callbacks;
+        return this;
+    }
+
+    /**
+     * Add focus callbacks to interfact with selection related focus changes.
+     * @param callbacks
+     * @return
+     */
+    public SelectionHelperBuilder<K> withFocusCallbacks(FocusCallbacks<K> callbacks) {
+        checkArgument(callbacks != null);
+        mFocusCallbacks = callbacks;
+        return this;
+    }
+
+    /**
+     * Configures mouse callbacks, replacing defaults.
+     *
+     * @param callbacks
+     * @return
+     */
+    public SelectionHelperBuilder<K> withMouseCallbacks(MouseCallbacks callbacks) {
+        checkArgument(callbacks != null);
+
+        mMouseCallbacks = callbacks;
+        return this;
+    }
+
+    /**
+     * Replaces default touch callbacks.
+     *
+     * @param callbacks
+     * @return
+     */
+    public SelectionHelperBuilder<K> withTouchCallbacks(TouchCallbacks callbacks) {
+        checkArgument(callbacks != null);
+
+        mTouchCallbacks = callbacks;
+        return this;
+    }
+
+    /**
+     * Replaces default gesture tooltypes.
+     * @param toolTypes
+     * @return
+     */
+    public SelectionHelperBuilder<K> withTouchTooltypes(int... toolTypes) {
+        mGestureToolTypes = toolTypes;
+        return this;
+    }
+
+    /**
+     * Replaces default band overlay.
+     *
+     * @param bandOverlayId
+     * @return
+     */
+    public SelectionHelperBuilder<K> withBandOverlay(@DrawableRes int bandOverlayId) {
+        mBandOverlayId = bandOverlayId;
+        return this;
+    }
+
+    /**
+     * Replaces default band predicate.
+     * @param bandPredicate
+     * @return
+     */
+    public SelectionHelperBuilder<K> withBandPredicate(BandPredicate bandPredicate) {
+
+        checkArgument(bandPredicate != null);
+
+        mBandPredicate = bandPredicate;
+        return this;
+    }
+
+    /**
+     * Replaces default band tools types.
+     * @param toolTypes
+     * @return
+     */
+    public SelectionHelperBuilder<K> withBandTooltypes(int... toolTypes) {
+        mBandToolTypes = toolTypes;
+        return this;
+    }
+
+    /**
+     * Prepares selection support and returns the corresponding SelectionHelper.
+     *
+     * @return
+     */
+    public SelectionHelper<K> build() {
+
+        SelectionHelper<K> selectionHelper =
+                new DefaultSelectionHelper<>(mKeyProvider, mSelectionPredicate);
+
+        // Event glue between RecyclerView and SelectionHelper keeps the classes separate
+        // so that a SelectionHelper can be shared across RecyclerView instances that
+        // represent the same data in different ways.
+        EventBridge.install(mAdapter, selectionHelper, mKeyProvider);
+
+        AutoScroller scroller = new ViewAutoScroller(ViewAutoScroller.createScrollHost(mRecView));
+
+        // Setup basic input handling, with the touch handler as the default consumer
+        // of events. If mouse handling is configured as well, the mouse input
+        // related handlers will intercept mouse input events.
+
+        // GestureRouter is responsible for routing GestureDetector events
+        // to tool-type specific handlers.
+        GestureRouter<MotionInputHandler> gestureRouter = new GestureRouter<>();
+        GestureDetector gestureDetector = new GestureDetector(mContext, gestureRouter);
+
+        // TouchEventRouter takes its name from RecyclerView#OnItemTouchListener.
+        // Despite "Touch" being in the name, it receives events for all types of tools.
+        // This class is responsible for routing events to tool-type specific handlers,
+        // and if not handled by a handler, on to a GestureDetector for analysis.
+        TouchEventRouter eventRouter = new TouchEventRouter(gestureDetector);
+
+        // GestureSelectionHelper provides logic that interprets a combination
+        // of motions and gestures in order to provide gesture driven selection support
+        // when used in conjunction with RecyclerView.
+        final GestureSelectionHelper gestureHelper =
+                GestureSelectionHelper.create(selectionHelper, mRecView, scroller, mLock);
+
+        // Finally hook the framework up to listening to recycle view events.
+        mRecView.addOnItemTouchListener(eventRouter);
+
+        // But before you move on, there's more work to do. Event plumbing has been
+        // installed, but we haven't registered any of our helpers or callbacks.
+        // Helpers contain predefined logic converting events into selection related events.
+        // Callbacks provide authors the ability to reponspond to other types of
+        // events (like "active" a tapped item). This is broken up into two main
+        // suites, one for "touch" and one for "mouse", though both can and should (usually)
+        // be configued to handle other types of input (to satisfy user expectation).);
+
+        // Provides high level glue for binding touch events
+        // and gestures to selection framework.
+        TouchInputHandler<K> touchHandler = new TouchInputHandler<K>(
+                selectionHelper,
+                mKeyProvider,
+                mDetailsLookup,
+                mSelectionPredicate,
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        if (mSelectionPredicate.canSelectMultiple()) {
+                            gestureHelper.start();
+                        }
+                    }
+                },
+                mTouchCallbacks,
+                mActivationCallbacks,
+                mFocusCallbacks,
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        mRecView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+                    }
+                });
+
+        for (int toolType : mGestureToolTypes) {
+            gestureRouter.register(toolType, touchHandler);
+            eventRouter.register(toolType, gestureHelper);
+        }
+
+        // Provides high level glue for binding mouse events and gestures
+        // to selection framework.
+        MouseInputHandler<K> mouseHandler = new MouseInputHandler<>(
+                selectionHelper,
+                mKeyProvider,
+                mDetailsLookup,
+                mMouseCallbacks,
+                mActivationCallbacks,
+                mFocusCallbacks);
+
+        for (int toolType : mBandToolTypes) {
+            gestureRouter.register(toolType, mouseHandler);
+        }
+
+        // Band selection not supported in single select mode, or when key access
+        // is limited to anything less than the entire corpus.
+        // TODO: Since we cach grid info from laid out items, we could cache key too.
+        // Then we couldn't have to limit to CORPUS access.
+        if (mKeyProvider.hasAccess(ItemKeyProvider.SCOPE_MAPPED)
+                && mSelectionPredicate.canSelectMultiple()) {
+            // BandSelectionHelper provides support for band selection on-top of a RecyclerView
+            // instance. Given the recycling nature of RecyclerView BandSelectionController
+            // necessarily models and caches list/grid information as the user's pointer
+            // interacts with the item in the RecyclerView. Selectable items that intersect
+            // with the band, both on and off screen, are selected.
+            BandSelectionHelper bandHelper = BandSelectionHelper.create(
+                    mRecView,
+                    scroller,
+                    mBandOverlayId,
+                    mKeyProvider,
+                    selectionHelper,
+                    mSelectionPredicate,
+                    mBandPredicate,
+                    mFocusCallbacks,
+                    mLock);
+
+            for (int toolType : mBandToolTypes) {
+                eventRouter.register(toolType, bandHelper);
+            }
+        }
+
+        return selectionHelper;
+    }
+}
diff --git a/recyclerview-selection/src/main/java/androidx/recyclerview/selection/SelectionPredicates.java b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/SelectionPredicates.java
new file mode 100644
index 0000000..26253d9
--- /dev/null
+++ b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/SelectionPredicates.java
@@ -0,0 +1,84 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.support.annotation.RestrictTo;
+
+import androidx.recyclerview.selection.SelectionHelper.SelectionPredicate;
+
+/**
+ * Utility class for creating SelectionPredicate instances.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public final class SelectionPredicates {
+
+    private SelectionPredicates() {}
+
+    /**
+     * Returns a selection predicate that allows multiples items to be selected, without
+     * any restrictions on which items can be selected.
+     * @param <K>
+     * @return
+     */
+    public static <K> SelectionPredicate<K> selectAnything() {
+        return new SelectionPredicate<K>() {
+            @Override
+            public boolean canSetStateForKey(K key, boolean nextState) {
+                return true;
+            }
+
+            @Override
+            public boolean canSetStateAtPosition(int position, boolean nextState) {
+                return true;
+            }
+
+            @Override
+            public boolean canSelectMultiple() {
+                return true;
+            }
+        };
+    }
+
+    /**
+     * Returns a selection predicate that allows a single item to be selected, without
+     * any restrictions on which item can be selected.
+     * @param <K>
+     * @return
+     */
+    public static <K> SelectionPredicate<K> selectSingleAnything() {
+        return new SelectionPredicate<K>() {
+            @Override
+            public boolean canSetStateForKey(K key, boolean nextState) {
+                return true;
+            }
+
+            @Override
+            public boolean canSetStateAtPosition(int position, boolean nextState) {
+                return true;
+            }
+
+            @Override
+            public boolean canSelectMultiple() {
+                return false;
+            }
+        };
+    }
+}
diff --git a/recyclerview-selection/src/main/java/androidx/recyclerview/selection/SelectionStorage.java b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/SelectionStorage.java
new file mode 100644
index 0000000..454a76b
--- /dev/null
+++ b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/SelectionStorage.java
@@ -0,0 +1,181 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+import static android.support.v4.util.Preconditions.checkArgument;
+
+import android.os.Bundle;
+import android.support.annotation.IntDef;
+import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+import android.support.annotation.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Set;
+
+/**
+ * Helper class binding SelectionHelper and Activity lifecycle events facilitating
+ * persistence of selection across activity lifecycle events.
+ *
+ * <p>Usage:<br><pre>
+ void onCreate() {
+    mLifecycleHelper = new SelectionStorage<>(SelectionStorage.TYPE_STRING, mSelectionHelper);
+    if (savedInstanceState != null) {
+        mSelectionStorage.onRestoreInstanceState(savedInstanceState);
+    }
+ }
+ protected void onSaveInstanceState(Bundle outState) {
+     super.onSaveInstanceState(outState);
+     mSelectionStorage.onSaveInstanceState(outState);
+ }
+ </pre>
+ * @param <K> Selection key type. Usually String or Long.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public final class SelectionStorage<K> {
+
+    @VisibleForTesting
+    static final String EXTRA_SAVED_SELECTION_TYPE = "androidx.recyclerview.selection.type";
+
+    @VisibleForTesting
+    static final String EXTRA_SAVED_SELECTION_ENTRIES = "androidx.recyclerview.selection.entries";
+
+    public static final int TYPE_STRING = 0;
+    public static final int TYPE_LONG = 1;
+    @IntDef({
+            TYPE_STRING,
+            TYPE_LONG
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface KeyType {}
+
+    private final @KeyType int mKeyType;
+    private final SelectionHelper<K> mHelper;
+
+    /**
+     * Creates a new lifecycle helper. {@code keyType}.
+     *
+     * @param keyType
+     * @param helper
+     */
+    public SelectionStorage(@KeyType int keyType, SelectionHelper<K> helper) {
+        checkArgument(
+                keyType == TYPE_STRING || keyType == TYPE_LONG,
+                "Only String and Integer presistence are supported by default.");
+        checkArgument(helper != null);
+
+        mKeyType = keyType;
+        mHelper = helper;
+    }
+
+    /**
+     * Preserves selection, if any.
+     *
+     * @param state
+     */
+    @SuppressWarnings("unchecked")
+    public void onSaveInstanceState(Bundle state) {
+        MutableSelection<K> sel = new MutableSelection<>();
+        mHelper.copySelection(sel);
+
+        state.putInt(EXTRA_SAVED_SELECTION_TYPE, mKeyType);
+        switch (mKeyType) {
+            case TYPE_STRING:
+                writeStringSelection(state, ((Selection<String>) sel).mSelection);
+                break;
+            case TYPE_LONG:
+                writeLongSelection(state, ((Selection<Long>) sel).mSelection);
+                break;
+            default:
+                throw new UnsupportedOperationException("Unsupported key type: " + mKeyType);
+        }
+    }
+
+    /**
+     * Restores selection from previously saved state.
+     *
+     * @param state
+     */
+    public void onRestoreInstanceState(@Nullable Bundle state) {
+        if (state == null) {
+            return;
+        }
+
+        int keyType = state.getInt(EXTRA_SAVED_SELECTION_TYPE, -1);
+        switch(keyType) {
+            case TYPE_STRING:
+                Selection<String> stringSel = readStringSelection(state);
+                if (stringSel != null && !stringSel.isEmpty()) {
+                    mHelper.restoreSelection(stringSel);
+                }
+                break;
+            case TYPE_LONG:
+                Selection<Long> longSel = readLongSelection(state);
+                if (longSel != null && !longSel.isEmpty()) {
+                    mHelper.restoreSelection(longSel);
+                }
+                break;
+            default:
+                throw new UnsupportedOperationException("Unsupported selection key type.");
+        }
+    }
+
+    private @Nullable Selection<String> readStringSelection(Bundle state) {
+        @Nullable ArrayList<String> stored =
+                state.getStringArrayList(EXTRA_SAVED_SELECTION_ENTRIES);
+        if (stored == null) {
+            return null;
+        }
+
+        Selection<String> selection = new Selection<>();
+        selection.mSelection.addAll(stored);
+        return selection;
+    }
+
+    private @Nullable Selection<Long> readLongSelection(Bundle state) {
+        @Nullable long[] stored = state.getLongArray(EXTRA_SAVED_SELECTION_ENTRIES);
+        if (stored == null) {
+            return null;
+        }
+
+        Selection<Long> selection = new Selection<>();
+        for (long key : stored) {
+            selection.mSelection.add(key);
+        }
+        return selection;
+    }
+
+    private void writeStringSelection(Bundle state, Set<String> selected) {
+        ArrayList<String> value = new ArrayList<>(selected.size());
+        value.addAll(selected);
+        state.putStringArrayList(EXTRA_SAVED_SELECTION_ENTRIES, value);
+    }
+
+    private void writeLongSelection(Bundle state, Set<Long> selected) {
+        long[] value = new long[selected.size()];
+        int i = 0;
+        for (Long key : selected) {
+            value[i++] = key;
+        }
+        state.putLongArray(EXTRA_SAVED_SELECTION_ENTRIES, value);
+    }
+}
diff --git a/recyclerview-selection/src/main/java/androidx/recyclerview/selection/Shared.java b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/Shared.java
new file mode 100644
index 0000000..3b79120
--- /dev/null
+++ b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/Shared.java
@@ -0,0 +1,28 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+/**
+ * Shared constants used in this package.
+ */
+final class Shared {
+
+    static final boolean DEBUG = false;
+    static final boolean VERBOSE = true;
+
+    private Shared() {}
+}
diff --git a/recyclerview-selection/src/main/java/androidx/recyclerview/selection/StableIdKeyProvider.java b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/StableIdKeyProvider.java
new file mode 100644
index 0000000..3dc78ca
--- /dev/null
+++ b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/StableIdKeyProvider.java
@@ -0,0 +1,99 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerView.OnChildAttachStateChangeListener;
+import android.util.SparseArray;
+import android.view.View;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * ItemKeyProvider that provides stable ids by way of cached RecyclerView.Adapter stable ids.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public final class StableIdKeyProvider extends ItemKeyProvider<Long> {
+
+    private final SparseArray<Long> mPositionToKey = new SparseArray<>();
+    private final Map<Long, Integer> mKeyToPosition = new HashMap<Long, Integer>();
+    private final RecyclerView mRecView;
+
+    public StableIdKeyProvider(RecyclerView recView) {
+
+        // Since this provide is based on stable ids based on whats laid out in the window
+        // we can only satisfy "window" scope key access.
+        super(SCOPE_CACHED);
+
+        mRecView = recView;
+
+        mRecView.addOnChildAttachStateChangeListener(
+                new OnChildAttachStateChangeListener() {
+                    @Override
+                    public void onChildViewAttachedToWindow(View view) {
+                        onAttached(view);
+                    }
+
+                    @Override
+                    public void onChildViewDetachedFromWindow(View view) {
+                        onDetached(view);
+                    }
+                }
+        );
+
+    }
+
+    private void onAttached(View view) {
+        RecyclerView.ViewHolder holder = mRecView.findContainingViewHolder(view);
+        int position = holder.getAdapterPosition();
+        long id = holder.getItemId();
+        if (position != RecyclerView.NO_POSITION && id != RecyclerView.NO_ID) {
+            mPositionToKey.put(position, id);
+            mKeyToPosition.put(id, position);
+        }
+    }
+
+    private void onDetached(View view) {
+        RecyclerView.ViewHolder holder = mRecView.findContainingViewHolder(view);
+        int position = holder.getAdapterPosition();
+        long id = holder.getItemId();
+        if (position != RecyclerView.NO_POSITION && id != RecyclerView.NO_ID) {
+            mPositionToKey.delete(position);
+            mKeyToPosition.remove(id);
+        }
+    }
+
+    @Override
+    public @Nullable Long getKey(int position) {
+        return mPositionToKey.get(position, null);
+    }
+
+    @Override
+    public int getPosition(Long key) {
+        if (mKeyToPosition.containsKey(key)) {
+            return mKeyToPosition.get(key);
+        }
+        return RecyclerView.NO_POSITION;
+    }
+}
diff --git a/recyclerview-selection/src/main/java/androidx/recyclerview/selection/ToolHandlerRegistry.java b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/ToolHandlerRegistry.java
new file mode 100644
index 0000000..c735529
--- /dev/null
+++ b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/ToolHandlerRegistry.java
@@ -0,0 +1,68 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static android.support.v4.util.Preconditions.checkArgument;
+import static android.support.v4.util.Preconditions.checkState;
+
+import android.support.annotation.Nullable;
+import android.view.MotionEvent;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Registry for tool specific event handler.
+ */
+final class ToolHandlerRegistry<T> {
+
+    // Currently there are four known input types. ERASER is the last one, so has the
+    // highest value. UNKNOWN is zero, so we add one. This allows delegates to be
+    // registered by type, and avoid the auto-boxing that would be necessary were we
+    // to store delegates in a Map<Integer, Delegate>.
+    private static final int sNumInputTypes = MotionEvent.TOOL_TYPE_ERASER + 1;
+
+    private final List<T> mHandlers = Arrays.asList(null, null, null, null, null);
+    private final T mDefault;
+
+    ToolHandlerRegistry(T defaultDelegate) {
+        checkArgument(defaultDelegate != null);
+        mDefault = defaultDelegate;
+
+        // Initialize all values to null.
+        for (int i = 0; i < sNumInputTypes; i++) {
+            mHandlers.set(i, null);
+        }
+    }
+
+    /**
+     * @param toolType
+     * @param delegate the delegate, or null to unregister.
+     * @throws IllegalStateException if an tooltype handler is already registered.
+     */
+    void set(int toolType, @Nullable T delegate) {
+        checkArgument(toolType >= 0 && toolType <= MotionEvent.TOOL_TYPE_ERASER);
+        checkState(mHandlers.get(toolType) == null);
+
+        mHandlers.set(toolType, delegate);
+    }
+
+    T get(MotionEvent e) {
+        T d = mHandlers.get(e.getToolType(0));
+        return d != null ? d : mDefault;
+    }
+}
diff --git a/recyclerview-selection/src/main/java/androidx/recyclerview/selection/TouchCallbacks.java b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/TouchCallbacks.java
new file mode 100644
index 0000000..5905392
--- /dev/null
+++ b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/TouchCallbacks.java
@@ -0,0 +1,55 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.support.annotation.RestrictTo;
+import android.view.MotionEvent;
+
+/**
+ * Override methods in this class to connect specialized behaviors of the selection
+ * code to the application environment.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public abstract class TouchCallbacks {
+
+    static final TouchCallbacks DUMMY = new TouchCallbacks() {
+        @Override
+        public boolean onDragInitiated(MotionEvent e) {
+            return false;
+        }
+    };
+
+    /**
+     * Called when a drag is initiated. Touch input handler only considers
+     * a drag to be initiated on long press on an existing selection,
+     * as normal touch and drag events are strongly associated with scrolling of the view.
+     *
+     * <p>Drag will only be initiated when the item under the event is already selected.
+     *
+     * <p>The RecyclerView item at the coordinates of the MotionEvent is not supplied as a parameter
+     * to this method as there may be multiple items selected. Clients can obtain the current
+     * list of selected items from {@link SelectionHelper#copySelection(Selection)}.
+     *
+     * @param e the event associated with the drag.
+     * @return true if the event was handled.
+     */
+    public abstract boolean onDragInitiated(MotionEvent e);
+}
diff --git a/recyclerview-selection/src/main/java/androidx/recyclerview/selection/TouchEventRouter.java b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/TouchEventRouter.java
new file mode 100644
index 0000000..fbbca23
--- /dev/null
+++ b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/TouchEventRouter.java
@@ -0,0 +1,105 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static android.support.v4.util.Preconditions.checkArgument;
+
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerView.OnItemTouchListener;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+
+/**
+ * A class responsible for routing MotionEvents to tool-type specific handlers,
+ * and if not handled by a handler, on to a {@link GestureDetector} for further
+ * processing.
+ *
+ * <p>TouchEventRouter takes its name from
+ * {@link RecyclerView#addOnItemTouchListener(OnItemTouchListener)}. Despite "Touch"
+ * being in the name, it receives MotionEvents for all types of tools.
+ */
+final class TouchEventRouter implements OnItemTouchListener {
+
+    private static final String TAG = "TouchEventRouter";
+
+    private final GestureDetector mDetector;
+    private final ToolHandlerRegistry<OnItemTouchListener> mDelegates;
+
+    TouchEventRouter(GestureDetector detector, OnItemTouchListener defaultDelegate) {
+        checkArgument(detector != null);
+        checkArgument(defaultDelegate != null);
+
+        mDetector = detector;
+        mDelegates = new ToolHandlerRegistry<>(defaultDelegate);
+    }
+
+    TouchEventRouter(GestureDetector detector) {
+        this(
+                detector,
+                // Supply a fallback listener does nothing...because the caller
+                // didn't supply a fallback.
+                new OnItemTouchListener() {
+                    @Override
+                    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
+                        return false;
+                    }
+
+                    @Override
+                    public void onTouchEvent(RecyclerView rv, MotionEvent e) {
+                    }
+
+                    @Override
+                    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
+                    }
+                });
+    }
+
+    /**
+     * @param toolType See MotionEvent for details on available types.
+     * @param delegate An {@link OnItemTouchListener} to receive events
+     *     of {@code toolType}.
+     */
+    void register(int toolType, OnItemTouchListener delegate) {
+        checkArgument(delegate != null);
+        mDelegates.set(toolType, delegate);
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
+        boolean handled = mDelegates.get(e).onInterceptTouchEvent(rv, e);
+
+        // Forward all events to UserInputHandler.
+        // This is necessary since UserInputHandler needs to always see the first DOWN event. Or
+        // else all future UP events will be tossed.
+        handled |= mDetector.onTouchEvent(e);
+
+        return handled;
+    }
+
+    @Override
+    public void onTouchEvent(RecyclerView rv, MotionEvent e) {
+        mDelegates.get(e).onTouchEvent(rv, e);
+
+        // Note: even though this event is being handled as part of gestures such as drag and band,
+        // continue forwarding to the GestureDetector. The detector needs to see the entire cluster
+        // of events in order to properly interpret other gestures, such as long press.
+        mDetector.onTouchEvent(e);
+    }
+
+    @Override
+    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {}
+}
diff --git a/recyclerview-selection/src/main/java/androidx/recyclerview/selection/TouchInputHandler.java b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/TouchInputHandler.java
new file mode 100644
index 0000000..e07aeb1
--- /dev/null
+++ b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/TouchInputHandler.java
@@ -0,0 +1,149 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static android.support.v4.util.Preconditions.checkArgument;
+
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+import android.view.MotionEvent;
+
+import androidx.recyclerview.selection.ItemDetailsLookup.ItemDetails;
+import androidx.recyclerview.selection.SelectionHelper.SelectionPredicate;
+
+/**
+ * A MotionInputHandler that provides the high-level glue for touch driven selection. This class
+ * works with {@link RecyclerView}, {@link GestureRouter}, and {@link GestureSelectionHelper} to
+ * provide robust user drive selection support.
+ */
+final class TouchInputHandler<K> extends MotionInputHandler<K> {
+
+    private static final String TAG = "TouchInputDelegate";
+    private static final boolean DEBUG = false;
+
+    private final ItemDetailsLookup<K> mDetailsLookup;
+    private final SelectionPredicate<K> mSelectionPredicate;
+    private final ActivationCallbacks<K> mActivationCallbacks;
+    private final TouchCallbacks mTouchCallbacks;
+    private final Runnable mGestureStarter;
+    private final Runnable mHapticPerformer;
+
+    TouchInputHandler(
+            SelectionHelper<K> selectionHelper,
+            ItemKeyProvider<K> keyProvider,
+            ItemDetailsLookup<K> detailsLookup,
+            SelectionPredicate<K> selectionPredicate,
+            Runnable gestureStarter,
+            TouchCallbacks touchCallbacks,
+            ActivationCallbacks<K> activationCallbacks,
+            FocusCallbacks<K> focusCallbacks,
+            Runnable hapticPerformer) {
+
+        super(selectionHelper, keyProvider, focusCallbacks);
+
+        checkArgument(detailsLookup != null);
+        checkArgument(selectionPredicate != null);
+        checkArgument(gestureStarter != null);
+        checkArgument(activationCallbacks != null);
+        checkArgument(touchCallbacks != null);
+        checkArgument(hapticPerformer != null);
+
+        mDetailsLookup = detailsLookup;
+        mSelectionPredicate = selectionPredicate;
+        mGestureStarter = gestureStarter;
+        mActivationCallbacks = activationCallbacks;
+        mTouchCallbacks = touchCallbacks;
+        mHapticPerformer = hapticPerformer;
+    }
+
+    @Override
+    public boolean onSingleTapUp(MotionEvent e) {
+        if (!mDetailsLookup.overItemWithSelectionKey(e)) {
+            if (DEBUG) Log.d(TAG, "Tap not associated w/ model item. Clearing selection.");
+            mSelectionHelper.clearSelection();
+            return false;
+        }
+
+        ItemDetails<K> item = mDetailsLookup.getItemDetails(e);
+        // Should really not be null at this point, but...
+        if (item == null) {
+            return false;
+        }
+
+        if (mSelectionHelper.hasSelection()) {
+            if (isRangeExtension(e)) {
+                extendSelectionRange(item);
+            } else if (mSelectionHelper.isSelected(item.getSelectionKey())) {
+                mSelectionHelper.deselect(item.getSelectionKey());
+            } else {
+                selectItem(item);
+            }
+
+            return true;
+        }
+
+        // Touch events select if they occur in the selection hotspot,
+        // otherwise they activate.
+        return item.inSelectionHotspot(e)
+                ? selectItem(item)
+                : mActivationCallbacks.onItemActivated(item, e);
+    }
+
+    @Override
+    public void onLongPress(MotionEvent e) {
+        if (!mDetailsLookup.overItemWithSelectionKey(e)) {
+            if (DEBUG) Log.d(TAG, "Ignoring LongPress on non-model-backed item.");
+            return;
+        }
+
+        ItemDetails<K> item = mDetailsLookup.getItemDetails(e);
+        // Should really not be null at this point, but...
+        if (item == null) {
+            return;
+        }
+
+        boolean handled = false;
+
+        if (isRangeExtension(e)) {
+            extendSelectionRange(item);
+            handled = true;
+        } else {
+            if (!mSelectionHelper.isSelected(item.getSelectionKey())
+                    && mSelectionPredicate.canSetStateForKey(item.getSelectionKey(), true)) {
+                // If we cannot select it, we didn't apply anchoring - therefore should not
+                // start gesture selection
+                if (selectItem(item)) {
+                    // And finally if the item was selected && we can select multiple
+                    // we kick off gesture selection.
+                    if (mSelectionPredicate.canSelectMultiple()) {
+                        mGestureStarter.run();
+                    }
+                    handled = true;
+                }
+            } else {
+                // We only initiate drag and drop on long press for touch to allow regular
+                // touch-based scrolling
+                mTouchCallbacks.onDragInitiated(e);
+                handled = true;
+            }
+        }
+
+        if (handled) {
+            mHapticPerformer.run();
+        }
+    }
+}
diff --git a/recyclerview-selection/src/main/java/androidx/recyclerview/selection/ViewAutoScroller.java b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/ViewAutoScroller.java
new file mode 100644
index 0000000..d13b0f2
--- /dev/null
+++ b/recyclerview-selection/src/main/java/androidx/recyclerview/selection/ViewAutoScroller.java
@@ -0,0 +1,271 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static android.support.v4.util.Preconditions.checkArgument;
+import static android.support.v4.util.Preconditions.checkState;
+
+import static androidx.recyclerview.selection.Shared.DEBUG;
+import static androidx.recyclerview.selection.Shared.VERBOSE;
+
+import android.graphics.Point;
+import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
+import android.support.v4.view.ViewCompat;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+
+/**
+ * Provides auto-scrolling upon request when user's interaction with the application
+ * introduces a natural intent to scroll. Used by BandSelectionHelper and GestureSelectionHelper,
+ * to provide auto scrolling when user is performing selection operations.
+ */
+final class ViewAutoScroller extends AutoScroller {
+
+    private static final String TAG = "ViewAutoScroller";
+
+    // ratio used to calculate the top/bottom hotspot region; used with view height
+    private static final float DEFAULT_SCROLL_THRESHOLD_RATIO = 0.125f;
+    private static final int MAX_SCROLL_STEP = 70;
+
+    private final float mScrollThresholdRatio;
+
+    private final ScrollHost mHost;
+    private final Runnable mRunner;
+
+    private @Nullable Point mOrigin;
+    private @Nullable Point mLastLocation;
+    private boolean mPassedInitialMotionThreshold;
+
+    ViewAutoScroller(ScrollHost scrollHost) {
+        this(scrollHost, DEFAULT_SCROLL_THRESHOLD_RATIO);
+    }
+
+    @VisibleForTesting
+    ViewAutoScroller(ScrollHost scrollHost, float scrollThresholdRatio) {
+
+        checkArgument(scrollHost != null);
+
+        mHost = scrollHost;
+        mScrollThresholdRatio = scrollThresholdRatio;
+
+        mRunner = new Runnable() {
+            @Override
+            public void run() {
+                runScroll();
+            }
+        };
+    }
+
+    @Override
+    protected void reset() {
+        mHost.removeCallback(mRunner);
+        mOrigin = null;
+        mLastLocation = null;
+        mPassedInitialMotionThreshold = false;
+    }
+
+    @Override
+    protected void scroll(Point location) {
+        mLastLocation = location;
+
+        // See #aboveMotionThreshold for details on how we track initial location.
+        if (mOrigin == null) {
+            mOrigin = location;
+            if (VERBOSE) Log.v(TAG, "Origin @ " + mOrigin);
+        }
+
+        if (VERBOSE) Log.v(TAG, "Current location @ " + mLastLocation);
+
+        mHost.runAtNextFrame(mRunner);
+    }
+
+    /**
+     * Attempts to smooth-scroll the view at the given UI frame. Application should be
+     * responsible to do any clean up (such as unsubscribing scrollListeners) after the run has
+     * finished, and re-run this method on the next UI frame if applicable.
+     */
+    private void runScroll() {
+        if (DEBUG) checkState(mLastLocation != null);
+
+        if (VERBOSE) Log.v(TAG, "Running in background using event location @ " + mLastLocation);
+
+        // Compute the number of pixels the pointer's y-coordinate is past the view.
+        // Negative values mean the pointer is at or before the top of the view, and
+        // positive values mean that the pointer is at or after the bottom of the view. Note
+        // that top/bottom threshold is added here so that the view still scrolls when the
+        // pointer are in these buffer pixels.
+        int pixelsPastView = 0;
+
+        final int verticalThreshold = (int) (mHost.getViewHeight()
+                * mScrollThresholdRatio);
+
+        if (mLastLocation.y <= verticalThreshold) {
+            pixelsPastView = mLastLocation.y - verticalThreshold;
+        } else if (mLastLocation.y >= mHost.getViewHeight()
+                - verticalThreshold) {
+            pixelsPastView = mLastLocation.y - mHost.getViewHeight()
+                    + verticalThreshold;
+        }
+
+        if (pixelsPastView == 0) {
+            // If the operation that started the scrolling is no longer inactive, or if it is active
+            // but not at the edge of the view, no scrolling is necessary.
+            return;
+        }
+
+        // We're in one of the endzones. Now determine if there's enough of a difference
+        // from the orgin to take any action. Basically if a user has somehow initiated
+        // selection, but is hovering at or near their initial contact point, we don't
+        // scroll. This avoids a situation where the user initiates selection in an "endzone"
+        // only to have scrolling start automatically.
+        if (!mPassedInitialMotionThreshold && !aboveMotionThreshold(mLastLocation)) {
+            if (VERBOSE) Log.v(TAG, "Ignoring event below motion threshold.");
+            return;
+        }
+        mPassedInitialMotionThreshold = true;
+
+        if (pixelsPastView > verticalThreshold) {
+            pixelsPastView = verticalThreshold;
+        }
+
+        // Compute the number of pixels to scroll, and scroll that many pixels.
+        final int numPixels = computeScrollDistance(pixelsPastView);
+        mHost.scrollBy(numPixels);
+
+        // Replace any existing scheduled jobs with the latest and greatest..
+        mHost.removeCallback(mRunner);
+        mHost.runAtNextFrame(mRunner);
+    }
+
+    private boolean aboveMotionThreshold(Point location) {
+        // We reuse the scroll threshold to calculate a much smaller area
+        // in which we ignore motion initially.
+        int motionThreshold =
+                (int) ((mHost.getViewHeight() * mScrollThresholdRatio)
+                        * (mScrollThresholdRatio * 2));
+        return Math.abs(mOrigin.y - location.y) >= motionThreshold;
+    }
+
+    /**
+     * Computes the number of pixels to scroll based on how far the pointer is past the end
+     * of the region. Roughly based on ItemTouchHelper's algorithm for computing the number of
+     * pixels to scroll when an item is dragged to the end of a view.
+     * @return
+     */
+    @VisibleForTesting
+    int computeScrollDistance(int pixelsPastView) {
+        final int topBottomThreshold =
+                (int) (mHost.getViewHeight() * mScrollThresholdRatio);
+
+        final int direction = (int) Math.signum(pixelsPastView);
+        final int absPastView = Math.abs(pixelsPastView);
+
+        // Calculate the ratio of how far out of the view the pointer currently resides to
+        // the top/bottom scrolling hotspot of the view.
+        final float outOfBoundsRatio = Math.min(
+                1.0f, (float) absPastView / topBottomThreshold);
+        // Interpolate this ratio and use it to compute the maximum scroll that should be
+        // possible for this step.
+        final int cappedScrollStep =
+                (int) (direction * MAX_SCROLL_STEP * smoothOutOfBoundsRatio(outOfBoundsRatio));
+
+        // If the final number of pixels to scroll ends up being 0, the view should still
+        // scroll at least one pixel.
+        return cappedScrollStep != 0 ? cappedScrollStep : direction;
+    }
+
+    /**
+     * Interpolates the given out of bounds ratio on a curve which starts at (0,0) and ends
+     * at (1,1) and quickly approaches 1 near the start of that interval. This ensures that
+     * drags that are at the edge or barely past the edge of the threshold does little to no
+     * scrolling, while drags that are near the edge of the view does a lot of
+     * scrolling. The equation y=x^10 is used, but this could also be tweaked if
+     * needed.
+     * @param ratio A ratio which is in the range [0, 1].
+     * @return A "smoothed" value, also in the range [0, 1].
+     */
+    private float smoothOutOfBoundsRatio(float ratio) {
+        return (float) Math.pow(ratio, 10);
+    }
+
+    /**
+     * Used by to calculate the proper amount of pixels to scroll given time passed
+     * since scroll started, and to properly scroll / proper listener clean up if necessary.
+     *
+     * Callback used by scroller to perform UI tasks, such as scrolling and rerunning at next UI
+     * cycle.
+     */
+    abstract static class ScrollHost {
+        /**
+         * @return height of the view.
+         */
+        abstract int getViewHeight();
+
+        /**
+         * @param dy distance to scroll.
+         */
+        abstract void scrollBy(int dy);
+
+        /**
+         * @param r schedule runnable to be run at next convenient time.
+         */
+        abstract void runAtNextFrame(Runnable r);
+
+        /**
+         * @param r remove runnable from being run.
+         */
+        abstract void removeCallback(Runnable r);
+    }
+
+    public static ScrollHost createScrollHost(final RecyclerView view) {
+        return new RuntimeHost(view);
+    }
+
+    /**
+     * Tracks location of last surface contact as reported by RecyclerView.
+     */
+    private static final class RuntimeHost extends ScrollHost {
+
+        private final RecyclerView mRecView;
+
+        RuntimeHost(RecyclerView recView) {
+            mRecView = recView;
+        }
+
+        @Override
+        void runAtNextFrame(Runnable r) {
+            ViewCompat.postOnAnimation(mRecView, r);
+        }
+
+        @Override
+        void removeCallback(Runnable r) {
+            mRecView.removeCallbacks(r);
+        }
+
+        @Override
+        void scrollBy(int dy) {
+            if (VERBOSE) Log.v(TAG, "Scrolling view by: " + dy);
+            mRecView.scrollBy(0, dy);
+        }
+
+        @Override
+        int getViewHeight() {
+            return mRecView.getHeight();
+        }
+    }
+}
diff --git a/recyclerview-selection/tests/AndroidManifest.xml b/recyclerview-selection/tests/AndroidManifest.xml
new file mode 100644
index 0000000..85f42d6
--- /dev/null
+++ b/recyclerview-selection/tests/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:tools="http://schemas.android.com/tools"
+          package="androidx.recyclerview.selection.test">
+    <uses-sdk android:minSdkVersion="14" />
+
+    <application android:supportsRtl="true">
+    </application>
+</manifest>
diff --git a/recyclerview-selection/tests/NO_DOCS b/recyclerview-selection/tests/NO_DOCS
new file mode 100644
index 0000000..61c9b1a
--- /dev/null
+++ b/recyclerview-selection/tests/NO_DOCS
@@ -0,0 +1,17 @@
+# 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.
+
+Having this file, named NO_DOCS, in a directory will prevent
+Android javadocs from being generated for java files under
+the directory. This is especially useful for test projects.
diff --git a/recyclerview-selection/tests/java/androidx/recyclerview/selection/BandSelectionHelperTest.java b/recyclerview-selection/tests/java/androidx/recyclerview/selection/BandSelectionHelperTest.java
new file mode 100644
index 0000000..8399539
--- /dev/null
+++ b/recyclerview-selection/tests/java/androidx/recyclerview/selection/BandSelectionHelperTest.java
@@ -0,0 +1,229 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.graphics.Rect;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v7.widget.RecyclerView.OnScrollListener;
+import android.view.MotionEvent;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Collections;
+import java.util.List;
+
+import androidx.recyclerview.selection.SelectionHelper.SelectionPredicate;
+import androidx.recyclerview.selection.testing.TestAdapter;
+import androidx.recyclerview.selection.testing.TestAutoScroller;
+import androidx.recyclerview.selection.testing.TestBandPredicate;
+import androidx.recyclerview.selection.testing.TestData;
+import androidx.recyclerview.selection.testing.TestEvents.Builder;
+import androidx.recyclerview.selection.testing.TestItemKeyProvider;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class BandSelectionHelperTest {
+
+    private List<String> mItems;
+    private BandSelectionHelper<String> mBandController;
+    private boolean mIsActive;
+    private Builder mStartBuilder;
+    private Builder mStopBuilder;
+    private MotionEvent mStartEvent;
+    private MotionEvent mStopEvent;
+    private TestBandHost mBandHost;
+    private TestBandPredicate mBandPredicate;
+    private TestAdapter<String> mAdapter;
+
+    @Before
+    public void setup() throws Exception {
+        mItems = TestData.createStringData(10);
+        mIsActive = false;
+        mAdapter = new TestAdapter<String>();
+        mAdapter.updateTestModelIds(mItems);
+        mBandHost = new TestBandHost();
+        mBandPredicate = new TestBandPredicate();
+        ItemKeyProvider<String> keyProvider =
+                new TestItemKeyProvider<>(ItemKeyProvider.SCOPE_MAPPED, mAdapter);
+        ContentLock contentLock = new ContentLock();
+        SelectionPredicate<String> selectionTest = SelectionPredicates.selectAnything();
+
+        SelectionHelper<String> helper = new DefaultSelectionHelper<String>(
+                keyProvider,
+                selectionTest);
+
+        EventBridge.install(mAdapter, helper, keyProvider);
+        FocusCallbacks<String> focusCallbacks = FocusCallbacks.dummy();
+
+        mBandController = new BandSelectionHelper<String>(
+                mBandHost,
+                new TestAutoScroller(),
+                keyProvider,
+                helper,
+                selectionTest,
+                mBandPredicate,
+                focusCallbacks,
+                contentLock) {
+                    @Override
+                    public boolean isActive() {
+                        return mIsActive;
+                    }
+                };
+
+        mStartBuilder = new Builder().mouse().primary().action(MotionEvent.ACTION_MOVE);
+        mStopBuilder = new Builder().mouse().action(MotionEvent.ACTION_UP);
+        mStartEvent = mStartBuilder.build();
+        mStopEvent = mStopBuilder.build();
+    }
+
+    @Test
+    public void testStartsBand() {
+        assertTrue(mBandController.shouldStart(mStartEvent));
+    }
+
+    @Test
+    public void testStartsBand_NoItems() {
+        mAdapter.updateTestModelIds(Collections.EMPTY_LIST);
+        assertTrue(mBandController.shouldStart(mStartEvent));
+    }
+
+    @Test
+    public void testBadStart_NoButtons() {
+        assertFalse(mBandController.shouldStart(
+                mStartBuilder.releaseButton(MotionEvent.BUTTON_PRIMARY).build()));
+    }
+
+    @Test
+    public void testBadStart_SecondaryButton() {
+        assertFalse(
+                mBandController.shouldStart(mStartBuilder.secondary().build()));
+    }
+
+    @Test
+    public void testBadStart_TertiaryButton() {
+        assertFalse(
+                mBandController.shouldStart(mStartBuilder.tertiary().build()));
+    }
+
+    @Test
+    public void testBadStart_Touch() {
+        assertFalse(mBandController.shouldStart(
+                mStartBuilder.touch().releaseButton(MotionEvent.BUTTON_PRIMARY).build()));
+    }
+
+    @Test
+    public void testBadStart_RespectsCanInitiateBand() {
+        mBandPredicate.setCanInitiate(false);
+        assertFalse(mBandController.shouldStart(mStartEvent));
+    }
+
+    @Test
+    public void testBadStart_ActionDown() {
+        assertFalse(mBandController
+                .shouldStart(mStartBuilder.action(MotionEvent.ACTION_DOWN).build()));
+    }
+
+    @Test
+    public void testBadStart_ActionUp() {
+        assertFalse(mBandController
+                .shouldStart(mStartBuilder.action(MotionEvent.ACTION_UP).build()));
+    }
+
+    @Test
+    public void testBadStart_ActionPointerDown() {
+        assertFalse(mBandController.shouldStart(
+                mStartBuilder.action(MotionEvent.ACTION_POINTER_DOWN).build()));
+    }
+
+    @Test
+    public void testBadStart_ActionPointerUp() {
+        assertFalse(mBandController.shouldStart(
+                mStartBuilder.action(MotionEvent.ACTION_POINTER_UP).build()));
+    }
+
+    @Test
+    public void testBadStart_alreadyActive() {
+        mIsActive = true;
+        assertFalse(mBandController.shouldStart(mStartEvent));
+    }
+
+    @Test
+    public void testGoodStop() {
+        mIsActive = true;
+        assertTrue(mBandController.shouldStop(mStopEvent));
+    }
+
+    @Test
+    public void testGoodStop_PointerUp() {
+        mIsActive = true;
+        assertTrue(mBandController
+                .shouldStop(mStopBuilder.action(MotionEvent.ACTION_POINTER_UP).build()));
+    }
+
+    @Test
+    public void testGoodStop_Cancel() {
+        mIsActive = true;
+        assertTrue(mBandController
+                .shouldStop(mStopBuilder.action(MotionEvent.ACTION_CANCEL).build()));
+    }
+
+    @Test
+    public void testBadStop_NotActive() {
+        assertFalse(mBandController.shouldStop(mStopEvent));
+    }
+
+    @Test
+    public void testBadStop_Move() {
+        mIsActive = true;
+        assertFalse(mBandController.shouldStop(
+                mStopBuilder.action(MotionEvent.ACTION_MOVE).touch().build()));
+    }
+
+    @Test
+    public void testBadStop_Down() {
+        mIsActive = true;
+        assertFalse(mBandController.shouldStop(
+                mStopBuilder.action(MotionEvent.ACTION_DOWN).touch().build()));
+    }
+
+    private final class TestBandHost extends BandSelectionHelper.BandHost<String> {
+        @Override
+        GridModel<String> createGridModel() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        void showBand(Rect rect) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        void hideBand() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        void addOnScrollListener(OnScrollListener listener) {
+        }
+    }
+}
diff --git a/recyclerview-selection/tests/java/androidx/recyclerview/selection/ContentLockTest.java b/recyclerview-selection/tests/java/androidx/recyclerview/selection/ContentLockTest.java
new file mode 100644
index 0000000..b012f84
--- /dev/null
+++ b/recyclerview-selection/tests/java/androidx/recyclerview/selection/ContentLockTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ContentLockTest {
+
+    private ContentLock mLock = new ContentLock();
+    private boolean mCalled;
+
+    private Runnable mRunnable = new Runnable() {
+        @Override
+        public void run() {
+            mCalled = true;
+        }
+    };
+
+    @Before
+    public void setUp() {
+        mCalled = false;
+    }
+
+    @Test
+    public void testNotBlocking_callbackNotBlocked() {
+        mLock.runWhenUnlocked(mRunnable);
+        assertTrue(mCalled);
+    }
+
+    @Test
+    public void testToggleBlocking_callbackNotBlocked() {
+        mLock.block();
+        mLock.unblock();
+        mLock.runWhenUnlocked(mRunnable);
+        assertTrue(mCalled);
+    }
+
+    @Test
+    public void testBlocking_callbackBlocked() {
+        mLock.block();
+        mLock.runWhenUnlocked(mRunnable);
+        assertFalse(mCalled);
+    }
+
+    @Test
+    public void testBlockthenUnblock_callbackNotBlocked() {
+        mLock.block();
+        mLock.runWhenUnlocked(mRunnable);
+        mLock.unblock();
+        assertTrue(mCalled);
+    }
+}
diff --git a/recyclerview-selection/tests/java/androidx/recyclerview/selection/DefaultSelectionHelperTest.java b/recyclerview-selection/tests/java/androidx/recyclerview/selection/DefaultSelectionHelperTest.java
new file mode 100644
index 0000000..ddcd9a8
--- /dev/null
+++ b/recyclerview-selection/tests/java/androidx/recyclerview/selection/DefaultSelectionHelperTest.java
@@ -0,0 +1,429 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static org.junit.Assert.assertFalse;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.SparseBooleanArray;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import androidx.recyclerview.selection.SelectionHelper.SelectionPredicate;
+import androidx.recyclerview.selection.testing.SelectionProbe;
+import androidx.recyclerview.selection.testing.TestAdapter;
+import androidx.recyclerview.selection.testing.TestItemKeyProvider;
+import androidx.recyclerview.selection.testing.TestSelectionObserver;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class DefaultSelectionHelperTest {
+
+    private List<String> mItems;
+    private Set<String> mIgnored;
+    private TestAdapter mAdapter;
+    private DefaultSelectionHelper<String> mHelper;
+    private TestSelectionObserver<String> mListener;
+    private SelectionProbe mSelection;
+
+    @Before
+    public void setUp() throws Exception {
+        mIgnored = new HashSet<>();
+        mItems = TestAdapter.createItemList(100);
+        mListener = new TestSelectionObserver<>();
+        mAdapter = new TestAdapter();
+        mAdapter.updateTestModelIds(mItems);
+
+        SelectionPredicate selectionPredicate = new SelectionPredicate<String>() {
+
+            @Override
+            public boolean canSetStateForKey(String id, boolean nextState) {
+                return !nextState || !mIgnored.contains(id);
+            }
+
+            @Override
+            public boolean canSetStateAtPosition(int position, boolean nextState) {
+                throw new UnsupportedOperationException("Not implemented.");
+            }
+
+            @Override
+            public boolean canSelectMultiple() {
+                return true;
+            }
+        };
+
+        ItemKeyProvider<String> keyProvider =
+                new TestItemKeyProvider<String>(ItemKeyProvider.SCOPE_MAPPED, mAdapter);
+        mHelper = new DefaultSelectionHelper<>(
+                keyProvider,
+                selectionPredicate);
+
+        EventBridge.install(mAdapter, mHelper, keyProvider);
+
+        mHelper.addObserver(mListener);
+
+        mSelection = new SelectionProbe(mHelper, mListener);
+
+        mIgnored.clear();
+    }
+
+    @Test
+    public void testSelect() {
+        mHelper.select(mItems.get(7));
+
+        mSelection.assertSelection(7);
+    }
+
+    @Test
+    public void testDeselect() {
+        mHelper.select(mItems.get(7));
+        mHelper.deselect(mItems.get(7));
+
+        mSelection.assertNoSelection();
+    }
+
+    @Test
+    public void testSelection_DoNothingOnUnselectableItem() {
+        mIgnored.add(mItems.get(7));
+        boolean selected = mHelper.select(mItems.get(7));
+
+        assertFalse(selected);
+        mSelection.assertNoSelection();
+    }
+
+    @Test
+    public void testSelect_NotifiesListenersOfChange() {
+        mHelper.select(mItems.get(7));
+
+        mListener.assertSelectionChanged();
+    }
+
+    @Test
+    public void testSelect_NotifiesAdapterOfSelect() {
+        mHelper.select(mItems.get(7));
+
+        mAdapter.assertNotifiedOfSelectionChange(7);
+    }
+
+    @Test
+    public void testSelect_NotifiesAdapterOfDeselect() {
+        mHelper.select(mItems.get(7));
+        mAdapter.resetSelectionNotifications();
+        mHelper.deselect(mItems.get(7));
+        mAdapter.assertNotifiedOfSelectionChange(7);
+    }
+
+    @Test
+    public void testDeselect_NotifiesSelectionChanged() {
+        mHelper.select(mItems.get(7));
+        mHelper.deselect(mItems.get(7));
+
+        mListener.assertSelectionChanged();
+    }
+
+    @Test
+    public void testSelection_PersistsOnUpdate() {
+        mHelper.select(mItems.get(7));
+        mAdapter.updateTestModelIds(mItems);
+
+        mSelection.assertSelection(7);
+    }
+
+    @Test
+    public void testSetItemsSelected() {
+        mHelper.setItemsSelected(getStringIds(6, 7, 8), true);
+
+        mSelection.assertRangeSelected(6, 8);
+    }
+
+    @Test
+    public void testSetItemsSelected_SkipUnselectableItem() {
+        mIgnored.add(mItems.get(7));
+
+        mHelper.setItemsSelected(getStringIds(6, 7, 8), true);
+
+        mSelection.assertSelected(6);
+        mSelection.assertNotSelected(7);
+        mSelection.assertSelected(8);
+    }
+
+    @Test
+    public void testClear_RemovesPrimarySelection() {
+        mHelper.select(mItems.get(1));
+        mHelper.select(mItems.get(2));
+        mHelper.clear();
+
+        assertFalse(mHelper.hasSelection());
+    }
+
+    @Test
+    public void testClear_RemovesProvisionalSelection() {
+        Set<String> prov = new HashSet<>();
+        prov.add(mItems.get(1));
+        prov.add(mItems.get(2));
+        mHelper.clear();
+        // if there is a provisional selection, convert it to regular selection so we can poke it.
+        mHelper.mergeProvisionalSelection();
+
+        assertFalse(mHelper.hasSelection());
+    }
+
+    @Test
+    public void testRangeSelection() {
+        mHelper.startRange(15);
+        mHelper.extendRange(19);
+        mSelection.assertRangeSelection(15, 19);
+    }
+
+    @Test
+    public void testRangeSelection_SkipUnselectableItem() {
+        mIgnored.add(mItems.get(17));
+
+        mHelper.startRange(15);
+        mHelper.extendRange(19);
+
+        mSelection.assertRangeSelected(15, 16);
+        mSelection.assertNotSelected(17);
+        mSelection.assertRangeSelected(18, 19);
+    }
+
+    @Test
+    public void testRangeSelection_snapExpand() {
+        mHelper.startRange(15);
+        mHelper.extendRange(19);
+        mHelper.extendRange(27);
+        mSelection.assertRangeSelection(15, 27);
+    }
+
+    @Test
+    public void testRangeSelection_snapContract() {
+        mHelper.startRange(15);
+        mHelper.extendRange(27);
+        mHelper.extendRange(19);
+        mSelection.assertRangeSelection(15, 19);
+    }
+
+    @Test
+    public void testRangeSelection_snapInvert() {
+        mHelper.startRange(15);
+        mHelper.extendRange(27);
+        mHelper.extendRange(3);
+        mSelection.assertRangeSelection(3, 15);
+    }
+
+    @Test
+    public void testRangeSelection_multiple() {
+        mHelper.startRange(15);
+        mHelper.extendRange(27);
+        mHelper.endRange();
+        mHelper.startRange(42);
+        mHelper.extendRange(57);
+        mSelection.assertSelectionSize(29);
+        mSelection.assertRangeSelected(15, 27);
+        mSelection.assertRangeSelected(42, 57);
+    }
+
+    @Test
+    public void testProvisionalRangeSelection() {
+        mHelper.startRange(13);
+        mHelper.extendProvisionalRange(15);
+        mSelection.assertRangeSelection(13, 15);
+        mHelper.getSelection().mergeProvisionalSelection();
+        mHelper.endRange();
+        mSelection.assertSelectionSize(3);
+    }
+
+    @Test
+    public void testProvisionalRangeSelection_endEarly() {
+        mHelper.startRange(13);
+        mHelper.extendProvisionalRange(15);
+        mSelection.assertRangeSelection(13, 15);
+
+        mHelper.endRange();
+        // If we end range selection prematurely for provision selection, nothing should be selected
+        // except the first item
+        mSelection.assertSelectionSize(1);
+    }
+
+    @Test
+    public void testProvisionalRangeSelection_snapExpand() {
+        mHelper.startRange(13);
+        mHelper.extendProvisionalRange(15);
+        mSelection.assertRangeSelection(13, 15);
+        mHelper.getSelection().mergeProvisionalSelection();
+        mHelper.extendRange(18);
+        mSelection.assertRangeSelection(13, 18);
+    }
+
+    @Test
+    public void testCombinationRangeSelection_IntersectsOldSelection() {
+        mHelper.startRange(13);
+        mHelper.extendRange(15);
+        mSelection.assertRangeSelection(13, 15);
+
+        mHelper.startRange(11);
+        mHelper.extendProvisionalRange(18);
+        mSelection.assertRangeSelected(11, 18);
+        mHelper.endRange();
+        mSelection.assertRangeSelected(13, 15);
+        mSelection.assertRangeSelected(11, 11);
+        mSelection.assertSelectionSize(4);
+    }
+
+    @Test
+    public void testProvisionalSelection() {
+        Selection s = mHelper.getSelection();
+        mSelection.assertNoSelection();
+
+        // Mimicking band selection case -- BandController notifies item callback by itself.
+        mListener.onItemStateChanged(mItems.get(1), true);
+        mListener.onItemStateChanged(mItems.get(2), true);
+
+        SparseBooleanArray provisional = new SparseBooleanArray();
+        provisional.append(1, true);
+        provisional.append(2, true);
+        s.setProvisionalSelection(getItemIds(provisional));
+        mSelection.assertSelection(1, 2);
+    }
+
+    @Test
+    public void testProvisionalSelection_Replace() {
+        Selection s = mHelper.getSelection();
+
+        // Mimicking band selection case -- BandController notifies item callback by itself.
+        mListener.onItemStateChanged(mItems.get(1), true);
+        mListener.onItemStateChanged(mItems.get(2), true);
+        SparseBooleanArray provisional = new SparseBooleanArray();
+        provisional.append(1, true);
+        provisional.append(2, true);
+        s.setProvisionalSelection(getItemIds(provisional));
+
+        mListener.onItemStateChanged(mItems.get(1), false);
+        mListener.onItemStateChanged(mItems.get(2), false);
+        provisional.clear();
+
+        mListener.onItemStateChanged(mItems.get(3), true);
+        mListener.onItemStateChanged(mItems.get(4), true);
+        provisional.append(3, true);
+        provisional.append(4, true);
+        s.setProvisionalSelection(getItemIds(provisional));
+        mSelection.assertSelection(3, 4);
+    }
+
+    @Test
+    public void testProvisionalSelection_IntersectsExistingProvisionalSelection() {
+        Selection s = mHelper.getSelection();
+
+        // Mimicking band selection case -- BandController notifies item callback by itself.
+        mListener.onItemStateChanged(mItems.get(1), true);
+        mListener.onItemStateChanged(mItems.get(2), true);
+        SparseBooleanArray provisional = new SparseBooleanArray();
+        provisional.append(1, true);
+        provisional.append(2, true);
+        s.setProvisionalSelection(getItemIds(provisional));
+
+        mListener.onItemStateChanged(mItems.get(1), false);
+        mListener.onItemStateChanged(mItems.get(2), false);
+        provisional.clear();
+
+        mListener.onItemStateChanged(mItems.get(1), true);
+        provisional.append(1, true);
+        s.setProvisionalSelection(getItemIds(provisional));
+        mSelection.assertSelection(1);
+    }
+
+    @Test
+    public void testProvisionalSelection_Apply() {
+        Selection s = mHelper.getSelection();
+
+        // Mimicking band selection case -- BandController notifies item callback by itself.
+        mListener.onItemStateChanged(mItems.get(1), true);
+        mListener.onItemStateChanged(mItems.get(2), true);
+        SparseBooleanArray provisional = new SparseBooleanArray();
+        provisional.append(1, true);
+        provisional.append(2, true);
+        s.setProvisionalSelection(getItemIds(provisional));
+        s.mergeProvisionalSelection();
+
+        mSelection.assertSelection(1, 2);
+    }
+
+    @Test
+    public void testProvisionalSelection_Cancel() {
+        mHelper.select(mItems.get(1));
+        mHelper.select(mItems.get(2));
+        Selection s = mHelper.getSelection();
+
+        SparseBooleanArray provisional = new SparseBooleanArray();
+        provisional.append(3, true);
+        provisional.append(4, true);
+        s.setProvisionalSelection(getItemIds(provisional));
+        s.clearProvisionalSelection();
+
+        // Original selection should remain.
+        mSelection.assertSelection(1, 2);
+    }
+
+    @Test
+    public void testProvisionalSelection_IntersectsAppliedSelection() {
+        mHelper.select(mItems.get(1));
+        mHelper.select(mItems.get(2));
+        Selection s = mHelper.getSelection();
+
+        // Mimicking band selection case -- BandController notifies item callback by itself.
+        mListener.onItemStateChanged(mItems.get(3), true);
+        SparseBooleanArray provisional = new SparseBooleanArray();
+        provisional.append(2, true);
+        provisional.append(3, true);
+        s.setProvisionalSelection(getItemIds(provisional));
+        mSelection.assertSelection(1, 2, 3);
+    }
+
+    private Set<String> getItemIds(SparseBooleanArray selection) {
+        Set<String> ids = new HashSet<>();
+
+        int count = selection.size();
+        for (int i = 0; i < count; ++i) {
+            ids.add(mItems.get(selection.keyAt(i)));
+        }
+
+        return ids;
+    }
+
+    @Test
+    public void testObserverOnChanged_NotifiesListenersOfChange() {
+        mAdapter.notifyDataSetChanged();
+
+        mListener.assertSelectionChanged();
+    }
+
+    private Iterable<String> getStringIds(int... ids) {
+        List<String> stringIds = new ArrayList<>(ids.length);
+        for (int id : ids) {
+            stringIds.add(mItems.get(id));
+        }
+        return stringIds;
+    }
+}
diff --git a/recyclerview-selection/tests/java/androidx/recyclerview/selection/DefaultSelectionHelper_SingleSelectTest.java b/recyclerview-selection/tests/java/androidx/recyclerview/selection/DefaultSelectionHelper_SingleSelectTest.java
new file mode 100644
index 0000000..02527b1
--- /dev/null
+++ b/recyclerview-selection/tests/java/androidx/recyclerview/selection/DefaultSelectionHelper_SingleSelectTest.java
@@ -0,0 +1,91 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static org.junit.Assert.fail;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+import androidx.recyclerview.selection.testing.SelectionProbe;
+import androidx.recyclerview.selection.testing.TestAdapter;
+import androidx.recyclerview.selection.testing.TestItemKeyProvider;
+import androidx.recyclerview.selection.testing.TestSelectionObserver;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class DefaultSelectionHelper_SingleSelectTest {
+
+    private List<String> mItems;
+    private SelectionHelper<String> mHelper;
+    private TestSelectionObserver<String> mListener;
+    private SelectionProbe mSelection;
+
+    @Before
+    public void setUp() throws Exception {
+        mItems = TestAdapter.createItemList(100);
+        mListener = new TestSelectionObserver<>();
+        TestAdapter adapter = new TestAdapter();
+        adapter.updateTestModelIds(mItems);
+
+        ItemKeyProvider<String> keyProvider =
+                new TestItemKeyProvider<>(ItemKeyProvider.SCOPE_MAPPED, adapter);
+        mHelper = new DefaultSelectionHelper<>(
+                keyProvider,
+                SelectionPredicates.selectSingleAnything());
+        EventBridge.install(adapter, mHelper, keyProvider);
+
+        mHelper.addObserver(mListener);
+
+        mSelection = new SelectionProbe(mHelper);
+    }
+
+    @Test
+    public void testSimpleSelect() {
+        mHelper.select(mItems.get(3));
+        mHelper.select(mItems.get(4));
+        mListener.assertSelectionChanged();
+        mSelection.assertSelection(4);
+    }
+
+    @Test
+    public void testRangeSelectionNotEstablished() {
+        mHelper.select(mItems.get(3));
+        mListener.reset();
+
+        try {
+            mHelper.extendRange(10);
+            fail("Should have thrown.");
+        } catch (Exception expected) { }
+
+        mListener.assertSelectionUnchanged();
+        mSelection.assertSelection(3);
+    }
+
+    @Test
+    public void testProvisionalRangeSelection_Ignored() {
+        mHelper.startRange(13);
+        mHelper.extendProvisionalRange(15);
+        mSelection.assertSelection(13);
+    }
+}
diff --git a/recyclerview-selection/tests/java/androidx/recyclerview/selection/GestureRouterTest.java b/recyclerview-selection/tests/java/androidx/recyclerview/selection/GestureRouterTest.java
new file mode 100644
index 0000000..0e5a5a9
--- /dev/null
+++ b/recyclerview-selection/tests/java/androidx/recyclerview/selection/GestureRouterTest.java
@@ -0,0 +1,288 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyFloat;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.GestureDetector.OnDoubleTapListener;
+import android.view.GestureDetector.OnGestureListener;
+import android.view.MotionEvent;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+
+import androidx.recyclerview.selection.testing.TestEvents.Mouse;
+import androidx.recyclerview.selection.testing.TestEvents.Touch;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public final class GestureRouterTest {
+
+    private TestHandler mHandler;
+    private TestHandler mAlt;
+    private GestureRouter<TestHandler> mRouter;
+
+    @Before
+    public void setUp() {
+        mAlt = new TestHandler();
+        mHandler = new TestHandler();
+    }
+
+    @Test
+    public void testDelegates() {
+        mRouter = new GestureRouter<>();
+        mRouter.register(MotionEvent.TOOL_TYPE_MOUSE, mHandler);
+        mRouter.register(MotionEvent.TOOL_TYPE_FINGER, mAlt);
+
+        mRouter.onDown(Mouse.CLICK);
+        mHandler.assertCalled_onDown(Mouse.CLICK);
+        mAlt.assertNotCalled_onDown();
+
+        mRouter.onShowPress(Mouse.CLICK);
+        mHandler.assertCalled_onShowPress(Mouse.CLICK);
+        mAlt.assertNotCalled_onShowPress();
+
+        mRouter.onSingleTapUp(Mouse.CLICK);
+        mHandler.assertCalled_onSingleTapUp(Mouse.CLICK);
+        mAlt.assertNotCalled_onSingleTapUp();
+
+        mRouter.onScroll(null, Mouse.CLICK, -1, -1);
+        mHandler.assertCalled_onScroll(null, Mouse.CLICK, -1, -1);
+        mAlt.assertNotCalled_onScroll();
+
+        mRouter.onLongPress(Mouse.CLICK);
+        mHandler.assertCalled_onLongPress(Mouse.CLICK);
+        mAlt.assertNotCalled_onLongPress();
+
+        mRouter.onFling(null, Mouse.CLICK, -1, -1);
+        mHandler.assertCalled_onFling(null, Mouse.CLICK, -1, -1);
+        mAlt.assertNotCalled_onFling();
+
+        mRouter.onSingleTapConfirmed(Mouse.CLICK);
+        mHandler.assertCalled_onSingleTapConfirmed(Mouse.CLICK);
+        mAlt.assertNotCalled_onSingleTapConfirmed();
+
+        mRouter.onDoubleTap(Mouse.CLICK);
+        mHandler.assertCalled_onDoubleTap(Mouse.CLICK);
+        mAlt.assertNotCalled_onDoubleTap();
+
+        mRouter.onDoubleTapEvent(Mouse.CLICK);
+        mHandler.assertCalled_onDoubleTapEvent(Mouse.CLICK);
+        mAlt.assertNotCalled_onDoubleTapEvent();
+    }
+
+    @Test
+    public void testFallsback() {
+        mRouter = new GestureRouter<>(mAlt);
+        mRouter.register(MotionEvent.TOOL_TYPE_MOUSE, mHandler);
+
+        mRouter.onDown(Touch.TAP);
+        mAlt.assertCalled_onDown(Touch.TAP);
+
+        mRouter.onShowPress(Touch.TAP);
+        mAlt.assertCalled_onShowPress(Touch.TAP);
+
+        mRouter.onSingleTapUp(Touch.TAP);
+        mAlt.assertCalled_onSingleTapUp(Touch.TAP);
+
+        mRouter.onScroll(null, Touch.TAP, -1, -1);
+        mAlt.assertCalled_onScroll(null, Touch.TAP, -1, -1);
+
+        mRouter.onLongPress(Touch.TAP);
+        mAlt.assertCalled_onLongPress(Touch.TAP);
+
+        mRouter.onFling(null, Touch.TAP, -1, -1);
+        mAlt.assertCalled_onFling(null, Touch.TAP, -1, -1);
+
+        mRouter.onSingleTapConfirmed(Touch.TAP);
+        mAlt.assertCalled_onSingleTapConfirmed(Touch.TAP);
+
+        mRouter.onDoubleTap(Touch.TAP);
+        mAlt.assertCalled_onDoubleTap(Touch.TAP);
+
+        mRouter.onDoubleTapEvent(Touch.TAP);
+        mAlt.assertCalled_onDoubleTapEvent(Touch.TAP);
+    }
+
+    @Test
+    public void testEatsEventsWhenNoFallback() {
+        mRouter = new GestureRouter<>();
+        // Register the the delegate on mouse so touch events don't get handled.
+        mRouter.register(MotionEvent.TOOL_TYPE_MOUSE, mHandler);
+
+        mRouter.onDown(Touch.TAP);
+        mAlt.assertNotCalled_onDown();
+
+        mRouter.onShowPress(Touch.TAP);
+        mAlt.assertNotCalled_onShowPress();
+
+        mRouter.onSingleTapUp(Touch.TAP);
+        mAlt.assertNotCalled_onSingleTapUp();
+
+        mRouter.onScroll(null, Touch.TAP, -1, -1);
+        mAlt.assertNotCalled_onScroll();
+
+        mRouter.onLongPress(Touch.TAP);
+        mAlt.assertNotCalled_onLongPress();
+
+        mRouter.onFling(null, Touch.TAP, -1, -1);
+        mAlt.assertNotCalled_onFling();
+
+        mRouter.onSingleTapConfirmed(Touch.TAP);
+        mAlt.assertNotCalled_onSingleTapConfirmed();
+
+        mRouter.onDoubleTap(Touch.TAP);
+        mAlt.assertNotCalled_onDoubleTap();
+
+        mRouter.onDoubleTapEvent(Touch.TAP);
+        mAlt.assertNotCalled_onDoubleTapEvent();
+    }
+
+    private static final class TestHandler implements OnGestureListener, OnDoubleTapListener {
+
+        private final Spy mSpy = Mockito.mock(Spy.class);
+
+        @Override
+        public boolean onDown(MotionEvent e) {
+            return mSpy.onDown(e);
+        }
+
+        @Override
+        public void onShowPress(MotionEvent e) {
+            mSpy.onShowPress(e);
+        }
+
+        @Override
+        public boolean onSingleTapUp(MotionEvent e) {
+            return mSpy.onSingleTapUp(e);
+        }
+
+        @Override
+        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
+            return mSpy.onScroll(e1, e2, distanceX, distanceY);
+        }
+
+        @Override
+        public void onLongPress(MotionEvent e) {
+            mSpy.onLongPress(e);
+        }
+
+        @Override
+        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
+            return mSpy.onFling(e1, e2, velocityX, velocityY);
+        }
+
+        @Override
+        public boolean onSingleTapConfirmed(MotionEvent e) {
+            return mSpy.onSingleTapConfirmed(e);
+        }
+
+        @Override
+        public boolean onDoubleTap(MotionEvent e) {
+            return mSpy.onDoubleTap(e);
+        }
+
+        @Override
+        public boolean onDoubleTapEvent(MotionEvent e) {
+            return mSpy.onDoubleTapEvent(e);
+        }
+
+        void assertCalled_onDown(MotionEvent e) {
+            verify(mSpy).onDown(e);
+        }
+
+        void assertCalled_onShowPress(MotionEvent e) {
+            verify(mSpy).onShowPress(e);
+        }
+
+        void assertCalled_onSingleTapUp(MotionEvent e) {
+            verify(mSpy).onSingleTapUp(e);
+        }
+
+        void assertCalled_onScroll(MotionEvent e1, MotionEvent e2, float x, float y) {
+            verify(mSpy).onScroll(e1, e2, x, y);
+        }
+
+        void assertCalled_onLongPress(MotionEvent e) {
+            verify(mSpy).onLongPress(e);
+        }
+
+        void assertCalled_onFling(MotionEvent e1, MotionEvent e2, float x, float y) {
+            Mockito.verify(mSpy).onFling(e1, e2, x, y);
+        }
+
+        void assertCalled_onSingleTapConfirmed(MotionEvent e) {
+            Mockito.verify(mSpy).onSingleTapConfirmed(e);
+        }
+
+        void assertCalled_onDoubleTap(MotionEvent e) {
+            Mockito.verify(mSpy).onDoubleTap(e);
+        }
+
+        void assertCalled_onDoubleTapEvent(MotionEvent e) {
+            Mockito.verify(mSpy).onDoubleTapEvent(e);
+        }
+
+        void assertNotCalled_onDown() {
+            verify(mSpy, never()).onDown((MotionEvent) any());
+        }
+
+        void assertNotCalled_onShowPress() {
+            verify(mSpy, never()).onShowPress((MotionEvent) any());
+        }
+
+        void assertNotCalled_onSingleTapUp() {
+            verify(mSpy, never()).onSingleTapUp((MotionEvent) any());
+        }
+
+        void assertNotCalled_onScroll() {
+            verify(mSpy, never()).onScroll(
+                    (MotionEvent) any(), (MotionEvent) any(), anyFloat(), anyFloat());
+        }
+
+        void assertNotCalled_onLongPress() {
+            verify(mSpy, never()).onLongPress((MotionEvent) any());
+        }
+
+        void assertNotCalled_onFling() {
+            Mockito.verify(mSpy, never()).onFling(
+                    (MotionEvent) any(), (MotionEvent) any(), anyFloat(), anyFloat());
+        }
+
+        void assertNotCalled_onSingleTapConfirmed() {
+            Mockito.verify(mSpy, never()).onSingleTapConfirmed((MotionEvent) any());
+        }
+
+        void assertNotCalled_onDoubleTap() {
+            Mockito.verify(mSpy, never()).onDoubleTap((MotionEvent) any());
+        }
+
+        void assertNotCalled_onDoubleTapEvent() {
+            Mockito.verify(mSpy, never()).onDoubleTapEvent((MotionEvent) any());
+        }
+    }
+
+    private interface Spy extends OnGestureListener, OnDoubleTapListener {
+    }
+}
diff --git a/recyclerview-selection/tests/java/androidx/recyclerview/selection/GestureSelectionHelperTest.java b/recyclerview-selection/tests/java/androidx/recyclerview/selection/GestureSelectionHelperTest.java
new file mode 100644
index 0000000..f1e38d9
--- /dev/null
+++ b/recyclerview-selection/tests/java/androidx/recyclerview/selection/GestureSelectionHelperTest.java
@@ -0,0 +1,151 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v7.widget.RecyclerView;
+import android.view.MotionEvent;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+import androidx.recyclerview.selection.testing.SelectionHelpers;
+import androidx.recyclerview.selection.testing.SelectionProbe;
+import androidx.recyclerview.selection.testing.TestAutoScroller;
+import androidx.recyclerview.selection.testing.TestData;
+import androidx.recyclerview.selection.testing.TestEvents;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class GestureSelectionHelperTest {
+
+    private static final List<String> ITEMS = TestData.createStringData(100);
+    private static final MotionEvent DOWN = TestEvents.builder()
+            .action(MotionEvent.ACTION_DOWN)
+            .location(1, 1)
+            .build();
+
+    private static final MotionEvent MOVE = TestEvents.builder()
+            .action(MotionEvent.ACTION_MOVE)
+            .location(1, 1)
+            .build();
+
+    private static final MotionEvent UP = TestEvents.builder()
+            .action(MotionEvent.ACTION_UP)
+            .location(1, 1)
+            .build();
+
+    private GestureSelectionHelper mHelper;
+    private SelectionHelper<String> mSelectionHelper;
+    private SelectionProbe mSelection;
+    private ContentLock mLock;
+    private TestViewDelegate mView;
+
+    @Before
+    public void setUp() {
+        mSelectionHelper = SelectionHelpers.createTestInstance(ITEMS);
+        mSelection = new SelectionProbe(mSelectionHelper);
+        mLock = new ContentLock();
+        mView = new TestViewDelegate();
+        mHelper = new GestureSelectionHelper(
+                mSelectionHelper, mView, new TestAutoScroller(), mLock);
+    }
+
+    @Test
+    public void testIgnoresDownOnNoPosition() {
+        mView.mNextPosition = RecyclerView.NO_POSITION;
+        assertFalse(mHelper.onInterceptTouchEvent(null, DOWN));
+    }
+
+
+    @Test
+    public void testNoStartOnIllegalPosition() {
+        mHelper.onInterceptTouchEvent(null, DOWN);
+        try {
+            mHelper.start();
+            fail("Should have thrown.");
+        } catch (Exception expected) {
+        }
+    }
+
+    @Test
+    public void testClaimsDownOnItem() {
+        mView.mNextPosition = 0;
+        assertTrue(mHelper.onInterceptTouchEvent(null, DOWN));
+    }
+
+    @Test
+    public void testClaimsMoveIfStarted() {
+        mView.mNextPosition = 0;
+        assertTrue(mHelper.onInterceptTouchEvent(null, DOWN));
+
+        // Normally, this is controller by the TouchSelectionHelper via a a long press gesture.
+        mSelectionHelper.select("1");
+        mSelectionHelper.anchorRange(1);
+        mHelper.start();
+        assertTrue(mHelper.onInterceptTouchEvent(null, MOVE));
+    }
+
+    @Test
+    public void testCreatesRangeSelection() {
+        mView.mNextPosition = 1;
+        mHelper.onInterceptTouchEvent(null, DOWN);
+        // Another way we are implicitly coupled to TouchInputHandler, is that we depend on
+        // long press to establish the initial anchor point. Without that we'll get an
+        // error when we try to extend the range.
+
+        mSelectionHelper.select("1");
+        mSelectionHelper.anchorRange(1);
+        mHelper.start();
+
+        mHelper.onTouchEvent(null, MOVE);
+
+        mView.mNextPosition = 9;
+        mHelper.onTouchEvent(null, MOVE);
+        mHelper.onTouchEvent(null, UP);
+
+        mSelection.assertRangeSelected(1,  9);
+    }
+
+    private static final class TestViewDelegate extends GestureSelectionHelper.ViewDelegate {
+
+        private int mNextPosition = RecyclerView.NO_POSITION;
+
+        @Override
+        int getHeight() {
+            return 1000;
+        }
+
+        @Override
+        int getItemUnder(MotionEvent e) {
+            return mNextPosition;
+        }
+
+        @Override
+        int getLastGlidedItemPosition(MotionEvent e) {
+            return mNextPosition;
+        }
+    }
+}
diff --git a/recyclerview-selection/tests/java/androidx/recyclerview/selection/GestureSelectionHelper_RecyclerViewDelegateTest.java b/recyclerview-selection/tests/java/androidx/recyclerview/selection/GestureSelectionHelper_RecyclerViewDelegateTest.java
new file mode 100644
index 0000000..7e5251a
--- /dev/null
+++ b/recyclerview-selection/tests/java/androidx/recyclerview/selection/GestureSelectionHelper_RecyclerViewDelegateTest.java
@@ -0,0 +1,74 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.MotionEvent;
+import android.view.View;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import androidx.recyclerview.selection.GestureSelectionHelper.RecyclerViewDelegate;
+import androidx.recyclerview.selection.testing.TestEvents;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class GestureSelectionHelper_RecyclerViewDelegateTest {
+
+    // Simulate a (20, 20) box locating at (20, 20)
+    static final int LEFT_BORDER = 20;
+    static final int RIGHT_BORDER = 40;
+    static final int TOP_BORDER = 20;
+    static final int BOTTOM_BORDER = 40;
+
+    @Test
+    public void testLtrPastLastItem() {
+        MotionEvent event = createEvent(100, 100);
+        assertTrue(RecyclerViewDelegate.isPastLastItem(
+                TOP_BORDER, LEFT_BORDER, RIGHT_BORDER, event, View.LAYOUT_DIRECTION_LTR));
+    }
+
+    @Test
+    public void testLtrPastLastItem_Inverse() {
+        MotionEvent event = createEvent(10, 10);
+        assertFalse(RecyclerViewDelegate.isPastLastItem(
+                TOP_BORDER, LEFT_BORDER, RIGHT_BORDER, event, View.LAYOUT_DIRECTION_LTR));
+    }
+
+    @Test
+    public void testRtlPastLastItem() {
+        MotionEvent event = createEvent(10, 30);
+        assertTrue(RecyclerViewDelegate.isPastLastItem(
+                TOP_BORDER, LEFT_BORDER, RIGHT_BORDER, event, View.LAYOUT_DIRECTION_RTL));
+    }
+
+    @Test
+    public void testRtlPastLastItem_Inverse() {
+        MotionEvent event = createEvent(100, 100);
+        assertFalse(RecyclerViewDelegate.isPastLastItem(
+                TOP_BORDER, LEFT_BORDER, RIGHT_BORDER, event, View.LAYOUT_DIRECTION_RTL));
+    }
+
+    private static MotionEvent createEvent(int x, int y) {
+        return TestEvents.builder().action(MotionEvent.ACTION_MOVE).location(x, y).build();
+    }
+}
diff --git a/recyclerview-selection/tests/java/androidx/recyclerview/selection/GridModelTest.java b/recyclerview-selection/tests/java/androidx/recyclerview/selection/GridModelTest.java
new file mode 100644
index 0000000..8924c52
--- /dev/null
+++ b/recyclerview-selection/tests/java/androidx/recyclerview/selection/GridModelTest.java
@@ -0,0 +1,467 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import static androidx.recyclerview.selection.GridModel.NOT_SET;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v7.widget.RecyclerView.OnScrollListener;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+import javax.annotation.Nullable;
+
+import androidx.recyclerview.selection.testing.TestAdapter;
+import androidx.recyclerview.selection.testing.TestItemKeyProvider;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class GridModelTest {
+
+    private static final int VIEW_PADDING_PX = 5;
+    private static final int CHILD_VIEW_EDGE_PX = 100;
+    private static final int VIEWPORT_HEIGHT = 500;
+
+    private GridModel<String> mModel;
+    private TestHost mHost;
+    private TestAdapter mAdapter;
+    private Set<String> mLastSelection;
+    private int mViewWidth;
+
+    // TLDR: Don't call model.{start|resize}Selection; use the local #startSelection and
+    // #resizeSelection methods instead.
+    //
+    // The reason for this is that selection is stateful and involves operations that take the
+    // current UI state (e.g scrolling) into account. This test maintains its own copy of the
+    // selection bounds as control data for verifying selections. Keep this data in sync by calling
+    // #startSelection and
+    // #resizeSelection.
+    private Point mSelectionOrigin;
+    private Point mSelectionPoint;
+
+    @After
+    public void tearDown() {
+        mModel = null;
+        mHost = null;
+        mLastSelection = null;
+    }
+
+    @Test
+    public void testSelectionLeftOfItems() {
+        initData(20, 5);
+        startSelection(new Point(0, 10));
+        resizeSelection(new Point(1, 11));
+        assertNoSelection();
+        assertEquals(NOT_SET, mModel.getPositionNearestOrigin());
+    }
+
+    @Test
+    public void testSelectionRightOfItems() {
+        initData(20, 4);
+        startSelection(new Point(mViewWidth - 1, 10));
+        resizeSelection(new Point(mViewWidth - 2, 11));
+        assertNoSelection();
+        assertEquals(NOT_SET, mModel.getPositionNearestOrigin());
+    }
+
+    @Test
+    public void testSelectionAboveItems() {
+        initData(20, 4);
+        startSelection(new Point(10, 0));
+        resizeSelection(new Point(11, 1));
+        assertNoSelection();
+        assertEquals(NOT_SET, mModel.getPositionNearestOrigin());
+    }
+
+    @Test
+    public void testSelectionBelowItems() {
+        initData(5, 4);
+        startSelection(new Point(10, VIEWPORT_HEIGHT - 1));
+        resizeSelection(new Point(11, VIEWPORT_HEIGHT - 2));
+        assertNoSelection();
+        assertEquals(NOT_SET, mModel.getPositionNearestOrigin());
+    }
+
+    @Test
+    public void testVerticalSelectionBetweenItems() {
+        initData(20, 4);
+        startSelection(new Point(106, 0));
+        resizeSelection(new Point(107, 200));
+        assertNoSelection();
+        assertEquals(NOT_SET, mModel.getPositionNearestOrigin());
+    }
+
+    @Test
+    public void testHorizontalSelectionBetweenItems() {
+        initData(20, 4);
+        startSelection(new Point(0, 105));
+        resizeSelection(new Point(200, 106));
+        assertNoSelection();
+        assertEquals(NOT_SET, mModel.getPositionNearestOrigin());
+    }
+
+    @Test
+    public void testGrowingAndShrinkingSelection() {
+        initData(20, 4);
+        startSelection(new Point(0, 0));
+
+        resizeSelection(new Point(5, 5));
+        verifySelection();
+
+        resizeSelection(new Point(109, 109));
+        verifySelection();
+
+        resizeSelection(new Point(110, 109));
+        verifySelection();
+
+        resizeSelection(new Point(110, 110));
+        verifySelection();
+
+        resizeSelection(new Point(214, 214));
+        verifySelection();
+
+        resizeSelection(new Point(215, 214));
+        verifySelection();
+
+        resizeSelection(new Point(214, 214));
+        verifySelection();
+
+        resizeSelection(new Point(110, 110));
+        verifySelection();
+
+        resizeSelection(new Point(110, 109));
+        verifySelection();
+
+        resizeSelection(new Point(109, 109));
+        verifySelection();
+
+        resizeSelection(new Point(5, 5));
+        verifySelection();
+
+        resizeSelection(new Point(0, 0));
+        verifySelection();
+
+        assertEquals(NOT_SET, mModel.getPositionNearestOrigin());
+    }
+
+    @Test
+    public void testSelectionMovingAroundOrigin() {
+        initData(16, 4);
+
+        startSelection(new Point(210, 210));
+        resizeSelection(new Point(mViewWidth - 1, 0));
+        verifySelection();
+
+        resizeSelection(new Point(0, 0));
+        verifySelection();
+
+        resizeSelection(new Point(0, 420));
+        verifySelection();
+
+        resizeSelection(new Point(mViewWidth - 1, 420));
+        verifySelection();
+
+        // This is manually figured and will need to be adjusted if the separator position is
+        // changed.
+        assertEquals(7, mModel.getPositionNearestOrigin());
+    }
+
+    @Test
+    public void testScrollingBandSelect() {
+        initData(40, 4);
+
+        startSelection(new Point(0, 0));
+        resizeSelection(new Point(100, VIEWPORT_HEIGHT - 1));
+        verifySelection();
+
+        scroll(CHILD_VIEW_EDGE_PX);
+        verifySelection();
+
+        resizeSelection(new Point(200, VIEWPORT_HEIGHT - 1));
+        verifySelection();
+
+        scroll(CHILD_VIEW_EDGE_PX);
+        verifySelection();
+
+        scroll(-2 * CHILD_VIEW_EDGE_PX);
+        verifySelection();
+
+        resizeSelection(new Point(100, VIEWPORT_HEIGHT - 1));
+        verifySelection();
+
+        assertEquals(0, mModel.getPositionNearestOrigin());
+    }
+
+    private void initData(final int numChildren, int numColumns) {
+        mHost = new TestHost(numChildren, numColumns);
+        mAdapter = new TestAdapter() {
+            @Override
+            public String getSelectionKey(int position) {
+                return Integer.toString(position);
+            }
+
+            @Override
+            public int getItemCount() {
+                return numChildren;
+            }
+        };
+
+        mViewWidth = VIEW_PADDING_PX + numColumns * (VIEW_PADDING_PX + CHILD_VIEW_EDGE_PX);
+
+        mModel = new GridModel<String>(
+                mHost,
+                new TestItemKeyProvider(ItemKeyProvider.SCOPE_MAPPED, mAdapter),
+                SelectionPredicates.<String>selectAnything());
+
+        mModel.addOnSelectionChangedListener(
+                new GridModel.SelectionObserver<String>() {
+                    @Override
+                    public void onSelectionChanged(Set<String> updatedSelection) {
+                        mLastSelection = updatedSelection;
+                    }
+                });
+    }
+
+    /** Returns the current selection area as a Rect. */
+    private Rect getSelectionArea() {
+        // Construct a rect from the two selection points.
+        Rect selectionArea = new Rect(
+                mSelectionOrigin.x, mSelectionOrigin.y, mSelectionOrigin.x, mSelectionOrigin.y);
+        selectionArea.union(mSelectionPoint.x, mSelectionPoint.y);
+        // Rect intersection tests are exclusive of bounds, while the MSM's selection code is
+        // inclusive. Expand the rect by 1 pixel in all directions to account for this.
+        selectionArea.inset(-1, -1);
+
+        return selectionArea;
+    }
+
+    /** Asserts that the selection is currently empty. */
+    private void assertNoSelection() {
+        assertEquals("Unexpected mItems " + mLastSelection + " in selection " + getSelectionArea(),
+                0, mLastSelection.size());
+    }
+
+    /** Verifies the selection using actual bbox checks. */
+    private void verifySelection() {
+        Rect selectionArea = getSelectionArea();
+        for (TestHost.Item item: mHost.mItems) {
+            if (Rect.intersects(selectionArea, item.rect)) {
+                assertTrue("Expected item " + item + " was not in selection " + selectionArea,
+                        mLastSelection.contains(item.name));
+            } else {
+                assertFalse("Unexpected item " + item + " in selection" + selectionArea,
+                        mLastSelection.contains(item.name));
+            }
+        }
+    }
+
+    private void startSelection(Point p) {
+        mModel.startCapturing(p);
+        mSelectionOrigin = mHost.createAbsolutePoint(p);
+    }
+
+    private void resizeSelection(Point p) {
+        mModel.resizeSelection(p);
+        mSelectionPoint = mHost.createAbsolutePoint(p);
+    }
+
+    private void scroll(int dy) {
+        assertTrue(mHost.verticalOffset + VIEWPORT_HEIGHT + dy <= mHost.getTotalHeight());
+        mHost.verticalOffset += dy;
+        // Correct the cached selection point as well.
+        mSelectionPoint.y += dy;
+        mHost.mScrollListener.onScrolled(null, 0, dy);
+    }
+
+    private static final class TestHost extends GridModel.GridHost<String> {
+
+        private final int mNumColumns;
+        private final int mNumRows;
+        private final int mNumChildren;
+        private final int mSeparatorPosition;
+
+        public int horizontalOffset = 0;
+        public int verticalOffset = 0;
+        private List<Item> mItems = new ArrayList<>();
+
+        // Installed by GridModel on construction.
+        private @Nullable OnScrollListener mScrollListener;
+
+        TestHost(int numChildren, int numColumns) {
+            mNumChildren = numChildren;
+            mNumColumns = numColumns;
+            mSeparatorPosition = mNumColumns + 1;
+            mNumRows = setupGrid();
+        }
+
+        private int setupGrid() {
+            // Split the input set into folders and documents. Do this such that there is a
+            // partially-populated row in the middle of the grid, to test corner cases in layout
+            // code.
+            int y = VIEW_PADDING_PX;
+            int i = 0;
+            int numRows = 0;
+            while (i < mNumChildren) {
+                int top = y;
+                int height = CHILD_VIEW_EDGE_PX;
+                int width = CHILD_VIEW_EDGE_PX;
+                for (int j = 0; j < mNumColumns && i < mNumChildren; j++) {
+                    int left = VIEW_PADDING_PX + (j * (width + VIEW_PADDING_PX));
+                    mItems.add(new Item(
+                            Integer.toString(i),
+                            new Rect(
+                                    left,
+                                    top,
+                                    left + width - 1,
+                                    top + height - 1)));
+
+                    // Create a partially populated row at the separator position.
+                    if (++i == mSeparatorPosition) {
+                        break;
+                    }
+                }
+                y += height + VIEW_PADDING_PX;
+                numRows++;
+            }
+
+            return numRows;
+        }
+
+        private int getTotalHeight() {
+            return CHILD_VIEW_EDGE_PX * mNumRows + VIEW_PADDING_PX * (mNumRows + 1);
+        }
+
+        private int getFirstVisibleRowIndex() {
+            return verticalOffset / (CHILD_VIEW_EDGE_PX + VIEW_PADDING_PX);
+        }
+
+        private int getLastVisibleRowIndex() {
+            int lastVisibleRowUncapped =
+                    (VIEWPORT_HEIGHT + verticalOffset - 1) / (CHILD_VIEW_EDGE_PX + VIEW_PADDING_PX);
+            return Math.min(lastVisibleRowUncapped, mNumRows - 1);
+        }
+
+        private int getNumItemsInRow(int index) {
+            assertTrue(index >= 0 && index < mNumRows);
+            int mod = mSeparatorPosition % mNumColumns;
+            if (index == (mSeparatorPosition / mNumColumns)) {
+                // The row containing the separator may be incomplete
+                return mod > 0 ? mod : mNumColumns;
+            }
+            // Account for the partial separator row in the final row tally.
+            if (index == mNumRows - 1) {
+                // The last row may be incomplete
+                int finalRowCount = (mNumChildren - mod) % mNumColumns;
+                return finalRowCount > 0 ? finalRowCount : mNumColumns;
+            }
+
+            return mNumColumns;
+        }
+
+        @Override
+        public GridModel<String> createGridModel() {
+            throw new UnsupportedOperationException("Not implemented.");
+        }
+
+        @Override
+        public void addOnScrollListener(OnScrollListener listener) {
+            mScrollListener = listener;
+        }
+
+        @Override
+        public void removeOnScrollListener(OnScrollListener listener) {}
+
+        @Override
+        public Point createAbsolutePoint(Point relativePoint) {
+            return new Point(
+                    relativePoint.x + horizontalOffset, relativePoint.y + verticalOffset);
+        }
+
+        @Override
+        public int getVisibleChildCount() {
+            int childCount = 0;
+            for (int i = getFirstVisibleRowIndex(); i <= getLastVisibleRowIndex(); i++) {
+                childCount += getNumItemsInRow(i);
+            }
+            return childCount;
+        }
+
+        @Override
+        public int getAdapterPositionAt(int index) {
+            // Account for partial rows by actually tallying up the mItems in hidden rows.
+            int hiddenCount = 0;
+            for (int i = 0; i < getFirstVisibleRowIndex(); i++) {
+                hiddenCount += getNumItemsInRow(i);
+            }
+            return index + hiddenCount;
+        }
+
+        @Override
+        public Rect getAbsoluteRectForChildViewAt(int index) {
+            int adapterPosition = getAdapterPositionAt(index);
+            return mItems.get(adapterPosition).rect;
+        }
+
+        @Override
+        public int getColumnCount() {
+            return mNumColumns;
+        }
+
+        @Override
+        public void showBand(Rect rect) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void hideBand() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public boolean hasView(int adapterPosition) {
+            return true;
+        }
+
+        public static final class Item {
+            public String name;
+            public Rect rect;
+
+            Item(String n, Rect r) {
+                name = n;
+                rect = r;
+            }
+
+            @Override
+            public String toString() {
+                return name + ": " + rect;
+            }
+        }
+    }
+}
diff --git a/recyclerview-selection/tests/java/androidx/recyclerview/selection/MouseInputHandlerTest.java b/recyclerview-selection/tests/java/androidx/recyclerview/selection/MouseInputHandlerTest.java
new file mode 100644
index 0000000..b0e7276
--- /dev/null
+++ b/recyclerview-selection/tests/java/androidx/recyclerview/selection/MouseInputHandlerTest.java
@@ -0,0 +1,301 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import static androidx.recyclerview.selection.testing.TestEvents.Mouse.ALT_CLICK;
+import static androidx.recyclerview.selection.testing.TestEvents.Mouse.CLICK;
+import static androidx.recyclerview.selection.testing.TestEvents.Mouse.CTRL_CLICK;
+import static androidx.recyclerview.selection.testing.TestEvents.Mouse.SECONDARY_CLICK;
+import static androidx.recyclerview.selection.testing.TestEvents.Mouse.SHIFT_CLICK;
+import static androidx.recyclerview.selection.testing.TestEvents.Mouse.TERTIARY_CLICK;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v7.widget.RecyclerView;
+import android.view.MotionEvent;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+import androidx.recyclerview.selection.testing.SelectionHelpers;
+import androidx.recyclerview.selection.testing.SelectionProbe;
+import androidx.recyclerview.selection.testing.TestActivationCallbacks;
+import androidx.recyclerview.selection.testing.TestAdapter;
+import androidx.recyclerview.selection.testing.TestData;
+import androidx.recyclerview.selection.testing.TestEvents;
+import androidx.recyclerview.selection.testing.TestFocusCallbacks;
+import androidx.recyclerview.selection.testing.TestItemDetails;
+import androidx.recyclerview.selection.testing.TestItemDetailsLookup;
+import androidx.recyclerview.selection.testing.TestItemKeyProvider;
+import androidx.recyclerview.selection.testing.TestMouseCallbacks;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public final class MouseInputHandlerTest {
+
+    private static final List<String> ITEMS = TestData.createStringData(100);
+
+    private MouseInputHandler mInputDelegate;
+
+    private TestMouseCallbacks mMouseCallbacks;
+    private TestActivationCallbacks mActivationCallbacks;
+    private TestFocusCallbacks mFocusCallbacks;
+
+    private TestItemDetailsLookup mDetailsLookup;
+    private SelectionProbe mSelection;
+    private SelectionHelper mSelectionMgr;
+
+    private TestEvents.Builder mEvent;
+
+    @Before
+    public void setUp() {
+
+        mSelectionMgr = SelectionHelpers.createTestInstance(ITEMS);
+        mDetailsLookup = new TestItemDetailsLookup();
+        mSelection = new SelectionProbe(mSelectionMgr);
+
+        mMouseCallbacks = new TestMouseCallbacks();
+        mActivationCallbacks = new TestActivationCallbacks();
+        mFocusCallbacks = new TestFocusCallbacks();
+
+        mInputDelegate = new MouseInputHandler(
+                mSelectionMgr,
+                new TestItemKeyProvider(ItemKeyProvider.SCOPE_MAPPED, new TestAdapter(ITEMS)),
+                mDetailsLookup,
+                mMouseCallbacks,
+                mActivationCallbacks,
+                mFocusCallbacks);
+
+        mEvent = TestEvents.builder().mouse();
+        mDetailsLookup.initAt(RecyclerView.NO_POSITION);
+    }
+
+    @Test
+    public void testConfirmedClick_StartsSelection() {
+        mDetailsLookup.initAt(11).setInItemSelectRegion(true);
+        mInputDelegate.onSingleTapConfirmed(CLICK);
+
+        mSelection.assertSelection(11);
+    }
+
+    @Test
+    public void testClickOnSelectRegion_AddsToSelection() {
+        mDetailsLookup.initAt(11).setInItemSelectRegion(true);
+        mInputDelegate.onSingleTapConfirmed(CLICK);
+
+        mDetailsLookup.initAt(10).setInItemSelectRegion(true);
+        mInputDelegate.onSingleTapUp(CLICK);
+
+        mSelection.assertSelected(10, 11);
+    }
+
+    @Test
+    public void testClickOnIconOfSelectedItem_RemovesFromSelection() {
+        mDetailsLookup.initAt(8).setInItemSelectRegion(true);
+        mInputDelegate.onSingleTapConfirmed(CLICK);
+
+        mDetailsLookup.initAt(11);
+        mInputDelegate.onSingleTapUp(SHIFT_CLICK);
+        mSelection.assertSelected(8, 9, 10, 11);
+
+        mDetailsLookup.initAt(9);
+        mInputDelegate.onSingleTapUp(CLICK);
+        mSelection.assertSelected(8, 10, 11);
+    }
+
+    @Test
+    public void testRightClickDown_StartsContextMenu() {
+        mInputDelegate.onDown(SECONDARY_CLICK);
+
+        mMouseCallbacks.assertLastEvent(SECONDARY_CLICK);
+    }
+
+    @Test
+    public void testAltClickDown_StartsContextMenu() {
+        mInputDelegate.onDown(ALT_CLICK);
+
+        mMouseCallbacks.assertLastEvent(ALT_CLICK);
+    }
+
+    @Test
+    public void testScroll_shouldTrap() {
+        mDetailsLookup.initAt(0);
+        assertTrue(mInputDelegate.onScroll(
+                null,
+                mEvent.action(MotionEvent.ACTION_MOVE).primary().build(),
+                -1,
+                -1));
+    }
+
+    @Test
+    public void testScroll_NoTrapForTwoFinger() {
+        mDetailsLookup.initAt(0);
+        assertFalse(mInputDelegate.onScroll(
+                null,
+                mEvent.action(MotionEvent.ACTION_MOVE).build(),
+                -1,
+                -1));
+    }
+
+    @Test
+    public void testUnconfirmedCtrlClick_AddsToExistingSelection() {
+        mDetailsLookup.initAt(7).setInItemSelectRegion(true);
+        mInputDelegate.onSingleTapConfirmed(CLICK);
+
+        mDetailsLookup.initAt(11);
+        mInputDelegate.onSingleTapUp(CTRL_CLICK);
+
+        mSelection.assertSelection(7, 11);
+    }
+
+    @Test
+    public void testUnconfirmedShiftClick_ExtendsSelection() {
+        mDetailsLookup.initAt(7).setInItemSelectRegion(true);
+        mInputDelegate.onSingleTapConfirmed(CLICK);
+
+        mDetailsLookup.initAt(11);
+        mInputDelegate.onSingleTapUp(SHIFT_CLICK);
+
+        mSelection.assertSelection(7, 8, 9, 10, 11);
+    }
+
+    @Test
+    public void testConfirmedShiftClick_ExtendsSelectionFromFocus() {
+        TestItemDetails item = mDetailsLookup.initAt(7);
+        mFocusCallbacks.focusItem(item);
+
+        // There should be no selected item at this point, just focus on "7".
+        mDetailsLookup.initAt(11);
+        mInputDelegate.onSingleTapConfirmed(SHIFT_CLICK);
+        mSelection.assertSelection(7, 8, 9, 10, 11);
+    }
+
+    @Test
+    public void testUnconfirmedShiftClick_RotatesAroundOrigin() {
+        mDetailsLookup.initAt(7).setInItemSelectRegion(true);
+        mInputDelegate.onSingleTapConfirmed(CLICK);
+
+        mDetailsLookup.initAt(11);
+        mInputDelegate.onSingleTapUp(SHIFT_CLICK);
+        mSelection.assertSelection(7, 8, 9, 10, 11);
+
+        mDetailsLookup.initAt(5);
+        mInputDelegate.onSingleTapUp(SHIFT_CLICK);
+
+        mSelection.assertSelection(5, 6, 7);
+        mSelection.assertNotSelected(8, 9, 10, 11);
+    }
+
+    @Test
+    public void testUnconfirmedShiftCtrlClick_Combination() {
+        mDetailsLookup.initAt(7).setInItemSelectRegion(true);
+        mInputDelegate.onSingleTapConfirmed(CLICK);
+
+        mDetailsLookup.initAt(11);
+        mInputDelegate.onSingleTapUp(SHIFT_CLICK);
+        mSelection.assertSelection(7, 8, 9, 10, 11);
+
+        mDetailsLookup.initAt(5);
+        mInputDelegate.onSingleTapUp(CTRL_CLICK);
+
+        mSelection.assertSelection(5, 7, 8, 9, 10, 11);
+    }
+
+    @Test
+    public void testUnconfirmedShiftCtrlClick_ShiftTakesPriority() {
+        mDetailsLookup.initAt(7).setInItemSelectRegion(true);
+        mInputDelegate.onSingleTapConfirmed(CLICK);
+
+        mDetailsLookup.initAt(11);
+        mInputDelegate.onSingleTapUp(mEvent.ctrl().shift().build());
+
+        mSelection.assertSelection(7, 8, 9, 10, 11);
+    }
+
+    // TODO: Add testSpaceBar_Previews, but we need to set a system property
+    // to have a deterministic state.
+
+    @Test
+    public void testDoubleClick_Opens() {
+        TestItemDetails doc = mDetailsLookup.initAt(11);
+        mInputDelegate.onDoubleTap(CLICK);
+
+        mActivationCallbacks.assertActivated(doc);
+    }
+
+    @Test
+    public void testMiddleClick_DoesNothing() {
+        mDetailsLookup.initAt(11).setInItemSelectRegion(true);
+        mInputDelegate.onSingleTapConfirmed(TERTIARY_CLICK);
+
+        mSelection.assertNoSelection();
+    }
+
+    @Test
+    public void testClickOff_ClearsSelection() {
+        mDetailsLookup.initAt(11).setInItemSelectRegion(true);
+        mInputDelegate.onSingleTapConfirmed(CLICK);
+
+        mDetailsLookup.initAt(RecyclerView.NO_POSITION);
+        mInputDelegate.onSingleTapUp(CLICK);
+
+        mSelection.assertNoSelection();
+    }
+
+    @Test
+    public void testClick_Focuses() {
+        mDetailsLookup.initAt(11).setInItemSelectRegion(false);
+        mInputDelegate.onSingleTapConfirmed(CLICK);
+
+        mFocusCallbacks.assertHasFocus(true);
+        mFocusCallbacks.assertFocused("11");
+    }
+
+    @Test
+    public void testClickOff_ClearsFocus() {
+        mDetailsLookup.initAt(11).setInItemSelectRegion(false);
+        mInputDelegate.onSingleTapConfirmed(CLICK);
+        mFocusCallbacks.assertHasFocus(true);
+
+        mDetailsLookup.initAt(RecyclerView.NO_POSITION);
+        mInputDelegate.onSingleTapUp(CLICK);
+        mFocusCallbacks.assertHasFocus(false);
+    }
+
+    @Test
+    public void testClickOffSelection_RemovesSelectionAndFocuses() {
+        mDetailsLookup.initAt(1).setInItemSelectRegion(true);
+        mInputDelegate.onSingleTapConfirmed(CLICK);
+
+        mDetailsLookup.initAt(5);
+        mInputDelegate.onSingleTapUp(SHIFT_CLICK);
+
+        mSelection.assertSelection(1, 2, 3, 4, 5);
+
+        mDetailsLookup.initAt(11);
+        mInputDelegate.onSingleTapUp(CLICK);
+
+        mFocusCallbacks.assertFocused("11");
+        mSelection.assertNoSelection();
+    }
+}
diff --git a/recyclerview-selection/tests/java/androidx/recyclerview/selection/MouseInputHandler_RangeTest.java b/recyclerview-selection/tests/java/androidx/recyclerview/selection/MouseInputHandler_RangeTest.java
new file mode 100644
index 0000000..3295ea0
--- /dev/null
+++ b/recyclerview-selection/tests/java/androidx/recyclerview/selection/MouseInputHandler_RangeTest.java
@@ -0,0 +1,200 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static androidx.recyclerview.selection.testing.TestEvents.Mouse.CLICK;
+import static androidx.recyclerview.selection.testing.TestEvents.Mouse.SECONDARY_CLICK;
+import static androidx.recyclerview.selection.testing.TestEvents.Mouse.SHIFT_CLICK;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+import androidx.recyclerview.selection.testing.SelectionHelpers;
+import androidx.recyclerview.selection.testing.SelectionProbe;
+import androidx.recyclerview.selection.testing.TestActivationCallbacks;
+import androidx.recyclerview.selection.testing.TestAdapter;
+import androidx.recyclerview.selection.testing.TestData;
+import androidx.recyclerview.selection.testing.TestFocusCallbacks;
+import androidx.recyclerview.selection.testing.TestItemDetails;
+import androidx.recyclerview.selection.testing.TestItemDetailsLookup;
+import androidx.recyclerview.selection.testing.TestItemKeyProvider;
+import androidx.recyclerview.selection.testing.TestMouseCallbacks;
+
+/**
+ * MouseInputDelegate / SelectHelper integration test covering the shared
+ * responsibility of range selection.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public final class MouseInputHandler_RangeTest {
+
+    private static final List<String> ITEMS = TestData.createStringData(100);
+
+    private MouseInputHandler<String> mInputDelegate;
+    private SelectionProbe mSelection;
+    private TestFocusCallbacks<String> mFocusCallbacks;
+    private TestItemDetailsLookup mDetailsLookup;
+
+    @Before
+    public void setUp() {
+        SelectionHelper<String> selectionMgr = SelectionHelpers.createTestInstance(ITEMS);
+        TestMouseCallbacks mouseCallbacks = new TestMouseCallbacks();
+        TestActivationCallbacks<String> activationCallbacks = new TestActivationCallbacks<>();
+
+        mDetailsLookup = new TestItemDetailsLookup();
+        mSelection = new SelectionProbe(selectionMgr);
+        mFocusCallbacks = new TestFocusCallbacks<>();
+
+        mInputDelegate = new MouseInputHandler<>(
+                selectionMgr,
+                new TestItemKeyProvider(ItemKeyProvider.SCOPE_MAPPED, new TestAdapter(ITEMS)),
+                mDetailsLookup,
+                mouseCallbacks,
+                activationCallbacks,
+                mFocusCallbacks);
+    }
+
+    @Test
+    public void testExtendRange() {
+        // uni-click just focuses.
+        mDetailsLookup.initAt(7).setInItemSelectRegion(true);
+        mInputDelegate.onSingleTapConfirmed(CLICK);
+
+        mDetailsLookup.initAt(11);
+        mInputDelegate.onSingleTapUp(SHIFT_CLICK);
+
+        mSelection.assertRangeSelection(7, 11);
+    }
+
+    @Test
+    public void testExtendRangeContinues() {
+        mDetailsLookup.initAt(7).setInItemSelectRegion(true);
+        mInputDelegate.onSingleTapConfirmed(CLICK);
+
+        mDetailsLookup.initAt(11);
+        mInputDelegate.onSingleTapUp(SHIFT_CLICK);
+
+        mDetailsLookup.initAt(21);
+        mInputDelegate.onSingleTapUp(SHIFT_CLICK);
+
+        mSelection.assertRangeSelection(7, 21);
+    }
+
+    @Test
+    public void testMultipleContiguousRanges() {
+        mDetailsLookup.initAt(7).setInItemSelectRegion(true);
+        mInputDelegate.onSingleTapConfirmed(CLICK);
+
+        mDetailsLookup.initAt(11);
+        mInputDelegate.onSingleTapUp(SHIFT_CLICK);
+
+        // click without shift sets a new range start point.
+        TestItemDetails item = mDetailsLookup.initAt(20);
+        mInputDelegate.onSingleTapUp(CLICK);
+        mInputDelegate.onSingleTapConfirmed(CLICK);
+
+        mFocusCallbacks.focusItem(item);
+
+        mDetailsLookup.initAt(25);
+        mInputDelegate.onSingleTapUp(SHIFT_CLICK);
+        mInputDelegate.onSingleTapConfirmed(SHIFT_CLICK);
+
+        mSelection.assertRangeNotSelected(7, 11);
+        mSelection.assertRangeSelected(20, 25);
+        mSelection.assertSelectionSize(6);
+    }
+
+    @Test
+    public void testReducesSelectionRange() {
+        mDetailsLookup.initAt(7).setInItemSelectRegion(true);
+        mInputDelegate.onSingleTapConfirmed(CLICK);
+
+        mDetailsLookup.initAt(17);
+        mInputDelegate.onSingleTapUp(SHIFT_CLICK);
+
+        mDetailsLookup.initAt(10);
+        mInputDelegate.onSingleTapUp(SHIFT_CLICK);
+
+        mSelection.assertRangeSelection(7, 10);
+    }
+
+    @Test
+    public void testReducesSelectionRange_Reverse() {
+        mDetailsLookup.initAt(17).setInItemSelectRegion(true);
+        mInputDelegate.onSingleTapConfirmed(CLICK);
+
+        mDetailsLookup.initAt(7);
+        mInputDelegate.onSingleTapUp(SHIFT_CLICK);
+
+        mDetailsLookup.initAt(14);
+        mInputDelegate.onSingleTapUp(SHIFT_CLICK);
+
+        mSelection.assertRangeSelection(14, 17);
+    }
+
+    @Test
+    public void testExtendsRange_Reverse() {
+        mDetailsLookup.initAt(12).setInItemSelectRegion(true);
+        mInputDelegate.onSingleTapConfirmed(CLICK);
+
+        mDetailsLookup.initAt(5);
+        mInputDelegate.onSingleTapUp(SHIFT_CLICK);
+
+        mSelection.assertRangeSelection(5, 12);
+    }
+
+    @Test
+    public void testExtendsRange_ReversesAfterForwardClick() {
+        mDetailsLookup.initAt(7).setInItemSelectRegion(true);
+        mInputDelegate.onSingleTapConfirmed(CLICK);
+
+        mDetailsLookup.initAt(11);
+        mInputDelegate.onSingleTapUp(SHIFT_CLICK);
+
+        mDetailsLookup.initAt(0);
+        mInputDelegate.onSingleTapUp(SHIFT_CLICK);
+
+        mSelection.assertRangeSelection(0, 7);
+    }
+
+    @Test
+    public void testRightClickEstablishesRange() {
+
+        mDetailsLookup.initAt(7).setInItemSelectRegion(true);
+        mInputDelegate.onDown(SECONDARY_CLICK);
+        // This next method call simulates the behavior of the system event dispatch code.
+        // UserInputHandler depends on a specific sequence of events for internal
+        // state to remain valid. It's not an awesome arrangement, but it is currently
+        // necessary.
+        //
+        // See: UserInputHandler.MouseDelegate#mHandledOnDown;
+        mInputDelegate.onSingleTapUp(SECONDARY_CLICK);
+
+        mDetailsLookup.initAt(11);
+        // Now we can send a subsequent event that should extend selection.
+        mInputDelegate.onDown(SHIFT_CLICK);
+        mInputDelegate.onSingleTapUp(SHIFT_CLICK);
+
+        mSelection.assertRangeSelection(7, 11);
+    }
+}
diff --git a/recyclerview-selection/tests/java/androidx/recyclerview/selection/RangeTest.java b/recyclerview-selection/tests/java/androidx/recyclerview/selection/RangeTest.java
new file mode 100644
index 0000000..0daeb41
--- /dev/null
+++ b/recyclerview-selection/tests/java/androidx/recyclerview/selection/RangeTest.java
@@ -0,0 +1,168 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static junit.framework.Assert.assertEquals;
+
+import static androidx.recyclerview.selection.Range.TYPE_PRIMARY;
+import static androidx.recyclerview.selection.Range.TYPE_PROVISIONAL;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+import java.util.Stack;
+
+import androidx.recyclerview.selection.testing.TestData;
+
+/**
+ * MouseInputDelegate / SelectHelper integration test covering the shared
+ * responsibility of range selection.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public final class RangeTest {
+
+    private static final List<String> ITEMS = TestData.createStringData(100);
+
+    private RangeSpy mSpy;
+    private Stack<Capture> mOperations;
+    private Range mRange;
+
+    @Before
+    public void setUp() {
+        mOperations = new Stack<>();
+        mSpy = new RangeSpy(mOperations);
+    }
+
+    @Test
+    public void testEstablishRange() {
+        mRange = new Range(0, mSpy);
+        mRange.extendRange(5, TYPE_PRIMARY);
+
+        // Origin is expected to have already been selected.
+        mOperations.pop().assertChanged(1, 5, true);
+    }
+
+    @Test
+    public void testExpandRange() {
+        mRange = new Range(0, mSpy);
+        mRange.extendRange(5, TYPE_PRIMARY);
+        mRange.extendRange(10, TYPE_PRIMARY);
+
+        mOperations.pop().assertChanged(6, 10, true);
+    }
+
+    @Test
+    public void testContractRange() {
+        mRange = new Range(0, mSpy);
+        mRange.extendRange(10, TYPE_PRIMARY);
+        mRange.extendRange(5, TYPE_PRIMARY);
+        mOperations.pop().assertChanged(6, 10, false);
+    }
+
+
+    @Test
+    public void testFlipRange_InitiallyDescending() {
+        mRange = new Range(10, mSpy);
+        mRange.extendRange(20, TYPE_PRIMARY);
+        mRange.extendRange(5, TYPE_PRIMARY);
+
+        // When a revision results in a flip two changes
+        // are sent to the callback. 1 to unselect the old items
+        // and one to select the new items.
+        mOperations.pop().assertChanged(5, 9, true);
+        // note that range never modifies the anchor.
+        mOperations.pop().assertChanged(11, 20, false);
+    }
+
+    @Test
+    public void testFlipRange_InitiallyAscending() {
+        mRange = new Range(10, mSpy);
+        mRange.extendRange(5, TYPE_PRIMARY);
+        mRange.extendRange(20, TYPE_PRIMARY);
+
+        // When a revision results in a flip two changes
+        // are sent to the callback. 1 to unselect the old items
+        // and one to select the new items.
+        mOperations.pop().assertChanged(11, 20, true);
+        // note that range never modifies the anchor.
+        mOperations.pop().assertChanged(5, 9, false);
+    }
+
+    // NOTE: The operation type is conveyed among methods, then
+    // returned to the caller. It's more of something we coury
+    // for the caller. So we won't verify courying the value
+    // with all behaviors. Just this once.
+    @Test
+    public void testCouriesRangeType() {
+        mRange = new Range(0, mSpy);
+
+        mRange.extendRange(5, TYPE_PRIMARY);
+        mOperations.pop().assertType(TYPE_PRIMARY);
+
+        mRange.extendRange(10, TYPE_PROVISIONAL);
+        mOperations.pop().assertType(TYPE_PROVISIONAL);
+    }
+
+    private static class Capture {
+
+        private int mBegin;
+        private int mEnd;
+        private boolean mSelected;
+        private int mType;
+
+        private Capture(int begin, int end, boolean selected, int type) {
+            mBegin = begin;
+            mEnd = end;
+            mSelected = selected;
+            mType = type;
+        }
+
+        private void assertType(int expected) {
+            assertEquals(expected, mType);
+        }
+
+        private void assertChanged(int begin, int end, boolean selected) {
+            assertEquals(begin, mBegin);
+            assertEquals(end, mEnd);
+            assertEquals(selected, mSelected);
+        }
+    }
+
+    private static final class RangeSpy extends Range.Callbacks {
+
+        private final Stack<Capture> mOperations;
+
+        RangeSpy(Stack<Capture> operations) {
+            mOperations = operations;
+        }
+
+        @Override
+        void updateForRange(int begin, int end, boolean selected, int type) {
+            mOperations.push(new Capture(begin, end, selected, type));
+        }
+
+        Capture popOp() {
+            return mOperations.pop();
+        }
+    }
+}
diff --git a/recyclerview-selection/tests/java/androidx/recyclerview/selection/SelectionStorage_LongsTest.java b/recyclerview-selection/tests/java/androidx/recyclerview/selection/SelectionStorage_LongsTest.java
new file mode 100644
index 0000000..5535935
--- /dev/null
+++ b/recyclerview-selection/tests/java/androidx/recyclerview/selection/SelectionStorage_LongsTest.java
@@ -0,0 +1,82 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static junit.framework.Assert.assertTrue;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.Bundle;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import androidx.recyclerview.selection.testing.Bundles;
+import androidx.recyclerview.selection.testing.SelectionHelpers;
+import androidx.recyclerview.selection.testing.TestData;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public final class SelectionStorage_LongsTest {
+
+    private SelectionHelper<Long> mSelectionHelper;
+    private SelectionStorage<Long> mSelectionStorage;
+    private Bundle mBundle;
+
+    @Before
+    public void setUp() {
+        mSelectionHelper = SelectionHelpers.createTestInstance(TestData.createLongData(100));
+        mSelectionStorage = new SelectionStorage<>(
+                SelectionStorage.TYPE_LONG, mSelectionHelper);
+        mBundle = new Bundle();
+    }
+
+    @Test
+    public void testWritesSelectionToBundle() {
+        mSelectionHelper.select(3L);
+        mSelectionStorage.onSaveInstanceState(mBundle);
+        Bundle out = Bundles.forceParceling(mBundle);
+        assertTrue(out.containsKey(SelectionStorage.EXTRA_SAVED_SELECTION_TYPE));
+        assertTrue(out.containsKey(SelectionStorage.EXTRA_SAVED_SELECTION_ENTRIES));
+    }
+
+    @Test
+    public void testRestoresFromSelectionInBundle() {
+        mSelectionHelper.select(3L);
+        mSelectionHelper.select(13L);
+        mSelectionHelper.select(33L);
+
+        MutableSelection orig = new MutableSelection();
+        mSelectionHelper.copySelection(orig);
+        mSelectionStorage.onSaveInstanceState(mBundle);
+        Bundle out = Bundles.forceParceling(mBundle);
+
+        mSelectionHelper.clearSelection();
+        mSelectionStorage.onRestoreInstanceState(out);
+        MutableSelection restored = new MutableSelection();
+        mSelectionHelper.copySelection(restored);
+        assertEquals(orig, restored);
+    }
+
+    @Test
+    public void testIgnoresNullBundle() {
+        mSelectionStorage.onRestoreInstanceState(null);
+    }
+}
diff --git a/recyclerview-selection/tests/java/androidx/recyclerview/selection/SelectionStorage_StringsTest.java b/recyclerview-selection/tests/java/androidx/recyclerview/selection/SelectionStorage_StringsTest.java
new file mode 100644
index 0000000..9442be9
--- /dev/null
+++ b/recyclerview-selection/tests/java/androidx/recyclerview/selection/SelectionStorage_StringsTest.java
@@ -0,0 +1,83 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static junit.framework.Assert.assertTrue;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.Bundle;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import androidx.recyclerview.selection.testing.Bundles;
+import androidx.recyclerview.selection.testing.SelectionHelpers;
+import androidx.recyclerview.selection.testing.TestData;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public final class SelectionStorage_StringsTest {
+
+    private SelectionHelper<String> mSelectionHelper;
+    private SelectionStorage<String> mSelectionStorage;
+    private Bundle mBundle;
+
+    @Before
+    public void setUp() {
+        mSelectionHelper = SelectionHelpers.createTestInstance(TestData.createStringData(100));
+        mSelectionStorage = new SelectionStorage<>(
+                SelectionStorage.TYPE_STRING, mSelectionHelper);
+        mBundle = new Bundle();
+    }
+
+    @Test
+    public void testWritesSelectionToBundle() {
+        mSelectionHelper.select("3");
+        mSelectionStorage.onSaveInstanceState(mBundle);
+        Bundle out = Bundles.forceParceling(mBundle);
+
+        assertTrue(mBundle.containsKey(SelectionStorage.EXTRA_SAVED_SELECTION_ENTRIES));
+    }
+
+    @Test
+    public void testRestoresFromSelectionInBundle() {
+        mSelectionHelper.select("3");
+        mSelectionHelper.select("13");
+        mSelectionHelper.select("33");
+
+        MutableSelection orig = new MutableSelection();
+        mSelectionHelper.copySelection(orig);
+        mSelectionStorage.onSaveInstanceState(mBundle);
+        Bundle out = Bundles.forceParceling(mBundle);
+
+        mSelectionHelper.clearSelection();
+
+        mSelectionStorage.onRestoreInstanceState(mBundle);
+        MutableSelection restored = new MutableSelection();
+        mSelectionHelper.copySelection(restored);
+        assertEquals(orig, restored);
+    }
+
+    @Test
+    public void testIgnoresNullBundle() {
+        mSelectionStorage.onRestoreInstanceState(null);
+    }
+}
diff --git a/recyclerview-selection/tests/java/androidx/recyclerview/selection/SelectionTest.java b/recyclerview-selection/tests/java/androidx/recyclerview/selection/SelectionTest.java
new file mode 100644
index 0000000..64bf01d
--- /dev/null
+++ b/recyclerview-selection/tests/java/androidx/recyclerview/selection/SelectionTest.java
@@ -0,0 +1,133 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class SelectionTest {
+
+    private final String[] mIds = new String[] {
+            "foo",
+            "43",
+            "auth|id=@53di*/f3#d"
+    };
+
+    private Selection mSelection;
+
+    @Before
+    public void setUp() throws Exception {
+        mSelection = new Selection();
+        mSelection.add(mIds[0]);
+        mSelection.add(mIds[1]);
+        mSelection.add(mIds[2]);
+    }
+
+    @Test
+    public void testAdd() {
+        // We added in setUp.
+        assertEquals(3, mSelection.size());
+        assertContains(mIds[0]);
+        assertContains(mIds[1]);
+        assertContains(mIds[2]);
+    }
+
+    @Test
+    public void testRemove() {
+        mSelection.remove(mIds[0]);
+        mSelection.remove(mIds[2]);
+        assertEquals(1, mSelection.size());
+        assertContains(mIds[1]);
+    }
+
+    @Test
+    public void testClear() {
+        mSelection.clear();
+        assertEquals(0, mSelection.size());
+    }
+
+    @Test
+    public void testIsEmpty() {
+        assertTrue(new Selection().isEmpty());
+        mSelection.clear();
+        assertTrue(mSelection.isEmpty());
+    }
+
+    @Test
+    public void testSize() {
+        Selection other = new Selection();
+        for (int i = 0; i < mSelection.size(); i++) {
+            other.add(mIds[i]);
+        }
+        assertEquals(mSelection.size(), other.size());
+    }
+
+    @Test
+    public void testEqualsSelf() {
+        assertEquals(mSelection, mSelection);
+    }
+
+    @Test
+    public void testEqualsOther() {
+        Selection other = new Selection();
+        other.add(mIds[0]);
+        other.add(mIds[1]);
+        other.add(mIds[2]);
+        assertEquals(mSelection, other);
+        assertEquals(mSelection.hashCode(), other.hashCode());
+    }
+
+    @Test
+    public void testEqualsCopy() {
+        Selection other = new Selection();
+        other.copyFrom(mSelection);
+        assertEquals(mSelection, other);
+        assertEquals(mSelection.hashCode(), other.hashCode());
+    }
+
+    @Test
+    public void testNotEquals() {
+        Selection other = new Selection();
+        other.add("foobar");
+        assertFalse(mSelection.equals(other));
+    }
+
+    private void assertContains(String id) {
+        String err = String.format("Selection %s does not contain %s", mSelection, id);
+        assertTrue(err, mSelection.contains(id));
+    }
+
+    public static <E> Set<E> newSet(E... elements) {
+        HashSet<E> set = new HashSet<>(elements.length);
+        Collections.addAll(set, elements);
+        return set;
+    }
+}
diff --git a/recyclerview-selection/tests/java/androidx/recyclerview/selection/TouchInputHandlerTest.java b/recyclerview-selection/tests/java/androidx/recyclerview/selection/TouchInputHandlerTest.java
new file mode 100644
index 0000000..476684a
--- /dev/null
+++ b/recyclerview-selection/tests/java/androidx/recyclerview/selection/TouchInputHandlerTest.java
@@ -0,0 +1,198 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static org.junit.Assert.assertFalse;
+
+import static androidx.recyclerview.selection.testing.TestEvents.Touch.TAP;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v7.widget.RecyclerView;
+import android.view.MotionEvent;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+import androidx.recyclerview.selection.ItemDetailsLookup.ItemDetails;
+import androidx.recyclerview.selection.testing.SelectionHelpers;
+import androidx.recyclerview.selection.testing.SelectionProbe;
+import androidx.recyclerview.selection.testing.TestActivationCallbacks;
+import androidx.recyclerview.selection.testing.TestAdapter;
+import androidx.recyclerview.selection.testing.TestData;
+import androidx.recyclerview.selection.testing.TestFocusCallbacks;
+import androidx.recyclerview.selection.testing.TestItemDetailsLookup;
+import androidx.recyclerview.selection.testing.TestItemKeyProvider;
+import androidx.recyclerview.selection.testing.TestRunnable;
+import androidx.recyclerview.selection.testing.TestSelectionPredicate;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public final class TouchInputHandlerTest {
+
+    private static final List<String> ITEMS = TestData.createStringData(100);
+
+    private TouchInputHandler mInputDelegate;
+    private SelectionHelper mSelectionMgr;
+    private TestSelectionPredicate mSelectionPredicate;
+    private TestRunnable mGestureStarted;
+    private TestRunnable mHapticPerformer;
+    private TestTouchCallbacks mMouseCallbacks;
+    private TestActivationCallbacks mActivationCallbacks;
+    private TestFocusCallbacks mFocusCallbacks;
+    private TestItemDetailsLookup mDetailsLookup;
+    private SelectionProbe mSelection;
+
+    @Before
+    public void setUp() {
+        mSelectionMgr = SelectionHelpers.createTestInstance(ITEMS);
+        mDetailsLookup = new TestItemDetailsLookup();
+        mSelectionPredicate = new TestSelectionPredicate();
+        mSelection = new SelectionProbe(mSelectionMgr);
+        mGestureStarted = new TestRunnable();
+        mHapticPerformer = new TestRunnable();
+        mMouseCallbacks = new TestTouchCallbacks();
+        mActivationCallbacks = new TestActivationCallbacks();
+        mFocusCallbacks = new TestFocusCallbacks();
+
+        mInputDelegate = new TouchInputHandler(
+                mSelectionMgr,
+                new TestItemKeyProvider(ItemKeyProvider.SCOPE_MAPPED, new TestAdapter(ITEMS)),
+                mDetailsLookup,
+                mSelectionPredicate,
+                mGestureStarted,
+                mMouseCallbacks,
+                mActivationCallbacks,
+                mFocusCallbacks,
+                mHapticPerformer);
+    }
+
+    @Test
+    public void testTap_ActivatesWhenNoExistingSelection() {
+        ItemDetails doc = mDetailsLookup.initAt(11);
+        mInputDelegate.onSingleTapUp(TAP);
+
+        mActivationCallbacks.assertActivated(doc);
+    }
+
+    @Test
+    public void testScroll_shouldNotBeTrapped() {
+        assertFalse(mInputDelegate.onScroll(null, TAP, -1, -1));
+    }
+
+    @Test
+    public void testLongPress_SelectsItem() {
+        mSelectionPredicate.setReturnValue(true);
+
+        mDetailsLookup.initAt(7);
+        mInputDelegate.onLongPress(TAP);
+
+        mSelection.assertSelection(7);
+    }
+
+    @Test
+    public void testLongPress_StartsGestureSelection() {
+        mSelectionPredicate.setReturnValue(true);
+
+        mDetailsLookup.initAt(7);
+        mInputDelegate.onLongPress(TAP);
+        mGestureStarted.assertRan();
+    }
+
+    @Test
+    public void testSelectHotspot_StartsSelectionMode() {
+        mSelectionPredicate.setReturnValue(true);
+
+        mDetailsLookup.initAt(7).setInItemSelectRegion(true);
+        mInputDelegate.onSingleTapUp(TAP);
+
+        mSelection.assertSelection(7);
+    }
+
+    @Test
+    public void testSelectionHotspot_UnselectsSelectedItem() {
+        mSelectionMgr.select("11");
+
+        mDetailsLookup.initAt(11).setInItemSelectRegion(true);
+        mInputDelegate.onSingleTapUp(TAP);
+
+        mSelection.assertNoSelection();
+    }
+
+    @Test
+    public void testStartsSelection_PerformsHapticFeedback() {
+        mSelectionPredicate.setReturnValue(true);
+
+        mDetailsLookup.initAt(7);
+        mInputDelegate.onLongPress(TAP);
+
+        mHapticPerformer.assertRan();
+    }
+
+    @Test
+    public void testLongPress_AddsToSelection() {
+        mSelectionPredicate.setReturnValue(true);
+
+        mDetailsLookup.initAt(7);
+        mInputDelegate.onLongPress(TAP);
+
+        mDetailsLookup.initAt(99);
+        mInputDelegate.onLongPress(TAP);
+
+        mDetailsLookup.initAt(13);
+        mInputDelegate.onLongPress(TAP);
+
+        mSelection.assertSelection(7, 13, 99);
+    }
+
+    @Test
+    public void testTap_UnselectsSelectedItem() {
+        mSelectionMgr.select("11");
+
+        mDetailsLookup.initAt(11);
+        mInputDelegate.onSingleTapUp(TAP);
+
+        mSelection.assertNoSelection();
+    }
+
+    @Test
+    public void testTapOff_ClearsSelection() {
+        mSelectionMgr.select("7");
+        mDetailsLookup.initAt(7);
+
+        mInputDelegate.onLongPress(TAP);
+
+        mSelectionMgr.select("11");
+        mDetailsLookup.initAt(11);
+        mInputDelegate.onSingleTapUp(TAP);
+
+        mDetailsLookup.initAt(RecyclerView.NO_POSITION).setInItemSelectRegion(false);
+        mInputDelegate.onSingleTapUp(TAP);
+
+        mSelection.assertNoSelection();
+    }
+
+    private static final class TestTouchCallbacks extends TouchCallbacks {
+        @Override
+        public boolean onDragInitiated(MotionEvent e) {
+            return false;
+        }
+    }
+}
diff --git a/recyclerview-selection/tests/java/androidx/recyclerview/selection/ViewAutoScrollerTest.java b/recyclerview-selection/tests/java/androidx/recyclerview/selection/ViewAutoScrollerTest.java
new file mode 100644
index 0000000..66cb721
--- /dev/null
+++ b/recyclerview-selection/tests/java/androidx/recyclerview/selection/ViewAutoScrollerTest.java
@@ -0,0 +1,165 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+
+import static org.junit.Assert.assertNull;
+
+import android.graphics.Point;
+import android.support.annotation.Nullable;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import androidx.recyclerview.selection.ViewAutoScroller.ScrollHost;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public final class ViewAutoScrollerTest {
+
+    private static final float SCROLL_THRESHOLD_RATIO = 0.125f;
+    private static final int VIEW_HEIGHT = 100;
+    private static final int TOP_Y_POINT = (int) (VIEW_HEIGHT * SCROLL_THRESHOLD_RATIO) - 1;
+    private static final int BOTTOM_Y_POINT =
+            VIEW_HEIGHT - (int) (VIEW_HEIGHT * SCROLL_THRESHOLD_RATIO) + 1;
+
+    private ViewAutoScroller mScroller;
+    private TestHost mHost;
+
+    @Before
+    public void setUp() {
+        mHost = new TestHost();
+        mScroller = new ViewAutoScroller(mHost, SCROLL_THRESHOLD_RATIO);
+    }
+
+    @Test
+    public void testNoScrollWhenOutOfScrollZone() {
+        mScroller.scroll(new Point(0, VIEW_HEIGHT / 2));
+        mHost.run();
+        mHost.assertNotScrolled();
+    }
+
+//    @Test
+//    public void testNoScrollWhenDisabled() {
+//        mScroller.reset();
+//        mScroller.scroll(mEvent.location(0, TOP_Y_POINT).build());
+//        mHost.assertNotScrolled();
+//    }
+
+    @Test
+    public void testMotionThreshold() {
+        mScroller.scroll(new Point(0, TOP_Y_POINT));
+        mHost.run();
+
+        mScroller.scroll(new Point(0, TOP_Y_POINT - 1));
+        mHost.run();
+
+        mHost.assertNotScrolled();
+    }
+
+    @Test
+    public void testMotionThreshold_Resets() {
+        int expectedScrollDistance = mScroller.computeScrollDistance(-21);
+        mScroller.scroll(new Point(0, TOP_Y_POINT));
+        mHost.run();
+        // We need enough y motion to overcome motion threshold
+        mScroller.scroll(new Point(0, TOP_Y_POINT - 20));
+        mHost.run();
+
+        mHost.reset();
+        // After resetting events should be required to cross the motion threshold
+        // before auto-scrolling again.
+        mScroller.reset();
+
+        mScroller.scroll(new Point(0, TOP_Y_POINT));
+        mHost.run();
+
+        mHost.assertNotScrolled();
+    }
+
+    @Test
+    public void testAutoScrolls_Top() {
+        int expectedScrollDistance = mScroller.computeScrollDistance(-21);
+        mScroller.scroll(new Point(0, TOP_Y_POINT));
+        mHost.run();
+        // We need enough y motion to overcome motion threshold
+        mScroller.scroll(new Point(0, TOP_Y_POINT - 20));
+        mHost.run();
+
+        mHost.assertScrolledBy(expectedScrollDistance);
+    }
+
+    @Test
+    public void testAutoScrolls_Bottom() {
+        int expectedScrollDistance = mScroller.computeScrollDistance(21);
+        mScroller.scroll(new Point(0, BOTTOM_Y_POINT));
+        mHost.run();
+        // We need enough y motion to overcome motion threshold
+        mScroller.scroll(new Point(0, BOTTOM_Y_POINT + 20));
+        mHost.run();
+
+        mHost.assertScrolledBy(expectedScrollDistance);
+    }
+
+    private final class TestHost extends ScrollHost {
+
+        private @Nullable Integer mScrollDistance;
+        private @Nullable Runnable mRunnable;
+
+        @Override
+        int getViewHeight() {
+            return VIEW_HEIGHT;
+        }
+
+        @Override
+        void scrollBy(int distance) {
+            mScrollDistance = distance;
+        }
+
+        @Override
+        void runAtNextFrame(Runnable r) {
+            mRunnable = r;
+        }
+
+        @Override
+        void removeCallback(Runnable r) {
+        }
+
+        private void reset() {
+            mScrollDistance = null;
+            mRunnable = null;
+        }
+
+        private void run() {
+            mRunnable.run();
+        }
+
+        private void assertNotScrolled() {
+            assertNull(mScrollDistance);
+        }
+
+        private void assertScrolledBy(int expectedDistance) {
+            assertNotNull(mScrollDistance);
+            assertEquals(expectedDistance, mScrollDistance.intValue());
+        }
+    }
+}
diff --git a/recyclerview-selection/tests/java/androidx/recyclerview/selection/testing/Bundles.java b/recyclerview-selection/tests/java/androidx/recyclerview/selection/testing/Bundles.java
new file mode 100644
index 0000000..6ceba34
--- /dev/null
+++ b/recyclerview-selection/tests/java/androidx/recyclerview/selection/testing/Bundles.java
@@ -0,0 +1,34 @@
+/*
+ * 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 androidx.recyclerview.selection.testing;
+
+import android.os.Bundle;
+import android.os.Parcel;
+
+public final class Bundles {
+
+    private Bundles() {
+    }
+
+    public static Bundle forceParceling(Bundle in) {
+        Parcel parcel = Parcel.obtain();
+        in.writeToParcel(parcel, 0);
+
+        parcel.setDataPosition(0);
+        return parcel.readBundle();
+    }
+}
diff --git a/recyclerview-selection/tests/java/androidx/recyclerview/selection/testing/SelectionHelpers.java b/recyclerview-selection/tests/java/androidx/recyclerview/selection/testing/SelectionHelpers.java
new file mode 100644
index 0000000..9a031a9
--- /dev/null
+++ b/recyclerview-selection/tests/java/androidx/recyclerview/selection/testing/SelectionHelpers.java
@@ -0,0 +1,43 @@
+/*
+ * 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 androidx.recyclerview.selection.testing;
+
+import java.util.List;
+
+import androidx.recyclerview.selection.DefaultSelectionHelper;
+import androidx.recyclerview.selection.EventBridge;
+import androidx.recyclerview.selection.ItemKeyProvider;
+import androidx.recyclerview.selection.SelectionHelper;
+import androidx.recyclerview.selection.SelectionPredicates;
+
+public final class SelectionHelpers {
+
+    private SelectionHelpers() {}
+
+    public static <K> SelectionHelper<K> createTestInstance(List<K> items) {
+        TestAdapter<K> adapter = new TestAdapter<>(items);
+        ItemKeyProvider<K> keyProvider =
+                new TestItemKeyProvider<>(ItemKeyProvider.SCOPE_MAPPED, adapter);
+        SelectionHelper<K> helper = new DefaultSelectionHelper<>(
+                keyProvider,
+                SelectionPredicates.selectAnything());
+
+        EventBridge.install(adapter, helper, keyProvider);
+
+        return helper;
+    }
+}
diff --git a/recyclerview-selection/tests/java/androidx/recyclerview/selection/testing/SelectionProbe.java b/recyclerview-selection/tests/java/androidx/recyclerview/selection/testing/SelectionProbe.java
new file mode 100644
index 0000000..4501078
--- /dev/null
+++ b/recyclerview-selection/tests/java/androidx/recyclerview/selection/testing/SelectionProbe.java
@@ -0,0 +1,105 @@
+/*
+ * 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 androidx.recyclerview.selection.testing;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import androidx.recyclerview.selection.DefaultSelectionHelper;
+import androidx.recyclerview.selection.Selection;
+import androidx.recyclerview.selection.SelectionHelper;
+
+/**
+ * Helper class for making assertions against the state of a {@link DefaultSelectionHelper} instance
+ * and the consistency of states between {@link DefaultSelectionHelper} and
+ * {@link DefaultSelectionHelper.SelectionObserver}.
+ */
+public final class SelectionProbe {
+
+    private final SelectionHelper<String> mMgr;
+    private final TestSelectionObserver<String> mSelectionListener;
+
+    public SelectionProbe(SelectionHelper<String> mgr) {
+        mMgr = mgr;
+        mSelectionListener = new TestSelectionObserver<String>();
+        mMgr.addObserver(mSelectionListener);
+    }
+
+    public SelectionProbe(
+            SelectionHelper<String> mgr, TestSelectionObserver<String> selectionListener) {
+        mMgr = mgr;
+        mSelectionListener = selectionListener;
+    }
+
+    public void assertRangeSelected(int begin, int end) {
+        for (int i = begin; i <= end; i++) {
+            assertSelected(i);
+        }
+    }
+
+    public void assertRangeNotSelected(int begin, int end) {
+        for (int i = begin; i <= end; i++) {
+            assertNotSelected(i);
+        }
+    }
+
+    public void assertRangeSelection(int begin, int end) {
+        assertSelectionSize(end - begin + 1);
+        assertRangeSelected(begin, end);
+    }
+
+    public void assertSelectionSize(int expected) {
+        Selection selection = mMgr.getSelection();
+        assertEquals(selection.toString(), expected, selection.size());
+
+        mSelectionListener.assertSelectionSize(expected);
+    }
+
+    public void assertNoSelection() {
+        assertSelectionSize(0);
+
+        mSelectionListener.assertNoSelection();
+    }
+
+    public void assertSelection(int... ids) {
+        assertSelected(ids);
+        assertEquals(ids.length, mMgr.getSelection().size());
+
+        mSelectionListener.assertSelectionSize(ids.length);
+    }
+
+    public void assertSelected(int... ids) {
+        Selection<String> sel = mMgr.getSelection();
+        for (int id : ids) {
+            String sid = String.valueOf(id);
+            assertTrue(sid + " is not in selection " + sel, sel.contains(sid));
+
+            mSelectionListener.assertSelected(sid);
+        }
+    }
+
+    public void assertNotSelected(int... ids) {
+        Selection<String> sel = mMgr.getSelection();
+        for (int id : ids) {
+            String sid = String.valueOf(id);
+            assertFalse(sid + " is in selection " + sel, sel.contains(sid));
+
+            mSelectionListener.assertNotSelected(sid);
+        }
+    }
+}
diff --git a/recyclerview-selection/tests/java/androidx/recyclerview/selection/testing/TestActivationCallbacks.java b/recyclerview-selection/tests/java/androidx/recyclerview/selection/testing/TestActivationCallbacks.java
new file mode 100644
index 0000000..b106a92
--- /dev/null
+++ b/recyclerview-selection/tests/java/androidx/recyclerview/selection/testing/TestActivationCallbacks.java
@@ -0,0 +1,39 @@
+/*
+ * 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 androidx.recyclerview.selection.testing;
+
+import static org.junit.Assert.assertEquals;
+
+import android.view.MotionEvent;
+
+import androidx.recyclerview.selection.ActivationCallbacks;
+import androidx.recyclerview.selection.ItemDetailsLookup.ItemDetails;
+
+public final class TestActivationCallbacks<K> extends ActivationCallbacks<K> {
+
+    private ItemDetails<K> mActivated;
+
+    @Override
+    public boolean onItemActivated(ItemDetails<K> item, MotionEvent e) {
+        mActivated = item;
+        return true;
+    }
+
+    public void assertActivated(ItemDetails<K> expected) {
+        assertEquals(expected, mActivated);
+    }
+}
diff --git a/recyclerview-selection/tests/java/androidx/recyclerview/selection/testing/TestAdapter.java b/recyclerview-selection/tests/java/androidx/recyclerview/selection/testing/TestAdapter.java
new file mode 100644
index 0000000..d9e952e
--- /dev/null
+++ b/recyclerview-selection/tests/java/androidx/recyclerview/selection/testing/TestAdapter.java
@@ -0,0 +1,123 @@
+/*
+ * 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 androidx.recyclerview.selection.testing;
+
+import static org.junit.Assert.assertTrue;
+
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerView.Adapter;
+import android.support.v7.widget.RecyclerView.AdapterDataObserver;
+import android.view.ViewGroup;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import androidx.recyclerview.selection.SelectionHelper;
+
+public class TestAdapter<K> extends Adapter<TestHolder> {
+
+    private final List<K> mItems = new ArrayList<>();
+    private final List<Integer> mNotifiedOfSelection = new ArrayList<>();
+    private final AdapterDataObserver mAdapterObserver;
+
+    public TestAdapter() {
+        this(Collections.EMPTY_LIST);
+    }
+
+    public TestAdapter(List<K> items) {
+        mItems.addAll(items);
+        mAdapterObserver = new RecyclerView.AdapterDataObserver() {
+
+            @Override
+            public void onChanged() {
+            }
+
+            @Override
+            public void onItemRangeChanged(int startPosition, int itemCount, Object payload) {
+                if (SelectionHelper.SELECTION_CHANGED_MARKER.equals(payload)) {
+                    int last = startPosition + itemCount;
+                    for (int i = startPosition; i < last; i++) {
+                        mNotifiedOfSelection.add(i);
+                    }
+                }
+            }
+
+            @Override
+            public void onItemRangeInserted(int startPosition, int itemCount) {
+            }
+
+            @Override
+            public void onItemRangeRemoved(int startPosition, int itemCount) {
+            }
+
+            @Override
+            public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
+                throw new UnsupportedOperationException();
+            }
+        };
+
+        registerAdapterDataObserver(mAdapterObserver);
+    }
+
+    @Override
+    public TestHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        return new TestHolder(parent);
+    }
+
+    @Override
+    public void onBindViewHolder(TestHolder holder, int position) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int getItemCount() {
+        return mItems.size();
+    }
+
+    public void updateTestModelIds(List<K> items) {
+        mItems.clear();
+        mItems.addAll(items);
+
+        notifyDataSetChanged();
+    }
+
+    public int getPosition(K key) {
+        return mItems.indexOf(key);
+    }
+
+    public K getSelectionKey(int position) {
+        return mItems.get(position);
+    }
+
+
+    public void resetSelectionNotifications() {
+        mNotifiedOfSelection.clear();
+    }
+
+    public void assertNotifiedOfSelectionChange(int position) {
+        assertTrue(mNotifiedOfSelection.contains(position));
+    }
+
+    public static List<String> createItemList(int num) {
+        List<String> items = new ArrayList<>(num);
+        for (int i = 0; i < num; ++i) {
+            items.add(Integer.toString(i));
+        }
+        return items;
+    }
+}
diff --git a/recyclerview-selection/tests/java/androidx/recyclerview/selection/testing/TestAutoScroller.java b/recyclerview-selection/tests/java/androidx/recyclerview/selection/testing/TestAutoScroller.java
new file mode 100644
index 0000000..4232205
--- /dev/null
+++ b/recyclerview-selection/tests/java/androidx/recyclerview/selection/testing/TestAutoScroller.java
@@ -0,0 +1,32 @@
+/*
+ * 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 androidx.recyclerview.selection.testing;
+
+import android.graphics.Point;
+
+import androidx.recyclerview.selection.AutoScroller;
+
+public class TestAutoScroller extends AutoScroller {
+
+    @Override
+    protected void reset() {
+    }
+
+    @Override
+    protected void scroll(Point location) {
+    }
+}
diff --git a/recyclerview-selection/tests/java/androidx/recyclerview/selection/testing/TestBandPredicate.java b/recyclerview-selection/tests/java/androidx/recyclerview/selection/testing/TestBandPredicate.java
new file mode 100644
index 0000000..ca21b9c
--- /dev/null
+++ b/recyclerview-selection/tests/java/androidx/recyclerview/selection/testing/TestBandPredicate.java
@@ -0,0 +1,36 @@
+/*
+ * 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 androidx.recyclerview.selection.testing;
+
+import android.view.MotionEvent;
+
+import androidx.recyclerview.selection.BandPredicate;
+
+public class TestBandPredicate extends BandPredicate {
+
+    private boolean mCanInitiate = true;
+
+    public void setCanInitiate(boolean canInitiate) {
+        mCanInitiate = canInitiate;
+    }
+
+    @Override
+    public boolean canInitiate(MotionEvent e) {
+        return mCanInitiate;
+    }
+
+}
diff --git a/recyclerview-selection/tests/java/androidx/recyclerview/selection/testing/TestData.java b/recyclerview-selection/tests/java/androidx/recyclerview/selection/testing/TestData.java
new file mode 100644
index 0000000..14e27aa
--- /dev/null
+++ b/recyclerview-selection/tests/java/androidx/recyclerview/selection/testing/TestData.java
@@ -0,0 +1,39 @@
+/*
+ * 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 androidx.recyclerview.selection.testing;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class TestData {
+
+    public static List<String> createStringData(int num) {
+        List<String> items = new ArrayList<>(num);
+        for (int i = 0; i < num; ++i) {
+            items.add(Integer.toString(i));
+        }
+        return items;
+    }
+
+    public static List<Long> createLongData(int num) {
+        List<Long> items = new ArrayList<>(num);
+        for (int i = 0; i < num; ++i) {
+            items.add(new Long(i));
+        }
+        return items;
+    }
+}
diff --git a/recyclerview-selection/tests/java/androidx/recyclerview/selection/testing/TestEvents.java b/recyclerview-selection/tests/java/androidx/recyclerview/selection/testing/TestEvents.java
new file mode 100644
index 0000000..fd4bea4
--- /dev/null
+++ b/recyclerview-selection/tests/java/androidx/recyclerview/selection/testing/TestEvents.java
@@ -0,0 +1,278 @@
+/*
+ * 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 androidx.recyclerview.selection.testing;
+
+import android.graphics.Point;
+import android.support.annotation.IntDef;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.MotionEvent.PointerCoords;
+import android.view.MotionEvent.PointerProperties;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Handy-dandy wrapper class to facilitate the creation of MotionEvents.
+ */
+public final class TestEvents {
+
+    /**
+     * Common mouse event types...for your convenience.
+     */
+    public static final class Mouse {
+        public static final MotionEvent CLICK =
+                TestEvents.builder().mouse().primary().build();
+        public static final MotionEvent CTRL_CLICK =
+                TestEvents.builder().mouse().primary().ctrl().build();
+        public static final MotionEvent ALT_CLICK =
+                TestEvents.builder().mouse().primary().alt().build();
+        public static final MotionEvent SHIFT_CLICK =
+                TestEvents.builder().mouse().primary().shift().build();
+        public static final MotionEvent SECONDARY_CLICK =
+                TestEvents.builder().mouse().secondary().build();
+        public static final MotionEvent TERTIARY_CLICK =
+                TestEvents.builder().mouse().tertiary().build();
+    }
+
+    /**
+     * Common touch event types...for your convenience.
+     */
+    public static final class Touch {
+        public static final MotionEvent TAP =
+                TestEvents.builder().touch().build();
+    }
+
+    static final int ACTION_UNSET = -1;
+
+    // Add other actions from MotionEvent.ACTION_ as needed.
+    @IntDef(flag = true, value = {
+            MotionEvent.ACTION_DOWN,
+            MotionEvent.ACTION_MOVE,
+            MotionEvent.ACTION_UP
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Action {}
+
+    // Add other types from MotionEvent.TOOL_TYPE_ as needed.
+    @IntDef(flag = true, value = {
+            MotionEvent.TOOL_TYPE_FINGER,
+            MotionEvent.TOOL_TYPE_MOUSE,
+            MotionEvent.TOOL_TYPE_STYLUS,
+            MotionEvent.TOOL_TYPE_UNKNOWN
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ToolType {}
+
+    @IntDef(flag = true, value = {
+            MotionEvent.BUTTON_PRIMARY,
+            MotionEvent.BUTTON_SECONDARY
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Button {}
+
+    @IntDef(flag = true, value = {
+            KeyEvent.META_SHIFT_ON,
+            KeyEvent.META_CTRL_ON
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Key {}
+
+    private static final class State {
+        private @Action int mAction = ACTION_UNSET;
+        private @ToolType int mToolType = MotionEvent.TOOL_TYPE_UNKNOWN;
+        private int mPointerCount = 1;
+        private Set<Integer> mButtons = new HashSet<>();
+        private Set<Integer> mKeys = new HashSet<>();
+        private Point mLocation = new Point(0, 0);
+        private Point mRawLocation = new Point(0, 0);
+    }
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    /**
+     * Test event builder with convenience methods for common event attrs.
+     */
+    public static final class Builder {
+
+        private State mState = new State();
+
+        /**
+         * @param action Any action specified in {@link MotionEvent}.
+         * @return
+         */
+        public Builder action(int action) {
+            mState.mAction = action;
+            return this;
+        }
+
+        public Builder type(@ToolType int type) {
+            mState.mToolType = type;
+            return this;
+        }
+
+        public Builder location(int x, int y) {
+            mState.mLocation = new Point(x, y);
+            return this;
+        }
+
+        public Builder rawLocation(int x, int y) {
+            mState.mRawLocation = new Point(x, y);
+            return this;
+        }
+
+        public Builder pointerCount(int count) {
+            mState.mPointerCount = count;
+            return this;
+        }
+
+        /**
+         * Adds one or more button press attributes.
+         */
+        public Builder pressButton(@Button int... buttons) {
+            for (int button : buttons) {
+                mState.mButtons.add(button);
+            }
+            return this;
+        }
+
+        /**
+         * Removes one or more button press attributes.
+         */
+        public Builder releaseButton(@Button int... buttons) {
+            for (int button : buttons) {
+                mState.mButtons.remove(button);
+            }
+            return this;
+        }
+
+        /**
+         * Adds one or more key press attributes.
+         */
+        public Builder pressKey(@Key int... keys) {
+            for (int key : keys) {
+                mState.mKeys.add(key);
+            }
+            return this;
+        }
+
+        /**
+         * Removes one or more key press attributes.
+         */
+        public Builder releaseKey(@Button int... keys) {
+            for (int key : keys) {
+                mState.mKeys.remove(key);
+            }
+            return this;
+        }
+
+        public Builder touch() {
+            type(MotionEvent.TOOL_TYPE_FINGER);
+            return this;
+        }
+
+        public Builder mouse() {
+            type(MotionEvent.TOOL_TYPE_MOUSE);
+            return this;
+        }
+
+        public Builder shift() {
+            pressKey(KeyEvent.META_SHIFT_ON);
+            return this;
+        }
+
+        public Builder unshift() {
+            releaseKey(KeyEvent.META_SHIFT_ON);
+            return this;
+        }
+
+        public Builder ctrl() {
+            pressKey(KeyEvent.META_CTRL_ON);
+            return this;
+        }
+
+        public Builder alt() {
+            pressKey(KeyEvent.META_ALT_ON);
+            return this;
+        }
+
+        public Builder primary() {
+            pressButton(MotionEvent.BUTTON_PRIMARY);
+            releaseButton(MotionEvent.BUTTON_SECONDARY);
+            releaseButton(MotionEvent.BUTTON_TERTIARY);
+            return this;
+        }
+
+        public Builder secondary() {
+            pressButton(MotionEvent.BUTTON_SECONDARY);
+            releaseButton(MotionEvent.BUTTON_PRIMARY);
+            releaseButton(MotionEvent.BUTTON_TERTIARY);
+            return this;
+        }
+
+        public Builder tertiary() {
+            pressButton(MotionEvent.BUTTON_TERTIARY);
+            releaseButton(MotionEvent.BUTTON_PRIMARY);
+            releaseButton(MotionEvent.BUTTON_SECONDARY);
+            return this;
+        }
+
+        public MotionEvent build() {
+
+            PointerProperties[] pointers = new PointerProperties[1];
+            pointers[0] = new PointerProperties();
+            pointers[0].id = 0;
+            pointers[0].toolType = mState.mToolType;
+
+            PointerCoords[] coords = new PointerCoords[1];
+            coords[0] = new PointerCoords();
+            coords[0].x = mState.mLocation.x;
+            coords[0].y = mState.mLocation.y;
+
+            int buttons = 0;
+            for (Integer button : mState.mButtons) {
+                buttons |= button;
+            }
+
+            int keys = 0;
+            for (Integer key : mState.mKeys) {
+                keys |= key;
+            }
+
+            return MotionEvent.obtain(
+                    0,     // down time
+                    1,     // event time
+                    mState.mAction,
+                    1,  // pointerCount,
+                    pointers,
+                    coords,
+                    keys,
+                    buttons,
+                    1.0f,  // x precision
+                    1.0f,  // y precision
+                    0,     // device id
+                    0,     // edge flags
+                    0,     // int source,
+                    0      // int flags
+                    );
+        }
+    }
+}
diff --git a/recyclerview-selection/tests/java/androidx/recyclerview/selection/testing/TestFocusCallbacks.java b/recyclerview-selection/tests/java/androidx/recyclerview/selection/testing/TestFocusCallbacks.java
new file mode 100644
index 0000000..46b02ad
--- /dev/null
+++ b/recyclerview-selection/tests/java/androidx/recyclerview/selection/testing/TestFocusCallbacks.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 androidx.recyclerview.selection.testing;
+
+import static org.junit.Assert.assertEquals;
+
+import android.support.v7.widget.RecyclerView;
+
+import androidx.recyclerview.selection.FocusCallbacks;
+import androidx.recyclerview.selection.ItemDetailsLookup.ItemDetails;
+
+public final class TestFocusCallbacks<K> extends FocusCallbacks<K> {
+
+    private K mFocusItemId;
+    private int mFocusPosition;
+
+    @Override
+    public void clearFocus() {
+        mFocusPosition = RecyclerView.NO_POSITION;
+        mFocusItemId = null;
+    }
+
+    @Override
+    public void focusItem(ItemDetails<K> item) {
+        mFocusItemId = item.getSelectionKey();
+        mFocusPosition = item.getPosition();
+    }
+
+    @Override
+    public int getFocusedPosition() {
+        return mFocusPosition;
+    }
+
+    @Override
+    public boolean hasFocusedItem() {
+        return mFocusItemId != null;
+    }
+
+    public void assertHasFocus(boolean focused) {
+        assertEquals(focused, hasFocusedItem());
+    }
+
+    public void assertFocused(String expectedId) {
+        assertEquals(expectedId, mFocusItemId);
+    }
+}
diff --git a/recyclerview-selection/tests/java/androidx/recyclerview/selection/testing/TestHolder.java b/recyclerview-selection/tests/java/androidx/recyclerview/selection/testing/TestHolder.java
new file mode 100644
index 0000000..25c6581
--- /dev/null
+++ b/recyclerview-selection/tests/java/androidx/recyclerview/selection/testing/TestHolder.java
@@ -0,0 +1,26 @@
+/*
+ * 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 androidx.recyclerview.selection.testing;
+
+import android.support.v7.widget.RecyclerView.ViewHolder;
+import android.view.View;
+
+public class TestHolder extends ViewHolder {
+    public TestHolder(View itemView) {
+        super(itemView);
+    }
+}
diff --git a/recyclerview-selection/tests/java/androidx/recyclerview/selection/testing/TestItemDetails.java b/recyclerview-selection/tests/java/androidx/recyclerview/selection/testing/TestItemDetails.java
new file mode 100644
index 0000000..f06e32c
--- /dev/null
+++ b/recyclerview-selection/tests/java/androidx/recyclerview/selection/testing/TestItemDetails.java
@@ -0,0 +1,96 @@
+/*
+ * 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 androidx.recyclerview.selection.testing;
+
+import android.support.v7.widget.RecyclerView;
+import android.view.MotionEvent;
+
+import androidx.recyclerview.selection.ItemDetailsLookup.ItemDetails;
+
+public final class TestItemDetails extends ItemDetails<String> {
+
+    private int mPosition;
+    private String mSelectionKey;
+    private boolean mInDragRegion;
+    private boolean mInSelectionHotspot;
+
+    public TestItemDetails() {
+        mPosition = RecyclerView.NO_POSITION;
+    }
+
+    public TestItemDetails(TestItemDetails source) {
+        mPosition = source.mPosition;
+        mSelectionKey = source.mSelectionKey;
+        mInDragRegion = source.mInDragRegion;
+        mInSelectionHotspot = source.mInSelectionHotspot;
+    }
+
+    public void at(int position) {
+        mPosition = position;  // this is both "adapter position" and "item position".
+        mSelectionKey = (position == RecyclerView.NO_POSITION)
+                ? null
+                : String.valueOf(position);
+    }
+
+    public void setInItemDragRegion(boolean inHotspot) {
+        mInDragRegion = inHotspot;
+    }
+
+    public void setInItemSelectRegion(boolean over) {
+        mInSelectionHotspot = over;
+    }
+
+    @Override
+    public boolean inDragRegion(MotionEvent event) {
+        return mInDragRegion;
+    }
+
+    @Override
+    public int hashCode() {
+        return mPosition;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+
+        if (!(o instanceof TestItemDetails)) {
+            return false;
+        }
+
+        TestItemDetails other = (TestItemDetails) o;
+        return mPosition == other.mPosition
+                && mSelectionKey == other.mSelectionKey;
+    }
+
+    @Override
+    public int getPosition() {
+        return mPosition;
+    }
+
+    @Override
+    public String getSelectionKey() {
+        return mSelectionKey;
+    }
+
+    @Override
+    public boolean inSelectionHotspot(MotionEvent e) {
+        return mInSelectionHotspot;
+    }
+}
diff --git a/recyclerview-selection/tests/java/androidx/recyclerview/selection/testing/TestItemDetailsLookup.java b/recyclerview-selection/tests/java/androidx/recyclerview/selection/testing/TestItemDetailsLookup.java
new file mode 100644
index 0000000..c201575
--- /dev/null
+++ b/recyclerview-selection/tests/java/androidx/recyclerview/selection/testing/TestItemDetailsLookup.java
@@ -0,0 +1,47 @@
+/*
+ * 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 androidx.recyclerview.selection.testing;
+
+import android.view.MotionEvent;
+
+import javax.annotation.Nullable;
+
+import androidx.recyclerview.selection.ItemDetailsLookup;
+
+/**
+ * Test impl of ItemDetailsLookup.
+ */
+public class TestItemDetailsLookup extends ItemDetailsLookup<String> {
+
+    private @Nullable TestItemDetails mItem;
+
+    @Override
+    public @Nullable ItemDetails<String> getItemDetails(MotionEvent e) {
+        return mItem;
+    }
+
+    /**
+     * Creates/installs/returns a new test document. Subsequent calls to
+     * any EventDocLookup methods will consult the newly created doc.
+     */
+    public TestItemDetails initAt(int position) {
+        TestItemDetails doc = new TestItemDetails();
+        doc.at(position);
+        mItem = doc;
+        return doc;
+    }
+}
diff --git a/recyclerview-selection/tests/java/androidx/recyclerview/selection/testing/TestItemKeyProvider.java b/recyclerview-selection/tests/java/androidx/recyclerview/selection/testing/TestItemKeyProvider.java
new file mode 100644
index 0000000..c874ac5
--- /dev/null
+++ b/recyclerview-selection/tests/java/androidx/recyclerview/selection/testing/TestItemKeyProvider.java
@@ -0,0 +1,46 @@
+/*
+ * 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 androidx.recyclerview.selection.testing;
+
+import static android.support.v4.util.Preconditions.checkArgument;
+
+import androidx.recyclerview.selection.ItemKeyProvider;
+
+/**
+ * Provides RecyclerView selection code access to stable ids backed
+ * by TestAdapter.
+ */
+public final class TestItemKeyProvider<K> extends ItemKeyProvider<K> {
+
+    private final TestAdapter<K> mAdapter;
+
+    public TestItemKeyProvider(@Scope int scope, TestAdapter<K> adapter) {
+        super(scope);
+        checkArgument(adapter != null);
+        mAdapter = adapter;
+    }
+
+    @Override
+    public K getKey(int position) {
+        return mAdapter.getSelectionKey(position);
+    }
+
+    @Override
+    public int getPosition(K key) {
+        return mAdapter.getPosition(key);
+    }
+}
diff --git a/recyclerview-selection/tests/java/androidx/recyclerview/selection/testing/TestMouseCallbacks.java b/recyclerview-selection/tests/java/androidx/recyclerview/selection/testing/TestMouseCallbacks.java
new file mode 100644
index 0000000..321ac7f
--- /dev/null
+++ b/recyclerview-selection/tests/java/androidx/recyclerview/selection/testing/TestMouseCallbacks.java
@@ -0,0 +1,39 @@
+/*
+ * 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 androidx.recyclerview.selection.testing;
+
+import static org.junit.Assert.assertTrue;
+
+import android.view.MotionEvent;
+
+import androidx.recyclerview.selection.MouseCallbacks;
+
+public final class TestMouseCallbacks extends MouseCallbacks {
+
+    private MotionEvent mLastContextEvent;
+
+    @Override
+    public boolean onContextClick(MotionEvent e) {
+        mLastContextEvent = e;
+        return false;
+    }
+
+    public void assertLastEvent(MotionEvent expected) {
+        // sadly, MotionEvent doesn't implement equals, so we compare references.
+        assertTrue(expected == mLastContextEvent);
+    }
+}
diff --git a/recyclerview-selection/tests/java/androidx/recyclerview/selection/testing/TestRunnable.java b/recyclerview-selection/tests/java/androidx/recyclerview/selection/testing/TestRunnable.java
new file mode 100644
index 0000000..7a1d45c
--- /dev/null
+++ b/recyclerview-selection/tests/java/androidx/recyclerview/selection/testing/TestRunnable.java
@@ -0,0 +1,33 @@
+/*
+ * 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 androidx.recyclerview.selection.testing;
+
+import static junit.framework.Assert.assertTrue;
+
+public final class TestRunnable implements Runnable {
+
+    private boolean mRan;
+
+    @Override
+    public void run() {
+        mRan = true;
+    }
+
+    public void assertRan() {
+        assertTrue(mRan);
+    }
+}
diff --git a/recyclerview-selection/tests/java/androidx/recyclerview/selection/testing/TestSelectionObserver.java b/recyclerview-selection/tests/java/androidx/recyclerview/selection/testing/TestSelectionObserver.java
new file mode 100644
index 0000000..3185d78
--- /dev/null
+++ b/recyclerview-selection/tests/java/androidx/recyclerview/selection/testing/TestSelectionObserver.java
@@ -0,0 +1,99 @@
+/*
+ * 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 androidx.recyclerview.selection.testing;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import androidx.recyclerview.selection.SelectionHelper.SelectionObserver;
+
+public class TestSelectionObserver<K> extends SelectionObserver<K> {
+
+    private final Set<K> mSelected = new HashSet<>();
+    private boolean mSelectionChanged = false;
+    private boolean mSelectionReset = false;
+    private boolean mSelectionRestored = false;
+
+    public void reset() {
+        mSelected.clear();
+        mSelectionChanged = false;
+        mSelectionReset = false;
+    }
+
+    @Override
+    public void onItemStateChanged(K key, boolean selected) {
+        if (selected) {
+            assertNotSelected(key);
+            mSelected.add(key);
+        } else {
+            assertSelected(key);
+            mSelected.remove(key);
+        }
+    }
+
+    @Override
+    public void onSelectionReset() {
+        mSelectionReset = true;
+        mSelected.clear();
+    }
+
+    @Override
+    public void onSelectionChanged() {
+        mSelectionChanged = true;
+    }
+
+    @Override
+    public void onSelectionRestored() {
+        mSelectionRestored = true;
+    }
+
+    void assertNoSelection() {
+        assertTrue(mSelected.isEmpty());
+    }
+
+    void assertSelectionSize(int expected) {
+        assertEquals(expected, mSelected.size());
+    }
+
+    void assertSelected(K key) {
+        assertTrue(key + " is not selected.", mSelected.contains(key));
+    }
+
+    void assertNotSelected(K key) {
+        assertFalse(key + " is already selected", mSelected.contains(key));
+    }
+
+    public void assertSelectionChanged() {
+        assertTrue(mSelectionChanged);
+    }
+
+    public void assertSelectionUnchanged() {
+        assertFalse(mSelectionChanged);
+    }
+
+    public void assertSelectionReset() {
+        assertTrue(mSelectionReset);
+    }
+
+    public void assertSelectionRestored() {
+        assertTrue(mSelectionRestored);
+    }
+}
diff --git a/recyclerview-selection/tests/java/androidx/recyclerview/selection/testing/TestSelectionPredicate.java b/recyclerview-selection/tests/java/androidx/recyclerview/selection/testing/TestSelectionPredicate.java
new file mode 100644
index 0000000..4baeb2b
--- /dev/null
+++ b/recyclerview-selection/tests/java/androidx/recyclerview/selection/testing/TestSelectionPredicate.java
@@ -0,0 +1,53 @@
+/*
+ * 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 androidx.recyclerview.selection.testing;
+
+import androidx.recyclerview.selection.SelectionHelper.SelectionPredicate;
+
+public final class TestSelectionPredicate<K> extends SelectionPredicate<K> {
+
+    private final boolean mMultiSelect;
+
+    private boolean mValue;
+
+    public TestSelectionPredicate(boolean multiSelect) {
+        mMultiSelect = multiSelect;
+    }
+
+    public TestSelectionPredicate() {
+        this(true);
+    }
+
+    public void setReturnValue(boolean value) {
+        mValue = value;
+    }
+
+    @Override
+    public boolean canSetStateForKey(K key, boolean nextState) {
+        return mValue;
+    }
+
+    @Override
+    public boolean canSetStateAtPosition(int position, boolean nextState) {
+        return mValue;
+    }
+
+    @Override
+    public boolean canSelectMultiple() {
+        return mMultiSelect;
+    }
+}
diff --git a/room/gradle/wrapper/gradle-wrapper.properties b/room/gradle/wrapper/gradle-wrapper.properties
index 2f8bf03..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.4-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.3-bin.zip
diff --git a/samples/Support7Demos/build.gradle b/samples/Support7Demos/build.gradle
index 841bcd9..6b2468b 100644
--- a/samples/Support7Demos/build.gradle
+++ b/samples/Support7Demos/build.gradle
@@ -7,6 +7,7 @@
     implementation(project(":mediarouter-v7"))
     implementation(project(":palette-v7"))
     implementation(project(":recyclerview-v7"))
+    implementation project(':recyclerview-selection')
 }
 
 android {
diff --git a/samples/Support7Demos/src/main/AndroidManifest.xml b/samples/Support7Demos/src/main/AndroidManifest.xml
index 1604267..d32ea97 100644
--- a/samples/Support7Demos/src/main/AndroidManifest.xml
+++ b/samples/Support7Demos/src/main/AndroidManifest.xml
@@ -563,7 +563,26 @@
                 <category android:name="com.example.android.supportv7.SAMPLE_CODE"/>
             </intent-filter>
         </activity>
-    </application>
 
+        <!-- Selection helper demo activity -->
+        <activity android:name=".widget.selection.simple.SimpleSelectionDemoActivity"
+                  android:label="@string/simple_selection_demo_activity"
+                  android:theme="@style/Theme.AppCompat.Light">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="com.example.android.supportv7.SAMPLE_CODE"/>
+            </intent-filter>
+        </activity>
+
+        <!-- Selection helper demo activity -->
+        <activity android:name=".widget.selection.fancy.FancySelectionDemoActivity"
+                  android:label="@string/fancy_selection_demo_activity"
+                  android:theme="@style/Theme.AppCompat.Light">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="com.example.android.supportv7.SAMPLE_CODE"/>
+            </intent-filter>
+        </activity>
+    </application>
 
 </manifest>
diff --git a/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/fancy/ContentUriKeyProvider.java b/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/fancy/ContentUriKeyProvider.java
new file mode 100644
index 0000000..4b9c158
--- /dev/null
+++ b/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/fancy/ContentUriKeyProvider.java
@@ -0,0 +1,59 @@
+/*
+ * 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 com.example.android.supportv7.widget.selection.fancy;
+
+import android.net.Uri;
+import android.support.annotation.Nullable;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import androidx.recyclerview.selection.ItemKeyProvider;
+
+class ContentUriKeyProvider extends ItemKeyProvider<Uri> {
+
+    private final Uri[] mUris;
+    private final Map<Uri, Integer> mPositions;
+
+    ContentUriKeyProvider(String authority, String[] values) {
+        // Advise the world we can supply ids/position for entire copus
+        // at any time.
+        super(SCOPE_MAPPED);
+
+        mUris = new Uri[values.length];
+        mPositions = new HashMap<>();
+
+        for (int i = 0; i < values.length; i++) {
+            mUris[i] = new Uri.Builder()
+                    .scheme("content")
+                    .encodedAuthority(authority)
+                    .appendPath(values[i])
+                    .build();
+            mPositions.put(mUris[i], i);
+        }
+    }
+
+    @Override
+    public @Nullable Uri getKey(int position) {
+        return mUris[position];
+    }
+
+    @Override
+    public int getPosition(Uri key) {
+        return mPositions.get(key);
+    }
+}
diff --git a/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/fancy/FancyDetailsLookup.java b/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/fancy/FancyDetailsLookup.java
new file mode 100644
index 0000000..249d3c2
--- /dev/null
+++ b/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/fancy/FancyDetailsLookup.java
@@ -0,0 +1,49 @@
+/*
+ * 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 com.example.android.supportv7.widget.selection.fancy;
+
+import android.net.Uri;
+import android.support.annotation.Nullable;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerView.ViewHolder;
+import android.view.MotionEvent;
+import android.view.View;
+
+import androidx.recyclerview.selection.ItemDetailsLookup;
+
+/**
+ * Access to details of an item associated with a {@link MotionEvent} instance.
+ */
+final class FancyDetailsLookup extends ItemDetailsLookup<Uri> {
+
+    private final RecyclerView mRecView;
+
+    FancyDetailsLookup(RecyclerView view) {
+        mRecView = view;
+    }
+
+    @Override
+    public ItemDetails<Uri> getItemDetails(MotionEvent e) {
+        @Nullable View view = mRecView.findChildViewUnder(e.getX(), e.getY());
+        if (view != null) {
+            ViewHolder holder = mRecView.getChildViewHolder(view);
+            if (holder instanceof FancyHolder) {
+                return ((FancyHolder) holder).getItemDetails();
+            }
+        }
+        return null;
+    }
+}
diff --git a/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/fancy/FancyHolder.java b/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/fancy/FancyHolder.java
new file mode 100644
index 0000000..f4b22b3
--- /dev/null
+++ b/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/fancy/FancyHolder.java
@@ -0,0 +1,111 @@
+/*
+ * 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 com.example.android.supportv7.widget.selection.fancy;
+
+import android.graphics.Rect;
+import android.net.Uri;
+import android.support.annotation.Nullable;
+import android.support.v7.widget.RecyclerView;
+import android.view.MotionEvent;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.example.android.supportv7.R;
+
+import androidx.recyclerview.selection.ItemDetailsLookup.ItemDetails;
+
+final class FancyHolder extends RecyclerView.ViewHolder {
+
+    private final LinearLayout mContainer;
+    public final TextView mSelector;
+    public final TextView mLabel;
+    private final ItemDetails<Uri> mDetails;
+
+    private @Nullable Uri mKey;
+
+    FancyHolder(LinearLayout layout) {
+        super(layout);
+        mContainer = layout.findViewById(R.id.container);
+        mSelector = layout.findViewById(R.id.selector);
+        mLabel = layout.findViewById(R.id.label);
+        mDetails = new ItemDetails<Uri>() {
+            @Override
+            public int getPosition() {
+                return FancyHolder.this.getAdapterPosition();
+            }
+
+            @Override
+            public Uri getSelectionKey() {
+                return FancyHolder.this.mKey;
+            }
+
+            @Override
+            public boolean inDragRegion(MotionEvent e) {
+                return FancyHolder.this.inDragRegion(e);
+            }
+
+            @Override
+            public boolean inSelectionHotspot(MotionEvent e) {
+                return FancyHolder.this.inSelectRegion(e);
+            }
+        };
+    }
+
+    void update(Uri key, String label, boolean selected) {
+        mKey = key;
+        mLabel.setText(label);
+        setSelected(selected);
+    }
+
+    private void setSelected(boolean selected) {
+        mContainer.setActivated(selected);
+        mSelector.setActivated(selected);
+    }
+
+    boolean inDragRegion(MotionEvent event) {
+        // If itemView is activated = selected, then whole region is interactive
+        if (itemView.isActivated()) {
+            return true;
+        }
+
+        // Do everything in global coordinates - it makes things simpler.
+        int[] coords = new int[2];
+        mSelector.getLocationOnScreen(coords);
+
+        Rect textBounds = new Rect();
+        mLabel.getPaint().getTextBounds(
+                mLabel.getText().toString(), 0, mLabel.getText().length(), textBounds);
+
+        Rect rect = new Rect(
+                coords[0],
+                coords[1],
+                coords[0] + mSelector.getWidth() + textBounds.width(),
+                coords[1] + Math.max(mSelector.getHeight(), textBounds.height()));
+
+        // If the tap occurred inside icon or the text, these are interactive spots.
+        return rect.contains((int) event.getRawX(), (int) event.getRawY());
+    }
+
+    boolean inSelectRegion(MotionEvent e) {
+        Rect iconRect = new Rect();
+        mSelector.getGlobalVisibleRect(iconRect);
+        return iconRect.contains((int) e.getRawX(), (int) e.getRawY());
+    }
+
+    ItemDetails<Uri> getItemDetails() {
+        return mDetails;
+    }
+}
diff --git a/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/fancy/FancySelectionDemoActivity.java b/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/fancy/FancySelectionDemoActivity.java
new file mode 100644
index 0000000..6c389d5
--- /dev/null
+++ b/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/fancy/FancySelectionDemoActivity.java
@@ -0,0 +1,265 @@
+/*
+ * 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 com.example.android.supportv7.widget.selection.fancy;
+
+import android.content.Context;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.annotation.CallSuper;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.GridLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.widget.Toast;
+
+import com.example.android.supportv7.R;
+
+import androidx.recyclerview.selection.ItemDetailsLookup.ItemDetails;
+import androidx.recyclerview.selection.ItemKeyProvider;
+import androidx.recyclerview.selection.SelectionHelper;
+import androidx.recyclerview.selection.SelectionHelper.SelectionObserver;
+import androidx.recyclerview.selection.SelectionHelperBuilder;
+import androidx.recyclerview.selection.SelectionPredicates;
+import androidx.recyclerview.selection.SelectionStorage;
+
+/**
+ * ContentPager demo activity.
+ */
+public class FancySelectionDemoActivity extends AppCompatActivity {
+
+    private static final String TAG = "SelectionDemos";
+    private static final String EXTRA_COLUMN_COUNT = "demo-column-count";
+
+    private FancySelectionDemoAdapter mAdapter;
+    private SelectionHelper<Uri> mSelectionHelper;
+    private SelectionStorage<Uri> mSelectionStorage;
+
+    private GridLayoutManager mLayout;
+    private int mColumnCount = 1;  // This will get updated when layout changes.
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.selection_demo_layout);
+        RecyclerView recView = (RecyclerView) findViewById(R.id.list);
+
+        mLayout = new GridLayoutManager(this, mColumnCount);
+        recView.setLayoutManager(mLayout);
+        mAdapter = new FancySelectionDemoAdapter(this);
+        recView.setAdapter(mAdapter);
+        ItemKeyProvider<Uri> keyProvider = mAdapter.getItemKeyProvider();
+
+        SelectionHelperBuilder<Uri> builder = new SelectionHelperBuilder<>(
+                recView,
+                keyProvider,
+                new FancyDetailsLookup(recView));
+
+        // Override default behaviors and build in multi select mode.
+        // Call .withSelectionPredicate(SelectionHelper.SelectionPredicate.SINGLE_ANYTHING)
+        // for single selection mode.
+        mSelectionHelper = builder
+                .withSelectionPredicate(SelectionPredicates.selectAnything())
+                .withTouchCallbacks(new TouchCallbacks(this))
+                .withMouseCallbacks(new MouseCallbacks(this))
+                .withActivationCallbacks(new ActivationCallbacks(this))
+                .withFocusCallbacks(new FocusCallbacks(this))
+                .withBandOverlay(R.drawable.selection_demo_band_overlay)
+                .build();
+
+        // Provide glue between activity lifecycle and selection for purposes
+        // restoring selection.
+        mSelectionStorage = new SelectionStorage<>(
+                SelectionStorage.TYPE_STRING, mSelectionHelper);
+
+        // Lazily bind SelectionHelper. Allows us to defer initialization of the
+        // SelectionHelper dependency until after the adapter is created.
+        mAdapter.bindSelectionHelper(mSelectionHelper);
+
+        // TODO: Glue selection to ActionMode, since that'll be a common practice.
+        mSelectionHelper.addObserver(
+                new SelectionObserver<Long>() {
+                    @Override
+                    public void onSelectionChanged() {
+                        Log.i(TAG, "Selection changed to: " + mSelectionHelper.getSelection());
+                    }
+                });
+
+        // Restore selection from saved state.
+        updateFromSavedState(savedInstanceState);
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle state) {
+        super.onSaveInstanceState(state);
+        mSelectionStorage.onSaveInstanceState(state);
+        state.putInt(EXTRA_COLUMN_COUNT, mColumnCount);
+    }
+
+    private void updateFromSavedState(Bundle state) {
+        mSelectionStorage.onRestoreInstanceState(state);
+
+        if (state != null) {
+            if (state.containsKey(EXTRA_COLUMN_COUNT)) {
+                mColumnCount = state.getInt(EXTRA_COLUMN_COUNT);
+                mLayout.setSpanCount(mColumnCount);
+            }
+        }
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        boolean showMenu = super.onCreateOptionsMenu(menu);
+        getMenuInflater().inflate(R.menu.selection_demo_actions, menu);
+        return showMenu;
+    }
+
+    @Override
+    @CallSuper
+    public boolean onPrepareOptionsMenu(Menu menu) {
+        super.onPrepareOptionsMenu(menu);
+        menu.findItem(R.id.option_menu_add_column).setEnabled(mColumnCount <= 3);
+        menu.findItem(R.id.option_menu_remove_column).setEnabled(mColumnCount > 1);
+        return true;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case R.id.option_menu_add_column:
+                // TODO: Add columns
+                mLayout.setSpanCount(++mColumnCount);
+                return true;
+
+            case R.id.option_menu_remove_column:
+                mLayout.setSpanCount(--mColumnCount);
+                return true;
+
+            default:
+                return super.onOptionsItemSelected(item);
+        }
+    }
+
+    @Override
+    public void onBackPressed() {
+        if (mSelectionHelper.clear()) {
+            return;
+        } else {
+            super.onBackPressed();
+        }
+    }
+
+    private static void toast(Context context, String msg) {
+        Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
+    }
+
+    @Override
+    protected void onDestroy() {
+        mSelectionHelper.clearSelection();
+        super.onDestroy();
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        mAdapter.loadData();
+    }
+
+    // Implementation of MouseInputHandler.Callbacks allows handling
+    // of higher level events, like onActivated.
+    private static final class ActivationCallbacks extends
+            androidx.recyclerview.selection.ActivationCallbacks<Uri> {
+
+        private final Context mContext;
+
+        ActivationCallbacks(Context context) {
+            mContext = context;
+        }
+
+        @Override
+        public boolean onItemActivated(ItemDetails<Uri> item, MotionEvent e) {
+            toast(mContext, "Activate item: " + item.getSelectionKey());
+            return true;
+        }
+    }
+
+    private static final class FocusCallbacks extends
+            androidx.recyclerview.selection.FocusCallbacks<Uri> {
+
+        private final Context mContext;
+
+        private FocusCallbacks(Context context) {
+            mContext = context;
+        }
+
+        @Override
+        public void focusItem(ItemDetails<Uri> item) {
+            toast(mContext, "Focused item: " + item.getSelectionKey());
+        }
+
+        @Override
+        public boolean hasFocusedItem() {
+            return false;
+        }
+
+        @Override
+        public int getFocusedPosition() {
+            return 0;
+        }
+
+        @Override
+        public void clearFocus() {
+            toast(mContext, "Cleared focus.");
+        }
+    }
+
+    // Implementation of MouseInputHandler.Callbacks allows handling
+    // of higher level events, like onActivated.
+    private static final class MouseCallbacks extends
+            androidx.recyclerview.selection.MouseCallbacks {
+
+        private final Context mContext;
+
+        MouseCallbacks(Context context) {
+            mContext = context;
+        }
+
+        @Override
+        public boolean onContextClick(MotionEvent e) {
+            toast(mContext, "Context click received.");
+            return true;
+        }
+    };
+
+    private static final class TouchCallbacks extends
+            androidx.recyclerview.selection.TouchCallbacks {
+
+        private final Context mContext;
+
+        private TouchCallbacks(Context context) {
+            mContext = context;
+        }
+
+        public boolean onDragInitiated(MotionEvent e) {
+            toast(mContext, "onDragInitiated received.");
+            return true;
+        }
+    }
+}
diff --git a/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/fancy/FancySelectionDemoAdapter.java b/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/fancy/FancySelectionDemoAdapter.java
new file mode 100644
index 0000000..204ec9e
--- /dev/null
+++ b/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/fancy/FancySelectionDemoAdapter.java
@@ -0,0 +1,116 @@
+/*
+ * 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 com.example.android.supportv7.widget.selection.fancy;
+
+import static android.support.v4.util.Preconditions.checkArgument;
+
+import android.content.Context;
+import android.net.Uri;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+import com.example.android.supportv7.Cheeses;
+import com.example.android.supportv7.R;
+
+import androidx.recyclerview.selection.ItemKeyProvider;
+import androidx.recyclerview.selection.SelectionHelper;
+
+final class FancySelectionDemoAdapter extends RecyclerView.Adapter<FancyHolder> {
+
+    private final ItemKeyProvider<Uri> mKeyProvider;
+    private final Context mContext;
+
+    // This should be replaced at "bind" time with a real test that
+    // asks SelectionHelper.
+    private SelectionTest mSelTest;
+
+    FancySelectionDemoAdapter(Context context) {
+        mContext = context;
+        mKeyProvider = new ContentUriKeyProvider("cheeses", Cheeses.sCheeseStrings);
+        mSelTest = new SelectionTest() {
+            @Override
+            public boolean isSelected(Uri id) {
+                throw new IllegalStateException(
+                        "Adapter must be initialized with SelectionHelper.");
+            }
+        };
+
+        // In the fancy edition of selection support we supply access to stable
+        // ids using content URI. Since we can map between position and selection key
+        // at will we get fancy dependent functionality like band selection and range support.
+        setHasStableIds(false);
+    }
+
+    ItemKeyProvider<Uri> getItemKeyProvider() {
+        return mKeyProvider;
+    }
+
+    // Glue together SelectionHelper and the adapter.
+    public void bindSelectionHelper(final SelectionHelper<Uri> selectionHelper) {
+        checkArgument(selectionHelper != null);
+        mSelTest = new SelectionTest() {
+            @Override
+            public boolean isSelected(Uri id) {
+                return selectionHelper.isSelected(id);
+            }
+        };
+    }
+
+    void loadData() {
+        onDataReady();
+    }
+
+    private void onDataReady() {
+        notifyDataSetChanged();
+    }
+
+    @Override
+    public int getItemCount() {
+        return Cheeses.sCheeseStrings.length;
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return position;
+    }
+
+    @Override
+    public void onBindViewHolder(FancyHolder holder, int position) {
+        Uri uri = mKeyProvider.getKey(position);
+        holder.update(uri, uri.getLastPathSegment(), mSelTest.isSelected(uri));
+    }
+
+    @Override
+    public FancyHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        LinearLayout layout = inflateLayout(mContext, parent, R.layout.selection_demo_list_item);
+        return new FancyHolder(layout);
+    }
+
+    @SuppressWarnings("TypeParameterUnusedInFormals")  // Convenience to avoid clumsy cast.
+    private static <V extends View> V inflateLayout(
+            Context context, ViewGroup parent, int layout) {
+
+        return (V) LayoutInflater.from(context).inflate(layout, parent, false);
+    }
+
+    private interface SelectionTest {
+        boolean isSelected(Uri id);
+    }
+}
diff --git a/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/simple/DemoDetailsLookup.java b/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/simple/DemoDetailsLookup.java
new file mode 100644
index 0000000..15aa086
--- /dev/null
+++ b/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/simple/DemoDetailsLookup.java
@@ -0,0 +1,48 @@
+/*
+ * 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 com.example.android.supportv7.widget.selection.simple;
+
+import android.support.annotation.Nullable;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerView.ViewHolder;
+import android.view.MotionEvent;
+import android.view.View;
+
+import androidx.recyclerview.selection.ItemDetailsLookup;
+
+/**
+ * Access to details of an item associated with a {@link MotionEvent} instance.
+ */
+final class DemoDetailsLookup extends ItemDetailsLookup<Long> {
+
+    private final RecyclerView mRecView;
+
+    DemoDetailsLookup(RecyclerView view) {
+        mRecView = view;
+    }
+
+    @Override
+    public ItemDetails<Long> getItemDetails(MotionEvent e) {
+        @Nullable View view = mRecView.findChildViewUnder(e.getX(), e.getY());
+        if (view != null) {
+            ViewHolder holder = mRecView.getChildViewHolder(view);
+            if (holder instanceof DemoHolder) {
+                return ((DemoHolder) holder).getItemDetails();
+            }
+        }
+        return null;
+    }
+}
diff --git a/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/simple/DemoHolder.java b/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/simple/DemoHolder.java
new file mode 100644
index 0000000..c1d8b99
--- /dev/null
+++ b/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/simple/DemoHolder.java
@@ -0,0 +1,108 @@
+/*
+ * 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 com.example.android.supportv7.widget.selection.simple;
+
+import android.graphics.Rect;
+import android.support.annotation.Nullable;
+import android.support.v7.widget.RecyclerView;
+import android.view.MotionEvent;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.example.android.supportv7.R;
+
+import androidx.recyclerview.selection.ItemDetailsLookup.ItemDetails;
+
+final class DemoHolder extends RecyclerView.ViewHolder {
+
+    private final LinearLayout mContainer;
+    private final TextView mSelector;
+    private final TextView mLabel;
+    private final ItemDetails<Long> mDetails;
+    private @Nullable Long mKey;
+
+    DemoHolder(LinearLayout layout) {
+        super(layout);
+        mContainer = layout.findViewById(R.id.container);
+        mSelector = layout.findViewById(R.id.selector);
+        mLabel = layout.findViewById(R.id.label);
+        mDetails = new ItemDetails<Long>() {
+            @Override
+            public int getPosition() {
+                return DemoHolder.this.getAdapterPosition();
+            }
+
+            @Override
+            public Long getSelectionKey() {
+                return DemoHolder.this.getItemId();
+            }
+
+            @Override
+            public boolean inDragRegion(MotionEvent e) {
+                return DemoHolder.this.inDragRegion(e);
+            }
+
+            @Override
+            public boolean inSelectionHotspot(MotionEvent e) {
+                return DemoHolder.this.inSelectRegion(e);
+            }
+        };
+    }
+
+    void update(String label, boolean selected) {
+        mLabel.setText(label);
+        setSelected(selected);
+    }
+
+    private void setSelected(boolean selected) {
+        mContainer.setActivated(selected);
+        mSelector.setActivated(selected);
+    }
+
+    boolean inDragRegion(MotionEvent event) {
+        // If itemView is activated = selected, then whole region is interactive
+        if (itemView.isActivated()) {
+            return true;
+        }
+
+        // Do everything in global coordinates - it makes things simpler.
+        int[] coords = new int[2];
+        mSelector.getLocationOnScreen(coords);
+
+        Rect textBounds = new Rect();
+        mLabel.getPaint().getTextBounds(
+                mLabel.getText().toString(), 0, mLabel.getText().length(), textBounds);
+
+        Rect rect = new Rect(
+                coords[0],
+                coords[1],
+                coords[0] + mSelector.getWidth() + textBounds.width(),
+                coords[1] + Math.max(mSelector.getHeight(), textBounds.height()));
+
+        // If the tap occurred inside icon or the text, these are interactive spots.
+        return rect.contains((int) event.getRawX(), (int) event.getRawY());
+    }
+
+    boolean inSelectRegion(MotionEvent e) {
+        Rect iconRect = new Rect();
+        mSelector.getGlobalVisibleRect(iconRect);
+        return iconRect.contains((int) e.getRawX(), (int) e.getRawY());
+    }
+
+    ItemDetails<Long> getItemDetails() {
+        return mDetails;
+    }
+}
diff --git a/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/simple/SimpleSelectionDemoActivity.java b/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/simple/SimpleSelectionDemoActivity.java
new file mode 100644
index 0000000..74d2fcc
--- /dev/null
+++ b/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/simple/SimpleSelectionDemoActivity.java
@@ -0,0 +1,273 @@
+/*
+ * 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 com.example.android.supportv7.widget.selection.simple;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.support.annotation.CallSuper;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.GridLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.widget.Toast;
+
+import com.example.android.supportv7.R;
+
+import androidx.recyclerview.selection.ItemDetailsLookup.ItemDetails;
+import androidx.recyclerview.selection.ItemKeyProvider;
+import androidx.recyclerview.selection.SelectionHelper;
+import androidx.recyclerview.selection.SelectionHelper.SelectionObserver;
+import androidx.recyclerview.selection.SelectionHelperBuilder;
+import androidx.recyclerview.selection.SelectionPredicates;
+import androidx.recyclerview.selection.SelectionStorage;
+import androidx.recyclerview.selection.StableIdKeyProvider;
+
+/**
+ * ContentPager demo activity.
+ */
+public class SimpleSelectionDemoActivity extends AppCompatActivity {
+
+    private static final String TAG = "SelectionDemos";
+    private static final String EXTRA_COLUMN_COUNT = "demo-column-count";
+
+    private SimpleSelectionDemoAdapter mAdapter;
+    private SelectionHelper<Long> mSelectionHelper;
+    private SelectionStorage<Long> mSelectionStorage;
+
+    private GridLayoutManager mLayout;
+    private int mColumnCount = 1;  // This will get updated when layout changes.
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.selection_demo_layout);
+        RecyclerView recView = (RecyclerView) findViewById(R.id.list);
+
+        // keyProvider depends on mAdapter.setHasStableIds(true).
+        ItemKeyProvider<Long> keyProvider = new StableIdKeyProvider(recView);
+
+        mLayout = new GridLayoutManager(this, mColumnCount);
+        recView.setLayoutManager(mLayout);
+
+        mAdapter = new SimpleSelectionDemoAdapter(this, keyProvider);
+        // The adapter is paired with a key provider that supports
+        // the native RecyclerView stableId. For this to work correctly
+        // the adapter must report that it supports stable ids.
+        mAdapter.setHasStableIds(true);
+
+        recView.setAdapter(mAdapter);
+
+        SelectionHelperBuilder<Long> builder = new SelectionHelperBuilder<>(
+                recView,
+                keyProvider,
+                new DemoDetailsLookup(recView));
+
+        // Override default behaviors and build in multi select mode.
+        // Call .withSelectionPredicate(SelectionHelper.SelectionPredicate.SINGLE_ANYTHING)
+        // for single selection mode.
+        mSelectionHelper = builder
+                .withSelectionPredicate(SelectionPredicates.selectAnything())
+                .withTouchCallbacks(new TouchCallbacks(this))
+                .withMouseCallbacks(new MouseCallbacks(this))
+                .withActivationCallbacks(new ActivationCallbacks(this))
+                .withFocusCallbacks(new FocusCallbacks(this))
+                .withBandOverlay(R.drawable.selection_demo_band_overlay)
+                .build();
+
+        // Provide glue between activity lifecycle and selection for purposes
+        // restoring selection.
+        mSelectionStorage = new SelectionStorage<>(
+                SelectionStorage.TYPE_STRING, mSelectionHelper);
+
+        // Lazily bind SelectionHelper. Allows us to defer initialization of the
+        // SelectionHelper dependency until after the adapter is created.
+        mAdapter.bindSelectionHelper(mSelectionHelper);
+
+        // TODO: Glue selection to ActionMode, since that'll be a common practice.
+        mSelectionHelper.addObserver(
+                new SelectionObserver<Long>() {
+                    @Override
+                    public void onSelectionChanged() {
+                        Log.i(TAG, "Selection changed to: " + mSelectionHelper.getSelection());
+                    }
+                });
+
+        // Restore selection from saved state.
+        updateFromSavedState(savedInstanceState);
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle state) {
+        super.onSaveInstanceState(state);
+        mSelectionStorage.onSaveInstanceState(state);
+        state.putInt(EXTRA_COLUMN_COUNT, mColumnCount);
+    }
+
+    private void updateFromSavedState(Bundle state) {
+        mSelectionStorage.onRestoreInstanceState(state);
+
+        if (state != null) {
+            if (state.containsKey(EXTRA_COLUMN_COUNT)) {
+                mColumnCount = state.getInt(EXTRA_COLUMN_COUNT);
+                mLayout.setSpanCount(mColumnCount);
+            }
+        }
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        boolean showMenu = super.onCreateOptionsMenu(menu);
+        getMenuInflater().inflate(R.menu.selection_demo_actions, menu);
+        return showMenu;
+    }
+
+    @Override
+    @CallSuper
+    public boolean onPrepareOptionsMenu(Menu menu) {
+        super.onPrepareOptionsMenu(menu);
+        menu.findItem(R.id.option_menu_add_column).setEnabled(mColumnCount <= 3);
+        menu.findItem(R.id.option_menu_remove_column).setEnabled(mColumnCount > 1);
+        return true;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case R.id.option_menu_add_column:
+                // TODO: Add columns
+                mLayout.setSpanCount(++mColumnCount);
+                return true;
+
+            case R.id.option_menu_remove_column:
+                mLayout.setSpanCount(--mColumnCount);
+                return true;
+
+            default:
+                return super.onOptionsItemSelected(item);
+        }
+    }
+
+    @Override
+    public void onBackPressed() {
+        if (mSelectionHelper.clear()) {
+            return;
+        } else {
+            super.onBackPressed();
+        }
+    }
+
+    private static void toast(Context context, String msg) {
+        Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
+    }
+
+    @Override
+    protected void onDestroy() {
+        mSelectionHelper.clearSelection();
+        super.onDestroy();
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        mAdapter.loadData();
+    }
+
+    // Implementation of MouseInputHandler.Callbacks allows handling
+    // of higher level events, like onActivated.
+    private static final class ActivationCallbacks extends
+            androidx.recyclerview.selection.ActivationCallbacks<Long> {
+
+        private final Context mContext;
+
+        ActivationCallbacks(Context context) {
+            mContext = context;
+        }
+
+        @Override
+        public boolean onItemActivated(ItemDetails<Long> item, MotionEvent e) {
+            toast(mContext, "Activate item: " + item.getSelectionKey());
+            return true;
+        }
+    }
+
+    private static final class FocusCallbacks extends
+            androidx.recyclerview.selection.FocusCallbacks<Long> {
+
+        private final Context mContext;
+
+        private FocusCallbacks(Context context) {
+            mContext = context;
+        }
+
+        @Override
+        public void focusItem(ItemDetails<Long> item) {
+            toast(mContext, "Focused item: " + item.getSelectionKey());
+        }
+
+        @Override
+        public boolean hasFocusedItem() {
+            return false;
+        }
+
+        @Override
+        public int getFocusedPosition() {
+            return 0;
+        }
+
+        @Override
+        public void clearFocus() {
+            toast(mContext, "Cleared focus.");
+        }
+    }
+
+    // Implementation of MouseInputHandler.Callbacks allows handling
+    // of higher level events, like onActivated.
+    private static final class MouseCallbacks extends
+            androidx.recyclerview.selection.MouseCallbacks {
+
+        private final Context mContext;
+
+        MouseCallbacks(Context context) {
+            mContext = context;
+        }
+
+        @Override
+        public boolean onContextClick(MotionEvent e) {
+            toast(mContext, "Context click received.");
+            return true;
+        }
+    };
+
+    private static final class TouchCallbacks extends
+            androidx.recyclerview.selection.TouchCallbacks {
+
+        private final Context mContext;
+
+        private TouchCallbacks(Context context) {
+            mContext = context;
+        }
+
+        public boolean onDragInitiated(MotionEvent e) {
+            toast(mContext, "onDragInitiated received.");
+            return true;
+        }
+    }
+}
diff --git a/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/simple/SimpleSelectionDemoAdapter.java b/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/simple/SimpleSelectionDemoAdapter.java
new file mode 100644
index 0000000..a60fda8
--- /dev/null
+++ b/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/simple/SimpleSelectionDemoAdapter.java
@@ -0,0 +1,109 @@
+/*
+ * 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 com.example.android.supportv7.widget.selection.simple;
+
+import static android.support.v4.util.Preconditions.checkArgument;
+
+import android.content.Context;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+import com.example.android.supportv7.Cheeses;
+import com.example.android.supportv7.R;
+
+import androidx.recyclerview.selection.ItemKeyProvider;
+import androidx.recyclerview.selection.SelectionHelper;
+
+final class SimpleSelectionDemoAdapter extends RecyclerView.Adapter<DemoHolder> {
+
+    private static final String TAG = "SelectionDemos";
+    private final Context mContext;
+    private final ItemKeyProvider<Long> mKeyProvider;
+
+    // This should be replaced at "bind" time with a real test that
+    // asks SelectionHelper.
+    private SelectionTest mSelTest;
+
+    SimpleSelectionDemoAdapter(Context context, ItemKeyProvider<Long> keyProvider) {
+        mContext = context;
+        mKeyProvider = keyProvider;
+        mSelTest = new SelectionTest() {
+            @Override
+            public boolean isSelected(Long id) {
+                throw new IllegalStateException(
+                        "Adapter must be initialized with SelectionHelper.");
+            }
+        };
+    }
+
+    // Glue together SelectionHelper and the adapter.
+    public void bindSelectionHelper(final SelectionHelper<Long> selectionHelper) {
+        checkArgument(selectionHelper != null);
+        mSelTest = new SelectionTest() {
+            @Override
+            public boolean isSelected(Long id) {
+                return selectionHelper.isSelected(id);
+            }
+        };
+    }
+
+    void loadData() {
+        onDataReady();
+    }
+
+    private void onDataReady() {
+        notifyDataSetChanged();
+    }
+
+    @Override
+    public int getItemCount() {
+        return Cheeses.sCheeseStrings.length;
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return position;
+    }
+
+    @Override
+    public void onBindViewHolder(DemoHolder holder, int position) {
+        Long key = getItemId(position);
+        Log.v(TAG, "Just before rendering item position=" + position + ", key=" + key);
+        holder.update(Cheeses.sCheeseStrings[position], mSelTest.isSelected(key));
+    }
+
+    @Override
+    public DemoHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        LinearLayout layout = inflateLayout(mContext, parent, R.layout.selection_demo_list_item);
+        return new DemoHolder(layout);
+    }
+
+    @SuppressWarnings("TypeParameterUnusedInFormals")  // Convenience to avoid clumsy cast.
+    private static <V extends View> V inflateLayout(
+            Context context, ViewGroup parent, int layout) {
+
+        return (V) LayoutInflater.from(context).inflate(layout, parent, false);
+    }
+
+    private interface SelectionTest {
+        boolean isSelected(Long id);
+    }
+}
diff --git a/samples/Support7Demos/src/main/res/color/selection_demo_item_selector.xml b/samples/Support7Demos/src/main/res/color/selection_demo_item_selector.xml
new file mode 100644
index 0000000..bd87b4c
--- /dev/null
+++ b/samples/Support7Demos/src/main/res/color/selection_demo_item_selector.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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:state_activated="true"
+        android:color="?android:attr/colorForeground"
+        />
+    <item
+        android:state_activated="false"
+        android:color="?android:attr/colorForeground"
+        android:alpha=".3"
+        />
+</selector>
diff --git a/samples/Support7Demos/src/main/res/drawable/selection_demo_band_overlay.xml b/samples/Support7Demos/src/main/res/drawable/selection_demo_band_overlay.xml
new file mode 100644
index 0000000..f9793aa
--- /dev/null
+++ b/samples/Support7Demos/src/main/res/drawable/selection_demo_band_overlay.xml
@@ -0,0 +1,22 @@
+<?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="#22FF0000" />
+    <stroke android:width="1dp" android:color="#44FF0000" />
+</shape>
diff --git a/samples/Support7Demos/src/main/res/drawable/selection_demo_item_background.xml b/samples/Support7Demos/src/main/res/drawable/selection_demo_item_background.xml
new file mode 100644
index 0000000..e4dbd5f
--- /dev/null
+++ b/samples/Support7Demos/src/main/res/drawable/selection_demo_item_background.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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_activated="true">
+        <color android:color="#220000FF"></color>
+    </item>
+</selector>
diff --git a/samples/Support7Demos/src/main/res/layout/selection_demo_layout.xml b/samples/Support7Demos/src/main/res/layout/selection_demo_layout.xml
new file mode 100644
index 0000000..bd85a14
--- /dev/null
+++ b/samples/Support7Demos/src/main/res/layout/selection_demo_layout.xml
@@ -0,0 +1,34 @@
+<?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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <android.support.v7.widget.RecyclerView
+        android:id="@+id/list"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:clipToPadding="false"
+        android:drawSelectorOnTop="true"
+        android:paddingBottom="5dp"
+        android:paddingEnd="0dp"
+        android:paddingStart="0dp"
+        android:paddingTop="5dp"
+        android:scrollbars="none" />
+
+</LinearLayout>
diff --git a/samples/Support7Demos/src/main/res/layout/selection_demo_list_item.xml b/samples/Support7Demos/src/main/res/layout/selection_demo_list_item.xml
new file mode 100644
index 0000000..0d4b718
--- /dev/null
+++ b/samples/Support7Demos/src/main/res/layout/selection_demo_list_item.xml
@@ -0,0 +1,53 @@
+<?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.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:paddingStart="10dp"
+    android:paddingEnd="10dp"
+    android:paddingTop="5dp"
+    android:paddingBottom="5dp"
+    android:layout_height="50dp">
+  <LinearLayout
+      android:id="@+id/container"
+      xmlns:android="http://schemas.android.com/apk/res/android"
+      android:layout_height="match_parent"
+      android:layout_width="match_parent"
+      android:background="@drawable/selection_demo_item_background">
+      <TextView
+          android:id="@+id/selector"
+          android:textSize="20sp"
+          android:textStyle="bold"
+          android:gravity="center"
+          android:layout_height="match_parent"
+          android:layout_width="40dp"
+          android:textColor="@color/selection_demo_item_selector"
+          android:pointerIcon="hand"
+          android:text="✕">
+      </TextView>
+      <TextView
+          android:id="@+id/label"
+          android:textSize="20sp"
+          android:textStyle="bold"
+          android:gravity="center_vertical"
+          android:paddingStart="10dp"
+          android:paddingEnd="10dp"
+          android:layout_height="match_parent"
+          android:layout_width="match_parent">
+      </TextView>
+  </LinearLayout>
+</LinearLayout>
diff --git a/samples/Support7Demos/src/main/res/menu/selection_demo_actions.xml b/samples/Support7Demos/src/main/res/menu/selection_demo_actions.xml
new file mode 100644
index 0000000..484d8b6
--- /dev/null
+++ b/samples/Support7Demos/src/main/res/menu/selection_demo_actions.xml
@@ -0,0 +1,24 @@
+<?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.
+-->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+   <item
+       android:id="@+id/option_menu_add_column"
+       android:title="Add column" />
+   <item
+       android:id="@+id/option_menu_remove_column"
+       android:title="Remove column" />
+</menu>
diff --git a/samples/Support7Demos/src/main/res/values/strings.xml b/samples/Support7Demos/src/main/res/values/strings.xml
index 097807f..2c4b0b6 100644
--- a/samples/Support7Demos/src/main/res/values/strings.xml
+++ b/samples/Support7Demos/src/main/res/values/strings.xml
@@ -233,10 +233,12 @@
     <string name="popup_menu_print">Print</string>
 
     <string name="list_view_activity">AppCompat/ListView styling</string>
-
     <string name="appcompat_vector_disabled">AnimatedVectorDrawableCompat does not work on devices running API v10 or below</string>
     <string name="appcompat_vector_title">AppCompat/Integrations/AnimatedVectorDrawable</string>
 
+    <string name="simple_selection_demo_activity">RecyclerView Selection</string>
+    <string name="fancy_selection_demo_activity">RecyclerView: Gesture+Pointer Selection</string>
+
     <string name="night_mode">DAY</string>
 
     <string name="text_plain_enabled">Plain enabled</string>
@@ -246,4 +248,3 @@
 
     <string name="menu_item_icon_tinting">AppCompat/Menu Item Icons</string>
 </resources>
-
diff --git a/samples/SupportCarDemos/OWNERS b/samples/SupportCarDemos/OWNERS
new file mode 100644
index 0000000..42ef8d4
--- /dev/null
+++ b/samples/SupportCarDemos/OWNERS
@@ -0,0 +1,2 @@
+ajchen@google.com
+yaoyx@google.com
\ No newline at end of file
diff --git a/samples/SupportCarDemos/build.gradle b/samples/SupportCarDemos/build.gradle
new file mode 100644
index 0000000..efd8550
--- /dev/null
+++ b/samples/SupportCarDemos/build.gradle
@@ -0,0 +1,32 @@
+apply plugin: 'com.android.application'
+
+dependencies {
+    implementation project(':car')
+}
+
+android {
+    compileSdkVersion project.ext.currentSdk
+
+    defaultConfig {
+        minSdkVersion 24
+        targetSdkVersion project.ext.currentSdk
+    }
+
+    signingConfigs {
+        debug {
+            // Use a local debug keystore to avoid build server issues.
+            storeFile project.rootProject.init.debugKeystore
+        }
+    }
+
+    lintOptions {
+        abortOnError true
+        check 'NewApi'
+    }
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+}
+
diff --git a/samples/SupportCarDemos/src/main/AndroidManifest.xml b/samples/SupportCarDemos/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..c32fcf4
--- /dev/null
+++ b/samples/SupportCarDemos/src/main/AndroidManifest.xml
@@ -0,0 +1,55 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.androidx.car">
+
+    <application android:label="@string/activity_sample_code"
+            android:supportsRtl="true"
+            android:icon="@drawable/app_sample_code"
+            android:theme="@style/CarTheme">
+
+        <activity android:name=".SupportCarDemoActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <activity android:name=".PagedListViewActivity"
+                  android:label="PagedListView"
+                  android:parentActivityName=".SupportCarDemoActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.SAMPLE_CODE" />
+            </intent-filter>
+            <meta-data android:name="android.support.PARENT_ACTIVITY"
+                       android:value=".SupportCarDemoActivity" />
+        </activity>
+
+        <activity android:name=".ListItemActivity"
+                  android:label="ListItem"
+                  android:parentActivityName=".SupportCarDemoActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.SAMPLE_CODE" />
+            </intent-filter>
+            <meta-data android:name="android.support.PARENT_ACTIVITY"
+                       android:value=".SupportCarDemoActivity" />
+        </activity>
+    </application>
+</manifest>
+
diff --git a/samples/SupportCarDemos/src/main/java/com/example/androidx/car/ListItemActivity.java b/samples/SupportCarDemos/src/main/java/com/example/androidx/car/ListItemActivity.java
new file mode 100644
index 0000000..6aa5ba6
--- /dev/null
+++ b/samples/SupportCarDemos/src/main/java/com/example/androidx/car/ListItemActivity.java
@@ -0,0 +1,223 @@
+/*
+ * 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 com.example.androidx.car;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Point;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+import android.widget.Toast;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import androidx.car.widget.ListItem;
+import androidx.car.widget.ListItemAdapter;
+import androidx.car.widget.ListItemProvider;
+import androidx.car.widget.PagedListView;
+
+/**
+ * Demo activity for {@link ListItem}.
+ */
+public class ListItemActivity extends Activity {
+
+    private static int pixelToDip(Context context, int pixels) {
+        return (int) (pixels / context.getResources().getDisplayMetrics().density);
+    }
+
+    PagedListView mPagedListView;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_paged_list_view);
+
+        mPagedListView = findViewById(R.id.paged_list_view);
+
+        ListItemAdapter adapter = new ListItemAdapter(this,
+                new SampleProvider(this));
+        mPagedListView.setAdapter(adapter);
+        mPagedListView.setMaxPages(PagedListView.UNLIMITED_PAGES);
+    }
+
+    private static class SampleProvider extends ListItemProvider {
+        private Context mContext;
+        private List<ListItem> mItems;
+
+        private View.OnClickListener mGetParentHeight = (v) -> {
+            int parentHeight = ((FrameLayout) v.getParent().getParent().getParent()).getHeight();
+            Toast.makeText(v.getContext(),
+                    "card height is " + pixelToDip(mContext, parentHeight) + " dp",
+                    Toast.LENGTH_SHORT).show();
+        };
+
+        private ListItemProvider.ListProvider mListProvider;
+
+        SampleProvider(Context context) {
+            mContext = context;
+            mItems = new ArrayList<>();
+
+            mItems.add(new ListItem.Builder(mContext)
+                    .withPrimaryActionIcon(android.R.drawable.sym_def_app_icon, true)
+                    .withTitle("single line with full icon and one action")
+                    .withAction("card height", true, mGetParentHeight)
+                    .build());
+
+            mItems.add(new ListItem.Builder(mContext)
+                    .withTitle("primary action set by drawable")
+                    .withPrimaryActionIcon(mContext.getDrawable(R.drawable.pressed_icon), true)
+                    .withViewBinder(vh -> vh.getPrimaryIcon().setClickable(true))
+                    .build());
+
+            mItems.add(new ListItem.Builder(mContext)
+                    .withPrimaryActionIcon(android.R.drawable.sym_def_app_icon, false)
+                    .withTitle("single line with small icon and clickable end icon")
+                    .withSupplementalIcon(android.R.drawable.sym_def_app_icon, true,
+                            mGetParentHeight)
+                    .build());
+
+            mItems.add(new ListItem.Builder(mContext)
+                    .withPrimaryActionEmptyIcon()
+                    .withTitle("single line with empty icon and end icon no divider")
+                    .withSupplementalIcon(android.R.drawable.sym_def_app_icon, false)
+                    .build());
+
+            mItems.add(new ListItem.Builder(mContext)
+                    .withTitle("title is single line and ellipsizes. "
+                            + mContext.getString(R.string.long_text))
+                    .withSupplementalIcon(android.R.drawable.sym_def_app_icon, true)
+                    .build());
+
+            mItems.add(new ListItem.Builder(mContext)
+                    .withPrimaryActionNoIcon()
+                    .withTitle("single line with two actions and no divider")
+                    .withActions("action 1", false,
+                            (v) -> Toast.makeText(
+                                    v.getContext(), "action 1", Toast.LENGTH_SHORT).show(),
+                            "action 2", false,
+                            (v) -> Toast.makeText(
+                                    v.getContext(), "action 2", Toast.LENGTH_SHORT).show())
+                    .build());
+
+            mItems.add(new ListItem.Builder(mContext)
+                    .withPrimaryActionNoIcon()
+                    .withTitle("single line with two actions and action 2 divider")
+                    .withActions("action 1", false,
+                            (v) -> Toast.makeText(
+                                    v.getContext(), "action 1", Toast.LENGTH_SHORT).show(),
+                            "action 2", true,
+                            (v) -> Toast.makeText(
+                                    v.getContext(), "action 2", Toast.LENGTH_SHORT).show())
+                    .build());
+
+            mItems.add(new ListItem.Builder(mContext)
+                    .withPrimaryActionNoIcon()
+                    .withTitle("single line with divider between actions. "
+                            + mContext.getString(R.string.long_text))
+                    .withActions("action 1", true,
+                            (v) -> Toast.makeText(
+                                    v.getContext(), "action 1", Toast.LENGTH_SHORT).show(),
+                            "action 2", false,
+                            (v) -> Toast.makeText(
+                                    v.getContext(), "action 2", Toast.LENGTH_SHORT).show())
+                    .build());
+
+            mItems.add(new ListItem.Builder(mContext)
+                    .withPrimaryActionIcon(android.R.drawable.sym_def_app_icon, true)
+                    .withTitle("double line with full icon and no end icon divider")
+                    .withBody("one line text")
+                    .withSupplementalIcon(android.R.drawable.sym_def_app_icon, false,
+                            mGetParentHeight)
+                    .build());
+
+            mItems.add(new ListItem.Builder(mContext)
+                    .withPrimaryActionIcon(android.R.drawable.sym_def_app_icon, false)
+                    .withTitle("double line with small icon and one action")
+                    .withBody("one line text")
+                    .withAction("card height", true, mGetParentHeight)
+                    .build());
+
+            String tenChars = "Ten Chars.";
+            mItems.add(new ListItem.Builder(mContext)
+                    .withPrimaryActionIcon(android.R.drawable.sym_def_app_icon, false)
+                    .withTitle("Card with small icon and text longer than limit")
+                    .withBody("some chars")
+                    .withBody(TextUtils.join("", Collections.nCopies(20, tenChars)))
+                    .withSupplementalIcon(android.R.drawable.sym_def_app_icon, true,
+                            mGetParentHeight)
+                    .build());
+
+            mItems.add(new ListItem.Builder(mContext)
+                    .withPrimaryActionEmptyIcon()
+                    .withTitle("double line with empty primary icon."
+                            + mContext.getString(R.string.long_text))
+                    .withBody("one line text as primary", true)
+                    .withActions("screen size", false, (v) -> {
+                        Context c = v.getContext();
+                        Point size = new Point();
+                        c.getSystemService(WindowManager.class).getDefaultDisplay().getSize(size);
+
+                        Toast.makeText(v.getContext(),
+                                String.format("%s x %s dp", pixelToDip(c, size.x),
+                                        pixelToDip(c, size.y)), Toast.LENGTH_SHORT).show();
+                    }, "card height", true, mGetParentHeight)
+                    .build());
+
+            mItems.add(new ListItem.Builder(mContext)
+                    .withTitle("double line with no primary action and one divider")
+                    .withBody("one line text as primary", true)
+                    .withActions("screen size", false, (v) -> {
+                        Context c = v.getContext();
+                        Point size = new Point();
+                        c.getSystemService(WindowManager.class).getDefaultDisplay().getSize(size);
+
+                        Toast.makeText(v.getContext(),
+                                String.format("%s x %s dp", pixelToDip(c, size.x),
+                                        pixelToDip(c, size.y)), Toast.LENGTH_SHORT).show();
+                    }, "card height", true, mGetParentHeight)
+                    .build());
+
+            mItems.add(new ListItem.Builder(mContext)
+                    .withPrimaryActionIcon(android.R.drawable.sym_def_app_icon, true)
+                    .withBody("Only body - no title is set")
+                    .withAction("card height", true, mGetParentHeight)
+                    .build());
+
+            mItems.add(new ListItem.Builder(mContext)
+                    .withPrimaryActionIcon(android.R.drawable.sym_def_app_icon, false)
+                    .withBody("Only body - no title. " + mContext.getString(R.string.long_text))
+                    .build());
+
+            mListProvider = new ListItemProvider.ListProvider(mItems);
+        }
+
+        @Override
+        public ListItem get(int position) {
+            return mListProvider.get(position);
+        }
+
+        @Override
+        public int size() {
+            return mListProvider.size();
+        }
+    }
+}
diff --git a/samples/SupportCarDemos/src/main/java/com/example/androidx/car/PagedListViewActivity.java b/samples/SupportCarDemos/src/main/java/com/example/androidx/car/PagedListViewActivity.java
new file mode 100644
index 0000000..2aa4e0c
--- /dev/null
+++ b/samples/SupportCarDemos/src/main/java/com/example/androidx/car/PagedListViewActivity.java
@@ -0,0 +1,101 @@
+/*
+ * 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 com.example.androidx.car;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import androidx.car.widget.PagedListView;
+
+/**
+ * Demo activity for PagedListView.
+ */
+public class PagedListViewActivity extends Activity {
+
+    private static final int ITEM_COUNT = 80;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_paged_list_view);
+
+        PagedListView pagedListView = findViewById(R.id.paged_list_view);
+        pagedListView.setAdapter(new DemoAdapter(ITEM_COUNT));
+
+        getActionBar().setDisplayHomeAsUpEnabled(true);
+    }
+
+    /**
+     * Adapter that populates a number of items for demo purposes.
+     */
+    public static class DemoAdapter extends RecyclerView.Adapter<DemoAdapter.ViewHolder> {
+
+        private final List<String> mItems = new ArrayList<>();
+
+        /**
+         * Generates a string for item text.
+         */
+        public static String getItemText(int index) {
+            return "Item " + index;
+        }
+
+
+        public DemoAdapter(int itemCount) {
+            for (int i = 0; i < itemCount; i++) {
+                mItems.add(getItemText(i));
+            }
+        }
+
+        @Override
+        public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+            LayoutInflater inflater = LayoutInflater.from(parent.getContext());
+            View view = inflater.inflate(R.layout.paged_list_item, parent, false);
+            return new ViewHolder(view);
+        }
+
+        @Override
+        public void onBindViewHolder(ViewHolder holder, int position) {
+            holder.mTextView.setText(mItems.get(position));
+        }
+
+        @Override
+        public int getItemCount() {
+            return mItems.size();
+        }
+
+        /**
+         * ViewHolder for DemoAdapter.
+         */
+        public static class ViewHolder extends RecyclerView.ViewHolder {
+            private TextView mTextView;
+
+            public ViewHolder(View itemView) {
+                super(itemView);
+                mTextView = itemView.findViewById(R.id.text);
+            }
+        }
+    }
+}
+
diff --git a/samples/SupportCarDemos/src/main/java/com/example/androidx/car/SupportCarDemoActivity.java b/samples/SupportCarDemos/src/main/java/com/example/androidx/car/SupportCarDemoActivity.java
new file mode 100644
index 0000000..049c5c6
--- /dev/null
+++ b/samples/SupportCarDemos/src/main/java/com/example/androidx/car/SupportCarDemoActivity.java
@@ -0,0 +1,124 @@
+/*
+ * 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 com.example.androidx.car;
+
+import android.app.ListActivity;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Home activity for car support library samples.
+ */
+public class SupportCarDemoActivity extends ListActivity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setListAdapter(new SampleAdapter(querySampleActivities()));
+    }
+
+    @Override
+    protected void onListItemClick(ListView lv, View v, int pos, long id) {
+        SampleInfo info = (SampleInfo) getListAdapter().getItem(pos);
+        startActivity(info.mIntent);
+    }
+
+    protected List<SampleInfo> querySampleActivities() {
+        Intent intent = new Intent(Intent.ACTION_MAIN, null);
+        intent.setPackage(getPackageName());
+        intent.addCategory(Intent.CATEGORY_SAMPLE_CODE);
+
+        PackageManager pm = getPackageManager();
+        List<ResolveInfo> infos = pm.queryIntentActivities(intent, 0);
+
+        ArrayList<SampleInfo> samples = new ArrayList<>();
+
+        final int count = infos.size();
+        for (int i = 0; i < count; i++) {
+            final ResolveInfo info = infos.get(i);
+            final CharSequence labelSeq = info.loadLabel(pm);
+            String label = TextUtils.isEmpty(labelSeq)
+                    ? info.activityInfo.name
+                    : labelSeq.toString();
+
+            Intent target = new Intent();
+            target.setClassName(info.activityInfo.applicationInfo.packageName,
+                    info.activityInfo.name);
+            SampleInfo sample = new SampleInfo(label, target);
+            samples.add(sample);
+        }
+
+        return samples;
+    }
+
+    static class SampleInfo {
+        String mName;
+        Intent mIntent;
+
+        SampleInfo(String name, Intent intent) {
+            this.mName = name;
+            this.mIntent = intent;
+        }
+    }
+
+    class SampleAdapter extends BaseAdapter {
+        private List<SampleInfo> mItems;
+
+        SampleAdapter(List<SampleInfo> items) {
+            mItems = items;
+        }
+
+        @Override
+        public int getCount() {
+            return mItems.size();
+        }
+
+        @Override
+        public Object getItem(int position) {
+            return mItems.get(position);
+        }
+
+        @Override
+        public long getItemId(int position) {
+            return position;
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            if (convertView == null) {
+                convertView = getLayoutInflater().inflate(android.R.layout.simple_list_item_1,
+                        parent, false);
+                convertView.setTag(convertView.findViewById(android.R.id.text1));
+            }
+            TextView tv = (TextView) convertView.getTag();
+            tv.setText(mItems.get(position).mName);
+            return convertView;
+        }
+
+    }
+}
diff --git a/samples/SupportCarDemos/src/main/res/drawable/app_sample_code.png b/samples/SupportCarDemos/src/main/res/drawable/app_sample_code.png
new file mode 100755
index 0000000..66a1984
--- /dev/null
+++ b/samples/SupportCarDemos/src/main/res/drawable/app_sample_code.png
Binary files differ
diff --git a/samples/SupportCarDemos/src/main/res/drawable/pressed_icon.xml b/samples/SupportCarDemos/src/main/res/drawable/pressed_icon.xml
new file mode 100644
index 0000000..32a497f
--- /dev/null
+++ b/samples/SupportCarDemos/src/main/res/drawable/pressed_icon.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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true" android:drawable="@android:drawable/btn_star_big_on"/>
+    <item android:drawable="@android:drawable/btn_star_big_off"/>
+</selector>
diff --git a/samples/SupportCarDemos/src/main/res/layout/activity_paged_list_view.xml b/samples/SupportCarDemos/src/main/res/layout/activity_paged_list_view.xml
new file mode 100644
index 0000000..5b9a1a5
--- /dev/null
+++ b/samples/SupportCarDemos/src/main/res/layout/activity_paged_list_view.xml
@@ -0,0 +1,29 @@
+<?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.
+  -->
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <androidx.car.widget.PagedListView
+        android:id="@+id/paged_list_view"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        app:showPagedListViewDivider="true"
+        app:offsetScrollBar="true"/>
+</FrameLayout>
+
diff --git a/samples/SupportCarDemos/src/main/res/layout/paged_list_item.xml b/samples/SupportCarDemos/src/main/res/layout/paged_list_item.xml
new file mode 100644
index 0000000..26f9c5a
--- /dev/null
+++ b/samples/SupportCarDemos/src/main/res/layout/paged_list_item.xml
@@ -0,0 +1,30 @@
+<?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.
+  -->
+<androidx.car.widget.ColumnCardView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="0dp"
+    android:layout_height="wrap_content"
+    app:cardBackgroundColor="@color/car_card">
+    <TextView
+        android:id="@+id/text"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:singleLine="true"
+        android:ellipsize="end"
+        android:textAppearance="@style/CarBody1"/>
+</androidx.car.widget.ColumnCardView>
\ No newline at end of file
diff --git a/samples/SupportCarDemos/src/main/res/values/strings.xml b/samples/SupportCarDemos/src/main/res/values/strings.xml
new file mode 100644
index 0000000..adffe89
--- /dev/null
+++ b/samples/SupportCarDemos/src/main/res/values/strings.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.
+-->
+<resources>
+    <string name="activity_sample_code">Support Car Demos</string>
+    <string name="long_text">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</string>
+</resources>
+
diff --git a/samples/SupportCarDemos/src/main/res/values/themes.xml b/samples/SupportCarDemos/src/main/res/values/themes.xml
new file mode 100644
index 0000000..4b82ecd
--- /dev/null
+++ b/samples/SupportCarDemos/src/main/res/values/themes.xml
@@ -0,0 +1,31 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android">
+    <!-- The main theme for all activities within the Car Demo. -->
+    <style name="CarTheme" parent="android:Theme.Material.Light">
+        <item name="android:windowBackground">@color/car_grey_50</item>
+        <item name="android:colorAccent">@color/car_yellow_500</item>
+        <item name="android:colorPrimary">@color/car_highlight</item>
+        <item name="android:colorPrimaryDark">@color/car_grey_300</item>
+        <item name="android:buttonStyle">@style/CarButton</item>
+        <item name="android:borderlessButtonStyle">@style/CarButton.Borderless</item>
+        <item name="android:progressBarStyleHorizontal">
+            @style/CarProgressBar.Horizontal
+        </item>
+        <item name="android:windowLightStatusBar">true</item>
+    </style>
+</resources>
diff --git a/samples/SupportDesignDemos/src/main/res/layout/design_fab.xml b/samples/SupportDesignDemos/src/main/res/layout/design_fab.xml
index ad26d5b..f00dd96 100644
--- a/samples/SupportDesignDemos/src/main/res/layout/design_fab.xml
+++ b/samples/SupportDesignDemos/src/main/res/layout/design_fab.xml
@@ -66,6 +66,21 @@
                 android:clickable="true"
                 app:fabSize="mini" />
 
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_margin="16dp"
+            android:textAppearance="@style/TextAppearance.AppCompat.Title"
+            android:text="@string/fab_size_custom" />
+
+        <android.support.design.widget.FloatingActionButton
+            android:id="@+id/custom_fab"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:layout_margin="16dp"
+            android:src="@drawable/ic_add"
+            android:clickable="true"
+            app:fabCustomSize="@dimen/custom_fab_size" />
     </LinearLayout>
 
 </FrameLayout>
\ No newline at end of file
diff --git a/samples/SupportDesignDemos/src/main/res/values/dimens.xml b/samples/SupportDesignDemos/src/main/res/values/dimens.xml
index c8a5ea9..68e4775 100644
--- a/samples/SupportDesignDemos/src/main/res/values/dimens.xml
+++ b/samples/SupportDesignDemos/src/main/res/values/dimens.xml
@@ -20,4 +20,6 @@
     <dimen name="bottom_sheet_peek_height">128dp</dimen>
 
     <dimen name="custom_snackbar_max_width">-1px</dimen>
+
+    <dimen name="custom_fab_size">45dp</dimen>
 </resources>
diff --git a/samples/SupportDesignDemos/src/main/res/values/strings.xml b/samples/SupportDesignDemos/src/main/res/values/strings.xml
index 6ebe24c..e1818af 100644
--- a/samples/SupportDesignDemos/src/main/res/values/strings.xml
+++ b/samples/SupportDesignDemos/src/main/res/values/strings.xml
@@ -37,6 +37,7 @@
 
     <string name="fab_size_normal">Normal size</string>
     <string name="fab_size_mini">Mini size</string>
+    <string name="fab_size_custom">Custom size</string>
 
     <string name="navigation_open">Open</string>
     <string name="navigation_close">Close</string>
diff --git a/samples/SupportEmojiDemos/OWNERS b/samples/SupportEmojiDemos/OWNERS
new file mode 100644
index 0000000..a2db8f4
--- /dev/null
+++ b/samples/SupportEmojiDemos/OWNERS
@@ -0,0 +1 @@
+siyamed@google.com
\ No newline at end of file
diff --git a/v17/leanback/OWNERS b/samples/SupportLeanbackDemos/OWNERS
similarity index 100%
copy from v17/leanback/OWNERS
copy to samples/SupportLeanbackDemos/OWNERS
diff --git a/samples/SupportLeanbackDemos/generatev4.py b/samples/SupportLeanbackDemos/generatev4.py
index 6a44e17..3ffec76 100755
--- a/samples/SupportLeanbackDemos/generatev4.py
+++ b/samples/SupportLeanbackDemos/generatev4.py
@@ -25,8 +25,8 @@
 def replace_xml_head(line, name):
     return line.replace('<?xml version="1.0" encoding="utf-8"?>', '<?xml version="1.0" encoding="utf-8"?>\n<!-- This file is auto-generated from {}.xml.  DO NOT MODIFY. -->\n'.format(name))
 
-file = open('src/com/example/android/leanback/GuidedStepActivity.java', 'r')
-outfile = open('src/com/example/android/leanback/GuidedStepSupportActivity.java', 'w')
+file = open('src/main/java/com/example/android/leanback/GuidedStepActivity.java', 'r')
+outfile = open('src/main/java/com/example/android/leanback/GuidedStepSupportActivity.java', 'w')
 write_java_head(outfile, "GuidedStepActivity")
 for line in file:
     line = line.replace('android.app.Fragment', 'android.support.v4.app.Fragment')
@@ -38,8 +38,8 @@
 file.close()
 outfile.close()
 
-file = open('src/com/example/android/leanback/GuidedStepHalfScreenActivity.java', 'r')
-outfile = open('src/com/example/android/leanback/GuidedStepSupportHalfScreenActivity.java', 'w')
+file = open('src/main/java/com/example/android/leanback/GuidedStepHalfScreenActivity.java', 'r')
+outfile = open('src/main/java/com/example/android/leanback/GuidedStepSupportHalfScreenActivity.java', 'w')
 write_java_head(outfile, "GuidedStepHalfScreenActivity")
 for line in file:
     line = line.replace('android.app.Fragment', 'android.support.v4.app.Fragment')
@@ -52,8 +52,8 @@
 file.close()
 outfile.close()
 
-file = open('src/com/example/android/leanback/BrowseFragment.java', 'r')
-outfile = open('src/com/example/android/leanback/BrowseSupportFragment.java', 'w')
+file = open('src/main/java/com/example/android/leanback/BrowseFragment.java', 'r')
+outfile = open('src/main/java/com/example/android/leanback/BrowseSupportFragment.java', 'w')
 write_java_head(outfile, "BrowseFragment")
 for line in file:
     line = line.replace('android.app.Fragment', 'android.support.v4.app.Fragment')
@@ -72,8 +72,8 @@
 file.close()
 outfile.close()
 
-file = open('src/com/example/android/leanback/BrowseActivity.java', 'r')
-outfile = open('src/com/example/android/leanback/BrowseSupportActivity.java', 'w')
+file = open('src/main/java/com/example/android/leanback/BrowseActivity.java', 'r')
+outfile = open('src/main/java/com/example/android/leanback/BrowseSupportActivity.java', 'w')
 write_java_head(outfile, "BrowseActivity")
 for line in file:
     line = line.replace('BrowseActivity', 'BrowseSupportActivity')
@@ -84,8 +84,8 @@
 file.close()
 outfile.close()
 
-file = open('res/layout/browse.xml', 'r')
-outfile = open('res/layout/browse_support.xml', 'w')
+file = open('src/main/res/layout/browse.xml', 'r')
+outfile = open('src/main/res/layout/browse_support.xml', 'w')
 for line in file:
     line = replace_xml_head(line, "browse")
     line = line.replace('com.example.android.leanback.BrowseFragment', 'com.example.android.leanback.BrowseSupportFragment')
@@ -94,8 +94,8 @@
 outfile.close()
 
 
-file = open('src/com/example/android/leanback/DetailsFragment.java', 'r')
-outfile = open('src/com/example/android/leanback/DetailsSupportFragment.java', 'w')
+file = open('src/main/java/com/example/android/leanback/DetailsFragment.java', 'r')
+outfile = open('src/main/java/com/example/android/leanback/DetailsSupportFragment.java', 'w')
 write_java_head(outfile, "DetailsFragment")
 for line in file:
     line = line.replace('android.app.Fragment', 'android.support.v4.app.Fragment')
@@ -108,8 +108,8 @@
 file.close()
 outfile.close()
 
-file = open('src/com/example/android/leanback/NewDetailsFragment.java', 'r')
-outfile = open('src/com/example/android/leanback/NewDetailsSupportFragment.java', 'w')
+file = open('src/main/java/com/example/android/leanback/NewDetailsFragment.java', 'r')
+outfile = open('src/main/java/com/example/android/leanback/NewDetailsSupportFragment.java', 'w')
 write_java_head(outfile, "NewDetailsFragment")
 for line in file:
     line = line.replace('android.app.Fragment', 'android.support.v4.app.Fragment')
@@ -127,8 +127,8 @@
 file.close()
 outfile.close()
 
-file = open('src/com/example/android/leanback/DetailsActivity.java', 'r')
-outfile = open('src/com/example/android/leanback/DetailsSupportActivity.java', 'w')
+file = open('src/main/java/com/example/android/leanback/DetailsActivity.java', 'r')
+outfile = open('src/main/java/com/example/android/leanback/DetailsSupportActivity.java', 'w')
 write_java_head(outfile, "DetailsActivity")
 for line in file:
     line = line.replace('DetailsActivity', 'DetailsSupportActivity')
@@ -141,8 +141,8 @@
 file.close()
 outfile.close()
 
-file = open('src/com/example/android/leanback/SearchDetailsActivity.java', 'r')
-outfile = open('src/com/example/android/leanback/SearchDetailsSupportActivity.java', 'w')
+file = open('src/main/java/com/example/android/leanback/SearchDetailsActivity.java', 'r')
+outfile = open('src/main/java/com/example/android/leanback/SearchDetailsSupportActivity.java', 'w')
 write_java_head(outfile, "SearchDetailsActivity")
 for line in file:
     line = line.replace('DetailsActivity', 'DetailsSupportActivity')
@@ -151,8 +151,8 @@
 outfile.close()
 
 
-file = open('src/com/example/android/leanback/SearchFragment.java', 'r')
-outfile = open('src/com/example/android/leanback/SearchSupportFragment.java', 'w')
+file = open('src/main/java/com/example/android/leanback/SearchFragment.java', 'r')
+outfile = open('src/main/java/com/example/android/leanback/SearchSupportFragment.java', 'w')
 write_java_head(outfile, "SearchFragment")
 for line in file:
     line = line.replace('SearchFragment', 'SearchSupportFragment')
@@ -161,8 +161,8 @@
 file.close()
 outfile.close()
 
-file = open('src/com/example/android/leanback/SearchActivity.java', 'r')
-outfile = open('src/com/example/android/leanback/SearchSupportActivity.java', 'w')
+file = open('src/main/java/com/example/android/leanback/SearchActivity.java', 'r')
+outfile = open('src/main/java/com/example/android/leanback/SearchSupportActivity.java', 'w')
 write_java_head(outfile, "SearchActivity")
 for line in file:
     line = line.replace('SearchActivity', 'SearchSupportActivity')
@@ -175,8 +175,8 @@
 file.close()
 outfile.close()
 
-file = open('res/layout/search.xml', 'r')
-outfile = open('res/layout/search_support.xml', 'w')
+file = open('src/main/res/layout/search.xml', 'r')
+outfile = open('src/main/res/layout/search_support.xml', 'w')
 for line in file:
     line = replace_xml_head(line, "search")
     line = line.replace('com.example.android.leanback.SearchFragment', 'com.example.android.leanback.SearchSupportFragment')
@@ -184,8 +184,8 @@
 file.close()
 outfile.close()
 
-file = open('src/com/example/android/leanback/VerticalGridFragment.java', 'r')
-outfile = open('src/com/example/android/leanback/VerticalGridSupportFragment.java', 'w')
+file = open('src/main/java/com/example/android/leanback/VerticalGridFragment.java', 'r')
+outfile = open('src/main/java/com/example/android/leanback/VerticalGridSupportFragment.java', 'w')
 write_java_head(outfile, "VerticalGridFragment")
 for line in file:
     line = line.replace('VerticalGridFragment', 'VerticalGridSupportFragment')
@@ -195,8 +195,8 @@
 file.close()
 outfile.close()
 
-file = open('src/com/example/android/leanback/VerticalGridActivity.java', 'r')
-outfile = open('src/com/example/android/leanback/VerticalGridSupportActivity.java', 'w')
+file = open('src/main/java/com/example/android/leanback/VerticalGridActivity.java', 'r')
+outfile = open('src/main/java/com/example/android/leanback/VerticalGridSupportActivity.java', 'w')
 write_java_head(outfile, "VerticalGridActivity")
 for line in file:
     line = line.replace('VerticalGridActivity', 'VerticalGridSupportActivity')
@@ -209,8 +209,8 @@
 file.close()
 outfile.close()
 
-file = open('res/layout/vertical_grid.xml', 'r')
-outfile = open('res/layout/vertical_grid_support.xml', 'w')
+file = open('src/main/res/layout/vertical_grid.xml', 'r')
+outfile = open('src/main/res/layout/vertical_grid_support.xml', 'w')
 for line in file:
     line = replace_xml_head(line, "vertical_grid")
     line = line.replace('com.example.android.leanback.VerticalGridFragment', 'com.example.android.leanback.VerticalGridSupportFragment')
@@ -219,8 +219,8 @@
 outfile.close()
 
 
-file = open('src/com/example/android/leanback/ErrorFragment.java', 'r')
-outfile = open('src/com/example/android/leanback/ErrorSupportFragment.java', 'w')
+file = open('src/main/java/com/example/android/leanback/ErrorFragment.java', 'r')
+outfile = open('src/main/java/com/example/android/leanback/ErrorSupportFragment.java', 'w')
 write_java_head(outfile, "ErrorFragment")
 for line in file:
     line = line.replace('ErrorFragment', 'ErrorSupportFragment')
@@ -228,8 +228,8 @@
 file.close()
 outfile.close()
 
-file = open('src/com/example/android/leanback/BrowseErrorActivity.java', 'r')
-outfile = open('src/com/example/android/leanback/BrowseErrorSupportActivity.java', 'w')
+file = open('src/main/java/com/example/android/leanback/BrowseErrorActivity.java', 'r')
+outfile = open('src/main/java/com/example/android/leanback/BrowseErrorSupportActivity.java', 'w')
 write_java_head(outfile, "BrowseErrorActivity")
 for line in file:
     line = line.replace('BrowseErrorActivity', 'BrowseErrorSupportActivity')
@@ -244,8 +244,8 @@
 file.close()
 outfile.close()
 
-file = open('src/com/example/android/leanback/RowsFragment.java', 'r')
-outfile = open('src/com/example/android/leanback/RowsSupportFragment.java', 'w')
+file = open('src/main/java/com/example/android/leanback/RowsFragment.java', 'r')
+outfile = open('src/main/java/com/example/android/leanback/RowsSupportFragment.java', 'w')
 write_java_head(outfile, "RowsFragment")
 for line in file:
     line = line.replace('RowsFragment', 'RowsSupportFragment')
@@ -254,8 +254,8 @@
 file.close()
 outfile.close()
 
-file = open('src/com/example/android/leanback/RowsActivity.java', 'r')
-outfile = open('src/com/example/android/leanback/RowsSupportActivity.java', 'w')
+file = open('src/main/java/com/example/android/leanback/RowsActivity.java', 'r')
+outfile = open('src/main/java/com/example/android/leanback/RowsSupportActivity.java', 'w')
 write_java_head(outfile, "RowsActivity")
 for line in file:
     line = line.replace('RowsActivity', 'RowsSupportActivity')
@@ -269,8 +269,8 @@
 file.close()
 outfile.close()
 
-file = open('res/layout/rows.xml', 'r')
-outfile = open('res/layout/rows_support.xml', 'w')
+file = open('src/main/res/layout/rows.xml', 'r')
+outfile = open('src/main/res/layout/rows_support.xml', 'w')
 for line in file:
     line = replace_xml_head(line, "rows")
     line = line.replace('com.example.android.leanback.RowsFragment', 'com.example.android.leanback.RowsSupportFragment')
@@ -278,8 +278,8 @@
 file.close()
 outfile.close()
 
-file = open('src/com/example/android/leanback/PlaybackFragment.java', 'r')
-outfile = open('src/com/example/android/leanback/PlaybackSupportFragment.java', 'w')
+file = open('src/main/java/com/example/android/leanback/PlaybackFragment.java', 'r')
+outfile = open('src/main/java/com/example/android/leanback/PlaybackSupportFragment.java', 'w')
 write_java_head(outfile, "PlaybackFragment")
 for line in file:
     line = line.replace('PlaybackFragment', 'PlaybackSupportFragment')
@@ -288,8 +288,8 @@
 file.close()
 outfile.close()
 
-file = open('src/com/example/android/leanback/PlaybackActivity.java', 'r')
-outfile = open('src/com/example/android/leanback/PlaybackSupportActivity.java', 'w')
+file = open('src/main/java/com/example/android/leanback/PlaybackActivity.java', 'r')
+outfile = open('src/main/java/com/example/android/leanback/PlaybackSupportActivity.java', 'w')
 write_java_head(outfile, "PlaybackActivity")
 for line in file:
     line = line.replace('PlaybackActivity', 'PlaybackSupportActivity')
@@ -300,8 +300,8 @@
 file.close()
 outfile.close()
 
-file = open('res/layout/playback_activity.xml', 'r')
-outfile = open('res/layout/playback_activity_support.xml', 'w')
+file = open('src/main/res/layout/playback_activity.xml', 'r')
+outfile = open('src/main/res/layout/playback_activity_support.xml', 'w')
 for line in file:
     line = replace_xml_head(line, "playback_controls")
     line = line.replace('com.example.android.leanback.PlaybackFragment', 'com.example.android.leanback.PlaybackSupportFragment')
@@ -309,8 +309,8 @@
 file.close()
 outfile.close()
 
-file = open('src/com/example/android/leanback/PlaybackTransportControlFragment.java', 'r')
-outfile = open('src/com/example/android/leanback/PlaybackTransportControlSupportFragment.java', 'w')
+file = open('src/main/java/com/example/android/leanback/PlaybackTransportControlFragment.java', 'r')
+outfile = open('src/main/java/com/example/android/leanback/PlaybackTransportControlSupportFragment.java', 'w')
 write_java_head(outfile, "PlaybackTransportControlFragment")
 for line in file:
     line = line.replace('PlaybackFragment', 'PlaybackSupportFragment')
@@ -320,8 +320,8 @@
 file.close()
 outfile.close()
 
-file = open('src/com/example/android/leanback/PlaybackTransportControlActivity.java', 'r')
-outfile = open('src/com/example/android/leanback/PlaybackTransportControlSupportActivity.java', 'w')
+file = open('src/main/java/com/example/android/leanback/PlaybackTransportControlActivity.java', 'r')
+outfile = open('src/main/java/com/example/android/leanback/PlaybackTransportControlSupportActivity.java', 'w')
 write_java_head(outfile, "PlaybackTransportControlActivity")
 for line in file:
     line = line.replace('PlaybackTransportControlActivity', 'PlaybackTransportControlSupportActivity')
@@ -332,8 +332,8 @@
 file.close()
 outfile.close()
 
-file = open('res/layout/playback_transportcontrol_activity.xml', 'r')
-outfile = open('res/layout/playback_transportcontrol_activity_support.xml', 'w')
+file = open('src/main/res/layout/playback_transportcontrol_activity.xml', 'r')
+outfile = open('src/main/res/layout/playback_transportcontrol_activity_support.xml', 'w')
 for line in file:
     line = replace_xml_head(line, "playback_transportcontrols")
     line = line.replace('com.example.android.leanback.PlaybackTransportControlFragment', 'com.example.android.leanback.PlaybackTransportControlSupportFragment')
@@ -341,45 +341,8 @@
 file.close()
 outfile.close()
 
-
-
-file = open('src/com/example/android/leanback/PlaybackOverlayFragment.java', 'r')
-outfile = open('src/com/example/android/leanback/PlaybackOverlaySupportFragment.java', 'w')
-write_java_head(outfile, "PlaybackOverlayFragment")
-for line in file:
-    line = line.replace('PlaybackOverlayFragment', 'PlaybackOverlaySupportFragment')
-    line = line.replace('PlaybackControlHelper', 'PlaybackControlSupportHelper')
-    line = line.replace('PlaybackOverlayActivity', 'PlaybackOverlaySupportActivity')
-    outfile.write(line)
-file.close()
-outfile.close()
-
-
-file = open('src/com/example/android/leanback/PlaybackControlHelper.java', 'r')
-outfile = open('src/com/example/android/leanback/PlaybackControlSupportHelper.java', 'w')
-write_java_head(outfile, "PlaybackControlHelper")
-for line in file:
-    line = line.replace('PlaybackControlHelper', 'PlaybackControlSupportHelper')
-    line = line.replace('PlaybackControlGlue', 'PlaybackControlSupportGlue')
-    line = line.replace('PlaybackOverlayFragment', 'PlaybackOverlaySupportFragment')
-    outfile.write(line)
-file.close()
-outfile.close()
-
-file = open('src/com/example/android/leanback/PlaybackOverlayActivity.java', 'r')
-outfile = open('src/com/example/android/leanback/PlaybackOverlaySupportActivity.java', 'w')
-write_java_head(outfile, "PlaybackOverlayActivity")
-for line in file:
-    line = line.replace('PlaybackOverlayActivity', 'PlaybackOverlaySupportActivity')
-    line = line.replace('extends Activity', 'extends FragmentActivity')
-    line = line.replace('R.layout.playback_controls', 'R.layout.playback_controls_support')
-    line = line.replace('android.app.Activity', 'android.support.v4.app.FragmentActivity')
-    outfile.write(line)
-file.close()
-outfile.close()
-
-file = open('res/layout/playback_controls.xml', 'r')
-outfile = open('res/layout/playback_controls_support.xml', 'w')
+file = open('src/main/res/layout/playback_controls.xml', 'r')
+outfile = open('src/main/res/layout/playback_controls_support.xml', 'w')
 for line in file:
     line = replace_xml_head(line, "playback_controls")
     line = line.replace('com.example.android.leanback.PlaybackOverlayFragment', 'com.example.android.leanback.PlaybackOverlaySupportFragment')
@@ -387,8 +350,8 @@
 file.close()
 outfile.close()
 
-file = open('src/com/example/android/leanback/OnboardingActivity.java', 'r')
-outfile = open('src/com/example/android/leanback/OnboardingSupportActivity.java', 'w')
+file = open('src/main/java/com/example/android/leanback/OnboardingActivity.java', 'r')
+outfile = open('src/main/java/com/example/android/leanback/OnboardingSupportActivity.java', 'w')
 write_java_head(outfile, "OnboardingActivity")
 for line in file:
     line = line.replace('android.app.Fragment', 'android.support.v4.app.Fragment')
@@ -401,8 +364,8 @@
 file.close()
 outfile.close()
 
-file = open('src/com/example/android/leanback/OnboardingDemoFragment.java', 'r')
-outfile = open('src/com/example/android/leanback/OnboardingDemoSupportFragment.java', 'w')
+file = open('src/main/java/com/example/android/leanback/OnboardingDemoFragment.java', 'r')
+outfile = open('src/main/java/com/example/android/leanback/OnboardingDemoSupportFragment.java', 'w')
 write_java_head(outfile, "OnboardingDemoFragment")
 for line in file:
     line = line.replace('android.app.Fragment', 'android.support.v4.app.Fragment')
@@ -414,8 +377,8 @@
 file.close()
 outfile.close()
 
-file = open('src/com/example/android/leanback/SampleVideoFragment.java', 'r')
-outfile = open('src/com/example/android/leanback/SampleVideoSupportFragment.java', 'w')
+file = open('src/main/java/com/example/android/leanback/SampleVideoFragment.java', 'r')
+outfile = open('src/main/java/com/example/android/leanback/SampleVideoSupportFragment.java', 'w')
 write_java_head(outfile, "OnboardingDemoFragment")
 for line in file:
     line = line.replace('android.app.Fragment', 'android.support.v4.app.Fragment')
@@ -426,8 +389,8 @@
 file.close()
 outfile.close()
 
-file = open('src/com/example/android/leanback/VideoActivity.java', 'r')
-outfile = open('src/com/example/android/leanback/VideoSupportActivity.java', 'w')
+file = open('src/main/java/com/example/android/leanback/VideoActivity.java', 'r')
+outfile = open('src/main/java/com/example/android/leanback/VideoSupportActivity.java', 'w')
 write_java_head(outfile, "OnboardingDemoFragment")
 for line in file:
     line = line.replace('android.app.Fragment', 'android.support.v4.app.Fragment')
diff --git a/samples/SupportLeanbackDemos/src/main/java/com/example/android/leanback/BrowseFragment.java b/samples/SupportLeanbackDemos/src/main/java/com/example/android/leanback/BrowseFragment.java
index 7b3f8f7..eb0b684 100644
--- a/samples/SupportLeanbackDemos/src/main/java/com/example/android/leanback/BrowseFragment.java
+++ b/samples/SupportLeanbackDemos/src/main/java/com/example/android/leanback/BrowseFragment.java
@@ -144,7 +144,6 @@
         ListRowPresenter listRowPresenter = new ListRowPresenter();
         listRowPresenter.setNumRows(1);
         mRowsAdapter = new ArrayObjectAdapter(listRowPresenter);
-        setAdapter(mRowsAdapter);
     }
 
     private void loadData() {
@@ -164,6 +163,7 @@
         mRowsAdapter.add(new PageRow(new HeaderItem(HEADER_ID2, "Page Row 1")));
 
         mRowsAdapter.add(new PageRow(new HeaderItem(HEADER_ID3, "Page Row 2")));
+        setAdapter(mRowsAdapter);
     }
 
     private ArrayObjectAdapter createListRowAdapter(int i) {
@@ -273,7 +273,7 @@
         final CardPresenter mCardPresenter2 = new CardPresenter(R.style.MyImageCardViewTheme);
 
         void loadFragmentData() {
-            ArrayObjectAdapter adapter = (ArrayObjectAdapter) getAdapter();
+            ArrayObjectAdapter adapter = new ArrayObjectAdapter(new ListRowPresenter());
             for (int i = 0; i < 4; i++) {
                 ListRow row = new ListRow(new HeaderItem("Row " + i), createListRowAdapter(i));
                 adapter.add(row);
@@ -282,11 +282,10 @@
                 getMainFragmentAdapter().getFragmentHost()
                         .notifyDataReady(getMainFragmentAdapter());
             }
+            setAdapter(adapter);
         }
 
         public SampleRowsFragment() {
-            ArrayObjectAdapter adapter = new ArrayObjectAdapter(new ListRowPresenter());
-            setAdapter(adapter);
             // simulates late data loading:
             new Handler().postDelayed(new Runnable() {
                 @Override
diff --git a/samples/SupportLeanbackDemos/src/main/java/com/example/android/leanback/BrowseSupportFragment.java b/samples/SupportLeanbackDemos/src/main/java/com/example/android/leanback/BrowseSupportFragment.java
index 395c498..7afd24f 100644
--- a/samples/SupportLeanbackDemos/src/main/java/com/example/android/leanback/BrowseSupportFragment.java
+++ b/samples/SupportLeanbackDemos/src/main/java/com/example/android/leanback/BrowseSupportFragment.java
@@ -147,7 +147,6 @@
         ListRowPresenter listRowPresenter = new ListRowPresenter();
         listRowPresenter.setNumRows(1);
         mRowsAdapter = new ArrayObjectAdapter(listRowPresenter);
-        setAdapter(mRowsAdapter);
     }
 
     private void loadData() {
@@ -167,6 +166,7 @@
         mRowsAdapter.add(new PageRow(new HeaderItem(HEADER_ID2, "Page Row 1")));
 
         mRowsAdapter.add(new PageRow(new HeaderItem(HEADER_ID3, "Page Row 2")));
+        setAdapter(mRowsAdapter);
     }
 
     private ArrayObjectAdapter createListRowAdapter(int i) {
@@ -276,7 +276,7 @@
         final CardPresenter mCardPresenter2 = new CardPresenter(R.style.MyImageCardViewTheme);
 
         void loadFragmentData() {
-            ArrayObjectAdapter adapter = (ArrayObjectAdapter) getAdapter();
+            ArrayObjectAdapter adapter = new ArrayObjectAdapter(new ListRowPresenter());
             for (int i = 0; i < 4; i++) {
                 ListRow row = new ListRow(new HeaderItem("Row " + i), createListRowAdapter(i));
                 adapter.add(row);
@@ -285,11 +285,10 @@
                 getMainFragmentAdapter().getFragmentHost()
                         .notifyDataReady(getMainFragmentAdapter());
             }
+            setAdapter(adapter);
         }
 
         public SampleRowsSupportFragment() {
-            ArrayObjectAdapter adapter = new ArrayObjectAdapter(new ListRowPresenter());
-            setAdapter(adapter);
             // simulates late data loading:
             new Handler().postDelayed(new Runnable() {
                 @Override
diff --git a/samples/SupportLeanbackDemos/src/main/java/com/example/android/leanback/DetailsSupportFragment.java b/samples/SupportLeanbackDemos/src/main/java/com/example/android/leanback/DetailsSupportFragment.java
index 0f15590..1af248f 100644
--- a/samples/SupportLeanbackDemos/src/main/java/com/example/android/leanback/DetailsSupportFragment.java
+++ b/samples/SupportLeanbackDemos/src/main/java/com/example/android/leanback/DetailsSupportFragment.java
@@ -119,7 +119,7 @@
                     actions.clear(ACTION_RENT);
                     dor.setItem(mPhotoItem.getTitle() + "(Rented)");
                 } else if (action.getId() == ACTION_PLAY) {
-                    Intent intent = new Intent(context, PlaybackSupportActivity.class);
+                    Intent intent = new Intent(context, PlaybackActivity.class);
                     getActivity().startActivity(intent);
                 }
             }
diff --git a/samples/SupportLeanbackDemos/src/main/java/com/example/android/leanback/GuidedStepActivity.java b/samples/SupportLeanbackDemos/src/main/java/com/example/android/leanback/GuidedStepActivity.java
index 7f898f4..7d20046 100644
--- a/samples/SupportLeanbackDemos/src/main/java/com/example/android/leanback/GuidedStepActivity.java
+++ b/samples/SupportLeanbackDemos/src/main/java/com/example/android/leanback/GuidedStepActivity.java
@@ -55,6 +55,7 @@
     private static final int PAYMENT = 6;
     private static final int NEW_PAYMENT = 7;
     private static final int PAYMENT_EXPIRE = 8;
+    private static final int REFRESH = 9;
 
     private static final long RADIO_ID_BASE = 0;
     private static final long CHECKBOX_ID_BASE = 100;
@@ -222,6 +223,10 @@
                     .description("Let's do it")
                     .build());
             actions.add(new GuidedAction.Builder(context)
+                    .id(REFRESH)
+                    .title("Refresh")
+                    .build());
+            actions.add(new GuidedAction.Builder(context)
                     .clickAction(GuidedAction.ACTION_ID_CANCEL)
                     .description("Never mind")
                     .build());
@@ -232,6 +237,24 @@
             FragmentManager fm = getFragmentManager();
             if (action.getId() == GuidedAction.ACTION_ID_CONTINUE) {
                 GuidedStepFragment.add(fm, new SecondStepFragment(), R.id.lb_guidedstep_host);
+            } else if (action.getId() == REFRESH) {
+                // swap actions position and change content:
+                Context context = getActivity();
+                ArrayList<GuidedAction> newActions = new ArrayList();
+                newActions.add(new GuidedAction.Builder(context)
+                        .id(REFRESH)
+                        .title("Refresh done")
+                        .build());
+                newActions.add(new GuidedAction.Builder(context)
+                        .clickAction(GuidedAction.ACTION_ID_CONTINUE)
+                        .description("Let's do it")
+                        .build());
+                newActions.add(new GuidedAction.Builder(context)
+                        .clickAction(GuidedAction.ACTION_ID_CANCEL)
+                        .description("Never mind")
+                        .build());
+                //setActionsDiffCallback(null);
+                setActions(newActions);
             } else if (action.getId() == GuidedAction.ACTION_ID_CANCEL){
                 finishGuidedStepFragments();
             }
diff --git a/samples/SupportLeanbackDemos/src/main/java/com/example/android/leanback/GuidedStepSupportActivity.java b/samples/SupportLeanbackDemos/src/main/java/com/example/android/leanback/GuidedStepSupportActivity.java
index c0f9361..6782b63 100644
--- a/samples/SupportLeanbackDemos/src/main/java/com/example/android/leanback/GuidedStepSupportActivity.java
+++ b/samples/SupportLeanbackDemos/src/main/java/com/example/android/leanback/GuidedStepSupportActivity.java
@@ -58,6 +58,7 @@
     private static final int PAYMENT = 6;
     private static final int NEW_PAYMENT = 7;
     private static final int PAYMENT_EXPIRE = 8;
+    private static final int REFRESH = 9;
 
     private static final long RADIO_ID_BASE = 0;
     private static final long CHECKBOX_ID_BASE = 100;
@@ -225,6 +226,10 @@
                     .description("Let's do it")
                     .build());
             actions.add(new GuidedAction.Builder(context)
+                    .id(REFRESH)
+                    .title("Refresh")
+                    .build());
+            actions.add(new GuidedAction.Builder(context)
                     .clickAction(GuidedAction.ACTION_ID_CANCEL)
                     .description("Never mind")
                     .build());
@@ -235,6 +240,24 @@
             FragmentManager fm = getFragmentManager();
             if (action.getId() == GuidedAction.ACTION_ID_CONTINUE) {
                 GuidedStepSupportFragment.add(fm, new SecondStepFragment(), R.id.lb_guidedstep_host);
+            } else if (action.getId() == REFRESH) {
+                // swap actions position and change content:
+                Context context = getActivity();
+                ArrayList<GuidedAction> newActions = new ArrayList();
+                newActions.add(new GuidedAction.Builder(context)
+                        .id(REFRESH)
+                        .title("Refresh done")
+                        .build());
+                newActions.add(new GuidedAction.Builder(context)
+                        .clickAction(GuidedAction.ACTION_ID_CONTINUE)
+                        .description("Let's do it")
+                        .build());
+                newActions.add(new GuidedAction.Builder(context)
+                        .clickAction(GuidedAction.ACTION_ID_CANCEL)
+                        .description("Never mind")
+                        .build());
+                //setActionsDiffCallback(null);
+                setActions(newActions);
             } else if (action.getId() == GuidedAction.ACTION_ID_CANCEL){
                 finishGuidedStepSupportFragments();
             }
diff --git a/samples/SupportLeanbackDemos/src/main/java/com/example/android/leanback/NewDetailsSupportFragment.java b/samples/SupportLeanbackDemos/src/main/java/com/example/android/leanback/NewDetailsSupportFragment.java
index 6002cf3..b2ff5b2 100644
--- a/samples/SupportLeanbackDemos/src/main/java/com/example/android/leanback/NewDetailsSupportFragment.java
+++ b/samples/SupportLeanbackDemos/src/main/java/com/example/android/leanback/NewDetailsSupportFragment.java
@@ -178,7 +178,7 @@
                             mDetailsBackground.switchToVideo();
                         }
                     } else {
-                        Intent intent = new Intent(context, PlaybackSupportActivity.class);
+                        Intent intent = new Intent(context, PlaybackActivity.class);
                         getActivity().startActivity(intent);
                     }
                 } else if (action.getId() == ACTION_RENT) {
@@ -193,14 +193,14 @@
                         setupMainVideo();
                         mDetailsBackground.switchToVideo();
                     } else {
-                        Intent intent = new Intent(context, PlaybackSupportActivity.class);
+                        Intent intent = new Intent(context, PlaybackActivity.class);
                         getActivity().startActivity(intent);
                     }
                 } else if (action.getId() == ACTION_PLAY) {
                     if (TEST_BACKGROUND_PLAYER) {
                         mDetailsBackground.switchToVideo();
                     } else {
-                        Intent intent = new Intent(context, PlaybackSupportActivity.class);
+                        Intent intent = new Intent(context, PlaybackActivity.class);
                         getActivity().startActivity(intent);
                     }
                 }
diff --git a/v17/leanback/OWNERS b/samples/SupportLeanbackJank/OWNERS
similarity index 100%
copy from v17/leanback/OWNERS
copy to samples/SupportLeanbackJank/OWNERS
diff --git a/v17/leanback/OWNERS b/samples/SupportLeanbackShowcase/OWNERS
similarity index 100%
copy from v17/leanback/OWNERS
copy to samples/SupportLeanbackShowcase/OWNERS
diff --git a/samples/SupportLeanbackShowcase/build.gradle b/samples/SupportLeanbackShowcase/build.gradle
index 74f1e76..287a234 100644
--- a/samples/SupportLeanbackShowcase/build.gradle
+++ b/samples/SupportLeanbackShowcase/build.gradle
@@ -18,7 +18,7 @@
         jcenter()
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:2.2.1'
+        classpath 'com.android.tools.build:gradle:3.0.0-rc1'
 
         // NOTE: Do not place your application dependencies here; they belong
         // in the individual module build.gradle files
diff --git a/samples/SupportWearDemos/OWNERS b/samples/SupportWearDemos/OWNERS
new file mode 100644
index 0000000..9cd6e52
--- /dev/null
+++ b/samples/SupportWearDemos/OWNERS
@@ -0,0 +1,2 @@
+amad@google.com
+griff@google.com
\ No newline at end of file
diff --git a/samples/SupportWearDemos/build.gradle b/samples/SupportWearDemos/build.gradle
index 4c2b471..96da969 100644
--- a/samples/SupportWearDemos/build.gradle
+++ b/samples/SupportWearDemos/build.gradle
@@ -18,6 +18,7 @@
 
 dependencies {
     implementation(project(":wear"))
+    implementation project(path: ':appcompat-v7')
 }
 
 android {
diff --git a/samples/SupportWearDemos/src/main/AndroidManifest.xml b/samples/SupportWearDemos/src/main/AndroidManifest.xml
index 957a539..b70982b 100644
--- a/samples/SupportWearDemos/src/main/AndroidManifest.xml
+++ b/samples/SupportWearDemos/src/main/AndroidManifest.xml
@@ -29,6 +29,8 @@
         <activity android:name=".app.RoundedDrawableDemo" />
         <activity android:name=".app.drawers.WearableDrawersDemo" android:exported="true" />
         <activity android:name=".app.AmbientModeDemo" />
+        <activity android:name=".app.AlertDialogDemo"
+            android:theme="@style/Theme.AppCompat.Light" />
         <activity android:name=".app.MainDemoActivity">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
diff --git a/samples/SupportWearDemos/src/main/java/com/example/android/support/wear/app/AlertDialogDemo.java b/samples/SupportWearDemos/src/main/java/com/example/android/support/wear/app/AlertDialogDemo.java
new file mode 100644
index 0000000..35a32ce
--- /dev/null
+++ b/samples/SupportWearDemos/src/main/java/com/example/android/support/wear/app/AlertDialogDemo.java
@@ -0,0 +1,77 @@
+/*
+ * 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 com.example.android.support.wear.app;
+
+import android.app.Activity;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v7.app.AlertDialog;
+import android.view.View;
+import android.widget.Button;
+
+import com.example.android.support.wear.R;
+
+/**
+ * Demo for AlertDialog on Wear.
+ */
+public class AlertDialogDemo extends Activity {
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.alert_dialog_demo);
+
+        AlertDialog v7Dialog = createV7Dialog();
+        android.app.AlertDialog frameworkDialog = createFrameworkDialog();
+
+        Button v7Trigger = findViewById(R.id.v7_dialog_button);
+        v7Trigger.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                v7Dialog.show();
+            }
+        });
+
+        Button frameworkTrigger = findViewById(R.id.framework_dialog_button);
+        frameworkTrigger.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                frameworkDialog.show();
+            }
+        });
+    }
+
+    private AlertDialog createV7Dialog() {
+        Drawable drawable = getDrawable(R.drawable.app_sample_code);
+        return new AlertDialog.Builder(this)
+                .setTitle("AppCompatDialog")
+                .setMessage("Lorem ipsum dolor...")
+                .setPositiveButton("Ok", null)
+                .setPositiveButtonIcon(drawable)
+                .setNegativeButton("Cancel", null)
+                .create();
+    }
+
+    private android.app.AlertDialog createFrameworkDialog() {
+        return new android.app.AlertDialog.Builder(this)
+                .setTitle("FrameworkDialog")
+                .setMessage("Lorem ipsum dolor...")
+                .setPositiveButton("Ok", null)
+                .setNegativeButton("Cancel", null)
+                .create();
+    }
+}
diff --git a/samples/SupportWearDemos/src/main/java/com/example/android/support/wear/app/MainDemoActivity.java b/samples/SupportWearDemos/src/main/java/com/example/android/support/wear/app/MainDemoActivity.java
index 0227559..3c50d92 100644
--- a/samples/SupportWearDemos/src/main/java/com/example/android/support/wear/app/MainDemoActivity.java
+++ b/samples/SupportWearDemos/src/main/java/com/example/android/support/wear/app/MainDemoActivity.java
@@ -29,7 +29,7 @@
 
 import com.example.android.support.wear.app.drawers.WearableDrawersDemo;
 
-import java.util.HashMap;
+import java.util.LinkedHashMap;
 import java.util.Map;
 
 /**
@@ -51,7 +51,7 @@
     }
 
     private Map<String, Intent> createContentMap() {
-        Map<String, Intent> contentMap = new HashMap<>();
+        Map<String, Intent> contentMap = new LinkedHashMap<>();
         contentMap.put("Wearable Recycler View", new Intent(
                 this, SimpleWearableRecyclerViewDemo.class));
         contentMap.put("Wearable Switch", new Intent(
@@ -64,6 +64,8 @@
                 this, RoundedDrawableDemo.class));
         contentMap.put("Ambient Fragment", new Intent(
                 this, AmbientModeDemo.class));
+        contentMap.put("Alert Dialog (v7)", new Intent(
+                this, AlertDialogDemo.class));
 
         return contentMap;
     }
diff --git a/samples/SupportWearDemos/src/main/res/layout/alert_dialog_demo.xml b/samples/SupportWearDemos/src/main/res/layout/alert_dialog_demo.xml
new file mode 100644
index 0000000..5df4f4c
--- /dev/null
+++ b/samples/SupportWearDemos/src/main/res/layout/alert_dialog_demo.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<android.support.wear.widget.BoxInsetLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical"
+        app:boxedEdges="all">
+
+        <Button
+            android:id="@+id/v7_dialog_button"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="Show V7 dialog"/>
+
+        <Button
+            android:id="@+id/framework_dialog_button"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="Show Framework dialog"/>
+    </LinearLayout>
+
+</android.support.wear.widget.BoxInsetLayout>
diff --git a/settings.gradle b/settings.gradle
index c281bb1..1eccbcf 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -40,6 +40,9 @@
 include ':recyclerview-v7'
 project(':recyclerview-v7').projectDir = new File(rootDir, 'v7/recyclerview')
 
+include ':recyclerview-selection'
+project(':recyclerview-selection').projectDir = new File(rootDir, 'recyclerview-selection')
+
 include ':cardview-v7'
 project(':cardview-v7').projectDir = new File(rootDir, 'v7/cardview')
 
@@ -50,13 +53,13 @@
 project(':preference-v14').projectDir = new File(rootDir, 'v14/preference')
 
 include ':preference-leanback-v17'
-project(':preference-leanback-v17').projectDir = new File(rootDir, 'v17/preference-leanback')
+project(':preference-leanback-v17').projectDir = new File(rootDir, 'preference-leanback')
 
 include ':support-v13'
 project(':support-v13').projectDir = new File(rootDir, 'v13')
 
 include ':leanback-v17'
-project(':leanback-v17').projectDir = new File(rootDir, 'v17/leanback')
+project(':leanback-v17').projectDir = new File(rootDir, 'leanback')
 
 include ':design'
 project(':design').projectDir = new File(rootDir, 'design')
@@ -103,6 +106,12 @@
 include ':support-content'
 project(':support-content').projectDir = new File(rootDir, 'content')
 
+include ':car'
+project(':car').projectDir = new File(rootDir, 'car')
+
+include ':webkit'
+project(':webkit').projectDir = new File(rootDir, 'webkit')
+
 /////////////////////////////
 //
 // Samples
@@ -156,6 +165,8 @@
 include ':support-emoji-demos'
 project(':support-emoji-demos').projectDir = new File(samplesRoot, 'SupportEmojiDemos')
 
+include ':support-car-demos'
+project(':support-car-demos').projectDir = new File(samplesRoot, 'SupportCarDemos')
 /////////////////////////////
 //
 // Testing libraries
@@ -172,13 +183,19 @@
 /////////////////////////////
 
 include ':support-media-compat-test-client'
-project(':support-media-compat-test-client').projectDir = new File(rootDir, 'media-compat-test-client')
+project(':support-media-compat-test-client').projectDir = new File(rootDir, 'media-compat/version-compat-tests/current/client')
+
+include ':support-media-compat-test-client-previous'
+project(':support-media-compat-test-client-previous').projectDir = new File(rootDir, 'media-compat/version-compat-tests/previous/client')
 
 include ':support-media-compat-test-service'
-project(':support-media-compat-test-service').projectDir = new File(rootDir, 'media-compat-test-service')
+project(':support-media-compat-test-service').projectDir = new File(rootDir, 'media-compat/version-compat-tests/current/service')
+
+include ':support-media-compat-test-service-previous'
+project(':support-media-compat-test-service-previous').projectDir = new File(rootDir, 'media-compat/version-compat-tests/previous/service')
 
 include ':support-media-compat-test-lib'
-project(':support-media-compat-test-lib').projectDir = new File(rootDir, 'media-compat-test-lib')
+project(':support-media-compat-test-lib').projectDir = new File(rootDir, 'media-compat/version-compat-tests/lib')
 
 /////////////////////////////
 //
diff --git a/testutils/build.gradle b/testutils/build.gradle
index 7aafa96..074ab34 100644
--- a/testutils/build.gradle
+++ b/testutils/build.gradle
@@ -21,7 +21,14 @@
 }
 
 dependencies {
-    api(JUNIT)
+    api(project(":support-fragment"))
+    api(project(":appcompat-v7"))
+
+    implementation(TEST_RUNNER)
+    implementation(ESPRESSO_CORE)
+    implementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
+    implementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
+    implementation(JUNIT)
 }
 
 android {
diff --git a/testutils/src/main/java/android/support/testutils/AppCompatActivityUtils.java b/testutils/src/main/java/android/support/testutils/AppCompatActivityUtils.java
new file mode 100644
index 0000000..49ccc1b
--- /dev/null
+++ b/testutils/src/main/java/android/support/testutils/AppCompatActivityUtils.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.testutils;
+
+import static org.junit.Assert.assertTrue;
+
+import android.os.Looper;
+import android.support.test.rule.ActivityTestRule;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Utility methods for testing AppCompat activities.
+ */
+public class AppCompatActivityUtils {
+    private static final Runnable DO_NOTHING = new Runnable() {
+        @Override
+        public void run() {
+        }
+    };
+
+    /**
+     * Waits for the execution of the provided activity test rule.
+     *
+     * @param rule Activity test rule to wait for.
+     */
+    public static void waitForExecution(
+            final ActivityTestRule<? extends RecreatedAppCompatActivity> rule) {
+        // Wait for two cycles. When starting a postponed transition, it will post to
+        // the UI thread and then the execution will be added onto the queue after that.
+        // The two-cycle wait makes sure fragments have the opportunity to complete both
+        // before returning.
+        try {
+            rule.runOnUiThread(DO_NOTHING);
+            rule.runOnUiThread(DO_NOTHING);
+        } catch (Throwable throwable) {
+            throw new RuntimeException(throwable);
+        }
+    }
+
+    private static void runOnUiThreadRethrow(
+            ActivityTestRule<? extends RecreatedAppCompatActivity> rule, Runnable r) {
+        if (Looper.getMainLooper() == Looper.myLooper()) {
+            r.run();
+        } else {
+            try {
+                rule.runOnUiThread(r);
+            } catch (Throwable t) {
+                throw new RuntimeException(t);
+            }
+        }
+    }
+
+    /**
+     * Restarts the RecreatedAppCompatActivity and waits for the new activity to be resumed.
+     *
+     * @return The newly-restarted RecreatedAppCompatActivity
+     */
+    public static <T extends RecreatedAppCompatActivity> T recreateActivity(
+            ActivityTestRule<? extends RecreatedAppCompatActivity> rule, final T activity)
+            throws InterruptedException {
+        // Now switch the orientation
+        RecreatedAppCompatActivity.sResumed = new CountDownLatch(1);
+        RecreatedAppCompatActivity.sDestroyed = new CountDownLatch(1);
+
+        runOnUiThreadRethrow(rule, new Runnable() {
+            @Override
+            public void run() {
+                activity.recreate();
+            }
+        });
+        assertTrue(RecreatedAppCompatActivity.sResumed.await(1, TimeUnit.SECONDS));
+        assertTrue(RecreatedAppCompatActivity.sDestroyed.await(1, TimeUnit.SECONDS));
+        T newActivity = (T) RecreatedAppCompatActivity.sActivity;
+
+        waitForExecution(rule);
+
+        RecreatedAppCompatActivity.clearState();
+        return newActivity;
+    }
+}
diff --git a/testutils/src/main/java/android/support/testutils/FragmentActivityUtils.java b/testutils/src/main/java/android/support/testutils/FragmentActivityUtils.java
new file mode 100644
index 0000000..7d12deb
--- /dev/null
+++ b/testutils/src/main/java/android/support/testutils/FragmentActivityUtils.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.testutils;
+
+import static org.junit.Assert.assertTrue;
+
+import android.app.Activity;
+import android.os.Looper;
+import android.support.test.rule.ActivityTestRule;
+import android.support.v4.app.FragmentActivity;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Utility methods for testing fragment activities.
+ */
+public class FragmentActivityUtils {
+    private static final Runnable DO_NOTHING = new Runnable() {
+        @Override
+        public void run() {
+        }
+    };
+
+    private static void waitForExecution(final ActivityTestRule<? extends FragmentActivity> rule) {
+        // Wait for two cycles. When starting a postponed transition, it will post to
+        // the UI thread and then the execution will be added onto the queue after that.
+        // The two-cycle wait makes sure fragments have the opportunity to complete both
+        // before returning.
+        try {
+            rule.runOnUiThread(DO_NOTHING);
+            rule.runOnUiThread(DO_NOTHING);
+        } catch (Throwable throwable) {
+            throw new RuntimeException(throwable);
+        }
+    }
+
+    private static void runOnUiThreadRethrow(ActivityTestRule<? extends Activity> rule,
+            Runnable r) {
+        if (Looper.getMainLooper() == Looper.myLooper()) {
+            r.run();
+        } else {
+            try {
+                rule.runOnUiThread(r);
+            } catch (Throwable t) {
+                throw new RuntimeException(t);
+            }
+        }
+    }
+
+    /**
+     * Restarts the RecreatedActivity and waits for the new activity to be resumed.
+     *
+     * @return The newly-restarted Activity
+     */
+    public static <T extends RecreatedActivity> T recreateActivity(
+            ActivityTestRule<? extends RecreatedActivity> rule, final T activity)
+            throws InterruptedException {
+        // Now switch the orientation
+        RecreatedActivity.sResumed = new CountDownLatch(1);
+        RecreatedActivity.sDestroyed = new CountDownLatch(1);
+
+        runOnUiThreadRethrow(rule, new Runnable() {
+            @Override
+            public void run() {
+                activity.recreate();
+            }
+        });
+        assertTrue(RecreatedActivity.sResumed.await(1, TimeUnit.SECONDS));
+        assertTrue(RecreatedActivity.sDestroyed.await(1, TimeUnit.SECONDS));
+        T newActivity = (T) RecreatedActivity.sActivity;
+
+        waitForExecution(rule);
+
+        RecreatedActivity.clearState();
+        return newActivity;
+    }
+}
diff --git a/testutils/src/main/java/android/support/testutils/RecreatedActivity.java b/testutils/src/main/java/android/support/testutils/RecreatedActivity.java
new file mode 100644
index 0000000..aaea3a9
--- /dev/null
+++ b/testutils/src/main/java/android/support/testutils/RecreatedActivity.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.testutils;
+
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.test.rule.ActivityTestRule;
+import android.support.v4.app.FragmentActivity;
+
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * Extension of {@link FragmentActivity} that keeps track of when it is recreated.
+ * In order to use this class, have your activity extend it and call
+ * {@link FragmentActivityUtils#recreateActivity(ActivityTestRule, RecreatedActivity)} API.
+ */
+public class RecreatedActivity extends FragmentActivity {
+    // These must be cleared after each test using clearState()
+    public static RecreatedActivity sActivity;
+    public static CountDownLatch sResumed;
+    public static CountDownLatch sDestroyed;
+
+    static void clearState() {
+        sActivity = null;
+        sResumed = null;
+        sDestroyed = null;
+    }
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        sActivity = this;
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        if (sResumed != null) {
+            sResumed.countDown();
+        }
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        if (sDestroyed != null) {
+            sDestroyed.countDown();
+        }
+    }
+}
diff --git a/testutils/src/main/java/android/support/testutils/RecreatedAppCompatActivity.java b/testutils/src/main/java/android/support/testutils/RecreatedAppCompatActivity.java
new file mode 100644
index 0000000..d5645a3
--- /dev/null
+++ b/testutils/src/main/java/android/support/testutils/RecreatedAppCompatActivity.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.testutils;
+
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.test.rule.ActivityTestRule;
+import android.support.v7.app.AppCompatActivity;
+
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * Extension of {@link AppCompatActivity} that keeps track of when it is recreated.
+ * In order to use this class, have your activity extend it and call
+ * {@link AppCompatActivityUtils#recreateActivity(ActivityTestRule, RecreatedAppCompatActivity)}
+ * API.
+ */
+public class RecreatedAppCompatActivity extends AppCompatActivity {
+    // These must be cleared after each test using clearState()
+    public static RecreatedAppCompatActivity sActivity;
+    public static CountDownLatch sResumed;
+    public static CountDownLatch sDestroyed;
+
+    static void clearState() {
+        sActivity = null;
+        sResumed = null;
+        sDestroyed = null;
+    }
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        sActivity = this;
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        if (sResumed != null) {
+            sResumed.countDown();
+        }
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        if (sDestroyed != null) {
+            sDestroyed.countDown();
+        }
+    }
+}
diff --git a/transition/Android.mk b/transition/Android.mk
index cbff183..8c76d6b 100644
--- a/transition/Android.mk
+++ b/transition/Android.mk
@@ -27,13 +27,7 @@
 LOCAL_MODULE := android-support-transition
 LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
 LOCAL_SRC_FILES := \
-    $(call all-java-files-under,base) \
-    $(call all-java-files-under,api14) \
-    $(call all-java-files-under,api18) \
-    $(call all-java-files-under,api19) \
-    $(call all-java-files-under,api21) \
-    $(call all-java-files-under,api22) \
-    $(call all-java-files-under,src)
+    $(call all-java-files-under,src/main/java)
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 LOCAL_SHARED_ANDROID_LIBRARIES := \
     android-support-annotations \
diff --git a/transition/api/current.txt b/transition/api/current.txt
index 805fcfc..b939299 100644
--- a/transition/api/current.txt
+++ b/transition/api/current.txt
@@ -118,7 +118,7 @@
     method public void setSlideEdge(int);
   }
 
-  public abstract class Transition {
+  public abstract class Transition implements java.lang.Cloneable {
     ctor public Transition();
     ctor public Transition(android.content.Context, android.util.AttributeSet);
     method public android.support.transition.Transition addListener(android.support.transition.Transition.TransitionListener);
diff --git a/transition/build.gradle b/transition/build.gradle
index d9f2452..dcf3a76 100644
--- a/transition/build.gradle
+++ b/transition/build.gradle
@@ -11,8 +11,8 @@
     api(project(":support-compat"))
     compileOnly project(':support-fragment')
 
-    androidTestImplementation(TEST_RUNNER, libs.exclude_annotations)
-    androidTestImplementation(ESPRESSO_CORE, libs.exclude_annotations)
+    androidTestImplementation(TEST_RUNNER)
+    androidTestImplementation(ESPRESSO_CORE)
     androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
     androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
     androidTestImplementation(project(":support-v4"))
@@ -25,15 +25,6 @@
     }
 
     sourceSets {
-        main.java.srcDirs = [
-                'base',
-                'api14',
-                'api18',
-                'api19',
-                'api21',
-                'api22',
-                'src'
-        ]
         main.res.srcDirs = [
                 'res',
                 'res-public'
diff --git a/transition/src/android/support/transition/AutoTransition.java b/transition/src/android/support/transition/AutoTransition.java
deleted file mode 100644
index 02b49e2..0000000
--- a/transition/src/android/support/transition/AutoTransition.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.transition;
-
-import android.content.Context;
-import android.util.AttributeSet;
-
-/**
- * Utility class for creating a default transition that automatically fades,
- * moves, and resizes views during a scene change.
- *
- * <p>An AutoTransition can be described in a resource file by using the
- * tag <code>autoTransition</code>, along with the other standard
- * attributes of {@link Transition}.</p>
- */
-public class AutoTransition extends TransitionSet {
-
-    /**
-     * Constructs an AutoTransition object, which is a TransitionSet which
-     * first fades out disappearing targets, then moves and resizes existing
-     * targets, and finally fades in appearing targets.
-     */
-    public AutoTransition() {
-        init();
-    }
-
-    public AutoTransition(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        init();
-    }
-
-    private void init() {
-        setOrdering(ORDERING_SEQUENTIAL);
-        addTransition(new Fade(Fade.OUT)).
-                addTransition(new ChangeBounds()).
-                addTransition(new Fade(Fade.IN));
-    }
-
-}
diff --git a/transition/src/android/support/transition/Transition.java b/transition/src/android/support/transition/Transition.java
deleted file mode 100644
index 04cc57b..0000000
--- a/transition/src/android/support/transition/Transition.java
+++ /dev/null
@@ -1,2437 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.transition;
-
-import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.TimeInterpolator;
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.content.res.XmlResourceParser;
-import android.graphics.Path;
-import android.graphics.Rect;
-import android.support.annotation.IdRes;
-import android.support.annotation.IntDef;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.annotation.RestrictTo;
-import android.support.v4.content.res.TypedArrayUtils;
-import android.support.v4.util.ArrayMap;
-import android.support.v4.util.LongSparseArray;
-import android.support.v4.view.ViewCompat;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.util.SparseArray;
-import android.util.SparseIntArray;
-import android.view.InflateException;
-import android.view.SurfaceView;
-import android.view.TextureView;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.animation.AnimationUtils;
-import android.widget.ListView;
-import android.widget.Spinner;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.StringTokenizer;
-
-/**
- * A Transition holds information about animations that will be run on its
- * targets during a scene change. Subclasses of this abstract class may
- * choreograph several child transitions ({@link TransitionSet} or they may
- * perform custom animations themselves. Any Transition has two main jobs:
- * (1) capture property values, and (2) play animations based on changes to
- * captured property values. A custom transition knows what property values
- * on View objects are of interest to it, and also knows how to animate
- * changes to those values. For example, the {@link Fade} transition tracks
- * changes to visibility-related properties and is able to construct and run
- * animations that fade items in or out based on changes to those properties.
- *
- * <p>Note: Transitions may not work correctly with either {@link SurfaceView}
- * or {@link TextureView}, due to the way that these views are displayed
- * on the screen. For SurfaceView, the problem is that the view is updated from
- * a non-UI thread, so changes to the view due to transitions (such as moving
- * and resizing the view) may be out of sync with the display inside those bounds.
- * TextureView is more compatible with transitions in general, but some
- * specific transitions (such as {@link Fade}) may not be compatible
- * with TextureView because they rely on {@link android.view.ViewOverlay}
- * functionality, which does not currently work with TextureView.</p>
- *
- * <p>Transitions can be declared in XML resource files inside the <code>res/transition</code>
- * directory. Transition resources consist of a tag name for one of the Transition
- * subclasses along with attributes to define some of the attributes of that transition.
- * For example, here is a minimal resource file that declares a {@link ChangeBounds}
- * transition:</p>
- *
- * <pre>
- *     &lt;changeBounds/&gt;
- * </pre>
- *
- * <p>Note that attributes for the transition are not required, just as they are
- * optional when declared in code; Transitions created from XML resources will use
- * the same defaults as their code-created equivalents. Here is a slightly more
- * elaborate example which declares a {@link TransitionSet} transition with
- * {@link ChangeBounds} and {@link Fade} child transitions:</p>
- *
- * <pre>
- *     &lt;transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
- *          android:transitionOrdering="sequential"&gt;
- *         &lt;changeBounds/&gt;
- *         &lt;fade android:fadingMode="fade_out"&gt;
- *             &lt;targets&gt;
- *                 &lt;target android:targetId="@id/grayscaleContainer"/&gt;
- *             &lt;/targets&gt;
- *         &lt;/fade&gt;
- *     &lt;/transitionSet&gt;
- * </pre>
- *
- * <p>In this example, the transitionOrdering attribute is used on the TransitionSet
- * object to change from the default {@link TransitionSet#ORDERING_TOGETHER} behavior
- * to be {@link TransitionSet#ORDERING_SEQUENTIAL} instead. Also, the {@link Fade}
- * transition uses a fadingMode of {@link Fade#OUT} instead of the default
- * out-in behavior. Finally, note the use of the <code>targets</code> sub-tag, which
- * takes a set of {code target} tags, each of which lists a specific <code>targetId</code> which
- * this transition acts upon. Use of targets is optional, but can be used to either limit the time
- * spent checking attributes on unchanging views, or limiting the types of animations run on
- * specific views. In this case, we know that only the <code>grayscaleContainer</code> will be
- * disappearing, so we choose to limit the {@link Fade} transition to only that view.</p>
- */
-public abstract class Transition implements Cloneable {
-
-    private static final String LOG_TAG = "Transition";
-    static final boolean DBG = false;
-
-    /**
-     * With {@link #setMatchOrder(int...)}, chooses to match by View instance.
-     */
-    public static final int MATCH_INSTANCE = 0x1;
-    private static final int MATCH_FIRST = MATCH_INSTANCE;
-
-    /**
-     * With {@link #setMatchOrder(int...)}, chooses to match by
-     * {@link android.view.View#getTransitionName()}. Null names will not be matched.
-     */
-    public static final int MATCH_NAME = 0x2;
-
-    /**
-     * With {@link #setMatchOrder(int...)}, chooses to match by
-     * {@link android.view.View#getId()}. Negative IDs will not be matched.
-     */
-    public static final int MATCH_ID = 0x3;
-
-    /**
-     * With {@link #setMatchOrder(int...)}, chooses to match by the {@link android.widget.Adapter}
-     * item id. When {@link android.widget.Adapter#hasStableIds()} returns false, no match
-     * will be made for items.
-     */
-    public static final int MATCH_ITEM_ID = 0x4;
-
-    private static final int MATCH_LAST = MATCH_ITEM_ID;
-
-    /** @hide */
-    @RestrictTo(LIBRARY_GROUP)
-    @IntDef({MATCH_INSTANCE, MATCH_NAME, MATCH_ID, MATCH_ITEM_ID})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface MatchOrder {
-    }
-
-    private static final String MATCH_INSTANCE_STR = "instance";
-    private static final String MATCH_NAME_STR = "name";
-    private static final String MATCH_ID_STR = "id";
-    private static final String MATCH_ITEM_ID_STR = "itemId";
-
-    private static final int[] DEFAULT_MATCH_ORDER = {
-            MATCH_NAME,
-            MATCH_INSTANCE,
-            MATCH_ID,
-            MATCH_ITEM_ID,
-    };
-
-    private static final PathMotion STRAIGHT_PATH_MOTION = new PathMotion() {
-        @Override
-        public Path getPath(float startX, float startY, float endX, float endY) {
-            Path path = new Path();
-            path.moveTo(startX, startY);
-            path.lineTo(endX, endY);
-            return path;
-        }
-    };
-
-    private String mName = getClass().getName();
-
-    private long mStartDelay = -1;
-    long mDuration = -1;
-    private TimeInterpolator mInterpolator = null;
-    ArrayList<Integer> mTargetIds = new ArrayList<>();
-    ArrayList<View> mTargets = new ArrayList<>();
-    private ArrayList<String> mTargetNames = null;
-    private ArrayList<Class> mTargetTypes = null;
-    private ArrayList<Integer> mTargetIdExcludes = null;
-    private ArrayList<View> mTargetExcludes = null;
-    private ArrayList<Class> mTargetTypeExcludes = null;
-    private ArrayList<String> mTargetNameExcludes = null;
-    private ArrayList<Integer> mTargetIdChildExcludes = null;
-    private ArrayList<View> mTargetChildExcludes = null;
-    private ArrayList<Class> mTargetTypeChildExcludes = null;
-    private TransitionValuesMaps mStartValues = new TransitionValuesMaps();
-    private TransitionValuesMaps mEndValues = new TransitionValuesMaps();
-    TransitionSet mParent = null;
-    private int[] mMatchOrder = DEFAULT_MATCH_ORDER;
-    private ArrayList<TransitionValues> mStartValuesList; // only valid after playTransition starts
-    private ArrayList<TransitionValues> mEndValuesList; // only valid after playTransitions starts
-
-    // Per-animator information used for later canceling when future transitions overlap
-    private static ThreadLocal<ArrayMap<Animator, Transition.AnimationInfo>> sRunningAnimators =
-            new ThreadLocal<>();
-
-    // Scene Root is set at createAnimator() time in the cloned Transition
-    private ViewGroup mSceneRoot = null;
-
-    // Whether removing views from their parent is possible. This is only for views
-    // in the start scene, which are no longer in the view hierarchy. This property
-    // is determined by whether the previous Scene was created from a layout
-    // resource, and thus the views from the exited scene are going away anyway
-    // and can be removed as necessary to achieve a particular effect, such as
-    // removing them from parents to add them to overlays.
-    boolean mCanRemoveViews = false;
-
-    // Track all animators in use in case the transition gets canceled and needs to
-    // cancel running animators
-    private ArrayList<Animator> mCurrentAnimators = new ArrayList<>();
-
-    // Number of per-target instances of this Transition currently running. This count is
-    // determined by calls to start() and end()
-    private int mNumInstances = 0;
-
-    // Whether this transition is currently paused, due to a call to pause()
-    private boolean mPaused = false;
-
-    // Whether this transition has ended. Used to avoid pause/resume on transitions
-    // that have completed
-    private boolean mEnded = false;
-
-    // The set of listeners to be sent transition lifecycle events.
-    private ArrayList<Transition.TransitionListener> mListeners = null;
-
-    // The set of animators collected from calls to createAnimator(),
-    // to be run in runAnimators()
-    private ArrayList<Animator> mAnimators = new ArrayList<>();
-
-    // The function for calculating the Animation start delay.
-    TransitionPropagation mPropagation;
-
-    // The rectangular region for Transitions like Explode and TransitionPropagations
-    // like CircularPropagation
-    private EpicenterCallback mEpicenterCallback;
-
-    // For Fragment shared element transitions, linking views explicitly by mismatching
-    // transitionNames.
-    private ArrayMap<String, String> mNameOverrides;
-
-    // The function used to interpolate along two-dimensional points. Typically used
-    // for adding curves to x/y View motion.
-    private PathMotion mPathMotion = STRAIGHT_PATH_MOTION;
-
-    /**
-     * Constructs a Transition object with no target objects. A transition with
-     * no targets defaults to running on all target objects in the scene hierarchy
-     * (if the transition is not contained in a TransitionSet), or all target
-     * objects passed down from its parent (if it is in a TransitionSet).
-     */
-    public Transition() {
-    }
-
-    /**
-     * Perform inflation from XML and apply a class-specific base style from a
-     * theme attribute or style resource. This constructor of Transition allows
-     * subclasses to use their own base style when they are inflating.
-     *
-     * @param context The Context the transition is running in, through which it can
-     *                access the current theme, resources, etc.
-     * @param attrs   The attributes of the XML tag that is inflating the transition.
-     */
-    public Transition(Context context, AttributeSet attrs) {
-        TypedArray a = context.obtainStyledAttributes(attrs, Styleable.TRANSITION);
-        XmlResourceParser parser = (XmlResourceParser) attrs;
-        long duration = TypedArrayUtils.getNamedInt(a, parser, "duration",
-                Styleable.Transition.DURATION, -1);
-        if (duration >= 0) {
-            setDuration(duration);
-        }
-        long startDelay = TypedArrayUtils.getNamedInt(a, parser, "startDelay",
-                Styleable.Transition.START_DELAY, -1);
-        if (startDelay > 0) {
-            setStartDelay(startDelay);
-        }
-        final int resId = TypedArrayUtils.getNamedResourceId(a, parser, "interpolator",
-                Styleable.Transition.INTERPOLATOR, 0);
-        if (resId > 0) {
-            setInterpolator(AnimationUtils.loadInterpolator(context, resId));
-        }
-        String matchOrder = TypedArrayUtils.getNamedString(a, parser, "matchOrder",
-                Styleable.Transition.MATCH_ORDER);
-        if (matchOrder != null) {
-            setMatchOrder(parseMatchOrder(matchOrder));
-        }
-        a.recycle();
-    }
-
-    @MatchOrder
-    private static int[] parseMatchOrder(String matchOrderString) {
-        StringTokenizer st = new StringTokenizer(matchOrderString, ",");
-        @MatchOrder
-        int[] matches = new int[st.countTokens()];
-        int index = 0;
-        while (st.hasMoreTokens()) {
-            String token = st.nextToken().trim();
-            if (MATCH_ID_STR.equalsIgnoreCase(token)) {
-                matches[index] = Transition.MATCH_ID;
-            } else if (MATCH_INSTANCE_STR.equalsIgnoreCase(token)) {
-                matches[index] = Transition.MATCH_INSTANCE;
-            } else if (MATCH_NAME_STR.equalsIgnoreCase(token)) {
-                matches[index] = Transition.MATCH_NAME;
-            } else if (MATCH_ITEM_ID_STR.equalsIgnoreCase(token)) {
-                matches[index] = Transition.MATCH_ITEM_ID;
-            } else if (token.isEmpty()) {
-                @MatchOrder
-                int[] smallerMatches = new int[matches.length - 1];
-                System.arraycopy(matches, 0, smallerMatches, 0, index);
-                matches = smallerMatches;
-                index--;
-            } else {
-                throw new InflateException("Unknown match type in matchOrder: '" + token + "'");
-            }
-            index++;
-        }
-        return matches;
-    }
-
-    /**
-     * Sets the duration of this transition. By default, there is no duration
-     * (indicated by a negative number), which means that the Animator created by
-     * the transition will have its own specified duration. If the duration of a
-     * Transition is set, that duration will override the Animator duration.
-     *
-     * @param duration The length of the animation, in milliseconds.
-     * @return This transition object.
-     */
-    @NonNull
-    public Transition setDuration(long duration) {
-        mDuration = duration;
-        return this;
-    }
-
-    /**
-     * Returns the duration set on this transition. If no duration has been set,
-     * the returned value will be negative, indicating that resulting animators will
-     * retain their own durations.
-     *
-     * @return The duration set on this transition, in milliseconds, if one has been
-     * set, otherwise returns a negative number.
-     */
-    public long getDuration() {
-        return mDuration;
-    }
-
-    /**
-     * Sets the startDelay of this transition. By default, there is no delay
-     * (indicated by a negative number), which means that the Animator created by
-     * the transition will have its own specified startDelay. If the delay of a
-     * Transition is set, that delay will override the Animator delay.
-     *
-     * @param startDelay The length of the delay, in milliseconds.
-     * @return This transition object.
-     */
-    @NonNull
-    public Transition setStartDelay(long startDelay) {
-        mStartDelay = startDelay;
-        return this;
-    }
-
-    /**
-     * Returns the startDelay set on this transition. If no startDelay has been set,
-     * the returned value will be negative, indicating that resulting animators will
-     * retain their own startDelays.
-     *
-     * @return The startDelay set on this transition, in milliseconds, if one has
-     * been set, otherwise returns a negative number.
-     */
-    public long getStartDelay() {
-        return mStartDelay;
-    }
-
-    /**
-     * Sets the interpolator of this transition. By default, the interpolator
-     * is null, which means that the Animator created by the transition
-     * will have its own specified interpolator. If the interpolator of a
-     * Transition is set, that interpolator will override the Animator interpolator.
-     *
-     * @param interpolator The time interpolator used by the transition
-     * @return This transition object.
-     */
-    @NonNull
-    public Transition setInterpolator(@Nullable TimeInterpolator interpolator) {
-        mInterpolator = interpolator;
-        return this;
-    }
-
-    /**
-     * Returns the interpolator set on this transition. If no interpolator has been set,
-     * the returned value will be null, indicating that resulting animators will
-     * retain their own interpolators.
-     *
-     * @return The interpolator set on this transition, if one has been set, otherwise
-     * returns null.
-     */
-    @Nullable
-    public TimeInterpolator getInterpolator() {
-        return mInterpolator;
-    }
-
-    /**
-     * Returns the set of property names used stored in the {@link TransitionValues}
-     * object passed into {@link #captureStartValues(TransitionValues)} that
-     * this transition cares about for the purposes of canceling overlapping animations.
-     * When any transition is started on a given scene root, all transitions
-     * currently running on that same scene root are checked to see whether the
-     * properties on which they based their animations agree with the end values of
-     * the same properties in the new transition. If the end values are not equal,
-     * then the old animation is canceled since the new transition will start a new
-     * animation to these new values. If the values are equal, the old animation is
-     * allowed to continue and no new animation is started for that transition.
-     *
-     * <p>A transition does not need to override this method. However, not doing so
-     * will mean that the cancellation logic outlined in the previous paragraph
-     * will be skipped for that transition, possibly leading to artifacts as
-     * old transitions and new transitions on the same targets run in parallel,
-     * animating views toward potentially different end values.</p>
-     *
-     * @return An array of property names as described in the class documentation for
-     * {@link TransitionValues}. The default implementation returns <code>null</code>.
-     */
-    @Nullable
-    public String[] getTransitionProperties() {
-        return null;
-    }
-
-    /**
-     * This method creates an animation that will be run for this transition
-     * given the information in the startValues and endValues structures captured
-     * earlier for the start and end scenes. Subclasses of Transition should override
-     * this method. The method should only be called by the transition system; it is
-     * not intended to be called from external classes.
-     *
-     * <p>This method is called by the transition's parent (all the way up to the
-     * topmost Transition in the hierarchy) with the sceneRoot and start/end
-     * values that the transition may need to set up initial target values
-     * and construct an appropriate animation. For example, if an overall
-     * Transition is a {@link TransitionSet} consisting of several
-     * child transitions in sequence, then some of the child transitions may
-     * want to set initial values on target views prior to the overall
-     * Transition commencing, to put them in an appropriate state for the
-     * delay between that start and the child Transition start time. For
-     * example, a transition that fades an item in may wish to set the starting
-     * alpha value to 0, to avoid it blinking in prior to the transition
-     * actually starting the animation. This is necessary because the scene
-     * change that triggers the Transition will automatically set the end-scene
-     * on all target views, so a Transition that wants to animate from a
-     * different value should set that value prior to returning from this method.</p>
-     *
-     * <p>Additionally, a Transition can perform logic to determine whether
-     * the transition needs to run on the given target and start/end values.
-     * For example, a transition that resizes objects on the screen may wish
-     * to avoid running for views which are not present in either the start
-     * or end scenes.</p>
-     *
-     * <p>If there is an animator created and returned from this method, the
-     * transition mechanism will apply any applicable duration, startDelay,
-     * and interpolator to that animation and start it. A return value of
-     * <code>null</code> indicates that no animation should run. The default
-     * implementation returns null.</p>
-     *
-     * <p>The method is called for every applicable target object, which is
-     * stored in the {@link TransitionValues#view} field.</p>
-     *
-     * @param sceneRoot   The root of the transition hierarchy.
-     * @param startValues The values for a specific target in the start scene.
-     * @param endValues   The values for the target in the end scene.
-     * @return A Animator to be started at the appropriate time in the
-     * overall transition for this scene change. A null value means no animation
-     * should be run.
-     */
-    @Nullable
-    public Animator createAnimator(@NonNull ViewGroup sceneRoot,
-            @Nullable TransitionValues startValues, @Nullable TransitionValues endValues) {
-        return null;
-    }
-
-    /**
-     * Sets the order in which Transition matches View start and end values.
-     * <p>
-     * The default behavior is to match first by {@link android.view.View#getTransitionName()},
-     * then by View instance, then by {@link android.view.View#getId()} and finally
-     * by its item ID if it is in a direct child of ListView. The caller can
-     * choose to have only some or all of the values of {@link #MATCH_INSTANCE},
-     * {@link #MATCH_NAME}, {@link #MATCH_ITEM_ID}, and {@link #MATCH_ID}. Only
-     * the match algorithms supplied will be used to determine whether Views are the
-     * the same in both the start and end Scene. Views that do not match will be considered
-     * as entering or leaving the Scene.
-     * </p>
-     *
-     * @param matches A list of zero or more of {@link #MATCH_INSTANCE},
-     *                {@link #MATCH_NAME}, {@link #MATCH_ITEM_ID}, and {@link #MATCH_ID}.
-     *                If none are provided, then the default match order will be set.
-     */
-    public void setMatchOrder(@MatchOrder int... matches) {
-        if (matches == null || matches.length == 0) {
-            mMatchOrder = DEFAULT_MATCH_ORDER;
-        } else {
-            for (int i = 0; i < matches.length; i++) {
-                int match = matches[i];
-                if (!isValidMatch(match)) {
-                    throw new IllegalArgumentException("matches contains invalid value");
-                }
-                if (alreadyContains(matches, i)) {
-                    throw new IllegalArgumentException("matches contains a duplicate value");
-                }
-            }
-            mMatchOrder = matches.clone();
-        }
-    }
-
-    private static boolean isValidMatch(int match) {
-        return (match >= MATCH_FIRST && match <= MATCH_LAST);
-    }
-
-    private static boolean alreadyContains(int[] array, int searchIndex) {
-        int value = array[searchIndex];
-        for (int i = 0; i < searchIndex; i++) {
-            if (array[i] == value) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Match start/end values by View instance. Adds matched values to mStartValuesList
-     * and mEndValuesList and removes them from unmatchedStart and unmatchedEnd.
-     */
-    private void matchInstances(ArrayMap<View, TransitionValues> unmatchedStart,
-            ArrayMap<View, TransitionValues> unmatchedEnd) {
-        for (int i = unmatchedStart.size() - 1; i >= 0; i--) {
-            View view = unmatchedStart.keyAt(i);
-            if (view != null && isValidTarget(view)) {
-                TransitionValues end = unmatchedEnd.remove(view);
-                if (end != null && end.view != null && isValidTarget(end.view)) {
-                    TransitionValues start = unmatchedStart.removeAt(i);
-                    mStartValuesList.add(start);
-                    mEndValuesList.add(end);
-                }
-            }
-        }
-    }
-
-    /**
-     * Match start/end values by Adapter item ID. Adds matched values to mStartValuesList
-     * and mEndValuesList and removes them from unmatchedStart and unmatchedEnd, using
-     * startItemIds and endItemIds as a guide for which Views have unique item IDs.
-     */
-    private void matchItemIds(ArrayMap<View, TransitionValues> unmatchedStart,
-            ArrayMap<View, TransitionValues> unmatchedEnd,
-            LongSparseArray<View> startItemIds, LongSparseArray<View> endItemIds) {
-        int numStartIds = startItemIds.size();
-        for (int i = 0; i < numStartIds; i++) {
-            View startView = startItemIds.valueAt(i);
-            if (startView != null && isValidTarget(startView)) {
-                View endView = endItemIds.get(startItemIds.keyAt(i));
-                if (endView != null && isValidTarget(endView)) {
-                    TransitionValues startValues = unmatchedStart.get(startView);
-                    TransitionValues endValues = unmatchedEnd.get(endView);
-                    if (startValues != null && endValues != null) {
-                        mStartValuesList.add(startValues);
-                        mEndValuesList.add(endValues);
-                        unmatchedStart.remove(startView);
-                        unmatchedEnd.remove(endView);
-                    }
-                }
-            }
-        }
-    }
-
-    /**
-     * Match start/end values by Adapter view ID. Adds matched values to mStartValuesList
-     * and mEndValuesList and removes them from unmatchedStart and unmatchedEnd, using
-     * startIds and endIds as a guide for which Views have unique IDs.
-     */
-    private void matchIds(ArrayMap<View, TransitionValues> unmatchedStart,
-            ArrayMap<View, TransitionValues> unmatchedEnd,
-            SparseArray<View> startIds, SparseArray<View> endIds) {
-        int numStartIds = startIds.size();
-        for (int i = 0; i < numStartIds; i++) {
-            View startView = startIds.valueAt(i);
-            if (startView != null && isValidTarget(startView)) {
-                View endView = endIds.get(startIds.keyAt(i));
-                if (endView != null && isValidTarget(endView)) {
-                    TransitionValues startValues = unmatchedStart.get(startView);
-                    TransitionValues endValues = unmatchedEnd.get(endView);
-                    if (startValues != null && endValues != null) {
-                        mStartValuesList.add(startValues);
-                        mEndValuesList.add(endValues);
-                        unmatchedStart.remove(startView);
-                        unmatchedEnd.remove(endView);
-                    }
-                }
-            }
-        }
-    }
-
-    /**
-     * Match start/end values by Adapter transitionName. Adds matched values to mStartValuesList
-     * and mEndValuesList and removes them from unmatchedStart and unmatchedEnd, using
-     * startNames and endNames as a guide for which Views have unique transitionNames.
-     */
-    private void matchNames(ArrayMap<View, TransitionValues> unmatchedStart,
-            ArrayMap<View, TransitionValues> unmatchedEnd,
-            ArrayMap<String, View> startNames, ArrayMap<String, View> endNames) {
-        int numStartNames = startNames.size();
-        for (int i = 0; i < numStartNames; i++) {
-            View startView = startNames.valueAt(i);
-            if (startView != null && isValidTarget(startView)) {
-                View endView = endNames.get(startNames.keyAt(i));
-                if (endView != null && isValidTarget(endView)) {
-                    TransitionValues startValues = unmatchedStart.get(startView);
-                    TransitionValues endValues = unmatchedEnd.get(endView);
-                    if (startValues != null && endValues != null) {
-                        mStartValuesList.add(startValues);
-                        mEndValuesList.add(endValues);
-                        unmatchedStart.remove(startView);
-                        unmatchedEnd.remove(endView);
-                    }
-                }
-            }
-        }
-    }
-
-    /**
-     * Adds all values from unmatchedStart and unmatchedEnd to mStartValuesList and mEndValuesList,
-     * assuming that there is no match between values in the list.
-     */
-    private void addUnmatched(ArrayMap<View, TransitionValues> unmatchedStart,
-            ArrayMap<View, TransitionValues> unmatchedEnd) {
-        // Views that only exist in the start Scene
-        for (int i = 0; i < unmatchedStart.size(); i++) {
-            final TransitionValues start = unmatchedStart.valueAt(i);
-            if (isValidTarget(start.view)) {
-                mStartValuesList.add(start);
-                mEndValuesList.add(null);
-            }
-        }
-
-        // Views that only exist in the end Scene
-        for (int i = 0; i < unmatchedEnd.size(); i++) {
-            final TransitionValues end = unmatchedEnd.valueAt(i);
-            if (isValidTarget(end.view)) {
-                mEndValuesList.add(end);
-                mStartValuesList.add(null);
-            }
-        }
-    }
-
-    private void matchStartAndEnd(TransitionValuesMaps startValues,
-            TransitionValuesMaps endValues) {
-        ArrayMap<View, TransitionValues> unmatchedStart = new ArrayMap<>(startValues.mViewValues);
-        ArrayMap<View, TransitionValues> unmatchedEnd = new ArrayMap<>(endValues.mViewValues);
-
-        for (int i = 0; i < mMatchOrder.length; i++) {
-            switch (mMatchOrder[i]) {
-                case MATCH_INSTANCE:
-                    matchInstances(unmatchedStart, unmatchedEnd);
-                    break;
-                case MATCH_NAME:
-                    matchNames(unmatchedStart, unmatchedEnd,
-                            startValues.mNameValues, endValues.mNameValues);
-                    break;
-                case MATCH_ID:
-                    matchIds(unmatchedStart, unmatchedEnd,
-                            startValues.mIdValues, endValues.mIdValues);
-                    break;
-                case MATCH_ITEM_ID:
-                    matchItemIds(unmatchedStart, unmatchedEnd,
-                            startValues.mItemIdValues, endValues.mItemIdValues);
-                    break;
-            }
-        }
-        addUnmatched(unmatchedStart, unmatchedEnd);
-    }
-
-    /**
-     * This method, essentially a wrapper around all calls to createAnimator for all
-     * possible target views, is called with the entire set of start/end
-     * values. The implementation in Transition iterates through these lists
-     * and calls {@link #createAnimator(ViewGroup, TransitionValues, TransitionValues)}
-     * with each set of start/end values on this transition. The
-     * TransitionSet subclass overrides this method and delegates it to
-     * each of its children in succession.
-     *
-     * @hide
-     */
-    @RestrictTo(LIBRARY_GROUP)
-    protected void createAnimators(ViewGroup sceneRoot, TransitionValuesMaps startValues,
-            TransitionValuesMaps endValues, ArrayList<TransitionValues> startValuesList,
-            ArrayList<TransitionValues> endValuesList) {
-        if (DBG) {
-            Log.d(LOG_TAG, "createAnimators() for " + this);
-        }
-        ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
-        long minStartDelay = Long.MAX_VALUE;
-        SparseIntArray startDelays = new SparseIntArray();
-        int startValuesListCount = startValuesList.size();
-        for (int i = 0; i < startValuesListCount; ++i) {
-            TransitionValues start = startValuesList.get(i);
-            TransitionValues end = endValuesList.get(i);
-            if (start != null && !start.mTargetedTransitions.contains(this)) {
-                start = null;
-            }
-            if (end != null && !end.mTargetedTransitions.contains(this)) {
-                end = null;
-            }
-            if (start == null && end == null) {
-                continue;
-            }
-            // Only bother trying to animate with values that differ between start/end
-            boolean isChanged = start == null || end == null || isTransitionRequired(start, end);
-            if (isChanged) {
-                if (DBG) {
-                    View view = (end != null) ? end.view : start.view;
-                    Log.d(LOG_TAG, "  differing start/end values for view " + view);
-                    if (start == null || end == null) {
-                        Log.d(LOG_TAG, "    " + ((start == null)
-                                ? "start null, end non-null" : "start non-null, end null"));
-                    } else {
-                        for (String key : start.values.keySet()) {
-                            Object startValue = start.values.get(key);
-                            Object endValue = end.values.get(key);
-                            if (startValue != endValue && !startValue.equals(endValue)) {
-                                Log.d(LOG_TAG, "    " + key + ": start(" + startValue
-                                        + "), end(" + endValue + ")");
-                            }
-                        }
-                    }
-                }
-                // TODO: what to do about targetIds and itemIds?
-                Animator animator = createAnimator(sceneRoot, start, end);
-                if (animator != null) {
-                    // Save animation info for future cancellation purposes
-                    View view;
-                    TransitionValues infoValues = null;
-                    if (end != null) {
-                        view = end.view;
-                        String[] properties = getTransitionProperties();
-                        if (view != null && properties != null && properties.length > 0) {
-                            infoValues = new TransitionValues();
-                            infoValues.view = view;
-                            TransitionValues newValues = endValues.mViewValues.get(view);
-                            if (newValues != null) {
-                                for (int j = 0; j < properties.length; ++j) {
-                                    infoValues.values.put(properties[j],
-                                            newValues.values.get(properties[j]));
-                                }
-                            }
-                            int numExistingAnims = runningAnimators.size();
-                            for (int j = 0; j < numExistingAnims; ++j) {
-                                Animator anim = runningAnimators.keyAt(j);
-                                AnimationInfo info = runningAnimators.get(anim);
-                                if (info.mValues != null && info.mView == view
-                                        && info.mName.equals(getName())) {
-                                    if (info.mValues.equals(infoValues)) {
-                                        // Favor the old animator
-                                        animator = null;
-                                        break;
-                                    }
-                                }
-                            }
-                        }
-                    } else {
-                        view = start.view;
-                    }
-                    if (animator != null) {
-                        if (mPropagation != null) {
-                            long delay = mPropagation.getStartDelay(sceneRoot, this, start, end);
-                            startDelays.put(mAnimators.size(), (int) delay);
-                            minStartDelay = Math.min(delay, minStartDelay);
-                        }
-                        AnimationInfo info = new AnimationInfo(view, getName(), this,
-                                ViewUtils.getWindowId(sceneRoot), infoValues);
-                        runningAnimators.put(animator, info);
-                        mAnimators.add(animator);
-                    }
-                }
-            }
-        }
-        if (minStartDelay != 0) {
-            for (int i = 0; i < startDelays.size(); i++) {
-                int index = startDelays.keyAt(i);
-                Animator animator = mAnimators.get(index);
-                long delay = startDelays.valueAt(i) - minStartDelay + animator.getStartDelay();
-                animator.setStartDelay(delay);
-            }
-        }
-    }
-
-    /**
-     * Internal utility method for checking whether a given view/id
-     * is valid for this transition, where "valid" means that either
-     * the Transition has no target/targetId list (the default, in which
-     * cause the transition should act on all views in the hiearchy), or
-     * the given view is in the target list or the view id is in the
-     * targetId list. If the target parameter is null, then the target list
-     * is not checked (this is in the case of ListView items, where the
-     * views are ignored and only the ids are used).
-     */
-    boolean isValidTarget(View target) {
-        int targetId = target.getId();
-        if (mTargetIdExcludes != null && mTargetIdExcludes.contains(targetId)) {
-            return false;
-        }
-        if (mTargetExcludes != null && mTargetExcludes.contains(target)) {
-            return false;
-        }
-        if (mTargetTypeExcludes != null) {
-            int numTypes = mTargetTypeExcludes.size();
-            for (int i = 0; i < numTypes; ++i) {
-                Class type = mTargetTypeExcludes.get(i);
-                if (type.isInstance(target)) {
-                    return false;
-                }
-            }
-        }
-        if (mTargetNameExcludes != null && ViewCompat.getTransitionName(target) != null) {
-            if (mTargetNameExcludes.contains(ViewCompat.getTransitionName(target))) {
-                return false;
-            }
-        }
-        if (mTargetIds.size() == 0 && mTargets.size() == 0
-                && (mTargetTypes == null || mTargetTypes.isEmpty())
-                && (mTargetNames == null || mTargetNames.isEmpty())) {
-            return true;
-        }
-        if (mTargetIds.contains(targetId) || mTargets.contains(target)) {
-            return true;
-        }
-        if (mTargetNames != null && mTargetNames.contains(ViewCompat.getTransitionName(target))) {
-            return true;
-        }
-        if (mTargetTypes != null) {
-            for (int i = 0; i < mTargetTypes.size(); ++i) {
-                if (mTargetTypes.get(i).isInstance(target)) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
-    private static ArrayMap<Animator, AnimationInfo> getRunningAnimators() {
-        ArrayMap<Animator, AnimationInfo> runningAnimators = sRunningAnimators.get();
-        if (runningAnimators == null) {
-            runningAnimators = new ArrayMap<>();
-            sRunningAnimators.set(runningAnimators);
-        }
-        return runningAnimators;
-    }
-
-    /**
-     * This is called internally once all animations have been set up by the
-     * transition hierarchy. \
-     *
-     * @hide
-     */
-    @RestrictTo(LIBRARY_GROUP)
-    protected void runAnimators() {
-        if (DBG) {
-            Log.d(LOG_TAG, "runAnimators() on " + this);
-        }
-        start();
-        ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
-        // Now start every Animator that was previously created for this transition
-        for (Animator anim : mAnimators) {
-            if (DBG) {
-                Log.d(LOG_TAG, "  anim: " + anim);
-            }
-            if (runningAnimators.containsKey(anim)) {
-                start();
-                runAnimator(anim, runningAnimators);
-            }
-        }
-        mAnimators.clear();
-        end();
-    }
-
-    private void runAnimator(Animator animator,
-            final ArrayMap<Animator, AnimationInfo> runningAnimators) {
-        if (animator != null) {
-            // TODO: could be a single listener instance for all of them since it uses the param
-            animator.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationStart(Animator animation) {
-                    mCurrentAnimators.add(animation);
-                }
-
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    runningAnimators.remove(animation);
-                    mCurrentAnimators.remove(animation);
-                }
-            });
-            animate(animator);
-        }
-    }
-
-    /**
-     * Captures the values in the start scene for the properties that this
-     * transition monitors. These values are then passed as the startValues
-     * structure in a later call to
-     * {@link #createAnimator(ViewGroup, TransitionValues, TransitionValues)}.
-     * The main concern for an implementation is what the
-     * properties are that the transition cares about and what the values are
-     * for all of those properties. The start and end values will be compared
-     * later during the
-     * {@link #createAnimator(ViewGroup, TransitionValues, TransitionValues)}
-     * method to determine what, if any, animations, should be run.
-     *
-     * <p>Subclasses must implement this method. The method should only be called by the
-     * transition system; it is not intended to be called from external classes.</p>
-     *
-     * @param transitionValues The holder for any values that the Transition
-     *                         wishes to store. Values are stored in the <code>values</code> field
-     *                         of this TransitionValues object and are keyed from
-     *                         a String value. For example, to store a view's rotation value,
-     *                         a transition might call
-     *                         <code>transitionValues.values.put("appname:transitionname:rotation",
-     *                         view.getRotation())</code>. The target view will already be stored
-     *                         in
-     *                         the transitionValues structure when this method is called.
-     * @see #captureEndValues(TransitionValues)
-     * @see #createAnimator(ViewGroup, TransitionValues, TransitionValues)
-     */
-    public abstract void captureStartValues(@NonNull TransitionValues transitionValues);
-
-    /**
-     * Captures the values in the end scene for the properties that this
-     * transition monitors. These values are then passed as the endValues
-     * structure in a later call to
-     * {@link #createAnimator(ViewGroup, TransitionValues, TransitionValues)}.
-     * The main concern for an implementation is what the
-     * properties are that the transition cares about and what the values are
-     * for all of those properties. The start and end values will be compared
-     * later during the
-     * {@link #createAnimator(ViewGroup, TransitionValues, TransitionValues)}
-     * method to determine what, if any, animations, should be run.
-     *
-     * <p>Subclasses must implement this method. The method should only be called by the
-     * transition system; it is not intended to be called from external classes.</p>
-     *
-     * @param transitionValues The holder for any values that the Transition
-     *                         wishes to store. Values are stored in the <code>values</code> field
-     *                         of this TransitionValues object and are keyed from
-     *                         a String value. For example, to store a view's rotation value,
-     *                         a transition might call
-     *                         <code>transitionValues.values.put("appname:transitionname:rotation",
-     *                         view.getRotation())</code>. The target view will already be stored
-     *                         in
-     *                         the transitionValues structure when this method is called.
-     * @see #captureStartValues(TransitionValues)
-     * @see #createAnimator(ViewGroup, TransitionValues, TransitionValues)
-     */
-    public abstract void captureEndValues(@NonNull TransitionValues transitionValues);
-
-    /**
-     * Sets the target view instances that this Transition is interested in
-     * animating. By default, there are no targets, and a Transition will
-     * listen for changes on every view in the hierarchy below the sceneRoot
-     * of the Scene being transitioned into. Setting targets constrains
-     * the Transition to only listen for, and act on, these views.
-     * All other views will be ignored.
-     *
-     * <p>The target list is like the {@link #addTarget(int) targetId}
-     * list except this list specifies the actual View instances, not the ids
-     * of the views. This is an important distinction when scene changes involve
-     * view hierarchies which have been inflated separately; different views may
-     * share the same id but not actually be the same instance. If the transition
-     * should treat those views as the same, then {@link #addTarget(int)} should be used
-     * instead of {@link #addTarget(View)}. If, on the other hand, scene changes involve
-     * changes all within the same view hierarchy, among views which do not
-     * necessarily have ids set on them, then the target list of views may be more
-     * convenient.</p>
-     *
-     * @param target A View on which the Transition will act, must be non-null.
-     * @return The Transition to which the target is added.
-     * Returning the same object makes it easier to chain calls during
-     * construction, such as
-     * <code>transitionSet.addTransitions(new Fade()).addTarget(someView);</code>
-     * @see #addTarget(int)
-     */
-    @NonNull
-    public Transition addTarget(@NonNull View target) {
-        mTargets.add(target);
-        return this;
-    }
-
-    /**
-     * Adds the id of a target view that this Transition is interested in
-     * animating. By default, there are no targetIds, and a Transition will
-     * listen for changes on every view in the hierarchy below the sceneRoot
-     * of the Scene being transitioned into. Setting targetIds constrains
-     * the Transition to only listen for, and act on, views with these IDs.
-     * Views with different IDs, or no IDs whatsoever, will be ignored.
-     *
-     * <p>Note that using ids to specify targets implies that ids should be unique
-     * within the view hierarchy underneath the scene root.</p>
-     *
-     * @param targetId The id of a target view, must be a positive number.
-     * @return The Transition to which the targetId is added.
-     * Returning the same object makes it easier to chain calls during
-     * construction, such as
-     * <code>transitionSet.addTransitions(new Fade()).addTarget(someId);</code>
-     * @see View#getId()
-     */
-    @NonNull
-    public Transition addTarget(@IdRes int targetId) {
-        if (targetId > 0) {
-            mTargetIds.add(targetId);
-        }
-        return this;
-    }
-
-    /**
-     * Adds the transitionName of a target view that this Transition is interested in
-     * animating. By default, there are no targetNames, and a Transition will
-     * listen for changes on every view in the hierarchy below the sceneRoot
-     * of the Scene being transitioned into. Setting targetNames constrains
-     * the Transition to only listen for, and act on, views with these transitionNames.
-     * Views with different transitionNames, or no transitionName whatsoever, will be ignored.
-     *
-     * <p>Note that transitionNames should be unique within the view hierarchy.</p>
-     *
-     * @param targetName The transitionName of a target view, must be non-null.
-     * @return The Transition to which the target transitionName is added.
-     * Returning the same object makes it easier to chain calls during
-     * construction, such as
-     * <code>transitionSet.addTransitions(new Fade()).addTarget(someName);</code>
-     * @see ViewCompat#getTransitionName(View)
-     */
-    @NonNull
-    public Transition addTarget(@NonNull String targetName) {
-        if (mTargetNames == null) {
-            mTargetNames = new ArrayList<>();
-        }
-        mTargetNames.add(targetName);
-        return this;
-    }
-
-    /**
-     * Adds the Class of a target view that this Transition is interested in
-     * animating. By default, there are no targetTypes, and a Transition will
-     * listen for changes on every view in the hierarchy below the sceneRoot
-     * of the Scene being transitioned into. Setting targetTypes constrains
-     * the Transition to only listen for, and act on, views with these classes.
-     * Views with different classes will be ignored.
-     *
-     * <p>Note that any View that can be cast to targetType will be included, so
-     * if targetType is <code>View.class</code>, all Views will be included.</p>
-     *
-     * @param targetType The type to include when running this transition.
-     * @return The Transition to which the target class was added.
-     * Returning the same object makes it easier to chain calls during
-     * construction, such as
-     * <code>transitionSet.addTransitions(new Fade()).addTarget(ImageView.class);</code>
-     * @see #addTarget(int)
-     * @see #addTarget(android.view.View)
-     * @see #excludeTarget(Class, boolean)
-     * @see #excludeChildren(Class, boolean)
-     */
-    @NonNull
-    public Transition addTarget(@NonNull Class targetType) {
-        if (mTargetTypes == null) {
-            mTargetTypes = new ArrayList<>();
-        }
-        mTargetTypes.add(targetType);
-        return this;
-    }
-
-    /**
-     * Removes the given target from the list of targets that this Transition
-     * is interested in animating.
-     *
-     * @param target The target view, must be non-null.
-     * @return Transition The Transition from which the target is removed.
-     * Returning the same object makes it easier to chain calls during
-     * construction, such as
-     * <code>transitionSet.addTransitions(new Fade()).removeTarget(someView);</code>
-     */
-    @NonNull
-    public Transition removeTarget(@NonNull View target) {
-        mTargets.remove(target);
-        return this;
-    }
-
-    /**
-     * Removes the given targetId from the list of ids that this Transition
-     * is interested in animating.
-     *
-     * @param targetId The id of a target view, must be a positive number.
-     * @return The Transition from which the targetId is removed.
-     * Returning the same object makes it easier to chain calls during
-     * construction, such as
-     * <code>transitionSet.addTransitions(new Fade()).removeTargetId(someId);</code>
-     */
-    @NonNull
-    public Transition removeTarget(@IdRes int targetId) {
-        if (targetId > 0) {
-            mTargetIds.remove((Integer) targetId);
-        }
-        return this;
-    }
-
-    /**
-     * Removes the given targetName from the list of transitionNames that this Transition
-     * is interested in animating.
-     *
-     * @param targetName The transitionName of a target view, must not be null.
-     * @return The Transition from which the targetName is removed.
-     * Returning the same object makes it easier to chain calls during
-     * construction, such as
-     * <code>transitionSet.addTransitions(new Fade()).removeTargetName(someName);</code>
-     */
-    @NonNull
-    public Transition removeTarget(@NonNull String targetName) {
-        if (mTargetNames != null) {
-            mTargetNames.remove(targetName);
-        }
-        return this;
-    }
-
-    /**
-     * Removes the given target from the list of targets that this Transition
-     * is interested in animating.
-     *
-     * @param target The type of the target view, must be non-null.
-     * @return Transition The Transition from which the target is removed.
-     * Returning the same object makes it easier to chain calls during
-     * construction, such as
-     * <code>transitionSet.addTransitions(new Fade()).removeTarget(someType);</code>
-     */
-    @NonNull
-    public Transition removeTarget(@NonNull Class target) {
-        if (mTargetTypes != null) {
-            mTargetTypes.remove(target);
-        }
-        return this;
-    }
-
-    /**
-     * Utility method to manage the boilerplate code that is the same whether we
-     * are excluding targets or their children.
-     */
-    private static <T> ArrayList<T> excludeObject(ArrayList<T> list, T target, boolean exclude) {
-        if (target != null) {
-            if (exclude) {
-                list = ArrayListManager.add(list, target);
-            } else {
-                list = ArrayListManager.remove(list, target);
-            }
-        }
-        return list;
-    }
-
-    /**
-     * Whether to add the given target to the list of targets to exclude from this
-     * transition. The <code>exclude</code> parameter specifies whether the target
-     * should be added to or removed from the excluded list.
-     *
-     * <p>Excluding targets is a general mechanism for allowing transitions to run on
-     * a view hierarchy while skipping target views that should not be part of
-     * the transition. For example, you may want to avoid animating children
-     * of a specific ListView or Spinner. Views can be excluded either by their
-     * id, or by their instance reference, or by the Class of that view
-     * (eg, {@link Spinner}).</p>
-     *
-     * @param target  The target to ignore when running this transition.
-     * @param exclude Whether to add the target to or remove the target from the
-     *                current list of excluded targets.
-     * @return This transition object.
-     * @see #excludeChildren(View, boolean)
-     * @see #excludeTarget(int, boolean)
-     * @see #excludeTarget(Class, boolean)
-     */
-    @NonNull
-    public Transition excludeTarget(@NonNull View target, boolean exclude) {
-        mTargetExcludes = excludeView(mTargetExcludes, target, exclude);
-        return this;
-    }
-
-    /**
-     * Whether to add the given id to the list of target ids to exclude from this
-     * transition. The <code>exclude</code> parameter specifies whether the target
-     * should be added to or removed from the excluded list.
-     *
-     * <p>Excluding targets is a general mechanism for allowing transitions to run on
-     * a view hierarchy while skipping target views that should not be part of
-     * the transition. For example, you may want to avoid animating children
-     * of a specific ListView or Spinner. Views can be excluded either by their
-     * id, or by their instance reference, or by the Class of that view
-     * (eg, {@link Spinner}).</p>
-     *
-     * @param targetId The id of a target to ignore when running this transition.
-     * @param exclude  Whether to add the target to or remove the target from the
-     *                 current list of excluded targets.
-     * @return This transition object.
-     * @see #excludeChildren(int, boolean)
-     * @see #excludeTarget(View, boolean)
-     * @see #excludeTarget(Class, boolean)
-     */
-    @NonNull
-    public Transition excludeTarget(@IdRes int targetId, boolean exclude) {
-        mTargetIdExcludes = excludeId(mTargetIdExcludes, targetId, exclude);
-        return this;
-    }
-
-    /**
-     * Whether to add the given transitionName to the list of target transitionNames to exclude
-     * from this transition. The <code>exclude</code> parameter specifies whether the target
-     * should be added to or removed from the excluded list.
-     *
-     * <p>Excluding targets is a general mechanism for allowing transitions to run on
-     * a view hierarchy while skipping target views that should not be part of
-     * the transition. For example, you may want to avoid animating children
-     * of a specific ListView or Spinner. Views can be excluded by their
-     * id, their instance reference, their transitionName, or by the Class of that view
-     * (eg, {@link Spinner}).</p>
-     *
-     * @param targetName The name of a target to ignore when running this transition.
-     * @param exclude    Whether to add the target to or remove the target from the
-     *                   current list of excluded targets.
-     * @return This transition object.
-     * @see #excludeTarget(View, boolean)
-     * @see #excludeTarget(int, boolean)
-     * @see #excludeTarget(Class, boolean)
-     */
-    @NonNull
-    public Transition excludeTarget(@NonNull String targetName, boolean exclude) {
-        mTargetNameExcludes = excludeObject(mTargetNameExcludes, targetName, exclude);
-        return this;
-    }
-
-    /**
-     * Whether to add the children of given target to the list of target children
-     * to exclude from this transition. The <code>exclude</code> parameter specifies
-     * whether the target should be added to or removed from the excluded list.
-     *
-     * <p>Excluding targets is a general mechanism for allowing transitions to run on
-     * a view hierarchy while skipping target views that should not be part of
-     * the transition. For example, you may want to avoid animating children
-     * of a specific ListView or Spinner. Views can be excluded either by their
-     * id, or by their instance reference, or by the Class of that view
-     * (eg, {@link Spinner}).</p>
-     *
-     * @param target  The target to ignore when running this transition.
-     * @param exclude Whether to add the target to or remove the target from the
-     *                current list of excluded targets.
-     * @return This transition object.
-     * @see #excludeTarget(View, boolean)
-     * @see #excludeChildren(int, boolean)
-     * @see #excludeChildren(Class, boolean)
-     */
-    @NonNull
-    public Transition excludeChildren(@NonNull View target, boolean exclude) {
-        mTargetChildExcludes = excludeView(mTargetChildExcludes, target, exclude);
-        return this;
-    }
-
-    /**
-     * Whether to add the children of the given id to the list of targets to exclude
-     * from this transition. The <code>exclude</code> parameter specifies whether
-     * the children of the target should be added to or removed from the excluded list.
-     * Excluding children in this way provides a simple mechanism for excluding all
-     * children of specific targets, rather than individually excluding each
-     * child individually.
-     *
-     * <p>Excluding targets is a general mechanism for allowing transitions to run on
-     * a view hierarchy while skipping target views that should not be part of
-     * the transition. For example, you may want to avoid animating children
-     * of a specific ListView or Spinner. Views can be excluded either by their
-     * id, or by their instance reference, or by the Class of that view
-     * (eg, {@link Spinner}).</p>
-     *
-     * @param targetId The id of a target whose children should be ignored when running
-     *                 this transition.
-     * @param exclude  Whether to add the target to or remove the target from the
-     *                 current list of excluded-child targets.
-     * @return This transition object.
-     * @see #excludeTarget(int, boolean)
-     * @see #excludeChildren(View, boolean)
-     * @see #excludeChildren(Class, boolean)
-     */
-    @NonNull
-    public Transition excludeChildren(@IdRes int targetId, boolean exclude) {
-        mTargetIdChildExcludes = excludeId(mTargetIdChildExcludes, targetId, exclude);
-        return this;
-    }
-
-    /**
-     * Utility method to manage the boilerplate code that is the same whether we
-     * are excluding targets or their children.
-     */
-    private ArrayList<Integer> excludeId(ArrayList<Integer> list, int targetId, boolean exclude) {
-        if (targetId > 0) {
-            if (exclude) {
-                list = ArrayListManager.add(list, targetId);
-            } else {
-                list = ArrayListManager.remove(list, targetId);
-            }
-        }
-        return list;
-    }
-
-    /**
-     * Utility method to manage the boilerplate code that is the same whether we
-     * are excluding targets or their children.
-     */
-    private ArrayList<View> excludeView(ArrayList<View> list, View target, boolean exclude) {
-        if (target != null) {
-            if (exclude) {
-                list = ArrayListManager.add(list, target);
-            } else {
-                list = ArrayListManager.remove(list, target);
-            }
-        }
-        return list;
-    }
-
-    /**
-     * Whether to add the given type to the list of types to exclude from this
-     * transition. The <code>exclude</code> parameter specifies whether the target
-     * type should be added to or removed from the excluded list.
-     *
-     * <p>Excluding targets is a general mechanism for allowing transitions to run on
-     * a view hierarchy while skipping target views that should not be part of
-     * the transition. For example, you may want to avoid animating children
-     * of a specific ListView or Spinner. Views can be excluded either by their
-     * id, or by their instance reference, or by the Class of that view
-     * (eg, {@link Spinner}).</p>
-     *
-     * @param type    The type to ignore when running this transition.
-     * @param exclude Whether to add the target type to or remove it from the
-     *                current list of excluded target types.
-     * @return This transition object.
-     * @see #excludeChildren(Class, boolean)
-     * @see #excludeTarget(int, boolean)
-     * @see #excludeTarget(View, boolean)
-     */
-    @NonNull
-    public Transition excludeTarget(@NonNull Class type, boolean exclude) {
-        mTargetTypeExcludes = excludeType(mTargetTypeExcludes, type, exclude);
-        return this;
-    }
-
-    /**
-     * Whether to add the given type to the list of types whose children should
-     * be excluded from this transition. The <code>exclude</code> parameter
-     * specifies whether the target type should be added to or removed from
-     * the excluded list.
-     *
-     * <p>Excluding targets is a general mechanism for allowing transitions to run on
-     * a view hierarchy while skipping target views that should not be part of
-     * the transition. For example, you may want to avoid animating children
-     * of a specific ListView or Spinner. Views can be excluded either by their
-     * id, or by their instance reference, or by the Class of that view
-     * (eg, {@link Spinner}).</p>
-     *
-     * @param type    The type to ignore when running this transition.
-     * @param exclude Whether to add the target type to or remove it from the
-     *                current list of excluded target types.
-     * @return This transition object.
-     * @see #excludeTarget(Class, boolean)
-     * @see #excludeChildren(int, boolean)
-     * @see #excludeChildren(View, boolean)
-     */
-    @NonNull
-    public Transition excludeChildren(@NonNull Class type, boolean exclude) {
-        mTargetTypeChildExcludes = excludeType(mTargetTypeChildExcludes, type, exclude);
-        return this;
-    }
-
-    /**
-     * Utility method to manage the boilerplate code that is the same whether we
-     * are excluding targets or their children.
-     */
-    private ArrayList<Class> excludeType(ArrayList<Class> list, Class type, boolean exclude) {
-        if (type != null) {
-            if (exclude) {
-                list = ArrayListManager.add(list, type);
-            } else {
-                list = ArrayListManager.remove(list, type);
-            }
-        }
-        return list;
-    }
-
-    /**
-     * Returns the array of target IDs that this transition limits itself to
-     * tracking and animating. If the array is null for both this method and
-     * {@link #getTargets()}, then this transition is
-     * not limited to specific views, and will handle changes to any views
-     * in the hierarchy of a scene change.
-     *
-     * @return the list of target IDs
-     */
-    @NonNull
-    public List<Integer> getTargetIds() {
-        return mTargetIds;
-    }
-
-    /**
-     * Returns the array of target views that this transition limits itself to
-     * tracking and animating. If the array is null for both this method and
-     * {@link #getTargetIds()}, then this transition is
-     * not limited to specific views, and will handle changes to any views
-     * in the hierarchy of a scene change.
-     *
-     * @return the list of target views
-     */
-    @NonNull
-    public List<View> getTargets() {
-        return mTargets;
-    }
-
-    /**
-     * Returns the list of target transitionNames that this transition limits itself to
-     * tracking and animating. If the list is null or empty for
-     * {@link #getTargetIds()}, {@link #getTargets()}, {@link #getTargetNames()}, and
-     * {@link #getTargetTypes()} then this transition is
-     * not limited to specific views, and will handle changes to any views
-     * in the hierarchy of a scene change.
-     *
-     * @return the list of target transitionNames
-     */
-    @Nullable
-    public List<String> getTargetNames() {
-        return mTargetNames;
-    }
-
-    /**
-     * Returns the list of target transitionNames that this transition limits itself to
-     * tracking and animating. If the list is null or empty for
-     * {@link #getTargetIds()}, {@link #getTargets()}, {@link #getTargetNames()}, and
-     * {@link #getTargetTypes()} then this transition is
-     * not limited to specific views, and will handle changes to any views
-     * in the hierarchy of a scene change.
-     *
-     * @return the list of target Types
-     */
-    @Nullable
-    public List<Class> getTargetTypes() {
-        return mTargetTypes;
-    }
-
-    /**
-     * Recursive method that captures values for the given view and the
-     * hierarchy underneath it.
-     *
-     * @param sceneRoot The root of the view hierarchy being captured
-     * @param start     true if this capture is happening before the scene change,
-     *                  false otherwise
-     */
-    void captureValues(ViewGroup sceneRoot, boolean start) {
-        clearValues(start);
-        if ((mTargetIds.size() > 0 || mTargets.size() > 0)
-                && (mTargetNames == null || mTargetNames.isEmpty())
-                && (mTargetTypes == null || mTargetTypes.isEmpty())) {
-            for (int i = 0; i < mTargetIds.size(); ++i) {
-                int id = mTargetIds.get(i);
-                View view = sceneRoot.findViewById(id);
-                if (view != null) {
-                    TransitionValues values = new TransitionValues();
-                    values.view = view;
-                    if (start) {
-                        captureStartValues(values);
-                    } else {
-                        captureEndValues(values);
-                    }
-                    values.mTargetedTransitions.add(this);
-                    capturePropagationValues(values);
-                    if (start) {
-                        addViewValues(mStartValues, view, values);
-                    } else {
-                        addViewValues(mEndValues, view, values);
-                    }
-                }
-            }
-            for (int i = 0; i < mTargets.size(); ++i) {
-                View view = mTargets.get(i);
-                TransitionValues values = new TransitionValues();
-                values.view = view;
-                if (start) {
-                    captureStartValues(values);
-                } else {
-                    captureEndValues(values);
-                }
-                values.mTargetedTransitions.add(this);
-                capturePropagationValues(values);
-                if (start) {
-                    addViewValues(mStartValues, view, values);
-                } else {
-                    addViewValues(mEndValues, view, values);
-                }
-            }
-        } else {
-            captureHierarchy(sceneRoot, start);
-        }
-        if (!start && mNameOverrides != null) {
-            int numOverrides = mNameOverrides.size();
-            ArrayList<View> overriddenViews = new ArrayList<>(numOverrides);
-            for (int i = 0; i < numOverrides; i++) {
-                String fromName = mNameOverrides.keyAt(i);
-                overriddenViews.add(mStartValues.mNameValues.remove(fromName));
-            }
-            for (int i = 0; i < numOverrides; i++) {
-                View view = overriddenViews.get(i);
-                if (view != null) {
-                    String toName = mNameOverrides.valueAt(i);
-                    mStartValues.mNameValues.put(toName, view);
-                }
-            }
-        }
-    }
-
-    private static void addViewValues(TransitionValuesMaps transitionValuesMaps,
-            View view, TransitionValues transitionValues) {
-        transitionValuesMaps.mViewValues.put(view, transitionValues);
-        int id = view.getId();
-        if (id >= 0) {
-            if (transitionValuesMaps.mIdValues.indexOfKey(id) >= 0) {
-                // Duplicate IDs cannot match by ID.
-                transitionValuesMaps.mIdValues.put(id, null);
-            } else {
-                transitionValuesMaps.mIdValues.put(id, view);
-            }
-        }
-        String name = ViewCompat.getTransitionName(view);
-        if (name != null) {
-            if (transitionValuesMaps.mNameValues.containsKey(name)) {
-                // Duplicate transitionNames: cannot match by transitionName.
-                transitionValuesMaps.mNameValues.put(name, null);
-            } else {
-                transitionValuesMaps.mNameValues.put(name, view);
-            }
-        }
-        if (view.getParent() instanceof ListView) {
-            ListView listview = (ListView) view.getParent();
-            if (listview.getAdapter().hasStableIds()) {
-                int position = listview.getPositionForView(view);
-                long itemId = listview.getItemIdAtPosition(position);
-                if (transitionValuesMaps.mItemIdValues.indexOfKey(itemId) >= 0) {
-                    // Duplicate item IDs: cannot match by item ID.
-                    View alreadyMatched = transitionValuesMaps.mItemIdValues.get(itemId);
-                    if (alreadyMatched != null) {
-                        ViewCompat.setHasTransientState(alreadyMatched, false);
-                        transitionValuesMaps.mItemIdValues.put(itemId, null);
-                    }
-                } else {
-                    ViewCompat.setHasTransientState(view, true);
-                    transitionValuesMaps.mItemIdValues.put(itemId, view);
-                }
-            }
-        }
-    }
-
-    /**
-     * Clear valuesMaps for specified start/end state
-     *
-     * @param start true if the start values should be cleared, false otherwise
-     */
-    void clearValues(boolean start) {
-        if (start) {
-            mStartValues.mViewValues.clear();
-            mStartValues.mIdValues.clear();
-            mStartValues.mItemIdValues.clear();
-        } else {
-            mEndValues.mViewValues.clear();
-            mEndValues.mIdValues.clear();
-            mEndValues.mItemIdValues.clear();
-        }
-    }
-
-    /**
-     * Recursive method which captures values for an entire view hierarchy,
-     * starting at some root view. Transitions without targetIDs will use this
-     * method to capture values for all possible views.
-     *
-     * @param view  The view for which to capture values. Children of this View
-     *              will also be captured, recursively down to the leaf nodes.
-     * @param start true if values are being captured in the start scene, false
-     *              otherwise.
-     */
-    private void captureHierarchy(View view, boolean start) {
-        if (view == null) {
-            return;
-        }
-        int id = view.getId();
-        if (mTargetIdExcludes != null && mTargetIdExcludes.contains(id)) {
-            return;
-        }
-        if (mTargetExcludes != null && mTargetExcludes.contains(view)) {
-            return;
-        }
-        if (mTargetTypeExcludes != null) {
-            int numTypes = mTargetTypeExcludes.size();
-            for (int i = 0; i < numTypes; ++i) {
-                if (mTargetTypeExcludes.get(i).isInstance(view)) {
-                    return;
-                }
-            }
-        }
-        if (view.getParent() instanceof ViewGroup) {
-            TransitionValues values = new TransitionValues();
-            values.view = view;
-            if (start) {
-                captureStartValues(values);
-            } else {
-                captureEndValues(values);
-            }
-            values.mTargetedTransitions.add(this);
-            capturePropagationValues(values);
-            if (start) {
-                addViewValues(mStartValues, view, values);
-            } else {
-                addViewValues(mEndValues, view, values);
-            }
-        }
-        if (view instanceof ViewGroup) {
-            // Don't traverse child hierarchy if there are any child-excludes on this view
-            if (mTargetIdChildExcludes != null && mTargetIdChildExcludes.contains(id)) {
-                return;
-            }
-            if (mTargetChildExcludes != null && mTargetChildExcludes.contains(view)) {
-                return;
-            }
-            if (mTargetTypeChildExcludes != null) {
-                int numTypes = mTargetTypeChildExcludes.size();
-                for (int i = 0; i < numTypes; ++i) {
-                    if (mTargetTypeChildExcludes.get(i).isInstance(view)) {
-                        return;
-                    }
-                }
-            }
-            ViewGroup parent = (ViewGroup) view;
-            for (int i = 0; i < parent.getChildCount(); ++i) {
-                captureHierarchy(parent.getChildAt(i), start);
-            }
-        }
-    }
-
-    /**
-     * This method can be called by transitions to get the TransitionValues for
-     * any particular view during the transition-playing process. This might be
-     * necessary, for example, to query the before/after state of related views
-     * for a given transition.
-     */
-    @Nullable
-    public TransitionValues getTransitionValues(@NonNull View view, boolean start) {
-        if (mParent != null) {
-            return mParent.getTransitionValues(view, start);
-        }
-        TransitionValuesMaps valuesMaps = start ? mStartValues : mEndValues;
-        return valuesMaps.mViewValues.get(view);
-    }
-
-    /**
-     * Find the matched start or end value for a given View. This is only valid
-     * after playTransition starts. For example, it will be valid in
-     * {@link #createAnimator(android.view.ViewGroup, TransitionValues, TransitionValues)}, but not
-     * in {@link #captureStartValues(TransitionValues)}.
-     *
-     * @param view        The view to find the match for.
-     * @param viewInStart Is View from the start values or end values.
-     * @return The matching TransitionValues for view in either start or end values, depending
-     * on viewInStart or null if there is no match for the given view.
-     */
-    TransitionValues getMatchedTransitionValues(View view, boolean viewInStart) {
-        if (mParent != null) {
-            return mParent.getMatchedTransitionValues(view, viewInStart);
-        }
-        ArrayList<TransitionValues> lookIn = viewInStart ? mStartValuesList : mEndValuesList;
-        if (lookIn == null) {
-            return null;
-        }
-        int count = lookIn.size();
-        int index = -1;
-        for (int i = 0; i < count; i++) {
-            TransitionValues values = lookIn.get(i);
-            if (values == null) {
-                return null;
-            }
-            if (values.view == view) {
-                index = i;
-                break;
-            }
-        }
-        TransitionValues values = null;
-        if (index >= 0) {
-            ArrayList<TransitionValues> matchIn = viewInStart ? mEndValuesList : mStartValuesList;
-            values = matchIn.get(index);
-        }
-        return values;
-    }
-
-    /**
-     * Pauses this transition, sending out calls to {@link
-     * TransitionListener#onTransitionPause(Transition)} to all listeners
-     * and pausing all running animators started by this transition.
-     *
-     * @hide
-     */
-    @RestrictTo(LIBRARY_GROUP)
-    public void pause(View sceneRoot) {
-        if (!mEnded) {
-            ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
-            int numOldAnims = runningAnimators.size();
-            WindowIdImpl windowId = ViewUtils.getWindowId(sceneRoot);
-            for (int i = numOldAnims - 1; i >= 0; i--) {
-                AnimationInfo info = runningAnimators.valueAt(i);
-                if (info.mView != null && windowId.equals(info.mWindowId)) {
-                    Animator anim = runningAnimators.keyAt(i);
-                    AnimatorUtils.pause(anim);
-                }
-            }
-            if (mListeners != null && mListeners.size() > 0) {
-                @SuppressWarnings("unchecked") ArrayList<TransitionListener> tmpListeners =
-                        (ArrayList<TransitionListener>) mListeners.clone();
-                int numListeners = tmpListeners.size();
-                for (int i = 0; i < numListeners; ++i) {
-                    tmpListeners.get(i).onTransitionPause(this);
-                }
-            }
-            mPaused = true;
-        }
-    }
-
-    /**
-     * Resumes this transition, sending out calls to {@link
-     * TransitionListener#onTransitionPause(Transition)} to all listeners
-     * and pausing all running animators started by this transition.
-     *
-     * @hide
-     */
-    @RestrictTo(LIBRARY_GROUP)
-    public void resume(View sceneRoot) {
-        if (mPaused) {
-            if (!mEnded) {
-                ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
-                int numOldAnims = runningAnimators.size();
-                WindowIdImpl windowId = ViewUtils.getWindowId(sceneRoot);
-                for (int i = numOldAnims - 1; i >= 0; i--) {
-                    AnimationInfo info = runningAnimators.valueAt(i);
-                    if (info.mView != null && windowId.equals(info.mWindowId)) {
-                        Animator anim = runningAnimators.keyAt(i);
-                        AnimatorUtils.resume(anim);
-                    }
-                }
-                if (mListeners != null && mListeners.size() > 0) {
-                    @SuppressWarnings("unchecked") ArrayList<TransitionListener> tmpListeners =
-                            (ArrayList<TransitionListener>) mListeners.clone();
-                    int numListeners = tmpListeners.size();
-                    for (int i = 0; i < numListeners; ++i) {
-                        tmpListeners.get(i).onTransitionResume(this);
-                    }
-                }
-            }
-            mPaused = false;
-        }
-    }
-
-    /**
-     * Called by TransitionManager to play the transition. This calls
-     * createAnimators() to set things up and create all of the animations and then
-     * runAnimations() to actually start the animations.
-     */
-    void playTransition(ViewGroup sceneRoot) {
-        mStartValuesList = new ArrayList<>();
-        mEndValuesList = new ArrayList<>();
-        matchStartAndEnd(mStartValues, mEndValues);
-
-        ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
-        int numOldAnims = runningAnimators.size();
-        WindowIdImpl windowId = ViewUtils.getWindowId(sceneRoot);
-        for (int i = numOldAnims - 1; i >= 0; i--) {
-            Animator anim = runningAnimators.keyAt(i);
-            if (anim != null) {
-                AnimationInfo oldInfo = runningAnimators.get(anim);
-                if (oldInfo != null && oldInfo.mView != null
-                        && windowId.equals(oldInfo.mWindowId)) {
-                    TransitionValues oldValues = oldInfo.mValues;
-                    View oldView = oldInfo.mView;
-                    TransitionValues startValues = getTransitionValues(oldView, true);
-                    TransitionValues endValues = getMatchedTransitionValues(oldView, true);
-                    boolean cancel = (startValues != null || endValues != null)
-                            && oldInfo.mTransition.isTransitionRequired(oldValues, endValues);
-                    if (cancel) {
-                        if (anim.isRunning() || anim.isStarted()) {
-                            if (DBG) {
-                                Log.d(LOG_TAG, "Canceling anim " + anim);
-                            }
-                            anim.cancel();
-                        } else {
-                            if (DBG) {
-                                Log.d(LOG_TAG, "removing anim from info list: " + anim);
-                            }
-                            runningAnimators.remove(anim);
-                        }
-                    }
-                }
-            }
-        }
-
-        createAnimators(sceneRoot, mStartValues, mEndValues, mStartValuesList, mEndValuesList);
-        runAnimators();
-    }
-
-    /**
-     * Returns whether or not the transition should create an Animator, based on the values
-     * captured during {@link #captureStartValues(TransitionValues)} and
-     * {@link #captureEndValues(TransitionValues)}. The default implementation compares the
-     * property values returned from {@link #getTransitionProperties()}, or all property values if
-     * {@code getTransitionProperties()} returns null. Subclasses may override this method to
-     * provide logic more specific to the transition implementation.
-     *
-     * @param startValues the values from captureStartValues, This may be {@code null} if the
-     *                    View did not exist in the start state.
-     * @param endValues   the values from captureEndValues. This may be {@code null} if the View
-     *                    did not exist in the end state.
-     */
-    public boolean isTransitionRequired(@Nullable TransitionValues startValues,
-            @Nullable TransitionValues endValues) {
-        boolean valuesChanged = false;
-        // if startValues null, then transition didn't care to stash values,
-        // and won't get canceled
-        if (startValues != null && endValues != null) {
-            String[] properties = getTransitionProperties();
-            if (properties != null) {
-                for (String property : properties) {
-                    if (isValueChanged(startValues, endValues, property)) {
-                        valuesChanged = true;
-                        break;
-                    }
-                }
-            } else {
-                for (String key : startValues.values.keySet()) {
-                    if (isValueChanged(startValues, endValues, key)) {
-                        valuesChanged = true;
-                        break;
-                    }
-                }
-            }
-        }
-        return valuesChanged;
-    }
-
-    private static boolean isValueChanged(TransitionValues oldValues, TransitionValues newValues,
-            String key) {
-        Object oldValue = oldValues.values.get(key);
-        Object newValue = newValues.values.get(key);
-        boolean changed;
-        if (oldValue == null && newValue == null) {
-            // both are null
-            changed = false;
-        } else if (oldValue == null || newValue == null) {
-            // one is null
-            changed = true;
-        } else {
-            // neither is null
-            changed = !oldValue.equals(newValue);
-        }
-        if (DBG && changed) {
-            Log.d(LOG_TAG, "Transition.playTransition: "
-                    + "oldValue != newValue for " + key
-                    + ": old, new = " + oldValue + ", " + newValue);
-        }
-        return changed;
-    }
-
-    /**
-     * This is a utility method used by subclasses to handle standard parts of
-     * setting up and running an Animator: it sets the {@link #getDuration()
-     * duration} and the {@link #getStartDelay() startDelay}, starts the
-     * animation, and, when the animator ends, calls {@link #end()}.
-     *
-     * @param animator The Animator to be run during this transition.
-     * @hide
-     */
-    @RestrictTo(LIBRARY_GROUP)
-    protected void animate(Animator animator) {
-        // TODO: maybe pass auto-end as a boolean parameter?
-        if (animator == null) {
-            end();
-        } else {
-            if (getDuration() >= 0) {
-                animator.setDuration(getDuration());
-            }
-            if (getStartDelay() >= 0) {
-                animator.setStartDelay(getStartDelay());
-            }
-            if (getInterpolator() != null) {
-                animator.setInterpolator(getInterpolator());
-            }
-            animator.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    end();
-                    animation.removeListener(this);
-                }
-            });
-            animator.start();
-        }
-    }
-
-    /**
-     * This method is called automatically by the transition and
-     * TransitionSet classes prior to a Transition subclass starting;
-     * subclasses should not need to call it directly.
-     *
-     * @hide
-     */
-    @RestrictTo(LIBRARY_GROUP)
-    protected void start() {
-        if (mNumInstances == 0) {
-            if (mListeners != null && mListeners.size() > 0) {
-                @SuppressWarnings("unchecked") ArrayList<TransitionListener> tmpListeners =
-                        (ArrayList<TransitionListener>) mListeners.clone();
-                int numListeners = tmpListeners.size();
-                for (int i = 0; i < numListeners; ++i) {
-                    tmpListeners.get(i).onTransitionStart(this);
-                }
-            }
-            mEnded = false;
-        }
-        mNumInstances++;
-    }
-
-    /**
-     * This method is called automatically by the Transition and
-     * TransitionSet classes when a transition finishes, either because
-     * a transition did nothing (returned a null Animator from
-     * {@link Transition#createAnimator(ViewGroup, TransitionValues,
-     * TransitionValues)}) or because the transition returned a valid
-     * Animator and end() was called in the onAnimationEnd()
-     * callback of the AnimatorListener.
-     *
-     * @hide
-     */
-    @RestrictTo(LIBRARY_GROUP)
-    protected void end() {
-        --mNumInstances;
-        if (mNumInstances == 0) {
-            if (mListeners != null && mListeners.size() > 0) {
-                @SuppressWarnings("unchecked") ArrayList<TransitionListener> tmpListeners =
-                        (ArrayList<TransitionListener>) mListeners.clone();
-                int numListeners = tmpListeners.size();
-                for (int i = 0; i < numListeners; ++i) {
-                    tmpListeners.get(i).onTransitionEnd(this);
-                }
-            }
-            for (int i = 0; i < mStartValues.mItemIdValues.size(); ++i) {
-                View view = mStartValues.mItemIdValues.valueAt(i);
-                if (view != null) {
-                    ViewCompat.setHasTransientState(view, false);
-                }
-            }
-            for (int i = 0; i < mEndValues.mItemIdValues.size(); ++i) {
-                View view = mEndValues.mItemIdValues.valueAt(i);
-                if (view != null) {
-                    ViewCompat.setHasTransientState(view, false);
-                }
-            }
-            mEnded = true;
-        }
-    }
-
-    /**
-     * Force the transition to move to its end state, ending all the animators.
-     *
-     * @hide
-     */
-    @RestrictTo(LIBRARY_GROUP)
-    void forceToEnd(ViewGroup sceneRoot) {
-        ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
-        int numOldAnims = runningAnimators.size();
-        if (sceneRoot != null) {
-            WindowIdImpl windowId = ViewUtils.getWindowId(sceneRoot);
-            for (int i = numOldAnims - 1; i >= 0; i--) {
-                AnimationInfo info = runningAnimators.valueAt(i);
-                if (info.mView != null && windowId != null && windowId.equals(info.mWindowId)) {
-                    Animator anim = runningAnimators.keyAt(i);
-                    anim.end();
-                }
-            }
-        }
-    }
-
-    /**
-     * This method cancels a transition that is currently running.
-     *
-     * @hide
-     */
-    @RestrictTo(LIBRARY_GROUP)
-    protected void cancel() {
-        int numAnimators = mCurrentAnimators.size();
-        for (int i = numAnimators - 1; i >= 0; i--) {
-            Animator animator = mCurrentAnimators.get(i);
-            animator.cancel();
-        }
-        if (mListeners != null && mListeners.size() > 0) {
-            @SuppressWarnings("unchecked") ArrayList<TransitionListener> tmpListeners =
-                    (ArrayList<TransitionListener>) mListeners.clone();
-            int numListeners = tmpListeners.size();
-            for (int i = 0; i < numListeners; ++i) {
-                tmpListeners.get(i).onTransitionCancel(this);
-            }
-        }
-    }
-
-    /**
-     * Adds a listener to the set of listeners that are sent events through the
-     * life of an animation, such as start, repeat, and end.
-     *
-     * @param listener the listener to be added to the current set of listeners
-     *                 for this animation.
-     * @return This transition object.
-     */
-    @NonNull
-    public Transition addListener(@NonNull TransitionListener listener) {
-        if (mListeners == null) {
-            mListeners = new ArrayList<>();
-        }
-        mListeners.add(listener);
-        return this;
-    }
-
-    /**
-     * Removes a listener from the set listening to this animation.
-     *
-     * @param listener the listener to be removed from the current set of
-     *                 listeners for this transition.
-     * @return This transition object.
-     */
-    @NonNull
-    public Transition removeListener(@NonNull TransitionListener listener) {
-        if (mListeners == null) {
-            return this;
-        }
-        mListeners.remove(listener);
-        if (mListeners.size() == 0) {
-            mListeners = null;
-        }
-        return this;
-    }
-
-    /**
-     * Sets the algorithm used to calculate two-dimensional interpolation.
-     * <p>
-     * Transitions such as {@link android.transition.ChangeBounds} move Views, typically
-     * in a straight path between the start and end positions. Applications that desire to
-     * have these motions move in a curve can change how Views interpolate in two dimensions
-     * by extending PathMotion and implementing
-     * {@link android.transition.PathMotion#getPath(float, float, float, float)}.
-     * </p>
-     *
-     * @param pathMotion Algorithm object to use for determining how to interpolate in two
-     *                   dimensions. If null, a straight-path algorithm will be used.
-     * @see android.transition.ArcMotion
-     * @see PatternPathMotion
-     * @see android.transition.PathMotion
-     */
-    public void setPathMotion(@Nullable PathMotion pathMotion) {
-        if (pathMotion == null) {
-            mPathMotion = STRAIGHT_PATH_MOTION;
-        } else {
-            mPathMotion = pathMotion;
-        }
-    }
-
-    /**
-     * Returns the algorithm object used to interpolate along two dimensions. This is typically
-     * used to determine the View motion between two points.
-     *
-     * @return The algorithm object used to interpolate along two dimensions.
-     * @see android.transition.ArcMotion
-     * @see PatternPathMotion
-     * @see android.transition.PathMotion
-     */
-    @NonNull
-    public PathMotion getPathMotion() {
-        return mPathMotion;
-    }
-
-    /**
-     * Sets the callback to use to find the epicenter of a Transition. A null value indicates
-     * that there is no epicenter in the Transition and onGetEpicenter() will return null.
-     * Transitions like {@link android.transition.Explode} use a point or Rect to orient
-     * the direction of travel. This is called the epicenter of the Transition and is
-     * typically centered on a touched View. The
-     * {@link android.transition.Transition.EpicenterCallback} allows a Transition to
-     * dynamically retrieve the epicenter during a Transition.
-     *
-     * @param epicenterCallback The callback to use to find the epicenter of the Transition.
-     */
-    public void setEpicenterCallback(@Nullable EpicenterCallback epicenterCallback) {
-        mEpicenterCallback = epicenterCallback;
-    }
-
-    /**
-     * Returns the callback used to find the epicenter of the Transition.
-     * Transitions like {@link android.transition.Explode} use a point or Rect to orient
-     * the direction of travel. This is called the epicenter of the Transition and is
-     * typically centered on a touched View. The
-     * {@link android.transition.Transition.EpicenterCallback} allows a Transition to
-     * dynamically retrieve the epicenter during a Transition.
-     *
-     * @return the callback used to find the epicenter of the Transition.
-     */
-    @Nullable
-    public EpicenterCallback getEpicenterCallback() {
-        return mEpicenterCallback;
-    }
-
-    /**
-     * Returns the epicenter as specified by the
-     * {@link android.transition.Transition.EpicenterCallback} or null if no callback exists.
-     *
-     * @return the epicenter as specified by the
-     * {@link android.transition.Transition.EpicenterCallback} or null if no callback exists.
-     * @see #setEpicenterCallback(EpicenterCallback)
-     */
-    @Nullable
-    public Rect getEpicenter() {
-        if (mEpicenterCallback == null) {
-            return null;
-        }
-        return mEpicenterCallback.onGetEpicenter(this);
-    }
-
-    /**
-     * Sets the method for determining Animator start delays.
-     * When a Transition affects several Views like {@link android.transition.Explode} or
-     * {@link android.transition.Slide}, there may be a desire to have a "wave-front" effect
-     * such that the Animator start delay depends on position of the View. The
-     * TransitionPropagation specifies how the start delays are calculated.
-     *
-     * @param transitionPropagation The class used to determine the start delay of
-     *                              Animators created by this Transition. A null value
-     *                              indicates that no delay should be used.
-     */
-    public void setPropagation(@Nullable TransitionPropagation transitionPropagation) {
-        mPropagation = transitionPropagation;
-    }
-
-    /**
-     * Returns the {@link android.transition.TransitionPropagation} used to calculate Animator
-     * start
-     * delays.
-     * When a Transition affects several Views like {@link android.transition.Explode} or
-     * {@link android.transition.Slide}, there may be a desire to have a "wave-front" effect
-     * such that the Animator start delay depends on position of the View. The
-     * TransitionPropagation specifies how the start delays are calculated.
-     *
-     * @return the {@link android.transition.TransitionPropagation} used to calculate Animator start
-     * delays. This is null by default.
-     */
-    @Nullable
-    public TransitionPropagation getPropagation() {
-        return mPropagation;
-    }
-
-    /**
-     * Captures TransitionPropagation values for the given view and the
-     * hierarchy underneath it.
-     */
-    void capturePropagationValues(TransitionValues transitionValues) {
-        if (mPropagation != null && !transitionValues.values.isEmpty()) {
-            String[] propertyNames = mPropagation.getPropagationProperties();
-            if (propertyNames == null) {
-                return;
-            }
-            boolean containsAll = true;
-            for (int i = 0; i < propertyNames.length; i++) {
-                if (!transitionValues.values.containsKey(propertyNames[i])) {
-                    containsAll = false;
-                    break;
-                }
-            }
-            if (!containsAll) {
-                mPropagation.captureValues(transitionValues);
-            }
-        }
-    }
-
-    Transition setSceneRoot(ViewGroup sceneRoot) {
-        mSceneRoot = sceneRoot;
-        return this;
-    }
-
-    void setCanRemoveViews(boolean canRemoveViews) {
-        mCanRemoveViews = canRemoveViews;
-    }
-
-    @Override
-    public String toString() {
-        return toString("");
-    }
-
-    @Override
-    public Transition clone() {
-        try {
-            Transition clone = (Transition) super.clone();
-            clone.mAnimators = new ArrayList<>();
-            clone.mStartValues = new TransitionValuesMaps();
-            clone.mEndValues = new TransitionValuesMaps();
-            clone.mStartValuesList = null;
-            clone.mEndValuesList = null;
-            return clone;
-        } catch (CloneNotSupportedException e) {
-            return null;
-        }
-    }
-
-    /**
-     * Returns the name of this Transition. This name is used internally to distinguish
-     * between different transitions to determine when interrupting transitions overlap.
-     * For example, a ChangeBounds running on the same target view as another ChangeBounds
-     * should determine whether the old transition is animating to different end values
-     * and should be canceled in favor of the new transition.
-     *
-     * <p>By default, a Transition's name is simply the value of {@link Class#getName()},
-     * but subclasses are free to override and return something different.</p>
-     *
-     * @return The name of this transition.
-     */
-    @NonNull
-    public String getName() {
-        return mName;
-    }
-
-    String toString(String indent) {
-        String result = indent + getClass().getSimpleName() + "@"
-                + Integer.toHexString(hashCode()) + ": ";
-        if (mDuration != -1) {
-            result += "dur(" + mDuration + ") ";
-        }
-        if (mStartDelay != -1) {
-            result += "dly(" + mStartDelay + ") ";
-        }
-        if (mInterpolator != null) {
-            result += "interp(" + mInterpolator + ") ";
-        }
-        if (mTargetIds.size() > 0 || mTargets.size() > 0) {
-            result += "tgts(";
-            if (mTargetIds.size() > 0) {
-                for (int i = 0; i < mTargetIds.size(); ++i) {
-                    if (i > 0) {
-                        result += ", ";
-                    }
-                    result += mTargetIds.get(i);
-                }
-            }
-            if (mTargets.size() > 0) {
-                for (int i = 0; i < mTargets.size(); ++i) {
-                    if (i > 0) {
-                        result += ", ";
-                    }
-                    result += mTargets.get(i);
-                }
-            }
-            result += ")";
-        }
-        return result;
-    }
-
-    /**
-     * A transition listener receives notifications from a transition.
-     * Notifications indicate transition lifecycle events.
-     */
-    public interface TransitionListener {
-
-        /**
-         * Notification about the start of the transition.
-         *
-         * @param transition The started transition.
-         */
-        void onTransitionStart(@NonNull Transition transition);
-
-        /**
-         * Notification about the end of the transition. Canceled transitions
-         * will always notify listeners of both the cancellation and end
-         * events. That is, {@link #onTransitionEnd(Transition)} is always called,
-         * regardless of whether the transition was canceled or played
-         * through to completion.
-         *
-         * @param transition The transition which reached its end.
-         */
-        void onTransitionEnd(@NonNull Transition transition);
-
-        /**
-         * Notification about the cancellation of the transition.
-         * Note that cancel may be called by a parent {@link TransitionSet} on
-         * a child transition which has not yet started. This allows the child
-         * transition to restore state on target objects which was set at
-         * {@link #createAnimator(android.view.ViewGroup, TransitionValues, TransitionValues)
-         * createAnimator()} time.
-         *
-         * @param transition The transition which was canceled.
-         */
-        void onTransitionCancel(@NonNull Transition transition);
-
-        /**
-         * Notification when a transition is paused.
-         * Note that createAnimator() may be called by a parent {@link TransitionSet} on
-         * a child transition which has not yet started. This allows the child
-         * transition to restore state on target objects which was set at
-         * {@link #createAnimator(android.view.ViewGroup, TransitionValues, TransitionValues)
-         * createAnimator()} time.
-         *
-         * @param transition The transition which was paused.
-         */
-        void onTransitionPause(@NonNull Transition transition);
-
-        /**
-         * Notification when a transition is resumed.
-         * Note that resume() may be called by a parent {@link TransitionSet} on
-         * a child transition which has not yet started. This allows the child
-         * transition to restore state which may have changed in an earlier call
-         * to {@link #onTransitionPause(Transition)}.
-         *
-         * @param transition The transition which was resumed.
-         */
-        void onTransitionResume(@NonNull Transition transition);
-    }
-
-    /**
-     * Holds information about each animator used when a new transition starts
-     * while other transitions are still running to determine whether a running
-     * animation should be canceled or a new animation noop'd. The structure holds
-     * information about the state that an animation is going to, to be compared to
-     * end state of a new animation.
-     */
-    private static class AnimationInfo {
-
-        View mView;
-
-        String mName;
-
-        TransitionValues mValues;
-
-        WindowIdImpl mWindowId;
-
-        Transition mTransition;
-
-        AnimationInfo(View view, String name, Transition transition, WindowIdImpl windowId,
-                TransitionValues values) {
-            mView = view;
-            mName = name;
-            mValues = values;
-            mWindowId = windowId;
-            mTransition = transition;
-        }
-    }
-
-    /**
-     * Utility class for managing typed ArrayLists efficiently. In particular, this
-     * can be useful for lists that we don't expect to be used often (eg, the exclude
-     * lists), so we'd like to keep them nulled out by default. This causes the code to
-     * become tedious, with constant null checks, code to allocate when necessary,
-     * and code to null out the reference when the list is empty. This class encapsulates
-     * all of that functionality into simple add()/remove() methods which perform the
-     * necessary checks, allocation/null-out as appropriate, and return the
-     * resulting list.
-     */
-    private static class ArrayListManager {
-
-        /**
-         * Add the specified item to the list, returning the resulting list.
-         * The returned list can either the be same list passed in or, if that
-         * list was null, the new list that was created.
-         *
-         * Note that the list holds unique items; if the item already exists in the
-         * list, the list is not modified.
-         */
-        static <T> ArrayList<T> add(ArrayList<T> list, T item) {
-            if (list == null) {
-                list = new ArrayList<>();
-            }
-            if (!list.contains(item)) {
-                list.add(item);
-            }
-            return list;
-        }
-
-        /**
-         * Remove the specified item from the list, returning the resulting list.
-         * The returned list can either the be same list passed in or, if that
-         * list becomes empty as a result of the remove(), the new list was created.
-         */
-        static <T> ArrayList<T> remove(ArrayList<T> list, T item) {
-            if (list != null) {
-                list.remove(item);
-                if (list.isEmpty()) {
-                    list = null;
-                }
-            }
-            return list;
-        }
-    }
-
-    /**
-     * Class to get the epicenter of Transition. Use
-     * {@link #setEpicenterCallback(EpicenterCallback)} to set the callback used to calculate the
-     * epicenter of the Transition. Override {@link #getEpicenter()} to return the rectangular
-     * region in screen coordinates of the epicenter of the transition.
-     *
-     * @see #setEpicenterCallback(EpicenterCallback)
-     */
-    public abstract static class EpicenterCallback {
-
-        /**
-         * Implementers must override to return the epicenter of the Transition in screen
-         * coordinates. Transitions like {@link android.transition.Explode} depend upon
-         * an epicenter for the Transition. In Explode, Views move toward or away from the
-         * center of the epicenter Rect along the vector between the epicenter and the center
-         * of the View appearing and disappearing. Some Transitions, such as
-         * {@link android.transition.Fade} pay no attention to the epicenter.
-         *
-         * @param transition The transition for which the epicenter applies.
-         * @return The Rect region of the epicenter of <code>transition</code> or null if
-         * there is no epicenter.
-         */
-        public abstract Rect onGetEpicenter(@NonNull Transition transition);
-    }
-
-}
diff --git a/transition/src/android/support/transition/package.html b/transition/src/android/support/transition/package.html
deleted file mode 100644
index b09005f..0000000
--- a/transition/src/android/support/transition/package.html
+++ /dev/null
@@ -1,8 +0,0 @@
-<body>
-
-Support android.transition classes to provide transition API back to android API level 14.
-This library contains {@link android.support.transition.Transition},
-{@link android.support.transition.TransitionManager}, and other related classes
-back-ported from their platform versions introduced Android API level 19.
-
-</body>
diff --git a/transition/src/android/support/transition/AnimatorUtils.java b/transition/src/main/java/android/support/transition/AnimatorUtils.java
similarity index 100%
rename from transition/src/android/support/transition/AnimatorUtils.java
rename to transition/src/main/java/android/support/transition/AnimatorUtils.java
diff --git a/transition/api14/android/support/transition/AnimatorUtilsApi14.java b/transition/src/main/java/android/support/transition/AnimatorUtilsApi14.java
similarity index 100%
rename from transition/api14/android/support/transition/AnimatorUtilsApi14.java
rename to transition/src/main/java/android/support/transition/AnimatorUtilsApi14.java
diff --git a/transition/api19/android/support/transition/AnimatorUtilsApi19.java b/transition/src/main/java/android/support/transition/AnimatorUtilsApi19.java
similarity index 100%
rename from transition/api19/android/support/transition/AnimatorUtilsApi19.java
rename to transition/src/main/java/android/support/transition/AnimatorUtilsApi19.java
diff --git a/transition/base/android/support/transition/AnimatorUtilsImpl.java b/transition/src/main/java/android/support/transition/AnimatorUtilsImpl.java
similarity index 100%
rename from transition/base/android/support/transition/AnimatorUtilsImpl.java
rename to transition/src/main/java/android/support/transition/AnimatorUtilsImpl.java
diff --git a/transition/src/android/support/transition/ArcMotion.java b/transition/src/main/java/android/support/transition/ArcMotion.java
similarity index 100%
rename from transition/src/android/support/transition/ArcMotion.java
rename to transition/src/main/java/android/support/transition/ArcMotion.java
diff --git a/transition/src/main/java/android/support/transition/AutoTransition.java b/transition/src/main/java/android/support/transition/AutoTransition.java
new file mode 100644
index 0000000..bf39c3c
--- /dev/null
+++ b/transition/src/main/java/android/support/transition/AutoTransition.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.transition;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+/**
+ * Utility class for creating a default transition that automatically fades,
+ * moves, and resizes views during a scene change.
+ *
+ * <p>An AutoTransition can be described in a resource file by using the
+ * tag <code>autoTransition</code>, along with the other standard
+ * attributes of {@link Transition}.</p>
+ */
+public class AutoTransition extends TransitionSet {
+
+    /**
+     * Constructs an AutoTransition object, which is a TransitionSet which
+     * first fades out disappearing targets, then moves and resizes existing
+     * targets, and finally fades in appearing targets.
+     */
+    public AutoTransition() {
+        init();
+    }
+
+    public AutoTransition(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init();
+    }
+
+    private void init() {
+        setOrdering(ORDERING_SEQUENTIAL);
+        addTransition(new Fade(Fade.OUT))
+                .addTransition(new ChangeBounds())
+                .addTransition(new Fade(Fade.IN));
+    }
+
+}
diff --git a/transition/src/android/support/transition/ChangeBounds.java b/transition/src/main/java/android/support/transition/ChangeBounds.java
similarity index 100%
rename from transition/src/android/support/transition/ChangeBounds.java
rename to transition/src/main/java/android/support/transition/ChangeBounds.java
diff --git a/transition/src/android/support/transition/ChangeClipBounds.java b/transition/src/main/java/android/support/transition/ChangeClipBounds.java
similarity index 100%
rename from transition/src/android/support/transition/ChangeClipBounds.java
rename to transition/src/main/java/android/support/transition/ChangeClipBounds.java
diff --git a/transition/src/android/support/transition/ChangeImageTransform.java b/transition/src/main/java/android/support/transition/ChangeImageTransform.java
similarity index 100%
rename from transition/src/android/support/transition/ChangeImageTransform.java
rename to transition/src/main/java/android/support/transition/ChangeImageTransform.java
diff --git a/transition/src/android/support/transition/ChangeScroll.java b/transition/src/main/java/android/support/transition/ChangeScroll.java
similarity index 100%
rename from transition/src/android/support/transition/ChangeScroll.java
rename to transition/src/main/java/android/support/transition/ChangeScroll.java
diff --git a/transition/src/android/support/transition/ChangeTransform.java b/transition/src/main/java/android/support/transition/ChangeTransform.java
similarity index 100%
rename from transition/src/android/support/transition/ChangeTransform.java
rename to transition/src/main/java/android/support/transition/ChangeTransform.java
diff --git a/transition/src/android/support/transition/CircularPropagation.java b/transition/src/main/java/android/support/transition/CircularPropagation.java
similarity index 100%
rename from transition/src/android/support/transition/CircularPropagation.java
rename to transition/src/main/java/android/support/transition/CircularPropagation.java
diff --git a/transition/src/android/support/transition/Explode.java b/transition/src/main/java/android/support/transition/Explode.java
similarity index 100%
rename from transition/src/android/support/transition/Explode.java
rename to transition/src/main/java/android/support/transition/Explode.java
diff --git a/transition/src/android/support/transition/Fade.java b/transition/src/main/java/android/support/transition/Fade.java
similarity index 100%
rename from transition/src/android/support/transition/Fade.java
rename to transition/src/main/java/android/support/transition/Fade.java
diff --git a/transition/src/android/support/transition/FloatArrayEvaluator.java b/transition/src/main/java/android/support/transition/FloatArrayEvaluator.java
similarity index 100%
rename from transition/src/android/support/transition/FloatArrayEvaluator.java
rename to transition/src/main/java/android/support/transition/FloatArrayEvaluator.java
diff --git a/transition/src/android/support/transition/FragmentTransitionSupport.java b/transition/src/main/java/android/support/transition/FragmentTransitionSupport.java
similarity index 100%
rename from transition/src/android/support/transition/FragmentTransitionSupport.java
rename to transition/src/main/java/android/support/transition/FragmentTransitionSupport.java
diff --git a/transition/api14/android/support/transition/GhostViewApi14.java b/transition/src/main/java/android/support/transition/GhostViewApi14.java
similarity index 100%
rename from transition/api14/android/support/transition/GhostViewApi14.java
rename to transition/src/main/java/android/support/transition/GhostViewApi14.java
diff --git a/transition/api21/android/support/transition/GhostViewApi21.java b/transition/src/main/java/android/support/transition/GhostViewApi21.java
similarity index 100%
rename from transition/api21/android/support/transition/GhostViewApi21.java
rename to transition/src/main/java/android/support/transition/GhostViewApi21.java
diff --git a/transition/base/android/support/transition/GhostViewImpl.java b/transition/src/main/java/android/support/transition/GhostViewImpl.java
similarity index 100%
rename from transition/base/android/support/transition/GhostViewImpl.java
rename to transition/src/main/java/android/support/transition/GhostViewImpl.java
diff --git a/transition/src/android/support/transition/GhostViewUtils.java b/transition/src/main/java/android/support/transition/GhostViewUtils.java
similarity index 100%
rename from transition/src/android/support/transition/GhostViewUtils.java
rename to transition/src/main/java/android/support/transition/GhostViewUtils.java
diff --git a/transition/src/android/support/transition/ImageViewUtils.java b/transition/src/main/java/android/support/transition/ImageViewUtils.java
similarity index 100%
rename from transition/src/android/support/transition/ImageViewUtils.java
rename to transition/src/main/java/android/support/transition/ImageViewUtils.java
diff --git a/transition/api14/android/support/transition/ImageViewUtilsApi14.java b/transition/src/main/java/android/support/transition/ImageViewUtilsApi14.java
similarity index 100%
rename from transition/api14/android/support/transition/ImageViewUtilsApi14.java
rename to transition/src/main/java/android/support/transition/ImageViewUtilsApi14.java
diff --git a/transition/api21/android/support/transition/ImageViewUtilsApi21.java b/transition/src/main/java/android/support/transition/ImageViewUtilsApi21.java
similarity index 100%
rename from transition/api21/android/support/transition/ImageViewUtilsApi21.java
rename to transition/src/main/java/android/support/transition/ImageViewUtilsApi21.java
diff --git a/transition/base/android/support/transition/ImageViewUtilsImpl.java b/transition/src/main/java/android/support/transition/ImageViewUtilsImpl.java
similarity index 100%
rename from transition/base/android/support/transition/ImageViewUtilsImpl.java
rename to transition/src/main/java/android/support/transition/ImageViewUtilsImpl.java
diff --git a/transition/src/android/support/transition/MatrixUtils.java b/transition/src/main/java/android/support/transition/MatrixUtils.java
similarity index 100%
rename from transition/src/android/support/transition/MatrixUtils.java
rename to transition/src/main/java/android/support/transition/MatrixUtils.java
diff --git a/transition/src/android/support/transition/ObjectAnimatorUtils.java b/transition/src/main/java/android/support/transition/ObjectAnimatorUtils.java
similarity index 100%
rename from transition/src/android/support/transition/ObjectAnimatorUtils.java
rename to transition/src/main/java/android/support/transition/ObjectAnimatorUtils.java
diff --git a/transition/api14/android/support/transition/ObjectAnimatorUtilsApi14.java b/transition/src/main/java/android/support/transition/ObjectAnimatorUtilsApi14.java
similarity index 100%
rename from transition/api14/android/support/transition/ObjectAnimatorUtilsApi14.java
rename to transition/src/main/java/android/support/transition/ObjectAnimatorUtilsApi14.java
diff --git a/transition/api21/android/support/transition/ObjectAnimatorUtilsApi21.java b/transition/src/main/java/android/support/transition/ObjectAnimatorUtilsApi21.java
similarity index 100%
rename from transition/api21/android/support/transition/ObjectAnimatorUtilsApi21.java
rename to transition/src/main/java/android/support/transition/ObjectAnimatorUtilsApi21.java
diff --git a/transition/base/android/support/transition/ObjectAnimatorUtilsImpl.java b/transition/src/main/java/android/support/transition/ObjectAnimatorUtilsImpl.java
similarity index 100%
rename from transition/base/android/support/transition/ObjectAnimatorUtilsImpl.java
rename to transition/src/main/java/android/support/transition/ObjectAnimatorUtilsImpl.java
diff --git a/transition/src/android/support/transition/PathMotion.java b/transition/src/main/java/android/support/transition/PathMotion.java
similarity index 100%
rename from transition/src/android/support/transition/PathMotion.java
rename to transition/src/main/java/android/support/transition/PathMotion.java
diff --git a/transition/api14/android/support/transition/PathProperty.java b/transition/src/main/java/android/support/transition/PathProperty.java
similarity index 100%
rename from transition/api14/android/support/transition/PathProperty.java
rename to transition/src/main/java/android/support/transition/PathProperty.java
diff --git a/transition/src/android/support/transition/PatternPathMotion.java b/transition/src/main/java/android/support/transition/PatternPathMotion.java
similarity index 100%
rename from transition/src/android/support/transition/PatternPathMotion.java
rename to transition/src/main/java/android/support/transition/PatternPathMotion.java
diff --git a/transition/src/android/support/transition/PropertyValuesHolderUtils.java b/transition/src/main/java/android/support/transition/PropertyValuesHolderUtils.java
similarity index 100%
rename from transition/src/android/support/transition/PropertyValuesHolderUtils.java
rename to transition/src/main/java/android/support/transition/PropertyValuesHolderUtils.java
diff --git a/transition/api14/android/support/transition/PropertyValuesHolderUtilsApi14.java b/transition/src/main/java/android/support/transition/PropertyValuesHolderUtilsApi14.java
similarity index 100%
rename from transition/api14/android/support/transition/PropertyValuesHolderUtilsApi14.java
rename to transition/src/main/java/android/support/transition/PropertyValuesHolderUtilsApi14.java
diff --git a/transition/api21/android/support/transition/PropertyValuesHolderUtilsApi21.java b/transition/src/main/java/android/support/transition/PropertyValuesHolderUtilsApi21.java
similarity index 100%
rename from transition/api21/android/support/transition/PropertyValuesHolderUtilsApi21.java
rename to transition/src/main/java/android/support/transition/PropertyValuesHolderUtilsApi21.java
diff --git a/transition/base/android/support/transition/PropertyValuesHolderUtilsImpl.java b/transition/src/main/java/android/support/transition/PropertyValuesHolderUtilsImpl.java
similarity index 100%
rename from transition/base/android/support/transition/PropertyValuesHolderUtilsImpl.java
rename to transition/src/main/java/android/support/transition/PropertyValuesHolderUtilsImpl.java
diff --git a/transition/src/android/support/transition/RectEvaluator.java b/transition/src/main/java/android/support/transition/RectEvaluator.java
similarity index 100%
rename from transition/src/android/support/transition/RectEvaluator.java
rename to transition/src/main/java/android/support/transition/RectEvaluator.java
diff --git a/transition/src/android/support/transition/Scene.java b/transition/src/main/java/android/support/transition/Scene.java
similarity index 100%
rename from transition/src/android/support/transition/Scene.java
rename to transition/src/main/java/android/support/transition/Scene.java
diff --git a/transition/src/android/support/transition/SidePropagation.java b/transition/src/main/java/android/support/transition/SidePropagation.java
similarity index 100%
rename from transition/src/android/support/transition/SidePropagation.java
rename to transition/src/main/java/android/support/transition/SidePropagation.java
diff --git a/transition/src/android/support/transition/Slide.java b/transition/src/main/java/android/support/transition/Slide.java
similarity index 100%
rename from transition/src/android/support/transition/Slide.java
rename to transition/src/main/java/android/support/transition/Slide.java
diff --git a/transition/src/android/support/transition/Styleable.java b/transition/src/main/java/android/support/transition/Styleable.java
similarity index 100%
rename from transition/src/android/support/transition/Styleable.java
rename to transition/src/main/java/android/support/transition/Styleable.java
diff --git a/transition/src/main/java/android/support/transition/Transition.java b/transition/src/main/java/android/support/transition/Transition.java
new file mode 100644
index 0000000..9c198a9
--- /dev/null
+++ b/transition/src/main/java/android/support/transition/Transition.java
@@ -0,0 +1,2437 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.transition;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.TimeInterpolator;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.graphics.Path;
+import android.graphics.Rect;
+import android.support.annotation.IdRes;
+import android.support.annotation.IntDef;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+import android.support.v4.content.res.TypedArrayUtils;
+import android.support.v4.util.ArrayMap;
+import android.support.v4.util.LongSparseArray;
+import android.support.v4.view.ViewCompat;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+import android.view.InflateException;
+import android.view.SurfaceView;
+import android.view.TextureView;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.AnimationUtils;
+import android.widget.ListView;
+import android.widget.Spinner;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringTokenizer;
+
+/**
+ * A Transition holds information about animations that will be run on its
+ * targets during a scene change. Subclasses of this abstract class may
+ * choreograph several child transitions ({@link TransitionSet} or they may
+ * perform custom animations themselves. Any Transition has two main jobs:
+ * (1) capture property values, and (2) play animations based on changes to
+ * captured property values. A custom transition knows what property values
+ * on View objects are of interest to it, and also knows how to animate
+ * changes to those values. For example, the {@link Fade} transition tracks
+ * changes to visibility-related properties and is able to construct and run
+ * animations that fade items in or out based on changes to those properties.
+ *
+ * <p>Note: Transitions may not work correctly with either {@link SurfaceView}
+ * or {@link TextureView}, due to the way that these views are displayed
+ * on the screen. For SurfaceView, the problem is that the view is updated from
+ * a non-UI thread, so changes to the view due to transitions (such as moving
+ * and resizing the view) may be out of sync with the display inside those bounds.
+ * TextureView is more compatible with transitions in general, but some
+ * specific transitions (such as {@link Fade}) may not be compatible
+ * with TextureView because they rely on {@link android.view.ViewOverlay}
+ * functionality, which does not currently work with TextureView.</p>
+ *
+ * <p>Transitions can be declared in XML resource files inside the <code>res/transition</code>
+ * directory. Transition resources consist of a tag name for one of the Transition
+ * subclasses along with attributes to define some of the attributes of that transition.
+ * For example, here is a minimal resource file that declares a {@link ChangeBounds}
+ * transition:</p>
+ *
+ * <pre>
+ *     &lt;changeBounds/&gt;
+ * </pre>
+ *
+ * <p>Note that attributes for the transition are not required, just as they are
+ * optional when declared in code; Transitions created from XML resources will use
+ * the same defaults as their code-created equivalents. Here is a slightly more
+ * elaborate example which declares a {@link TransitionSet} transition with
+ * {@link ChangeBounds} and {@link Fade} child transitions:</p>
+ *
+ * <pre>
+ *     &lt;transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
+ *          android:transitionOrdering="sequential"&gt;
+ *         &lt;changeBounds/&gt;
+ *         &lt;fade android:fadingMode="fade_out"&gt;
+ *             &lt;targets&gt;
+ *                 &lt;target android:targetId="@id/grayscaleContainer"/&gt;
+ *             &lt;/targets&gt;
+ *         &lt;/fade&gt;
+ *     &lt;/transitionSet&gt;
+ * </pre>
+ *
+ * <p>In this example, the transitionOrdering attribute is used on the TransitionSet
+ * object to change from the default {@link TransitionSet#ORDERING_TOGETHER} behavior
+ * to be {@link TransitionSet#ORDERING_SEQUENTIAL} instead. Also, the {@link Fade}
+ * transition uses a fadingMode of {@link Fade#OUT} instead of the default
+ * out-in behavior. Finally, note the use of the <code>targets</code> sub-tag, which
+ * takes a set of {code target} tags, each of which lists a specific <code>targetId</code> which
+ * this transition acts upon. Use of targets is optional, but can be used to either limit the time
+ * spent checking attributes on unchanging views, or limiting the types of animations run on
+ * specific views. In this case, we know that only the <code>grayscaleContainer</code> will be
+ * disappearing, so we choose to limit the {@link Fade} transition to only that view.</p>
+ */
+public abstract class Transition implements Cloneable {
+
+    private static final String LOG_TAG = "Transition";
+    static final boolean DBG = false;
+
+    /**
+     * With {@link #setMatchOrder(int...)}, chooses to match by View instance.
+     */
+    public static final int MATCH_INSTANCE = 0x1;
+    private static final int MATCH_FIRST = MATCH_INSTANCE;
+
+    /**
+     * With {@link #setMatchOrder(int...)}, chooses to match by
+     * {@link android.view.View#getTransitionName()}. Null names will not be matched.
+     */
+    public static final int MATCH_NAME = 0x2;
+
+    /**
+     * With {@link #setMatchOrder(int...)}, chooses to match by
+     * {@link android.view.View#getId()}. Negative IDs will not be matched.
+     */
+    public static final int MATCH_ID = 0x3;
+
+    /**
+     * With {@link #setMatchOrder(int...)}, chooses to match by the {@link android.widget.Adapter}
+     * item id. When {@link android.widget.Adapter#hasStableIds()} returns false, no match
+     * will be made for items.
+     */
+    public static final int MATCH_ITEM_ID = 0x4;
+
+    private static final int MATCH_LAST = MATCH_ITEM_ID;
+
+    /** @hide */
+    @RestrictTo(LIBRARY_GROUP)
+    @IntDef({MATCH_INSTANCE, MATCH_NAME, MATCH_ID, MATCH_ITEM_ID})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface MatchOrder {
+    }
+
+    private static final String MATCH_INSTANCE_STR = "instance";
+    private static final String MATCH_NAME_STR = "name";
+    private static final String MATCH_ID_STR = "id";
+    private static final String MATCH_ITEM_ID_STR = "itemId";
+
+    private static final int[] DEFAULT_MATCH_ORDER = {
+            MATCH_NAME,
+            MATCH_INSTANCE,
+            MATCH_ID,
+            MATCH_ITEM_ID,
+    };
+
+    private static final PathMotion STRAIGHT_PATH_MOTION = new PathMotion() {
+        @Override
+        public Path getPath(float startX, float startY, float endX, float endY) {
+            Path path = new Path();
+            path.moveTo(startX, startY);
+            path.lineTo(endX, endY);
+            return path;
+        }
+    };
+
+    private String mName = getClass().getName();
+
+    private long mStartDelay = -1;
+    long mDuration = -1;
+    private TimeInterpolator mInterpolator = null;
+    ArrayList<Integer> mTargetIds = new ArrayList<>();
+    ArrayList<View> mTargets = new ArrayList<>();
+    private ArrayList<String> mTargetNames = null;
+    private ArrayList<Class> mTargetTypes = null;
+    private ArrayList<Integer> mTargetIdExcludes = null;
+    private ArrayList<View> mTargetExcludes = null;
+    private ArrayList<Class> mTargetTypeExcludes = null;
+    private ArrayList<String> mTargetNameExcludes = null;
+    private ArrayList<Integer> mTargetIdChildExcludes = null;
+    private ArrayList<View> mTargetChildExcludes = null;
+    private ArrayList<Class> mTargetTypeChildExcludes = null;
+    private TransitionValuesMaps mStartValues = new TransitionValuesMaps();
+    private TransitionValuesMaps mEndValues = new TransitionValuesMaps();
+    TransitionSet mParent = null;
+    private int[] mMatchOrder = DEFAULT_MATCH_ORDER;
+    private ArrayList<TransitionValues> mStartValuesList; // only valid after playTransition starts
+    private ArrayList<TransitionValues> mEndValuesList; // only valid after playTransitions starts
+
+    // Per-animator information used for later canceling when future transitions overlap
+    private static ThreadLocal<ArrayMap<Animator, Transition.AnimationInfo>> sRunningAnimators =
+            new ThreadLocal<>();
+
+    // Scene Root is set at createAnimator() time in the cloned Transition
+    private ViewGroup mSceneRoot = null;
+
+    // Whether removing views from their parent is possible. This is only for views
+    // in the start scene, which are no longer in the view hierarchy. This property
+    // is determined by whether the previous Scene was created from a layout
+    // resource, and thus the views from the exited scene are going away anyway
+    // and can be removed as necessary to achieve a particular effect, such as
+    // removing them from parents to add them to overlays.
+    boolean mCanRemoveViews = false;
+
+    // Track all animators in use in case the transition gets canceled and needs to
+    // cancel running animators
+    private ArrayList<Animator> mCurrentAnimators = new ArrayList<>();
+
+    // Number of per-target instances of this Transition currently running. This count is
+    // determined by calls to start() and end()
+    private int mNumInstances = 0;
+
+    // Whether this transition is currently paused, due to a call to pause()
+    private boolean mPaused = false;
+
+    // Whether this transition has ended. Used to avoid pause/resume on transitions
+    // that have completed
+    private boolean mEnded = false;
+
+    // The set of listeners to be sent transition lifecycle events.
+    private ArrayList<Transition.TransitionListener> mListeners = null;
+
+    // The set of animators collected from calls to createAnimator(),
+    // to be run in runAnimators()
+    private ArrayList<Animator> mAnimators = new ArrayList<>();
+
+    // The function for calculating the Animation start delay.
+    TransitionPropagation mPropagation;
+
+    // The rectangular region for Transitions like Explode and TransitionPropagations
+    // like CircularPropagation
+    private EpicenterCallback mEpicenterCallback;
+
+    // For Fragment shared element transitions, linking views explicitly by mismatching
+    // transitionNames.
+    private ArrayMap<String, String> mNameOverrides;
+
+    // The function used to interpolate along two-dimensional points. Typically used
+    // for adding curves to x/y View motion.
+    private PathMotion mPathMotion = STRAIGHT_PATH_MOTION;
+
+    /**
+     * Constructs a Transition object with no target objects. A transition with
+     * no targets defaults to running on all target objects in the scene hierarchy
+     * (if the transition is not contained in a TransitionSet), or all target
+     * objects passed down from its parent (if it is in a TransitionSet).
+     */
+    public Transition() {
+    }
+
+    /**
+     * Perform inflation from XML and apply a class-specific base style from a
+     * theme attribute or style resource. This constructor of Transition allows
+     * subclasses to use their own base style when they are inflating.
+     *
+     * @param context The Context the transition is running in, through which it can
+     *                access the current theme, resources, etc.
+     * @param attrs   The attributes of the XML tag that is inflating the transition.
+     */
+    public Transition(Context context, AttributeSet attrs) {
+        TypedArray a = context.obtainStyledAttributes(attrs, Styleable.TRANSITION);
+        XmlResourceParser parser = (XmlResourceParser) attrs;
+        long duration = TypedArrayUtils.getNamedInt(a, parser, "duration",
+                Styleable.Transition.DURATION, -1);
+        if (duration >= 0) {
+            setDuration(duration);
+        }
+        long startDelay = TypedArrayUtils.getNamedInt(a, parser, "startDelay",
+                Styleable.Transition.START_DELAY, -1);
+        if (startDelay > 0) {
+            setStartDelay(startDelay);
+        }
+        final int resId = TypedArrayUtils.getNamedResourceId(a, parser, "interpolator",
+                Styleable.Transition.INTERPOLATOR, 0);
+        if (resId > 0) {
+            setInterpolator(AnimationUtils.loadInterpolator(context, resId));
+        }
+        String matchOrder = TypedArrayUtils.getNamedString(a, parser, "matchOrder",
+                Styleable.Transition.MATCH_ORDER);
+        if (matchOrder != null) {
+            setMatchOrder(parseMatchOrder(matchOrder));
+        }
+        a.recycle();
+    }
+
+    @MatchOrder
+    private static int[] parseMatchOrder(String matchOrderString) {
+        StringTokenizer st = new StringTokenizer(matchOrderString, ",");
+        @MatchOrder
+        int[] matches = new int[st.countTokens()];
+        int index = 0;
+        while (st.hasMoreTokens()) {
+            String token = st.nextToken().trim();
+            if (MATCH_ID_STR.equalsIgnoreCase(token)) {
+                matches[index] = Transition.MATCH_ID;
+            } else if (MATCH_INSTANCE_STR.equalsIgnoreCase(token)) {
+                matches[index] = Transition.MATCH_INSTANCE;
+            } else if (MATCH_NAME_STR.equalsIgnoreCase(token)) {
+                matches[index] = Transition.MATCH_NAME;
+            } else if (MATCH_ITEM_ID_STR.equalsIgnoreCase(token)) {
+                matches[index] = Transition.MATCH_ITEM_ID;
+            } else if (token.isEmpty()) {
+                @MatchOrder
+                int[] smallerMatches = new int[matches.length - 1];
+                System.arraycopy(matches, 0, smallerMatches, 0, index);
+                matches = smallerMatches;
+                index--;
+            } else {
+                throw new InflateException("Unknown match type in matchOrder: '" + token + "'");
+            }
+            index++;
+        }
+        return matches;
+    }
+
+    /**
+     * Sets the duration of this transition. By default, there is no duration
+     * (indicated by a negative number), which means that the Animator created by
+     * the transition will have its own specified duration. If the duration of a
+     * Transition is set, that duration will override the Animator duration.
+     *
+     * @param duration The length of the animation, in milliseconds.
+     * @return This transition object.
+     */
+    @NonNull
+    public Transition setDuration(long duration) {
+        mDuration = duration;
+        return this;
+    }
+
+    /**
+     * Returns the duration set on this transition. If no duration has been set,
+     * the returned value will be negative, indicating that resulting animators will
+     * retain their own durations.
+     *
+     * @return The duration set on this transition, in milliseconds, if one has been
+     * set, otherwise returns a negative number.
+     */
+    public long getDuration() {
+        return mDuration;
+    }
+
+    /**
+     * Sets the startDelay of this transition. By default, there is no delay
+     * (indicated by a negative number), which means that the Animator created by
+     * the transition will have its own specified startDelay. If the delay of a
+     * Transition is set, that delay will override the Animator delay.
+     *
+     * @param startDelay The length of the delay, in milliseconds.
+     * @return This transition object.
+     */
+    @NonNull
+    public Transition setStartDelay(long startDelay) {
+        mStartDelay = startDelay;
+        return this;
+    }
+
+    /**
+     * Returns the startDelay set on this transition. If no startDelay has been set,
+     * the returned value will be negative, indicating that resulting animators will
+     * retain their own startDelays.
+     *
+     * @return The startDelay set on this transition, in milliseconds, if one has
+     * been set, otherwise returns a negative number.
+     */
+    public long getStartDelay() {
+        return mStartDelay;
+    }
+
+    /**
+     * Sets the interpolator of this transition. By default, the interpolator
+     * is null, which means that the Animator created by the transition
+     * will have its own specified interpolator. If the interpolator of a
+     * Transition is set, that interpolator will override the Animator interpolator.
+     *
+     * @param interpolator The time interpolator used by the transition
+     * @return This transition object.
+     */
+    @NonNull
+    public Transition setInterpolator(@Nullable TimeInterpolator interpolator) {
+        mInterpolator = interpolator;
+        return this;
+    }
+
+    /**
+     * Returns the interpolator set on this transition. If no interpolator has been set,
+     * the returned value will be null, indicating that resulting animators will
+     * retain their own interpolators.
+     *
+     * @return The interpolator set on this transition, if one has been set, otherwise
+     * returns null.
+     */
+    @Nullable
+    public TimeInterpolator getInterpolator() {
+        return mInterpolator;
+    }
+
+    /**
+     * Returns the set of property names used stored in the {@link TransitionValues}
+     * object passed into {@link #captureStartValues(TransitionValues)} that
+     * this transition cares about for the purposes of canceling overlapping animations.
+     * When any transition is started on a given scene root, all transitions
+     * currently running on that same scene root are checked to see whether the
+     * properties on which they based their animations agree with the end values of
+     * the same properties in the new transition. If the end values are not equal,
+     * then the old animation is canceled since the new transition will start a new
+     * animation to these new values. If the values are equal, the old animation is
+     * allowed to continue and no new animation is started for that transition.
+     *
+     * <p>A transition does not need to override this method. However, not doing so
+     * will mean that the cancellation logic outlined in the previous paragraph
+     * will be skipped for that transition, possibly leading to artifacts as
+     * old transitions and new transitions on the same targets run in parallel,
+     * animating views toward potentially different end values.</p>
+     *
+     * @return An array of property names as described in the class documentation for
+     * {@link TransitionValues}. The default implementation returns <code>null</code>.
+     */
+    @Nullable
+    public String[] getTransitionProperties() {
+        return null;
+    }
+
+    /**
+     * This method creates an animation that will be run for this transition
+     * given the information in the startValues and endValues structures captured
+     * earlier for the start and end scenes. Subclasses of Transition should override
+     * this method. The method should only be called by the transition system; it is
+     * not intended to be called from external classes.
+     *
+     * <p>This method is called by the transition's parent (all the way up to the
+     * topmost Transition in the hierarchy) with the sceneRoot and start/end
+     * values that the transition may need to set up initial target values
+     * and construct an appropriate animation. For example, if an overall
+     * Transition is a {@link TransitionSet} consisting of several
+     * child transitions in sequence, then some of the child transitions may
+     * want to set initial values on target views prior to the overall
+     * Transition commencing, to put them in an appropriate state for the
+     * delay between that start and the child Transition start time. For
+     * example, a transition that fades an item in may wish to set the starting
+     * alpha value to 0, to avoid it blinking in prior to the transition
+     * actually starting the animation. This is necessary because the scene
+     * change that triggers the Transition will automatically set the end-scene
+     * on all target views, so a Transition that wants to animate from a
+     * different value should set that value prior to returning from this method.</p>
+     *
+     * <p>Additionally, a Transition can perform logic to determine whether
+     * the transition needs to run on the given target and start/end values.
+     * For example, a transition that resizes objects on the screen may wish
+     * to avoid running for views which are not present in either the start
+     * or end scenes.</p>
+     *
+     * <p>If there is an animator created and returned from this method, the
+     * transition mechanism will apply any applicable duration, startDelay,
+     * and interpolator to that animation and start it. A return value of
+     * <code>null</code> indicates that no animation should run. The default
+     * implementation returns null.</p>
+     *
+     * <p>The method is called for every applicable target object, which is
+     * stored in the {@link TransitionValues#view} field.</p>
+     *
+     * @param sceneRoot   The root of the transition hierarchy.
+     * @param startValues The values for a specific target in the start scene.
+     * @param endValues   The values for the target in the end scene.
+     * @return A Animator to be started at the appropriate time in the
+     * overall transition for this scene change. A null value means no animation
+     * should be run.
+     */
+    @Nullable
+    public Animator createAnimator(@NonNull ViewGroup sceneRoot,
+            @Nullable TransitionValues startValues, @Nullable TransitionValues endValues) {
+        return null;
+    }
+
+    /**
+     * Sets the order in which Transition matches View start and end values.
+     * <p>
+     * The default behavior is to match first by {@link android.view.View#getTransitionName()},
+     * then by View instance, then by {@link android.view.View#getId()} and finally
+     * by its item ID if it is in a direct child of ListView. The caller can
+     * choose to have only some or all of the values of {@link #MATCH_INSTANCE},
+     * {@link #MATCH_NAME}, {@link #MATCH_ITEM_ID}, and {@link #MATCH_ID}. Only
+     * the match algorithms supplied will be used to determine whether Views are the
+     * the same in both the start and end Scene. Views that do not match will be considered
+     * as entering or leaving the Scene.
+     * </p>
+     *
+     * @param matches A list of zero or more of {@link #MATCH_INSTANCE},
+     *                {@link #MATCH_NAME}, {@link #MATCH_ITEM_ID}, and {@link #MATCH_ID}.
+     *                If none are provided, then the default match order will be set.
+     */
+    public void setMatchOrder(@MatchOrder int... matches) {
+        if (matches == null || matches.length == 0) {
+            mMatchOrder = DEFAULT_MATCH_ORDER;
+        } else {
+            for (int i = 0; i < matches.length; i++) {
+                int match = matches[i];
+                if (!isValidMatch(match)) {
+                    throw new IllegalArgumentException("matches contains invalid value");
+                }
+                if (alreadyContains(matches, i)) {
+                    throw new IllegalArgumentException("matches contains a duplicate value");
+                }
+            }
+            mMatchOrder = matches.clone();
+        }
+    }
+
+    private static boolean isValidMatch(int match) {
+        return (match >= MATCH_FIRST && match <= MATCH_LAST);
+    }
+
+    private static boolean alreadyContains(int[] array, int searchIndex) {
+        int value = array[searchIndex];
+        for (int i = 0; i < searchIndex; i++) {
+            if (array[i] == value) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Match start/end values by View instance. Adds matched values to mStartValuesList
+     * and mEndValuesList and removes them from unmatchedStart and unmatchedEnd.
+     */
+    private void matchInstances(ArrayMap<View, TransitionValues> unmatchedStart,
+            ArrayMap<View, TransitionValues> unmatchedEnd) {
+        for (int i = unmatchedStart.size() - 1; i >= 0; i--) {
+            View view = unmatchedStart.keyAt(i);
+            if (view != null && isValidTarget(view)) {
+                TransitionValues end = unmatchedEnd.remove(view);
+                if (end != null && end.view != null && isValidTarget(end.view)) {
+                    TransitionValues start = unmatchedStart.removeAt(i);
+                    mStartValuesList.add(start);
+                    mEndValuesList.add(end);
+                }
+            }
+        }
+    }
+
+    /**
+     * Match start/end values by Adapter item ID. Adds matched values to mStartValuesList
+     * and mEndValuesList and removes them from unmatchedStart and unmatchedEnd, using
+     * startItemIds and endItemIds as a guide for which Views have unique item IDs.
+     */
+    private void matchItemIds(ArrayMap<View, TransitionValues> unmatchedStart,
+            ArrayMap<View, TransitionValues> unmatchedEnd,
+            LongSparseArray<View> startItemIds, LongSparseArray<View> endItemIds) {
+        int numStartIds = startItemIds.size();
+        for (int i = 0; i < numStartIds; i++) {
+            View startView = startItemIds.valueAt(i);
+            if (startView != null && isValidTarget(startView)) {
+                View endView = endItemIds.get(startItemIds.keyAt(i));
+                if (endView != null && isValidTarget(endView)) {
+                    TransitionValues startValues = unmatchedStart.get(startView);
+                    TransitionValues endValues = unmatchedEnd.get(endView);
+                    if (startValues != null && endValues != null) {
+                        mStartValuesList.add(startValues);
+                        mEndValuesList.add(endValues);
+                        unmatchedStart.remove(startView);
+                        unmatchedEnd.remove(endView);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Match start/end values by Adapter view ID. Adds matched values to mStartValuesList
+     * and mEndValuesList and removes them from unmatchedStart and unmatchedEnd, using
+     * startIds and endIds as a guide for which Views have unique IDs.
+     */
+    private void matchIds(ArrayMap<View, TransitionValues> unmatchedStart,
+            ArrayMap<View, TransitionValues> unmatchedEnd,
+            SparseArray<View> startIds, SparseArray<View> endIds) {
+        int numStartIds = startIds.size();
+        for (int i = 0; i < numStartIds; i++) {
+            View startView = startIds.valueAt(i);
+            if (startView != null && isValidTarget(startView)) {
+                View endView = endIds.get(startIds.keyAt(i));
+                if (endView != null && isValidTarget(endView)) {
+                    TransitionValues startValues = unmatchedStart.get(startView);
+                    TransitionValues endValues = unmatchedEnd.get(endView);
+                    if (startValues != null && endValues != null) {
+                        mStartValuesList.add(startValues);
+                        mEndValuesList.add(endValues);
+                        unmatchedStart.remove(startView);
+                        unmatchedEnd.remove(endView);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Match start/end values by Adapter transitionName. Adds matched values to mStartValuesList
+     * and mEndValuesList and removes them from unmatchedStart and unmatchedEnd, using
+     * startNames and endNames as a guide for which Views have unique transitionNames.
+     */
+    private void matchNames(ArrayMap<View, TransitionValues> unmatchedStart,
+            ArrayMap<View, TransitionValues> unmatchedEnd,
+            ArrayMap<String, View> startNames, ArrayMap<String, View> endNames) {
+        int numStartNames = startNames.size();
+        for (int i = 0; i < numStartNames; i++) {
+            View startView = startNames.valueAt(i);
+            if (startView != null && isValidTarget(startView)) {
+                View endView = endNames.get(startNames.keyAt(i));
+                if (endView != null && isValidTarget(endView)) {
+                    TransitionValues startValues = unmatchedStart.get(startView);
+                    TransitionValues endValues = unmatchedEnd.get(endView);
+                    if (startValues != null && endValues != null) {
+                        mStartValuesList.add(startValues);
+                        mEndValuesList.add(endValues);
+                        unmatchedStart.remove(startView);
+                        unmatchedEnd.remove(endView);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Adds all values from unmatchedStart and unmatchedEnd to mStartValuesList and mEndValuesList,
+     * assuming that there is no match between values in the list.
+     */
+    private void addUnmatched(ArrayMap<View, TransitionValues> unmatchedStart,
+            ArrayMap<View, TransitionValues> unmatchedEnd) {
+        // Views that only exist in the start Scene
+        for (int i = 0; i < unmatchedStart.size(); i++) {
+            final TransitionValues start = unmatchedStart.valueAt(i);
+            if (isValidTarget(start.view)) {
+                mStartValuesList.add(start);
+                mEndValuesList.add(null);
+            }
+        }
+
+        // Views that only exist in the end Scene
+        for (int i = 0; i < unmatchedEnd.size(); i++) {
+            final TransitionValues end = unmatchedEnd.valueAt(i);
+            if (isValidTarget(end.view)) {
+                mEndValuesList.add(end);
+                mStartValuesList.add(null);
+            }
+        }
+    }
+
+    private void matchStartAndEnd(TransitionValuesMaps startValues,
+            TransitionValuesMaps endValues) {
+        ArrayMap<View, TransitionValues> unmatchedStart = new ArrayMap<>(startValues.mViewValues);
+        ArrayMap<View, TransitionValues> unmatchedEnd = new ArrayMap<>(endValues.mViewValues);
+
+        for (int i = 0; i < mMatchOrder.length; i++) {
+            switch (mMatchOrder[i]) {
+                case MATCH_INSTANCE:
+                    matchInstances(unmatchedStart, unmatchedEnd);
+                    break;
+                case MATCH_NAME:
+                    matchNames(unmatchedStart, unmatchedEnd,
+                            startValues.mNameValues, endValues.mNameValues);
+                    break;
+                case MATCH_ID:
+                    matchIds(unmatchedStart, unmatchedEnd,
+                            startValues.mIdValues, endValues.mIdValues);
+                    break;
+                case MATCH_ITEM_ID:
+                    matchItemIds(unmatchedStart, unmatchedEnd,
+                            startValues.mItemIdValues, endValues.mItemIdValues);
+                    break;
+            }
+        }
+        addUnmatched(unmatchedStart, unmatchedEnd);
+    }
+
+    /**
+     * This method, essentially a wrapper around all calls to createAnimator for all
+     * possible target views, is called with the entire set of start/end
+     * values. The implementation in Transition iterates through these lists
+     * and calls {@link #createAnimator(ViewGroup, TransitionValues, TransitionValues)}
+     * with each set of start/end values on this transition. The
+     * TransitionSet subclass overrides this method and delegates it to
+     * each of its children in succession.
+     *
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    protected void createAnimators(ViewGroup sceneRoot, TransitionValuesMaps startValues,
+            TransitionValuesMaps endValues, ArrayList<TransitionValues> startValuesList,
+            ArrayList<TransitionValues> endValuesList) {
+        if (DBG) {
+            Log.d(LOG_TAG, "createAnimators() for " + this);
+        }
+        ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
+        long minStartDelay = Long.MAX_VALUE;
+        SparseIntArray startDelays = new SparseIntArray();
+        int startValuesListCount = startValuesList.size();
+        for (int i = 0; i < startValuesListCount; ++i) {
+            TransitionValues start = startValuesList.get(i);
+            TransitionValues end = endValuesList.get(i);
+            if (start != null && !start.mTargetedTransitions.contains(this)) {
+                start = null;
+            }
+            if (end != null && !end.mTargetedTransitions.contains(this)) {
+                end = null;
+            }
+            if (start == null && end == null) {
+                continue;
+            }
+            // Only bother trying to animate with values that differ between start/end
+            boolean isChanged = start == null || end == null || isTransitionRequired(start, end);
+            if (isChanged) {
+                if (DBG) {
+                    View view = (end != null) ? end.view : start.view;
+                    Log.d(LOG_TAG, "  differing start/end values for view " + view);
+                    if (start == null || end == null) {
+                        Log.d(LOG_TAG, "    " + ((start == null)
+                                ? "start null, end non-null" : "start non-null, end null"));
+                    } else {
+                        for (String key : start.values.keySet()) {
+                            Object startValue = start.values.get(key);
+                            Object endValue = end.values.get(key);
+                            if (startValue != endValue && !startValue.equals(endValue)) {
+                                Log.d(LOG_TAG, "    " + key + ": start(" + startValue
+                                        + "), end(" + endValue + ")");
+                            }
+                        }
+                    }
+                }
+                // TODO: what to do about targetIds and itemIds?
+                Animator animator = createAnimator(sceneRoot, start, end);
+                if (animator != null) {
+                    // Save animation info for future cancellation purposes
+                    View view;
+                    TransitionValues infoValues = null;
+                    if (end != null) {
+                        view = end.view;
+                        String[] properties = getTransitionProperties();
+                        if (view != null && properties != null && properties.length > 0) {
+                            infoValues = new TransitionValues();
+                            infoValues.view = view;
+                            TransitionValues newValues = endValues.mViewValues.get(view);
+                            if (newValues != null) {
+                                for (int j = 0; j < properties.length; ++j) {
+                                    infoValues.values.put(properties[j],
+                                            newValues.values.get(properties[j]));
+                                }
+                            }
+                            int numExistingAnims = runningAnimators.size();
+                            for (int j = 0; j < numExistingAnims; ++j) {
+                                Animator anim = runningAnimators.keyAt(j);
+                                AnimationInfo info = runningAnimators.get(anim);
+                                if (info.mValues != null && info.mView == view
+                                        && info.mName.equals(getName())) {
+                                    if (info.mValues.equals(infoValues)) {
+                                        // Favor the old animator
+                                        animator = null;
+                                        break;
+                                    }
+                                }
+                            }
+                        }
+                    } else {
+                        view = start.view;
+                    }
+                    if (animator != null) {
+                        if (mPropagation != null) {
+                            long delay = mPropagation.getStartDelay(sceneRoot, this, start, end);
+                            startDelays.put(mAnimators.size(), (int) delay);
+                            minStartDelay = Math.min(delay, minStartDelay);
+                        }
+                        AnimationInfo info = new AnimationInfo(view, getName(), this,
+                                ViewUtils.getWindowId(sceneRoot), infoValues);
+                        runningAnimators.put(animator, info);
+                        mAnimators.add(animator);
+                    }
+                }
+            }
+        }
+        if (minStartDelay != 0) {
+            for (int i = 0; i < startDelays.size(); i++) {
+                int index = startDelays.keyAt(i);
+                Animator animator = mAnimators.get(index);
+                long delay = startDelays.valueAt(i) - minStartDelay + animator.getStartDelay();
+                animator.setStartDelay(delay);
+            }
+        }
+    }
+
+    /**
+     * Internal utility method for checking whether a given view/id
+     * is valid for this transition, where "valid" means that either
+     * the Transition has no target/targetId list (the default, in which
+     * cause the transition should act on all views in the hiearchy), or
+     * the given view is in the target list or the view id is in the
+     * targetId list. If the target parameter is null, then the target list
+     * is not checked (this is in the case of ListView items, where the
+     * views are ignored and only the ids are used).
+     */
+    boolean isValidTarget(View target) {
+        int targetId = target.getId();
+        if (mTargetIdExcludes != null && mTargetIdExcludes.contains(targetId)) {
+            return false;
+        }
+        if (mTargetExcludes != null && mTargetExcludes.contains(target)) {
+            return false;
+        }
+        if (mTargetTypeExcludes != null) {
+            int numTypes = mTargetTypeExcludes.size();
+            for (int i = 0; i < numTypes; ++i) {
+                Class type = mTargetTypeExcludes.get(i);
+                if (type.isInstance(target)) {
+                    return false;
+                }
+            }
+        }
+        if (mTargetNameExcludes != null && ViewCompat.getTransitionName(target) != null) {
+            if (mTargetNameExcludes.contains(ViewCompat.getTransitionName(target))) {
+                return false;
+            }
+        }
+        if (mTargetIds.size() == 0 && mTargets.size() == 0
+                && (mTargetTypes == null || mTargetTypes.isEmpty())
+                && (mTargetNames == null || mTargetNames.isEmpty())) {
+            return true;
+        }
+        if (mTargetIds.contains(targetId) || mTargets.contains(target)) {
+            return true;
+        }
+        if (mTargetNames != null && mTargetNames.contains(ViewCompat.getTransitionName(target))) {
+            return true;
+        }
+        if (mTargetTypes != null) {
+            for (int i = 0; i < mTargetTypes.size(); ++i) {
+                if (mTargetTypes.get(i).isInstance(target)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    private static ArrayMap<Animator, AnimationInfo> getRunningAnimators() {
+        ArrayMap<Animator, AnimationInfo> runningAnimators = sRunningAnimators.get();
+        if (runningAnimators == null) {
+            runningAnimators = new ArrayMap<>();
+            sRunningAnimators.set(runningAnimators);
+        }
+        return runningAnimators;
+    }
+
+    /**
+     * This is called internally once all animations have been set up by the
+     * transition hierarchy. \
+     *
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    protected void runAnimators() {
+        if (DBG) {
+            Log.d(LOG_TAG, "runAnimators() on " + this);
+        }
+        start();
+        ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
+        // Now start every Animator that was previously created for this transition
+        for (Animator anim : mAnimators) {
+            if (DBG) {
+                Log.d(LOG_TAG, "  anim: " + anim);
+            }
+            if (runningAnimators.containsKey(anim)) {
+                start();
+                runAnimator(anim, runningAnimators);
+            }
+        }
+        mAnimators.clear();
+        end();
+    }
+
+    private void runAnimator(Animator animator,
+            final ArrayMap<Animator, AnimationInfo> runningAnimators) {
+        if (animator != null) {
+            // TODO: could be a single listener instance for all of them since it uses the param
+            animator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationStart(Animator animation) {
+                    mCurrentAnimators.add(animation);
+                }
+
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    runningAnimators.remove(animation);
+                    mCurrentAnimators.remove(animation);
+                }
+            });
+            animate(animator);
+        }
+    }
+
+    /**
+     * Captures the values in the start scene for the properties that this
+     * transition monitors. These values are then passed as the startValues
+     * structure in a later call to
+     * {@link #createAnimator(ViewGroup, TransitionValues, TransitionValues)}.
+     * The main concern for an implementation is what the
+     * properties are that the transition cares about and what the values are
+     * for all of those properties. The start and end values will be compared
+     * later during the
+     * {@link #createAnimator(ViewGroup, TransitionValues, TransitionValues)}
+     * method to determine what, if any, animations, should be run.
+     *
+     * <p>Subclasses must implement this method. The method should only be called by the
+     * transition system; it is not intended to be called from external classes.</p>
+     *
+     * @param transitionValues The holder for any values that the Transition
+     *                         wishes to store. Values are stored in the <code>values</code> field
+     *                         of this TransitionValues object and are keyed from
+     *                         a String value. For example, to store a view's rotation value,
+     *                         a transition might call
+     *                         <code>transitionValues.values.put("appname:transitionname:rotation",
+     *                         view.getRotation())</code>. The target view will already be stored
+     *                         in
+     *                         the transitionValues structure when this method is called.
+     * @see #captureEndValues(TransitionValues)
+     * @see #createAnimator(ViewGroup, TransitionValues, TransitionValues)
+     */
+    public abstract void captureStartValues(@NonNull TransitionValues transitionValues);
+
+    /**
+     * Captures the values in the end scene for the properties that this
+     * transition monitors. These values are then passed as the endValues
+     * structure in a later call to
+     * {@link #createAnimator(ViewGroup, TransitionValues, TransitionValues)}.
+     * The main concern for an implementation is what the
+     * properties are that the transition cares about and what the values are
+     * for all of those properties. The start and end values will be compared
+     * later during the
+     * {@link #createAnimator(ViewGroup, TransitionValues, TransitionValues)}
+     * method to determine what, if any, animations, should be run.
+     *
+     * <p>Subclasses must implement this method. The method should only be called by the
+     * transition system; it is not intended to be called from external classes.</p>
+     *
+     * @param transitionValues The holder for any values that the Transition
+     *                         wishes to store. Values are stored in the <code>values</code> field
+     *                         of this TransitionValues object and are keyed from
+     *                         a String value. For example, to store a view's rotation value,
+     *                         a transition might call
+     *                         <code>transitionValues.values.put("appname:transitionname:rotation",
+     *                         view.getRotation())</code>. The target view will already be stored
+     *                         in
+     *                         the transitionValues structure when this method is called.
+     * @see #captureStartValues(TransitionValues)
+     * @see #createAnimator(ViewGroup, TransitionValues, TransitionValues)
+     */
+    public abstract void captureEndValues(@NonNull TransitionValues transitionValues);
+
+    /**
+     * Sets the target view instances that this Transition is interested in
+     * animating. By default, there are no targets, and a Transition will
+     * listen for changes on every view in the hierarchy below the sceneRoot
+     * of the Scene being transitioned into. Setting targets constrains
+     * the Transition to only listen for, and act on, these views.
+     * All other views will be ignored.
+     *
+     * <p>The target list is like the {@link #addTarget(int) targetId}
+     * list except this list specifies the actual View instances, not the ids
+     * of the views. This is an important distinction when scene changes involve
+     * view hierarchies which have been inflated separately; different views may
+     * share the same id but not actually be the same instance. If the transition
+     * should treat those views as the same, then {@link #addTarget(int)} should be used
+     * instead of {@link #addTarget(View)}. If, on the other hand, scene changes involve
+     * changes all within the same view hierarchy, among views which do not
+     * necessarily have ids set on them, then the target list of views may be more
+     * convenient.</p>
+     *
+     * @param target A View on which the Transition will act, must be non-null.
+     * @return The Transition to which the target is added.
+     * Returning the same object makes it easier to chain calls during
+     * construction, such as
+     * <code>transitionSet.addTransitions(new Fade()).addTarget(someView);</code>
+     * @see #addTarget(int)
+     */
+    @NonNull
+    public Transition addTarget(@NonNull View target) {
+        mTargets.add(target);
+        return this;
+    }
+
+    /**
+     * Adds the id of a target view that this Transition is interested in
+     * animating. By default, there are no targetIds, and a Transition will
+     * listen for changes on every view in the hierarchy below the sceneRoot
+     * of the Scene being transitioned into. Setting targetIds constrains
+     * the Transition to only listen for, and act on, views with these IDs.
+     * Views with different IDs, or no IDs whatsoever, will be ignored.
+     *
+     * <p>Note that using ids to specify targets implies that ids should be unique
+     * within the view hierarchy underneath the scene root.</p>
+     *
+     * @param targetId The id of a target view, must be a positive number.
+     * @return The Transition to which the targetId is added.
+     * Returning the same object makes it easier to chain calls during
+     * construction, such as
+     * <code>transitionSet.addTransitions(new Fade()).addTarget(someId);</code>
+     * @see View#getId()
+     */
+    @NonNull
+    public Transition addTarget(@IdRes int targetId) {
+        if (targetId != 0) {
+            mTargetIds.add(targetId);
+        }
+        return this;
+    }
+
+    /**
+     * Adds the transitionName of a target view that this Transition is interested in
+     * animating. By default, there are no targetNames, and a Transition will
+     * listen for changes on every view in the hierarchy below the sceneRoot
+     * of the Scene being transitioned into. Setting targetNames constrains
+     * the Transition to only listen for, and act on, views with these transitionNames.
+     * Views with different transitionNames, or no transitionName whatsoever, will be ignored.
+     *
+     * <p>Note that transitionNames should be unique within the view hierarchy.</p>
+     *
+     * @param targetName The transitionName of a target view, must be non-null.
+     * @return The Transition to which the target transitionName is added.
+     * Returning the same object makes it easier to chain calls during
+     * construction, such as
+     * <code>transitionSet.addTransitions(new Fade()).addTarget(someName);</code>
+     * @see ViewCompat#getTransitionName(View)
+     */
+    @NonNull
+    public Transition addTarget(@NonNull String targetName) {
+        if (mTargetNames == null) {
+            mTargetNames = new ArrayList<>();
+        }
+        mTargetNames.add(targetName);
+        return this;
+    }
+
+    /**
+     * Adds the Class of a target view that this Transition is interested in
+     * animating. By default, there are no targetTypes, and a Transition will
+     * listen for changes on every view in the hierarchy below the sceneRoot
+     * of the Scene being transitioned into. Setting targetTypes constrains
+     * the Transition to only listen for, and act on, views with these classes.
+     * Views with different classes will be ignored.
+     *
+     * <p>Note that any View that can be cast to targetType will be included, so
+     * if targetType is <code>View.class</code>, all Views will be included.</p>
+     *
+     * @param targetType The type to include when running this transition.
+     * @return The Transition to which the target class was added.
+     * Returning the same object makes it easier to chain calls during
+     * construction, such as
+     * <code>transitionSet.addTransitions(new Fade()).addTarget(ImageView.class);</code>
+     * @see #addTarget(int)
+     * @see #addTarget(android.view.View)
+     * @see #excludeTarget(Class, boolean)
+     * @see #excludeChildren(Class, boolean)
+     */
+    @NonNull
+    public Transition addTarget(@NonNull Class targetType) {
+        if (mTargetTypes == null) {
+            mTargetTypes = new ArrayList<>();
+        }
+        mTargetTypes.add(targetType);
+        return this;
+    }
+
+    /**
+     * Removes the given target from the list of targets that this Transition
+     * is interested in animating.
+     *
+     * @param target The target view, must be non-null.
+     * @return Transition The Transition from which the target is removed.
+     * Returning the same object makes it easier to chain calls during
+     * construction, such as
+     * <code>transitionSet.addTransitions(new Fade()).removeTarget(someView);</code>
+     */
+    @NonNull
+    public Transition removeTarget(@NonNull View target) {
+        mTargets.remove(target);
+        return this;
+    }
+
+    /**
+     * Removes the given targetId from the list of ids that this Transition
+     * is interested in animating.
+     *
+     * @param targetId The id of a target view, must be a positive number.
+     * @return The Transition from which the targetId is removed.
+     * Returning the same object makes it easier to chain calls during
+     * construction, such as
+     * <code>transitionSet.addTransitions(new Fade()).removeTargetId(someId);</code>
+     */
+    @NonNull
+    public Transition removeTarget(@IdRes int targetId) {
+        if (targetId != 0) {
+            mTargetIds.remove((Integer) targetId);
+        }
+        return this;
+    }
+
+    /**
+     * Removes the given targetName from the list of transitionNames that this Transition
+     * is interested in animating.
+     *
+     * @param targetName The transitionName of a target view, must not be null.
+     * @return The Transition from which the targetName is removed.
+     * Returning the same object makes it easier to chain calls during
+     * construction, such as
+     * <code>transitionSet.addTransitions(new Fade()).removeTargetName(someName);</code>
+     */
+    @NonNull
+    public Transition removeTarget(@NonNull String targetName) {
+        if (mTargetNames != null) {
+            mTargetNames.remove(targetName);
+        }
+        return this;
+    }
+
+    /**
+     * Removes the given target from the list of targets that this Transition
+     * is interested in animating.
+     *
+     * @param target The type of the target view, must be non-null.
+     * @return Transition The Transition from which the target is removed.
+     * Returning the same object makes it easier to chain calls during
+     * construction, such as
+     * <code>transitionSet.addTransitions(new Fade()).removeTarget(someType);</code>
+     */
+    @NonNull
+    public Transition removeTarget(@NonNull Class target) {
+        if (mTargetTypes != null) {
+            mTargetTypes.remove(target);
+        }
+        return this;
+    }
+
+    /**
+     * Utility method to manage the boilerplate code that is the same whether we
+     * are excluding targets or their children.
+     */
+    private static <T> ArrayList<T> excludeObject(ArrayList<T> list, T target, boolean exclude) {
+        if (target != null) {
+            if (exclude) {
+                list = ArrayListManager.add(list, target);
+            } else {
+                list = ArrayListManager.remove(list, target);
+            }
+        }
+        return list;
+    }
+
+    /**
+     * Whether to add the given target to the list of targets to exclude from this
+     * transition. The <code>exclude</code> parameter specifies whether the target
+     * should be added to or removed from the excluded list.
+     *
+     * <p>Excluding targets is a general mechanism for allowing transitions to run on
+     * a view hierarchy while skipping target views that should not be part of
+     * the transition. For example, you may want to avoid animating children
+     * of a specific ListView or Spinner. Views can be excluded either by their
+     * id, or by their instance reference, or by the Class of that view
+     * (eg, {@link Spinner}).</p>
+     *
+     * @param target  The target to ignore when running this transition.
+     * @param exclude Whether to add the target to or remove the target from the
+     *                current list of excluded targets.
+     * @return This transition object.
+     * @see #excludeChildren(View, boolean)
+     * @see #excludeTarget(int, boolean)
+     * @see #excludeTarget(Class, boolean)
+     */
+    @NonNull
+    public Transition excludeTarget(@NonNull View target, boolean exclude) {
+        mTargetExcludes = excludeView(mTargetExcludes, target, exclude);
+        return this;
+    }
+
+    /**
+     * Whether to add the given id to the list of target ids to exclude from this
+     * transition. The <code>exclude</code> parameter specifies whether the target
+     * should be added to or removed from the excluded list.
+     *
+     * <p>Excluding targets is a general mechanism for allowing transitions to run on
+     * a view hierarchy while skipping target views that should not be part of
+     * the transition. For example, you may want to avoid animating children
+     * of a specific ListView or Spinner. Views can be excluded either by their
+     * id, or by their instance reference, or by the Class of that view
+     * (eg, {@link Spinner}).</p>
+     *
+     * @param targetId The id of a target to ignore when running this transition.
+     * @param exclude  Whether to add the target to or remove the target from the
+     *                 current list of excluded targets.
+     * @return This transition object.
+     * @see #excludeChildren(int, boolean)
+     * @see #excludeTarget(View, boolean)
+     * @see #excludeTarget(Class, boolean)
+     */
+    @NonNull
+    public Transition excludeTarget(@IdRes int targetId, boolean exclude) {
+        mTargetIdExcludes = excludeId(mTargetIdExcludes, targetId, exclude);
+        return this;
+    }
+
+    /**
+     * Whether to add the given transitionName to the list of target transitionNames to exclude
+     * from this transition. The <code>exclude</code> parameter specifies whether the target
+     * should be added to or removed from the excluded list.
+     *
+     * <p>Excluding targets is a general mechanism for allowing transitions to run on
+     * a view hierarchy while skipping target views that should not be part of
+     * the transition. For example, you may want to avoid animating children
+     * of a specific ListView or Spinner. Views can be excluded by their
+     * id, their instance reference, their transitionName, or by the Class of that view
+     * (eg, {@link Spinner}).</p>
+     *
+     * @param targetName The name of a target to ignore when running this transition.
+     * @param exclude    Whether to add the target to or remove the target from the
+     *                   current list of excluded targets.
+     * @return This transition object.
+     * @see #excludeTarget(View, boolean)
+     * @see #excludeTarget(int, boolean)
+     * @see #excludeTarget(Class, boolean)
+     */
+    @NonNull
+    public Transition excludeTarget(@NonNull String targetName, boolean exclude) {
+        mTargetNameExcludes = excludeObject(mTargetNameExcludes, targetName, exclude);
+        return this;
+    }
+
+    /**
+     * Whether to add the children of given target to the list of target children
+     * to exclude from this transition. The <code>exclude</code> parameter specifies
+     * whether the target should be added to or removed from the excluded list.
+     *
+     * <p>Excluding targets is a general mechanism for allowing transitions to run on
+     * a view hierarchy while skipping target views that should not be part of
+     * the transition. For example, you may want to avoid animating children
+     * of a specific ListView or Spinner. Views can be excluded either by their
+     * id, or by their instance reference, or by the Class of that view
+     * (eg, {@link Spinner}).</p>
+     *
+     * @param target  The target to ignore when running this transition.
+     * @param exclude Whether to add the target to or remove the target from the
+     *                current list of excluded targets.
+     * @return This transition object.
+     * @see #excludeTarget(View, boolean)
+     * @see #excludeChildren(int, boolean)
+     * @see #excludeChildren(Class, boolean)
+     */
+    @NonNull
+    public Transition excludeChildren(@NonNull View target, boolean exclude) {
+        mTargetChildExcludes = excludeView(mTargetChildExcludes, target, exclude);
+        return this;
+    }
+
+    /**
+     * Whether to add the children of the given id to the list of targets to exclude
+     * from this transition. The <code>exclude</code> parameter specifies whether
+     * the children of the target should be added to or removed from the excluded list.
+     * Excluding children in this way provides a simple mechanism for excluding all
+     * children of specific targets, rather than individually excluding each
+     * child individually.
+     *
+     * <p>Excluding targets is a general mechanism for allowing transitions to run on
+     * a view hierarchy while skipping target views that should not be part of
+     * the transition. For example, you may want to avoid animating children
+     * of a specific ListView or Spinner. Views can be excluded either by their
+     * id, or by their instance reference, or by the Class of that view
+     * (eg, {@link Spinner}).</p>
+     *
+     * @param targetId The id of a target whose children should be ignored when running
+     *                 this transition.
+     * @param exclude  Whether to add the target to or remove the target from the
+     *                 current list of excluded-child targets.
+     * @return This transition object.
+     * @see #excludeTarget(int, boolean)
+     * @see #excludeChildren(View, boolean)
+     * @see #excludeChildren(Class, boolean)
+     */
+    @NonNull
+    public Transition excludeChildren(@IdRes int targetId, boolean exclude) {
+        mTargetIdChildExcludes = excludeId(mTargetIdChildExcludes, targetId, exclude);
+        return this;
+    }
+
+    /**
+     * Utility method to manage the boilerplate code that is the same whether we
+     * are excluding targets or their children.
+     */
+    private ArrayList<Integer> excludeId(ArrayList<Integer> list, int targetId, boolean exclude) {
+        if (targetId > 0) {
+            if (exclude) {
+                list = ArrayListManager.add(list, targetId);
+            } else {
+                list = ArrayListManager.remove(list, targetId);
+            }
+        }
+        return list;
+    }
+
+    /**
+     * Utility method to manage the boilerplate code that is the same whether we
+     * are excluding targets or their children.
+     */
+    private ArrayList<View> excludeView(ArrayList<View> list, View target, boolean exclude) {
+        if (target != null) {
+            if (exclude) {
+                list = ArrayListManager.add(list, target);
+            } else {
+                list = ArrayListManager.remove(list, target);
+            }
+        }
+        return list;
+    }
+
+    /**
+     * Whether to add the given type to the list of types to exclude from this
+     * transition. The <code>exclude</code> parameter specifies whether the target
+     * type should be added to or removed from the excluded list.
+     *
+     * <p>Excluding targets is a general mechanism for allowing transitions to run on
+     * a view hierarchy while skipping target views that should not be part of
+     * the transition. For example, you may want to avoid animating children
+     * of a specific ListView or Spinner. Views can be excluded either by their
+     * id, or by their instance reference, or by the Class of that view
+     * (eg, {@link Spinner}).</p>
+     *
+     * @param type    The type to ignore when running this transition.
+     * @param exclude Whether to add the target type to or remove it from the
+     *                current list of excluded target types.
+     * @return This transition object.
+     * @see #excludeChildren(Class, boolean)
+     * @see #excludeTarget(int, boolean)
+     * @see #excludeTarget(View, boolean)
+     */
+    @NonNull
+    public Transition excludeTarget(@NonNull Class type, boolean exclude) {
+        mTargetTypeExcludes = excludeType(mTargetTypeExcludes, type, exclude);
+        return this;
+    }
+
+    /**
+     * Whether to add the given type to the list of types whose children should
+     * be excluded from this transition. The <code>exclude</code> parameter
+     * specifies whether the target type should be added to or removed from
+     * the excluded list.
+     *
+     * <p>Excluding targets is a general mechanism for allowing transitions to run on
+     * a view hierarchy while skipping target views that should not be part of
+     * the transition. For example, you may want to avoid animating children
+     * of a specific ListView or Spinner. Views can be excluded either by their
+     * id, or by their instance reference, or by the Class of that view
+     * (eg, {@link Spinner}).</p>
+     *
+     * @param type    The type to ignore when running this transition.
+     * @param exclude Whether to add the target type to or remove it from the
+     *                current list of excluded target types.
+     * @return This transition object.
+     * @see #excludeTarget(Class, boolean)
+     * @see #excludeChildren(int, boolean)
+     * @see #excludeChildren(View, boolean)
+     */
+    @NonNull
+    public Transition excludeChildren(@NonNull Class type, boolean exclude) {
+        mTargetTypeChildExcludes = excludeType(mTargetTypeChildExcludes, type, exclude);
+        return this;
+    }
+
+    /**
+     * Utility method to manage the boilerplate code that is the same whether we
+     * are excluding targets or their children.
+     */
+    private ArrayList<Class> excludeType(ArrayList<Class> list, Class type, boolean exclude) {
+        if (type != null) {
+            if (exclude) {
+                list = ArrayListManager.add(list, type);
+            } else {
+                list = ArrayListManager.remove(list, type);
+            }
+        }
+        return list;
+    }
+
+    /**
+     * Returns the array of target IDs that this transition limits itself to
+     * tracking and animating. If the array is null for both this method and
+     * {@link #getTargets()}, then this transition is
+     * not limited to specific views, and will handle changes to any views
+     * in the hierarchy of a scene change.
+     *
+     * @return the list of target IDs
+     */
+    @NonNull
+    public List<Integer> getTargetIds() {
+        return mTargetIds;
+    }
+
+    /**
+     * Returns the array of target views that this transition limits itself to
+     * tracking and animating. If the array is null for both this method and
+     * {@link #getTargetIds()}, then this transition is
+     * not limited to specific views, and will handle changes to any views
+     * in the hierarchy of a scene change.
+     *
+     * @return the list of target views
+     */
+    @NonNull
+    public List<View> getTargets() {
+        return mTargets;
+    }
+
+    /**
+     * Returns the list of target transitionNames that this transition limits itself to
+     * tracking and animating. If the list is null or empty for
+     * {@link #getTargetIds()}, {@link #getTargets()}, {@link #getTargetNames()}, and
+     * {@link #getTargetTypes()} then this transition is
+     * not limited to specific views, and will handle changes to any views
+     * in the hierarchy of a scene change.
+     *
+     * @return the list of target transitionNames
+     */
+    @Nullable
+    public List<String> getTargetNames() {
+        return mTargetNames;
+    }
+
+    /**
+     * Returns the list of target transitionNames that this transition limits itself to
+     * tracking and animating. If the list is null or empty for
+     * {@link #getTargetIds()}, {@link #getTargets()}, {@link #getTargetNames()}, and
+     * {@link #getTargetTypes()} then this transition is
+     * not limited to specific views, and will handle changes to any views
+     * in the hierarchy of a scene change.
+     *
+     * @return the list of target Types
+     */
+    @Nullable
+    public List<Class> getTargetTypes() {
+        return mTargetTypes;
+    }
+
+    /**
+     * Recursive method that captures values for the given view and the
+     * hierarchy underneath it.
+     *
+     * @param sceneRoot The root of the view hierarchy being captured
+     * @param start     true if this capture is happening before the scene change,
+     *                  false otherwise
+     */
+    void captureValues(ViewGroup sceneRoot, boolean start) {
+        clearValues(start);
+        if ((mTargetIds.size() > 0 || mTargets.size() > 0)
+                && (mTargetNames == null || mTargetNames.isEmpty())
+                && (mTargetTypes == null || mTargetTypes.isEmpty())) {
+            for (int i = 0; i < mTargetIds.size(); ++i) {
+                int id = mTargetIds.get(i);
+                View view = sceneRoot.findViewById(id);
+                if (view != null) {
+                    TransitionValues values = new TransitionValues();
+                    values.view = view;
+                    if (start) {
+                        captureStartValues(values);
+                    } else {
+                        captureEndValues(values);
+                    }
+                    values.mTargetedTransitions.add(this);
+                    capturePropagationValues(values);
+                    if (start) {
+                        addViewValues(mStartValues, view, values);
+                    } else {
+                        addViewValues(mEndValues, view, values);
+                    }
+                }
+            }
+            for (int i = 0; i < mTargets.size(); ++i) {
+                View view = mTargets.get(i);
+                TransitionValues values = new TransitionValues();
+                values.view = view;
+                if (start) {
+                    captureStartValues(values);
+                } else {
+                    captureEndValues(values);
+                }
+                values.mTargetedTransitions.add(this);
+                capturePropagationValues(values);
+                if (start) {
+                    addViewValues(mStartValues, view, values);
+                } else {
+                    addViewValues(mEndValues, view, values);
+                }
+            }
+        } else {
+            captureHierarchy(sceneRoot, start);
+        }
+        if (!start && mNameOverrides != null) {
+            int numOverrides = mNameOverrides.size();
+            ArrayList<View> overriddenViews = new ArrayList<>(numOverrides);
+            for (int i = 0; i < numOverrides; i++) {
+                String fromName = mNameOverrides.keyAt(i);
+                overriddenViews.add(mStartValues.mNameValues.remove(fromName));
+            }
+            for (int i = 0; i < numOverrides; i++) {
+                View view = overriddenViews.get(i);
+                if (view != null) {
+                    String toName = mNameOverrides.valueAt(i);
+                    mStartValues.mNameValues.put(toName, view);
+                }
+            }
+        }
+    }
+
+    private static void addViewValues(TransitionValuesMaps transitionValuesMaps,
+            View view, TransitionValues transitionValues) {
+        transitionValuesMaps.mViewValues.put(view, transitionValues);
+        int id = view.getId();
+        if (id >= 0) {
+            if (transitionValuesMaps.mIdValues.indexOfKey(id) >= 0) {
+                // Duplicate IDs cannot match by ID.
+                transitionValuesMaps.mIdValues.put(id, null);
+            } else {
+                transitionValuesMaps.mIdValues.put(id, view);
+            }
+        }
+        String name = ViewCompat.getTransitionName(view);
+        if (name != null) {
+            if (transitionValuesMaps.mNameValues.containsKey(name)) {
+                // Duplicate transitionNames: cannot match by transitionName.
+                transitionValuesMaps.mNameValues.put(name, null);
+            } else {
+                transitionValuesMaps.mNameValues.put(name, view);
+            }
+        }
+        if (view.getParent() instanceof ListView) {
+            ListView listview = (ListView) view.getParent();
+            if (listview.getAdapter().hasStableIds()) {
+                int position = listview.getPositionForView(view);
+                long itemId = listview.getItemIdAtPosition(position);
+                if (transitionValuesMaps.mItemIdValues.indexOfKey(itemId) >= 0) {
+                    // Duplicate item IDs: cannot match by item ID.
+                    View alreadyMatched = transitionValuesMaps.mItemIdValues.get(itemId);
+                    if (alreadyMatched != null) {
+                        ViewCompat.setHasTransientState(alreadyMatched, false);
+                        transitionValuesMaps.mItemIdValues.put(itemId, null);
+                    }
+                } else {
+                    ViewCompat.setHasTransientState(view, true);
+                    transitionValuesMaps.mItemIdValues.put(itemId, view);
+                }
+            }
+        }
+    }
+
+    /**
+     * Clear valuesMaps for specified start/end state
+     *
+     * @param start true if the start values should be cleared, false otherwise
+     */
+    void clearValues(boolean start) {
+        if (start) {
+            mStartValues.mViewValues.clear();
+            mStartValues.mIdValues.clear();
+            mStartValues.mItemIdValues.clear();
+        } else {
+            mEndValues.mViewValues.clear();
+            mEndValues.mIdValues.clear();
+            mEndValues.mItemIdValues.clear();
+        }
+    }
+
+    /**
+     * Recursive method which captures values for an entire view hierarchy,
+     * starting at some root view. Transitions without targetIDs will use this
+     * method to capture values for all possible views.
+     *
+     * @param view  The view for which to capture values. Children of this View
+     *              will also be captured, recursively down to the leaf nodes.
+     * @param start true if values are being captured in the start scene, false
+     *              otherwise.
+     */
+    private void captureHierarchy(View view, boolean start) {
+        if (view == null) {
+            return;
+        }
+        int id = view.getId();
+        if (mTargetIdExcludes != null && mTargetIdExcludes.contains(id)) {
+            return;
+        }
+        if (mTargetExcludes != null && mTargetExcludes.contains(view)) {
+            return;
+        }
+        if (mTargetTypeExcludes != null) {
+            int numTypes = mTargetTypeExcludes.size();
+            for (int i = 0; i < numTypes; ++i) {
+                if (mTargetTypeExcludes.get(i).isInstance(view)) {
+                    return;
+                }
+            }
+        }
+        if (view.getParent() instanceof ViewGroup) {
+            TransitionValues values = new TransitionValues();
+            values.view = view;
+            if (start) {
+                captureStartValues(values);
+            } else {
+                captureEndValues(values);
+            }
+            values.mTargetedTransitions.add(this);
+            capturePropagationValues(values);
+            if (start) {
+                addViewValues(mStartValues, view, values);
+            } else {
+                addViewValues(mEndValues, view, values);
+            }
+        }
+        if (view instanceof ViewGroup) {
+            // Don't traverse child hierarchy if there are any child-excludes on this view
+            if (mTargetIdChildExcludes != null && mTargetIdChildExcludes.contains(id)) {
+                return;
+            }
+            if (mTargetChildExcludes != null && mTargetChildExcludes.contains(view)) {
+                return;
+            }
+            if (mTargetTypeChildExcludes != null) {
+                int numTypes = mTargetTypeChildExcludes.size();
+                for (int i = 0; i < numTypes; ++i) {
+                    if (mTargetTypeChildExcludes.get(i).isInstance(view)) {
+                        return;
+                    }
+                }
+            }
+            ViewGroup parent = (ViewGroup) view;
+            for (int i = 0; i < parent.getChildCount(); ++i) {
+                captureHierarchy(parent.getChildAt(i), start);
+            }
+        }
+    }
+
+    /**
+     * This method can be called by transitions to get the TransitionValues for
+     * any particular view during the transition-playing process. This might be
+     * necessary, for example, to query the before/after state of related views
+     * for a given transition.
+     */
+    @Nullable
+    public TransitionValues getTransitionValues(@NonNull View view, boolean start) {
+        if (mParent != null) {
+            return mParent.getTransitionValues(view, start);
+        }
+        TransitionValuesMaps valuesMaps = start ? mStartValues : mEndValues;
+        return valuesMaps.mViewValues.get(view);
+    }
+
+    /**
+     * Find the matched start or end value for a given View. This is only valid
+     * after playTransition starts. For example, it will be valid in
+     * {@link #createAnimator(android.view.ViewGroup, TransitionValues, TransitionValues)}, but not
+     * in {@link #captureStartValues(TransitionValues)}.
+     *
+     * @param view        The view to find the match for.
+     * @param viewInStart Is View from the start values or end values.
+     * @return The matching TransitionValues for view in either start or end values, depending
+     * on viewInStart or null if there is no match for the given view.
+     */
+    TransitionValues getMatchedTransitionValues(View view, boolean viewInStart) {
+        if (mParent != null) {
+            return mParent.getMatchedTransitionValues(view, viewInStart);
+        }
+        ArrayList<TransitionValues> lookIn = viewInStart ? mStartValuesList : mEndValuesList;
+        if (lookIn == null) {
+            return null;
+        }
+        int count = lookIn.size();
+        int index = -1;
+        for (int i = 0; i < count; i++) {
+            TransitionValues values = lookIn.get(i);
+            if (values == null) {
+                return null;
+            }
+            if (values.view == view) {
+                index = i;
+                break;
+            }
+        }
+        TransitionValues values = null;
+        if (index >= 0) {
+            ArrayList<TransitionValues> matchIn = viewInStart ? mEndValuesList : mStartValuesList;
+            values = matchIn.get(index);
+        }
+        return values;
+    }
+
+    /**
+     * Pauses this transition, sending out calls to {@link
+     * TransitionListener#onTransitionPause(Transition)} to all listeners
+     * and pausing all running animators started by this transition.
+     *
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    public void pause(View sceneRoot) {
+        if (!mEnded) {
+            ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
+            int numOldAnims = runningAnimators.size();
+            WindowIdImpl windowId = ViewUtils.getWindowId(sceneRoot);
+            for (int i = numOldAnims - 1; i >= 0; i--) {
+                AnimationInfo info = runningAnimators.valueAt(i);
+                if (info.mView != null && windowId.equals(info.mWindowId)) {
+                    Animator anim = runningAnimators.keyAt(i);
+                    AnimatorUtils.pause(anim);
+                }
+            }
+            if (mListeners != null && mListeners.size() > 0) {
+                @SuppressWarnings("unchecked") ArrayList<TransitionListener> tmpListeners =
+                        (ArrayList<TransitionListener>) mListeners.clone();
+                int numListeners = tmpListeners.size();
+                for (int i = 0; i < numListeners; ++i) {
+                    tmpListeners.get(i).onTransitionPause(this);
+                }
+            }
+            mPaused = true;
+        }
+    }
+
+    /**
+     * Resumes this transition, sending out calls to {@link
+     * TransitionListener#onTransitionPause(Transition)} to all listeners
+     * and pausing all running animators started by this transition.
+     *
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    public void resume(View sceneRoot) {
+        if (mPaused) {
+            if (!mEnded) {
+                ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
+                int numOldAnims = runningAnimators.size();
+                WindowIdImpl windowId = ViewUtils.getWindowId(sceneRoot);
+                for (int i = numOldAnims - 1; i >= 0; i--) {
+                    AnimationInfo info = runningAnimators.valueAt(i);
+                    if (info.mView != null && windowId.equals(info.mWindowId)) {
+                        Animator anim = runningAnimators.keyAt(i);
+                        AnimatorUtils.resume(anim);
+                    }
+                }
+                if (mListeners != null && mListeners.size() > 0) {
+                    @SuppressWarnings("unchecked") ArrayList<TransitionListener> tmpListeners =
+                            (ArrayList<TransitionListener>) mListeners.clone();
+                    int numListeners = tmpListeners.size();
+                    for (int i = 0; i < numListeners; ++i) {
+                        tmpListeners.get(i).onTransitionResume(this);
+                    }
+                }
+            }
+            mPaused = false;
+        }
+    }
+
+    /**
+     * Called by TransitionManager to play the transition. This calls
+     * createAnimators() to set things up and create all of the animations and then
+     * runAnimations() to actually start the animations.
+     */
+    void playTransition(ViewGroup sceneRoot) {
+        mStartValuesList = new ArrayList<>();
+        mEndValuesList = new ArrayList<>();
+        matchStartAndEnd(mStartValues, mEndValues);
+
+        ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
+        int numOldAnims = runningAnimators.size();
+        WindowIdImpl windowId = ViewUtils.getWindowId(sceneRoot);
+        for (int i = numOldAnims - 1; i >= 0; i--) {
+            Animator anim = runningAnimators.keyAt(i);
+            if (anim != null) {
+                AnimationInfo oldInfo = runningAnimators.get(anim);
+                if (oldInfo != null && oldInfo.mView != null
+                        && windowId.equals(oldInfo.mWindowId)) {
+                    TransitionValues oldValues = oldInfo.mValues;
+                    View oldView = oldInfo.mView;
+                    TransitionValues startValues = getTransitionValues(oldView, true);
+                    TransitionValues endValues = getMatchedTransitionValues(oldView, true);
+                    boolean cancel = (startValues != null || endValues != null)
+                            && oldInfo.mTransition.isTransitionRequired(oldValues, endValues);
+                    if (cancel) {
+                        if (anim.isRunning() || anim.isStarted()) {
+                            if (DBG) {
+                                Log.d(LOG_TAG, "Canceling anim " + anim);
+                            }
+                            anim.cancel();
+                        } else {
+                            if (DBG) {
+                                Log.d(LOG_TAG, "removing anim from info list: " + anim);
+                            }
+                            runningAnimators.remove(anim);
+                        }
+                    }
+                }
+            }
+        }
+
+        createAnimators(sceneRoot, mStartValues, mEndValues, mStartValuesList, mEndValuesList);
+        runAnimators();
+    }
+
+    /**
+     * Returns whether or not the transition should create an Animator, based on the values
+     * captured during {@link #captureStartValues(TransitionValues)} and
+     * {@link #captureEndValues(TransitionValues)}. The default implementation compares the
+     * property values returned from {@link #getTransitionProperties()}, or all property values if
+     * {@code getTransitionProperties()} returns null. Subclasses may override this method to
+     * provide logic more specific to the transition implementation.
+     *
+     * @param startValues the values from captureStartValues, This may be {@code null} if the
+     *                    View did not exist in the start state.
+     * @param endValues   the values from captureEndValues. This may be {@code null} if the View
+     *                    did not exist in the end state.
+     */
+    public boolean isTransitionRequired(@Nullable TransitionValues startValues,
+            @Nullable TransitionValues endValues) {
+        boolean valuesChanged = false;
+        // if startValues null, then transition didn't care to stash values,
+        // and won't get canceled
+        if (startValues != null && endValues != null) {
+            String[] properties = getTransitionProperties();
+            if (properties != null) {
+                for (String property : properties) {
+                    if (isValueChanged(startValues, endValues, property)) {
+                        valuesChanged = true;
+                        break;
+                    }
+                }
+            } else {
+                for (String key : startValues.values.keySet()) {
+                    if (isValueChanged(startValues, endValues, key)) {
+                        valuesChanged = true;
+                        break;
+                    }
+                }
+            }
+        }
+        return valuesChanged;
+    }
+
+    private static boolean isValueChanged(TransitionValues oldValues, TransitionValues newValues,
+            String key) {
+        Object oldValue = oldValues.values.get(key);
+        Object newValue = newValues.values.get(key);
+        boolean changed;
+        if (oldValue == null && newValue == null) {
+            // both are null
+            changed = false;
+        } else if (oldValue == null || newValue == null) {
+            // one is null
+            changed = true;
+        } else {
+            // neither is null
+            changed = !oldValue.equals(newValue);
+        }
+        if (DBG && changed) {
+            Log.d(LOG_TAG, "Transition.playTransition: "
+                    + "oldValue != newValue for " + key
+                    + ": old, new = " + oldValue + ", " + newValue);
+        }
+        return changed;
+    }
+
+    /**
+     * This is a utility method used by subclasses to handle standard parts of
+     * setting up and running an Animator: it sets the {@link #getDuration()
+     * duration} and the {@link #getStartDelay() startDelay}, starts the
+     * animation, and, when the animator ends, calls {@link #end()}.
+     *
+     * @param animator The Animator to be run during this transition.
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    protected void animate(Animator animator) {
+        // TODO: maybe pass auto-end as a boolean parameter?
+        if (animator == null) {
+            end();
+        } else {
+            if (getDuration() >= 0) {
+                animator.setDuration(getDuration());
+            }
+            if (getStartDelay() >= 0) {
+                animator.setStartDelay(getStartDelay());
+            }
+            if (getInterpolator() != null) {
+                animator.setInterpolator(getInterpolator());
+            }
+            animator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    end();
+                    animation.removeListener(this);
+                }
+            });
+            animator.start();
+        }
+    }
+
+    /**
+     * This method is called automatically by the transition and
+     * TransitionSet classes prior to a Transition subclass starting;
+     * subclasses should not need to call it directly.
+     *
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    protected void start() {
+        if (mNumInstances == 0) {
+            if (mListeners != null && mListeners.size() > 0) {
+                @SuppressWarnings("unchecked") ArrayList<TransitionListener> tmpListeners =
+                        (ArrayList<TransitionListener>) mListeners.clone();
+                int numListeners = tmpListeners.size();
+                for (int i = 0; i < numListeners; ++i) {
+                    tmpListeners.get(i).onTransitionStart(this);
+                }
+            }
+            mEnded = false;
+        }
+        mNumInstances++;
+    }
+
+    /**
+     * This method is called automatically by the Transition and
+     * TransitionSet classes when a transition finishes, either because
+     * a transition did nothing (returned a null Animator from
+     * {@link Transition#createAnimator(ViewGroup, TransitionValues,
+     * TransitionValues)}) or because the transition returned a valid
+     * Animator and end() was called in the onAnimationEnd()
+     * callback of the AnimatorListener.
+     *
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    protected void end() {
+        --mNumInstances;
+        if (mNumInstances == 0) {
+            if (mListeners != null && mListeners.size() > 0) {
+                @SuppressWarnings("unchecked") ArrayList<TransitionListener> tmpListeners =
+                        (ArrayList<TransitionListener>) mListeners.clone();
+                int numListeners = tmpListeners.size();
+                for (int i = 0; i < numListeners; ++i) {
+                    tmpListeners.get(i).onTransitionEnd(this);
+                }
+            }
+            for (int i = 0; i < mStartValues.mItemIdValues.size(); ++i) {
+                View view = mStartValues.mItemIdValues.valueAt(i);
+                if (view != null) {
+                    ViewCompat.setHasTransientState(view, false);
+                }
+            }
+            for (int i = 0; i < mEndValues.mItemIdValues.size(); ++i) {
+                View view = mEndValues.mItemIdValues.valueAt(i);
+                if (view != null) {
+                    ViewCompat.setHasTransientState(view, false);
+                }
+            }
+            mEnded = true;
+        }
+    }
+
+    /**
+     * Force the transition to move to its end state, ending all the animators.
+     *
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    void forceToEnd(ViewGroup sceneRoot) {
+        ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
+        int numOldAnims = runningAnimators.size();
+        if (sceneRoot != null) {
+            WindowIdImpl windowId = ViewUtils.getWindowId(sceneRoot);
+            for (int i = numOldAnims - 1; i >= 0; i--) {
+                AnimationInfo info = runningAnimators.valueAt(i);
+                if (info.mView != null && windowId != null && windowId.equals(info.mWindowId)) {
+                    Animator anim = runningAnimators.keyAt(i);
+                    anim.end();
+                }
+            }
+        }
+    }
+
+    /**
+     * This method cancels a transition that is currently running.
+     *
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    protected void cancel() {
+        int numAnimators = mCurrentAnimators.size();
+        for (int i = numAnimators - 1; i >= 0; i--) {
+            Animator animator = mCurrentAnimators.get(i);
+            animator.cancel();
+        }
+        if (mListeners != null && mListeners.size() > 0) {
+            @SuppressWarnings("unchecked") ArrayList<TransitionListener> tmpListeners =
+                    (ArrayList<TransitionListener>) mListeners.clone();
+            int numListeners = tmpListeners.size();
+            for (int i = 0; i < numListeners; ++i) {
+                tmpListeners.get(i).onTransitionCancel(this);
+            }
+        }
+    }
+
+    /**
+     * Adds a listener to the set of listeners that are sent events through the
+     * life of an animation, such as start, repeat, and end.
+     *
+     * @param listener the listener to be added to the current set of listeners
+     *                 for this animation.
+     * @return This transition object.
+     */
+    @NonNull
+    public Transition addListener(@NonNull TransitionListener listener) {
+        if (mListeners == null) {
+            mListeners = new ArrayList<>();
+        }
+        mListeners.add(listener);
+        return this;
+    }
+
+    /**
+     * Removes a listener from the set listening to this animation.
+     *
+     * @param listener the listener to be removed from the current set of
+     *                 listeners for this transition.
+     * @return This transition object.
+     */
+    @NonNull
+    public Transition removeListener(@NonNull TransitionListener listener) {
+        if (mListeners == null) {
+            return this;
+        }
+        mListeners.remove(listener);
+        if (mListeners.size() == 0) {
+            mListeners = null;
+        }
+        return this;
+    }
+
+    /**
+     * Sets the algorithm used to calculate two-dimensional interpolation.
+     * <p>
+     * Transitions such as {@link android.transition.ChangeBounds} move Views, typically
+     * in a straight path between the start and end positions. Applications that desire to
+     * have these motions move in a curve can change how Views interpolate in two dimensions
+     * by extending PathMotion and implementing
+     * {@link android.transition.PathMotion#getPath(float, float, float, float)}.
+     * </p>
+     *
+     * @param pathMotion Algorithm object to use for determining how to interpolate in two
+     *                   dimensions. If null, a straight-path algorithm will be used.
+     * @see android.transition.ArcMotion
+     * @see PatternPathMotion
+     * @see android.transition.PathMotion
+     */
+    public void setPathMotion(@Nullable PathMotion pathMotion) {
+        if (pathMotion == null) {
+            mPathMotion = STRAIGHT_PATH_MOTION;
+        } else {
+            mPathMotion = pathMotion;
+        }
+    }
+
+    /**
+     * Returns the algorithm object used to interpolate along two dimensions. This is typically
+     * used to determine the View motion between two points.
+     *
+     * @return The algorithm object used to interpolate along two dimensions.
+     * @see android.transition.ArcMotion
+     * @see PatternPathMotion
+     * @see android.transition.PathMotion
+     */
+    @NonNull
+    public PathMotion getPathMotion() {
+        return mPathMotion;
+    }
+
+    /**
+     * Sets the callback to use to find the epicenter of a Transition. A null value indicates
+     * that there is no epicenter in the Transition and onGetEpicenter() will return null.
+     * Transitions like {@link android.transition.Explode} use a point or Rect to orient
+     * the direction of travel. This is called the epicenter of the Transition and is
+     * typically centered on a touched View. The
+     * {@link android.transition.Transition.EpicenterCallback} allows a Transition to
+     * dynamically retrieve the epicenter during a Transition.
+     *
+     * @param epicenterCallback The callback to use to find the epicenter of the Transition.
+     */
+    public void setEpicenterCallback(@Nullable EpicenterCallback epicenterCallback) {
+        mEpicenterCallback = epicenterCallback;
+    }
+
+    /**
+     * Returns the callback used to find the epicenter of the Transition.
+     * Transitions like {@link android.transition.Explode} use a point or Rect to orient
+     * the direction of travel. This is called the epicenter of the Transition and is
+     * typically centered on a touched View. The
+     * {@link android.transition.Transition.EpicenterCallback} allows a Transition to
+     * dynamically retrieve the epicenter during a Transition.
+     *
+     * @return the callback used to find the epicenter of the Transition.
+     */
+    @Nullable
+    public EpicenterCallback getEpicenterCallback() {
+        return mEpicenterCallback;
+    }
+
+    /**
+     * Returns the epicenter as specified by the
+     * {@link android.transition.Transition.EpicenterCallback} or null if no callback exists.
+     *
+     * @return the epicenter as specified by the
+     * {@link android.transition.Transition.EpicenterCallback} or null if no callback exists.
+     * @see #setEpicenterCallback(EpicenterCallback)
+     */
+    @Nullable
+    public Rect getEpicenter() {
+        if (mEpicenterCallback == null) {
+            return null;
+        }
+        return mEpicenterCallback.onGetEpicenter(this);
+    }
+
+    /**
+     * Sets the method for determining Animator start delays.
+     * When a Transition affects several Views like {@link android.transition.Explode} or
+     * {@link android.transition.Slide}, there may be a desire to have a "wave-front" effect
+     * such that the Animator start delay depends on position of the View. The
+     * TransitionPropagation specifies how the start delays are calculated.
+     *
+     * @param transitionPropagation The class used to determine the start delay of
+     *                              Animators created by this Transition. A null value
+     *                              indicates that no delay should be used.
+     */
+    public void setPropagation(@Nullable TransitionPropagation transitionPropagation) {
+        mPropagation = transitionPropagation;
+    }
+
+    /**
+     * Returns the {@link android.transition.TransitionPropagation} used to calculate Animator
+     * start
+     * delays.
+     * When a Transition affects several Views like {@link android.transition.Explode} or
+     * {@link android.transition.Slide}, there may be a desire to have a "wave-front" effect
+     * such that the Animator start delay depends on position of the View. The
+     * TransitionPropagation specifies how the start delays are calculated.
+     *
+     * @return the {@link android.transition.TransitionPropagation} used to calculate Animator start
+     * delays. This is null by default.
+     */
+    @Nullable
+    public TransitionPropagation getPropagation() {
+        return mPropagation;
+    }
+
+    /**
+     * Captures TransitionPropagation values for the given view and the
+     * hierarchy underneath it.
+     */
+    void capturePropagationValues(TransitionValues transitionValues) {
+        if (mPropagation != null && !transitionValues.values.isEmpty()) {
+            String[] propertyNames = mPropagation.getPropagationProperties();
+            if (propertyNames == null) {
+                return;
+            }
+            boolean containsAll = true;
+            for (int i = 0; i < propertyNames.length; i++) {
+                if (!transitionValues.values.containsKey(propertyNames[i])) {
+                    containsAll = false;
+                    break;
+                }
+            }
+            if (!containsAll) {
+                mPropagation.captureValues(transitionValues);
+            }
+        }
+    }
+
+    Transition setSceneRoot(ViewGroup sceneRoot) {
+        mSceneRoot = sceneRoot;
+        return this;
+    }
+
+    void setCanRemoveViews(boolean canRemoveViews) {
+        mCanRemoveViews = canRemoveViews;
+    }
+
+    @Override
+    public String toString() {
+        return toString("");
+    }
+
+    @Override
+    public Transition clone() {
+        try {
+            Transition clone = (Transition) super.clone();
+            clone.mAnimators = new ArrayList<>();
+            clone.mStartValues = new TransitionValuesMaps();
+            clone.mEndValues = new TransitionValuesMaps();
+            clone.mStartValuesList = null;
+            clone.mEndValuesList = null;
+            return clone;
+        } catch (CloneNotSupportedException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Returns the name of this Transition. This name is used internally to distinguish
+     * between different transitions to determine when interrupting transitions overlap.
+     * For example, a ChangeBounds running on the same target view as another ChangeBounds
+     * should determine whether the old transition is animating to different end values
+     * and should be canceled in favor of the new transition.
+     *
+     * <p>By default, a Transition's name is simply the value of {@link Class#getName()},
+     * but subclasses are free to override and return something different.</p>
+     *
+     * @return The name of this transition.
+     */
+    @NonNull
+    public String getName() {
+        return mName;
+    }
+
+    String toString(String indent) {
+        String result = indent + getClass().getSimpleName() + "@"
+                + Integer.toHexString(hashCode()) + ": ";
+        if (mDuration != -1) {
+            result += "dur(" + mDuration + ") ";
+        }
+        if (mStartDelay != -1) {
+            result += "dly(" + mStartDelay + ") ";
+        }
+        if (mInterpolator != null) {
+            result += "interp(" + mInterpolator + ") ";
+        }
+        if (mTargetIds.size() > 0 || mTargets.size() > 0) {
+            result += "tgts(";
+            if (mTargetIds.size() > 0) {
+                for (int i = 0; i < mTargetIds.size(); ++i) {
+                    if (i > 0) {
+                        result += ", ";
+                    }
+                    result += mTargetIds.get(i);
+                }
+            }
+            if (mTargets.size() > 0) {
+                for (int i = 0; i < mTargets.size(); ++i) {
+                    if (i > 0) {
+                        result += ", ";
+                    }
+                    result += mTargets.get(i);
+                }
+            }
+            result += ")";
+        }
+        return result;
+    }
+
+    /**
+     * A transition listener receives notifications from a transition.
+     * Notifications indicate transition lifecycle events.
+     */
+    public interface TransitionListener {
+
+        /**
+         * Notification about the start of the transition.
+         *
+         * @param transition The started transition.
+         */
+        void onTransitionStart(@NonNull Transition transition);
+
+        /**
+         * Notification about the end of the transition. Canceled transitions
+         * will always notify listeners of both the cancellation and end
+         * events. That is, {@link #onTransitionEnd(Transition)} is always called,
+         * regardless of whether the transition was canceled or played
+         * through to completion.
+         *
+         * @param transition The transition which reached its end.
+         */
+        void onTransitionEnd(@NonNull Transition transition);
+
+        /**
+         * Notification about the cancellation of the transition.
+         * Note that cancel may be called by a parent {@link TransitionSet} on
+         * a child transition which has not yet started. This allows the child
+         * transition to restore state on target objects which was set at
+         * {@link #createAnimator(android.view.ViewGroup, TransitionValues, TransitionValues)
+         * createAnimator()} time.
+         *
+         * @param transition The transition which was canceled.
+         */
+        void onTransitionCancel(@NonNull Transition transition);
+
+        /**
+         * Notification when a transition is paused.
+         * Note that createAnimator() may be called by a parent {@link TransitionSet} on
+         * a child transition which has not yet started. This allows the child
+         * transition to restore state on target objects which was set at
+         * {@link #createAnimator(android.view.ViewGroup, TransitionValues, TransitionValues)
+         * createAnimator()} time.
+         *
+         * @param transition The transition which was paused.
+         */
+        void onTransitionPause(@NonNull Transition transition);
+
+        /**
+         * Notification when a transition is resumed.
+         * Note that resume() may be called by a parent {@link TransitionSet} on
+         * a child transition which has not yet started. This allows the child
+         * transition to restore state which may have changed in an earlier call
+         * to {@link #onTransitionPause(Transition)}.
+         *
+         * @param transition The transition which was resumed.
+         */
+        void onTransitionResume(@NonNull Transition transition);
+    }
+
+    /**
+     * Holds information about each animator used when a new transition starts
+     * while other transitions are still running to determine whether a running
+     * animation should be canceled or a new animation noop'd. The structure holds
+     * information about the state that an animation is going to, to be compared to
+     * end state of a new animation.
+     */
+    private static class AnimationInfo {
+
+        View mView;
+
+        String mName;
+
+        TransitionValues mValues;
+
+        WindowIdImpl mWindowId;
+
+        Transition mTransition;
+
+        AnimationInfo(View view, String name, Transition transition, WindowIdImpl windowId,
+                TransitionValues values) {
+            mView = view;
+            mName = name;
+            mValues = values;
+            mWindowId = windowId;
+            mTransition = transition;
+        }
+    }
+
+    /**
+     * Utility class for managing typed ArrayLists efficiently. In particular, this
+     * can be useful for lists that we don't expect to be used often (eg, the exclude
+     * lists), so we'd like to keep them nulled out by default. This causes the code to
+     * become tedious, with constant null checks, code to allocate when necessary,
+     * and code to null out the reference when the list is empty. This class encapsulates
+     * all of that functionality into simple add()/remove() methods which perform the
+     * necessary checks, allocation/null-out as appropriate, and return the
+     * resulting list.
+     */
+    private static class ArrayListManager {
+
+        /**
+         * Add the specified item to the list, returning the resulting list.
+         * The returned list can either the be same list passed in or, if that
+         * list was null, the new list that was created.
+         *
+         * Note that the list holds unique items; if the item already exists in the
+         * list, the list is not modified.
+         */
+        static <T> ArrayList<T> add(ArrayList<T> list, T item) {
+            if (list == null) {
+                list = new ArrayList<>();
+            }
+            if (!list.contains(item)) {
+                list.add(item);
+            }
+            return list;
+        }
+
+        /**
+         * Remove the specified item from the list, returning the resulting list.
+         * The returned list can either the be same list passed in or, if that
+         * list becomes empty as a result of the remove(), the new list was created.
+         */
+        static <T> ArrayList<T> remove(ArrayList<T> list, T item) {
+            if (list != null) {
+                list.remove(item);
+                if (list.isEmpty()) {
+                    list = null;
+                }
+            }
+            return list;
+        }
+    }
+
+    /**
+     * Class to get the epicenter of Transition. Use
+     * {@link #setEpicenterCallback(EpicenterCallback)} to set the callback used to calculate the
+     * epicenter of the Transition. Override {@link #getEpicenter()} to return the rectangular
+     * region in screen coordinates of the epicenter of the transition.
+     *
+     * @see #setEpicenterCallback(EpicenterCallback)
+     */
+    public abstract static class EpicenterCallback {
+
+        /**
+         * Implementers must override to return the epicenter of the Transition in screen
+         * coordinates. Transitions like {@link android.transition.Explode} depend upon
+         * an epicenter for the Transition. In Explode, Views move toward or away from the
+         * center of the epicenter Rect along the vector between the epicenter and the center
+         * of the View appearing and disappearing. Some Transitions, such as
+         * {@link android.transition.Fade} pay no attention to the epicenter.
+         *
+         * @param transition The transition for which the epicenter applies.
+         * @return The Rect region of the epicenter of <code>transition</code> or null if
+         * there is no epicenter.
+         */
+        public abstract Rect onGetEpicenter(@NonNull Transition transition);
+    }
+
+}
diff --git a/transition/src/android/support/transition/TransitionInflater.java b/transition/src/main/java/android/support/transition/TransitionInflater.java
similarity index 100%
rename from transition/src/android/support/transition/TransitionInflater.java
rename to transition/src/main/java/android/support/transition/TransitionInflater.java
diff --git a/transition/src/android/support/transition/TransitionListenerAdapter.java b/transition/src/main/java/android/support/transition/TransitionListenerAdapter.java
similarity index 100%
rename from transition/src/android/support/transition/TransitionListenerAdapter.java
rename to transition/src/main/java/android/support/transition/TransitionListenerAdapter.java
diff --git a/transition/src/android/support/transition/TransitionManager.java b/transition/src/main/java/android/support/transition/TransitionManager.java
similarity index 100%
rename from transition/src/android/support/transition/TransitionManager.java
rename to transition/src/main/java/android/support/transition/TransitionManager.java
diff --git a/transition/src/android/support/transition/TransitionPropagation.java b/transition/src/main/java/android/support/transition/TransitionPropagation.java
similarity index 100%
rename from transition/src/android/support/transition/TransitionPropagation.java
rename to transition/src/main/java/android/support/transition/TransitionPropagation.java
diff --git a/transition/src/android/support/transition/TransitionSet.java b/transition/src/main/java/android/support/transition/TransitionSet.java
similarity index 100%
rename from transition/src/android/support/transition/TransitionSet.java
rename to transition/src/main/java/android/support/transition/TransitionSet.java
diff --git a/transition/src/android/support/transition/TransitionUtils.java b/transition/src/main/java/android/support/transition/TransitionUtils.java
similarity index 100%
rename from transition/src/android/support/transition/TransitionUtils.java
rename to transition/src/main/java/android/support/transition/TransitionUtils.java
diff --git a/transition/src/android/support/transition/TransitionValues.java b/transition/src/main/java/android/support/transition/TransitionValues.java
similarity index 100%
rename from transition/src/android/support/transition/TransitionValues.java
rename to transition/src/main/java/android/support/transition/TransitionValues.java
diff --git a/transition/src/android/support/transition/TransitionValuesMaps.java b/transition/src/main/java/android/support/transition/TransitionValuesMaps.java
similarity index 100%
rename from transition/src/android/support/transition/TransitionValuesMaps.java
rename to transition/src/main/java/android/support/transition/TransitionValuesMaps.java
diff --git a/transition/src/android/support/transition/TranslationAnimationCreator.java b/transition/src/main/java/android/support/transition/TranslationAnimationCreator.java
similarity index 100%
rename from transition/src/android/support/transition/TranslationAnimationCreator.java
rename to transition/src/main/java/android/support/transition/TranslationAnimationCreator.java
diff --git a/transition/api14/android/support/transition/ViewGroupOverlayApi14.java b/transition/src/main/java/android/support/transition/ViewGroupOverlayApi14.java
similarity index 100%
rename from transition/api14/android/support/transition/ViewGroupOverlayApi14.java
rename to transition/src/main/java/android/support/transition/ViewGroupOverlayApi14.java
diff --git a/transition/api18/android/support/transition/ViewGroupOverlayApi18.java b/transition/src/main/java/android/support/transition/ViewGroupOverlayApi18.java
similarity index 100%
rename from transition/api18/android/support/transition/ViewGroupOverlayApi18.java
rename to transition/src/main/java/android/support/transition/ViewGroupOverlayApi18.java
diff --git a/transition/base/android/support/transition/ViewGroupOverlayImpl.java b/transition/src/main/java/android/support/transition/ViewGroupOverlayImpl.java
similarity index 100%
rename from transition/base/android/support/transition/ViewGroupOverlayImpl.java
rename to transition/src/main/java/android/support/transition/ViewGroupOverlayImpl.java
diff --git a/transition/src/android/support/transition/ViewGroupUtils.java b/transition/src/main/java/android/support/transition/ViewGroupUtils.java
similarity index 100%
rename from transition/src/android/support/transition/ViewGroupUtils.java
rename to transition/src/main/java/android/support/transition/ViewGroupUtils.java
diff --git a/transition/api14/android/support/transition/ViewGroupUtilsApi14.java b/transition/src/main/java/android/support/transition/ViewGroupUtilsApi14.java
similarity index 100%
rename from transition/api14/android/support/transition/ViewGroupUtilsApi14.java
rename to transition/src/main/java/android/support/transition/ViewGroupUtilsApi14.java
diff --git a/transition/api18/android/support/transition/ViewGroupUtilsApi18.java b/transition/src/main/java/android/support/transition/ViewGroupUtilsApi18.java
similarity index 100%
rename from transition/api18/android/support/transition/ViewGroupUtilsApi18.java
rename to transition/src/main/java/android/support/transition/ViewGroupUtilsApi18.java
diff --git a/transition/base/android/support/transition/ViewGroupUtilsImpl.java b/transition/src/main/java/android/support/transition/ViewGroupUtilsImpl.java
similarity index 100%
rename from transition/base/android/support/transition/ViewGroupUtilsImpl.java
rename to transition/src/main/java/android/support/transition/ViewGroupUtilsImpl.java
diff --git a/transition/api14/android/support/transition/ViewOverlayApi14.java b/transition/src/main/java/android/support/transition/ViewOverlayApi14.java
similarity index 100%
rename from transition/api14/android/support/transition/ViewOverlayApi14.java
rename to transition/src/main/java/android/support/transition/ViewOverlayApi14.java
diff --git a/transition/api18/android/support/transition/ViewOverlayApi18.java b/transition/src/main/java/android/support/transition/ViewOverlayApi18.java
similarity index 100%
rename from transition/api18/android/support/transition/ViewOverlayApi18.java
rename to transition/src/main/java/android/support/transition/ViewOverlayApi18.java
diff --git a/transition/base/android/support/transition/ViewOverlayImpl.java b/transition/src/main/java/android/support/transition/ViewOverlayImpl.java
similarity index 100%
rename from transition/base/android/support/transition/ViewOverlayImpl.java
rename to transition/src/main/java/android/support/transition/ViewOverlayImpl.java
diff --git a/transition/src/android/support/transition/ViewUtils.java b/transition/src/main/java/android/support/transition/ViewUtils.java
similarity index 100%
rename from transition/src/android/support/transition/ViewUtils.java
rename to transition/src/main/java/android/support/transition/ViewUtils.java
diff --git a/transition/api14/android/support/transition/ViewUtilsApi14.java b/transition/src/main/java/android/support/transition/ViewUtilsApi14.java
similarity index 100%
rename from transition/api14/android/support/transition/ViewUtilsApi14.java
rename to transition/src/main/java/android/support/transition/ViewUtilsApi14.java
diff --git a/transition/api18/android/support/transition/ViewUtilsApi18.java b/transition/src/main/java/android/support/transition/ViewUtilsApi18.java
similarity index 100%
rename from transition/api18/android/support/transition/ViewUtilsApi18.java
rename to transition/src/main/java/android/support/transition/ViewUtilsApi18.java
diff --git a/transition/api19/android/support/transition/ViewUtilsApi19.java b/transition/src/main/java/android/support/transition/ViewUtilsApi19.java
similarity index 100%
rename from transition/api19/android/support/transition/ViewUtilsApi19.java
rename to transition/src/main/java/android/support/transition/ViewUtilsApi19.java
diff --git a/transition/api21/android/support/transition/ViewUtilsApi21.java b/transition/src/main/java/android/support/transition/ViewUtilsApi21.java
similarity index 100%
rename from transition/api21/android/support/transition/ViewUtilsApi21.java
rename to transition/src/main/java/android/support/transition/ViewUtilsApi21.java
diff --git a/transition/api22/android/support/transition/ViewUtilsApi22.java b/transition/src/main/java/android/support/transition/ViewUtilsApi22.java
similarity index 100%
rename from transition/api22/android/support/transition/ViewUtilsApi22.java
rename to transition/src/main/java/android/support/transition/ViewUtilsApi22.java
diff --git a/transition/base/android/support/transition/ViewUtilsImpl.java b/transition/src/main/java/android/support/transition/ViewUtilsImpl.java
similarity index 100%
rename from transition/base/android/support/transition/ViewUtilsImpl.java
rename to transition/src/main/java/android/support/transition/ViewUtilsImpl.java
diff --git a/transition/src/android/support/transition/Visibility.java b/transition/src/main/java/android/support/transition/Visibility.java
similarity index 100%
rename from transition/src/android/support/transition/Visibility.java
rename to transition/src/main/java/android/support/transition/Visibility.java
diff --git a/transition/src/android/support/transition/VisibilityPropagation.java b/transition/src/main/java/android/support/transition/VisibilityPropagation.java
similarity index 100%
rename from transition/src/android/support/transition/VisibilityPropagation.java
rename to transition/src/main/java/android/support/transition/VisibilityPropagation.java
diff --git a/transition/api14/android/support/transition/WindowIdApi14.java b/transition/src/main/java/android/support/transition/WindowIdApi14.java
similarity index 100%
rename from transition/api14/android/support/transition/WindowIdApi14.java
rename to transition/src/main/java/android/support/transition/WindowIdApi14.java
diff --git a/transition/api18/android/support/transition/WindowIdApi18.java b/transition/src/main/java/android/support/transition/WindowIdApi18.java
similarity index 100%
rename from transition/api18/android/support/transition/WindowIdApi18.java
rename to transition/src/main/java/android/support/transition/WindowIdApi18.java
diff --git a/transition/base/android/support/transition/WindowIdImpl.java b/transition/src/main/java/android/support/transition/WindowIdImpl.java
similarity index 100%
rename from transition/base/android/support/transition/WindowIdImpl.java
rename to transition/src/main/java/android/support/transition/WindowIdImpl.java
diff --git a/transition/src/main/java/android/support/transition/package.html b/transition/src/main/java/android/support/transition/package.html
new file mode 100644
index 0000000..d8394a5
--- /dev/null
+++ b/transition/src/main/java/android/support/transition/package.html
@@ -0,0 +1,8 @@
+<body>
+
+Support android.transition classes to provide transition API back to Android API level 14.
+This library contains {@link android.support.transition.Transition},
+{@link android.support.transition.TransitionManager}, and other related classes
+back-ported from their platform versions introduced Android API level 19.
+
+</body>
diff --git a/tv-provider/api/27.0.0.ignore b/tv-provider/api/27.0.0.ignore
new file mode 100644
index 0000000..3bd938a
--- /dev/null
+++ b/tv-provider/api/27.0.0.ignore
@@ -0,0 +1,7 @@
+122b8cc
+7a7f371
+906efec
+355dc8b
+1490eba
+e3d27cc
+cd7673a
diff --git a/tv-provider/api/current.txt b/tv-provider/api/current.txt
index 42cad9f..ef10221 100644
--- a/tv-provider/api/current.txt
+++ b/tv-provider/api/current.txt
@@ -74,12 +74,10 @@
   }
 
   public final class PreviewProgram {
-    method public boolean equals(java.lang.Object);
     method public static android.support.media.tv.PreviewProgram fromCursor(android.database.Cursor);
     method public long getChannelId();
     method public int getWeight();
     method public android.content.ContentValues toContentValues();
-    method public java.lang.String toString();
   }
 
   public static final class PreviewProgram.Builder {
@@ -92,16 +90,13 @@
 
   public final class Program implements java.lang.Comparable {
     method public int compareTo(android.support.media.tv.Program);
-    method public boolean equals(java.lang.Object);
     method public static android.support.media.tv.Program fromCursor(android.database.Cursor);
     method public java.lang.String[] getBroadcastGenres();
     method public long getChannelId();
     method public long getEndTimeUtcMillis();
     method public long getStartTimeUtcMillis();
-    method public int hashCode();
     method public boolean isRecordingProhibited();
     method public android.content.ContentValues toContentValues();
-    method public java.lang.String toString();
   }
 
   public static class Program.Builder {
@@ -149,7 +144,7 @@
     field public static final java.lang.String EXTRA_WATCH_NEXT_PROGRAM_ID = "android.media.tv.extra.WATCH_NEXT_PROGRAM_ID";
   }
 
-  public static abstract interface TvContractCompat.BaseTvColumns {
+  public static abstract interface TvContractCompat.BaseTvColumns implements android.provider.BaseColumns {
     field public static final java.lang.String COLUMN_PACKAGE_NAME = "package_name";
   }
 
@@ -525,12 +520,11 @@
   }
 
   public final class WatchNextProgram {
-    method public boolean equals(java.lang.Object);
     method public static android.support.media.tv.WatchNextProgram fromCursor(android.database.Cursor);
     method public long getLastEngagementTimeUtcMillis();
     method public int getWatchNextType();
     method public android.content.ContentValues toContentValues();
-    method public java.lang.String toString();
+    field public static final int WATCH_NEXT_TYPE_UNKNOWN = -1; // 0xffffffff
   }
 
   public static final class WatchNextProgram.Builder {
diff --git a/tv-provider/build.gradle b/tv-provider/build.gradle
index eb5d297..7090108 100644
--- a/tv-provider/build.gradle
+++ b/tv-provider/build.gradle
@@ -10,7 +10,7 @@
     api(project(":support-annotations"))
     api(project(":support-compat"))
 
-    androidTestImplementation(TEST_RUNNER, libs.exclude_annotations)
+    androidTestImplementation(TEST_RUNNER)
 }
 
 android {
diff --git a/tv-provider/lint-baseline.xml b/tv-provider/lint-baseline.xml
index 9814796..4387a5a 100644
--- a/tv-provider/lint-baseline.xml
+++ b/tv-provider/lint-baseline.xml
@@ -1,92 +1,4 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="4" by="lint 3.0.0-beta6">
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: PreviewProgramColumns.TYPE_MOVIE, PreviewProgramColumns.TYPE_TV_SERIES, PreviewProgramColumns.TYPE_TV_SEASON, PreviewProgramColumns.TYPE_TV_EPISODE, PreviewProgramColumns.TYPE_CLIP, PreviewProgramColumns.TYPE_EVENT, PreviewProgramColumns.TYPE_CHANNEL, PreviewProgramColumns.TYPE_TRACK, PreviewProgramColumns.TYPE_ALBUM, PreviewProgramColumns.TYPE_ARTIST, PreviewProgramColumns.TYPE_PLAYLIST, PreviewProgramColumns.TYPE_STATION, PreviewProgramColumns.TYPE_GAME"
-        errorLine1="        return i == null ? INVALID_INT_VALUE : i;"
-        errorLine2="                           ~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/android/support/media/tv/BasePreviewProgram.java"
-            line="130"
-            column="28"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: PreviewProgramColumns.ASPECT_RATIO_16_9, PreviewProgramColumns.ASPECT_RATIO_3_2, PreviewProgramColumns.ASPECT_RATIO_4_3, PreviewProgramColumns.ASPECT_RATIO_1_1, PreviewProgramColumns.ASPECT_RATIO_2_3, PreviewProgramColumns.ASPECT_RATIO_MOVIE_POSTER"
-        errorLine1="        return i == null ? INVALID_INT_VALUE : i;"
-        errorLine2="                           ~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/android/support/media/tv/BasePreviewProgram.java"
-            line="140"
-            column="28"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: PreviewProgramColumns.ASPECT_RATIO_16_9, PreviewProgramColumns.ASPECT_RATIO_3_2, PreviewProgramColumns.ASPECT_RATIO_4_3, PreviewProgramColumns.ASPECT_RATIO_1_1, PreviewProgramColumns.ASPECT_RATIO_2_3, PreviewProgramColumns.ASPECT_RATIO_MOVIE_POSTER"
-        errorLine1="        return i == null ? INVALID_INT_VALUE : i;"
-        errorLine2="                           ~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/android/support/media/tv/BasePreviewProgram.java"
-            line="150"
-            column="28"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: PreviewProgramColumns.AVAILABILITY_AVAILABLE, PreviewProgramColumns.AVAILABILITY_FREE_WITH_SUBSCRIPTION, PreviewProgramColumns.AVAILABILITY_PAID_CONTENT, PreviewProgramColumns.AVAILABILITY_PURCHASED, PreviewProgramColumns.AVAILABILITY_FREE"
-        errorLine1="        return i == null ? INVALID_INT_VALUE : i;"
-        errorLine2="                           ~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/android/support/media/tv/BasePreviewProgram.java"
-            line="168"
-            column="28"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: PreviewProgramColumns.INTERACTION_TYPE_VIEWS, PreviewProgramColumns.INTERACTION_TYPE_LISTENS, PreviewProgramColumns.INTERACTION_TYPE_FOLLOWERS, PreviewProgramColumns.INTERACTION_TYPE_FANS, PreviewProgramColumns.INTERACTION_TYPE_LIKES, PreviewProgramColumns.INTERACTION_TYPE_THUMBS, PreviewProgramColumns.INTERACTION_TYPE_VIEWERS"
-        errorLine1="        return i == null ? INVALID_INT_VALUE : i;"
-        errorLine2="                           ~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/android/support/media/tv/BasePreviewProgram.java"
-            line="219"
-            column="28"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: ProgramColumns.REVIEW_RATING_STYLE_STARS, ProgramColumns.REVIEW_RATING_STYLE_THUMBS_UP_DOWN, ProgramColumns.REVIEW_RATING_STYLE_PERCENTAGE"
-        errorLine1="        return i == null ? INVALID_INT_VALUE : i;"
-        errorLine2="                           ~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/android/support/media/tv/BaseProgram.java"
-            line="257"
-            column="28"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: Genres.FAMILY_KIDS, Genres.SPORTS, Genres.SHOPPING, Genres.MOVIES, Genres.COMEDY, Genres.TRAVEL, Genres.DRAMA, Genres.EDUCATION, Genres.ANIMAL_WILDLIFE, Genres.NEWS, Genres.GAMING, Genres.ARTS, Genres.ENTERTAINMENT, Genres.LIFE_STYLE, Genres.MUSIC, Genres.PREMIER, Genres.TECH_SCIENCE"
-        errorLine1="            mValues.put(Programs.COLUMN_BROADCAST_GENRE, Programs.Genres.encode(genres));"
-        errorLine2="                                                                                ~~~~~~">
-        <location
-            file="src/main/java/android/support/media/tv/Program.java"
-            line="286"
-            column="81"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: WatchNextPrograms.WATCH_NEXT_TYPE_CONTINUE, WatchNextPrograms.WATCH_NEXT_TYPE_NEXT, WatchNextPrograms.WATCH_NEXT_TYPE_NEW, WatchNextPrograms.WATCH_NEXT_TYPE_WATCHLIST"
-        errorLine1="        return i == null ? INVALID_INT_VALUE : i;"
-        errorLine2="                           ~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/android/support/media/tv/WatchNextProgram.java"
-            line="99"
-            column="28"/>
-    </issue>
+<issues format="4" by="lint 3.0.0-beta7">
 
 </issues>
diff --git a/tv-provider/src/main/java/android/support/media/tv/BasePreviewProgram.java b/tv-provider/src/main/java/android/support/media/tv/BasePreviewProgram.java
index 1423d9d..39c3014 100644
--- a/tv-provider/src/main/java/android/support/media/tv/BasePreviewProgram.java
+++ b/tv-provider/src/main/java/android/support/media/tv/BasePreviewProgram.java
@@ -23,14 +23,13 @@
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Build;
+import android.support.annotation.IntDef;
 import android.support.annotation.RestrictTo;
 import android.support.media.tv.TvContractCompat.PreviewProgramColumns;
-import android.support.media.tv.TvContractCompat.PreviewProgramColumns.AspectRatio;
-import android.support.media.tv.TvContractCompat.PreviewProgramColumns.Availability;
-import android.support.media.tv.TvContractCompat.PreviewProgramColumns.InteractionType;
-import android.support.media.tv.TvContractCompat.PreviewProgramColumns.Type;
 import android.support.media.tv.TvContractCompat.PreviewPrograms;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.net.URISyntaxException;
 import java.text.SimpleDateFormat;
 import java.util.Date;
@@ -55,6 +54,89 @@
     private static final int IS_LIVE = 1;
     private static final int IS_BROWSABLE = 1;
 
+    /** @hide */
+    @IntDef({
+            TYPE_UNKNOWN,
+            PreviewProgramColumns.TYPE_MOVIE,
+            PreviewProgramColumns.TYPE_TV_SERIES,
+            PreviewProgramColumns.TYPE_TV_SEASON,
+            PreviewProgramColumns.TYPE_TV_EPISODE,
+            PreviewProgramColumns.TYPE_CLIP,
+            PreviewProgramColumns.TYPE_EVENT,
+            PreviewProgramColumns.TYPE_CHANNEL,
+            PreviewProgramColumns.TYPE_TRACK,
+            PreviewProgramColumns.TYPE_ALBUM,
+            PreviewProgramColumns.TYPE_ARTIST,
+            PreviewProgramColumns.TYPE_PLAYLIST,
+            PreviewProgramColumns.TYPE_STATION,
+            PreviewProgramColumns.TYPE_GAME
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @RestrictTo(LIBRARY_GROUP)
+    public @interface Type {}
+
+    /**
+     * The unknown program type.
+     */
+    private static final int TYPE_UNKNOWN = -1;
+
+    /** @hide */
+    @IntDef({
+            ASPECT_RATIO_UNKNOWN,
+            PreviewProgramColumns.ASPECT_RATIO_16_9,
+            PreviewProgramColumns.ASPECT_RATIO_3_2,
+            PreviewProgramColumns.ASPECT_RATIO_4_3,
+            PreviewProgramColumns.ASPECT_RATIO_1_1,
+            PreviewProgramColumns.ASPECT_RATIO_2_3,
+            PreviewProgramColumns.ASPECT_RATIO_MOVIE_POSTER
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @RestrictTo(LIBRARY_GROUP)
+    public @interface AspectRatio {}
+
+    /**
+     * The aspect ratio for unknown aspect ratios.
+     */
+    private static final int ASPECT_RATIO_UNKNOWN = -1;
+
+    /** @hide */
+    @IntDef({
+            AVAILABILITY_UNKNOWN,
+            PreviewProgramColumns.AVAILABILITY_AVAILABLE,
+            PreviewProgramColumns.AVAILABILITY_FREE_WITH_SUBSCRIPTION,
+            PreviewProgramColumns.AVAILABILITY_PAID_CONTENT,
+            PreviewProgramColumns.AVAILABILITY_PURCHASED,
+            PreviewProgramColumns.AVAILABILITY_FREE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @RestrictTo(LIBRARY_GROUP)
+    public @interface Availability {}
+
+    /**
+     * The unknown availability.
+     */
+    private static final int AVAILABILITY_UNKNOWN = -1;
+
+    /** @hide */
+    @IntDef({
+            INTERACTION_TYPE_UNKNOWN,
+            PreviewProgramColumns.INTERACTION_TYPE_VIEWS,
+            PreviewProgramColumns.INTERACTION_TYPE_LISTENS,
+            PreviewProgramColumns.INTERACTION_TYPE_FOLLOWERS,
+            PreviewProgramColumns.INTERACTION_TYPE_FANS,
+            PreviewProgramColumns.INTERACTION_TYPE_LIKES,
+            PreviewProgramColumns.INTERACTION_TYPE_THUMBS,
+            PreviewProgramColumns.INTERACTION_TYPE_VIEWERS,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @RestrictTo(LIBRARY_GROUP)
+    public @interface InteractionType {}
+
+    /**
+     * The unknown interaction type.
+     */
+    private static final int INTERACTION_TYPE_UNKNOWN = -1;
+
     BasePreviewProgram(Builder builder) {
         super(builder);
     }
@@ -127,7 +209,7 @@
      */
     public @Type int getType() {
         Integer i = mValues.getAsInteger(PreviewPrograms.COLUMN_TYPE);
-        return i == null ? INVALID_INT_VALUE : i;
+        return i == null ? TYPE_UNKNOWN : i;
     }
 
     /**
@@ -137,7 +219,7 @@
      */
     public @AspectRatio int getPosterArtAspectRatio() {
         Integer i = mValues.getAsInteger(PreviewPrograms.COLUMN_POSTER_ART_ASPECT_RATIO);
-        return i == null ? INVALID_INT_VALUE : i;
+        return i == null ? ASPECT_RATIO_UNKNOWN : i;
     }
 
     /**
@@ -147,7 +229,7 @@
      */
     public @AspectRatio int getThumbnailAspectRatio() {
         Integer i = mValues.getAsInteger(PreviewPrograms.COLUMN_THUMBNAIL_ASPECT_RATIO);
-        return i == null ? INVALID_INT_VALUE : i;
+        return i == null ? ASPECT_RATIO_UNKNOWN : i;
     }
 
     /**
@@ -165,7 +247,7 @@
      */
     public @Availability int getAvailability() {
         Integer i = mValues.getAsInteger(PreviewPrograms.COLUMN_AVAILABILITY);
-        return i == null ? INVALID_INT_VALUE : i;
+        return i == null ? AVAILABILITY_UNKNOWN : i;
     }
 
     /**
@@ -216,7 +298,7 @@
      */
     public @InteractionType int getInteractionType() {
         Integer i = mValues.getAsInteger(PreviewPrograms.COLUMN_INTERACTION_TYPE);
-        return i == null ? INVALID_INT_VALUE : i;
+        return i == null ? INTERACTION_TYPE_UNKNOWN : i;
     }
 
     /**
diff --git a/tv-provider/src/main/java/android/support/media/tv/BaseProgram.java b/tv-provider/src/main/java/android/support/media/tv/BaseProgram.java
index e4ce9d1..23b5cf9 100644
--- a/tv-provider/src/main/java/android/support/media/tv/BaseProgram.java
+++ b/tv-provider/src/main/java/android/support/media/tv/BaseProgram.java
@@ -22,13 +22,16 @@
 import android.media.tv.TvContentRating;
 import android.net.Uri;
 import android.os.Build;
+import android.support.annotation.IntDef;
 import android.support.annotation.RestrictTo;
 import android.support.media.tv.TvContractCompat.BaseTvColumns;
 import android.support.media.tv.TvContractCompat.ProgramColumns;
-import android.support.media.tv.TvContractCompat.ProgramColumns.ReviewRatingStyle;
 import android.support.media.tv.TvContractCompat.Programs;
 import android.support.media.tv.TvContractCompat.Programs.Genres.Genre;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * Base class for derived classes that want to have common fields for programs defined in
  * {@link TvContractCompat}.
@@ -46,6 +49,22 @@
     private static final int IS_SEARCHABLE = 1;
 
     /** @hide */
+    @IntDef({
+            REVIEW_RATING_STYLE_UNKNOWN,
+            ProgramColumns.REVIEW_RATING_STYLE_STARS,
+            ProgramColumns.REVIEW_RATING_STYLE_THUMBS_UP_DOWN,
+            ProgramColumns.REVIEW_RATING_STYLE_PERCENTAGE,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @RestrictTo(LIBRARY_GROUP)
+    @interface ReviewRatingStyle {}
+
+    /**
+     * The unknown review rating style.
+     */
+    private static final int REVIEW_RATING_STYLE_UNKNOWN = -1;
+
+    /** @hide */
     @RestrictTo(LIBRARY_GROUP)
     protected ContentValues mValues;
 
@@ -254,7 +273,7 @@
      */
     public @ReviewRatingStyle int getReviewRatingStyle() {
         Integer i = mValues.getAsInteger(Programs.COLUMN_REVIEW_RATING_STYLE);
-        return i == null ? INVALID_INT_VALUE : i;
+        return i == null ? REVIEW_RATING_STYLE_UNKNOWN : i;
     }
 
     /**
diff --git a/tv-provider/src/main/java/android/support/media/tv/Program.java b/tv-provider/src/main/java/android/support/media/tv/Program.java
index 4e3bd7a..233f1ba 100644
--- a/tv-provider/src/main/java/android/support/media/tv/Program.java
+++ b/tv-provider/src/main/java/android/support/media/tv/Program.java
@@ -25,6 +25,7 @@
 import android.support.annotation.NonNull;
 import android.support.annotation.RestrictTo;
 import android.support.media.tv.TvContractCompat.Programs;
+import android.support.media.tv.TvContractCompat.Programs.Genres.Genre;
 
 /**
  * A convenience class to access {@link TvContractCompat.Programs} entries in the system content
@@ -282,7 +283,7 @@
          * @return This Builder object to allow for chaining of calls to builder methods.
          * @see Programs#COLUMN_BROADCAST_GENRE
          */
-        public Builder setBroadcastGenres(String[] genres) {
+        public Builder setBroadcastGenres(@Genre String[] genres) {
             mValues.put(Programs.COLUMN_BROADCAST_GENRE, Programs.Genres.encode(genres));
             return this;
         }
diff --git a/tv-provider/src/main/java/android/support/media/tv/TvContractCompat.java b/tv-provider/src/main/java/android/support/media/tv/TvContractCompat.java
index 5a46e79..de4fd04 100644
--- a/tv-provider/src/main/java/android/support/media/tv/TvContractCompat.java
+++ b/tv-provider/src/main/java/android/support/media/tv/TvContractCompat.java
@@ -30,7 +30,6 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.provider.BaseColumns;
-import android.support.annotation.IntDef;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.annotation.RequiresApi;
@@ -606,16 +605,6 @@
      */
     @RestrictTo(LIBRARY_GROUP)
     interface ProgramColumns {
-        /** @hide */
-        @IntDef({
-                REVIEW_RATING_STYLE_STARS,
-                REVIEW_RATING_STYLE_THUMBS_UP_DOWN,
-                REVIEW_RATING_STYLE_PERCENTAGE,
-        })
-        @Retention(RetentionPolicy.SOURCE)
-        @RestrictTo(LIBRARY_GROUP)
-        @interface ReviewRatingStyle {}
-
         /**
          * The review rating style for five star rating.
          *
@@ -934,27 +923,6 @@
      */
     @RestrictTo(LIBRARY_GROUP)
     public interface PreviewProgramColumns {
-
-        /** @hide */
-        @IntDef({
-                TYPE_MOVIE,
-                TYPE_TV_SERIES,
-                TYPE_TV_SEASON,
-                TYPE_TV_EPISODE,
-                TYPE_CLIP,
-                TYPE_EVENT,
-                TYPE_CHANNEL,
-                TYPE_TRACK,
-                TYPE_ALBUM,
-                TYPE_ARTIST,
-                TYPE_PLAYLIST,
-                TYPE_STATION,
-                TYPE_GAME
-        })
-        @Retention(RetentionPolicy.SOURCE)
-        @RestrictTo(LIBRARY_GROUP)
-        public @interface Type {}
-
         /**
          * The program type for movie.
          *
@@ -1046,19 +1014,6 @@
          */
         int TYPE_GAME = 12;
 
-        /** @hide */
-        @IntDef({
-                ASPECT_RATIO_16_9,
-                ASPECT_RATIO_3_2,
-                ASPECT_RATIO_4_3,
-                ASPECT_RATIO_1_1,
-                ASPECT_RATIO_2_3,
-                ASPECT_RATIO_MOVIE_POSTER,
-        })
-        @Retention(RetentionPolicy.SOURCE)
-        @RestrictTo(LIBRARY_GROUP)
-        public @interface AspectRatio {}
-
         /**
          * The aspect ratio for 16:9.
          *
@@ -1107,18 +1062,6 @@
          */
         int ASPECT_RATIO_MOVIE_POSTER = 5;
 
-        /** @hide */
-        @IntDef({
-                AVAILABILITY_AVAILABLE,
-                AVAILABILITY_FREE_WITH_SUBSCRIPTION,
-                AVAILABILITY_PAID_CONTENT,
-                AVAILABILITY_PURCHASED,
-                AVAILABILITY_FREE,
-        })
-        @Retention(RetentionPolicy.SOURCE)
-        @RestrictTo(LIBRARY_GROUP)
-        public @interface Availability {}
-
         /**
          * The availability for "available to this user".
          *
@@ -1155,20 +1098,6 @@
          */
         int AVAILABILITY_FREE = 4;
 
-        /** @hide */
-        @IntDef({
-                INTERACTION_TYPE_VIEWS,
-                INTERACTION_TYPE_LISTENS,
-                INTERACTION_TYPE_FOLLOWERS,
-                INTERACTION_TYPE_FANS,
-                INTERACTION_TYPE_LIKES,
-                INTERACTION_TYPE_THUMBS,
-                INTERACTION_TYPE_VIEWERS,
-        })
-        @Retention(RetentionPolicy.SOURCE)
-        @RestrictTo(LIBRARY_GROUP)
-        public @interface InteractionType {}
-
         /**
          * The interaction type for "views".
          *
@@ -2895,17 +2824,6 @@
         /** The MIME type of a single preview TV program. */
         public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/watch_next_program";
 
-        /** @hide */
-        @IntDef({
-                WATCH_NEXT_TYPE_CONTINUE,
-                WATCH_NEXT_TYPE_NEXT,
-                WATCH_NEXT_TYPE_NEW,
-                WATCH_NEXT_TYPE_WATCHLIST,
-        })
-        @Retention(RetentionPolicy.SOURCE)
-        @RestrictTo(LIBRARY_GROUP)
-        public @interface WatchNextType {}
-
         /**
          * The watch next type for CONTINUE. Use this type when the user has already watched more
          * than 1 minute of this content.
diff --git a/tv-provider/src/main/java/android/support/media/tv/WatchNextProgram.java b/tv-provider/src/main/java/android/support/media/tv/WatchNextProgram.java
index f466584..c192745 100644
--- a/tv-provider/src/main/java/android/support/media/tv/WatchNextProgram.java
+++ b/tv-provider/src/main/java/android/support/media/tv/WatchNextProgram.java
@@ -22,12 +22,15 @@
 import android.database.Cursor;
 import android.media.tv.TvContentRating;  // For javadoc gen of super class
 import android.os.Build;
+import android.support.annotation.IntDef;
 import android.support.annotation.RestrictTo;
 import android.support.media.tv.TvContractCompat.PreviewPrograms;  // For javadoc gen of super class
 import android.support.media.tv.TvContractCompat.Programs;  // For javadoc gen of super class
 import android.support.media.tv.TvContractCompat.Programs.Genres;  // For javadoc gen of super class
 import android.support.media.tv.TvContractCompat.WatchNextPrograms;
-import android.support.media.tv.TvContractCompat.WatchNextPrograms.WatchNextType;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 
 /**
  * A convenience class to access {@link WatchNextPrograms} entries in the system content
@@ -87,16 +90,34 @@
     private static final long INVALID_LONG_VALUE = -1;
     private static final int INVALID_INT_VALUE = -1;
 
+    /** @hide */
+    @IntDef({
+            WATCH_NEXT_TYPE_UNKNOWN,
+            WatchNextPrograms.WATCH_NEXT_TYPE_CONTINUE,
+            WatchNextPrograms.WATCH_NEXT_TYPE_NEXT,
+            WatchNextPrograms.WATCH_NEXT_TYPE_NEW,
+            WatchNextPrograms.WATCH_NEXT_TYPE_WATCHLIST,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @RestrictTo(LIBRARY_GROUP)
+    public @interface WatchNextType {}
+
+    /**
+     * The unknown watch next type. Use this type when the actual type is not known.
+     */
+    public static final int WATCH_NEXT_TYPE_UNKNOWN = -1;
+
     private WatchNextProgram(Builder builder) {
         super(builder);
     }
 
     /**
-     * @return The value of {@link WatchNextPrograms#COLUMN_WATCH_NEXT_TYPE} for the program.
+     * @return The value of {@link WatchNextPrograms#COLUMN_WATCH_NEXT_TYPE} for the program,
+     * or {@link #WATCH_NEXT_TYPE_UNKNOWN} if it's unknown.
      */
     public @WatchNextType int getWatchNextType() {
         Integer i = mValues.getAsInteger(WatchNextPrograms.COLUMN_WATCH_NEXT_TYPE);
-        return i == null ? INVALID_INT_VALUE : i;
+        return i == null ? WATCH_NEXT_TYPE_UNKNOWN : i;
     }
 
     /**
diff --git a/v13/api/current.txt b/v13/api/current.txt
index 73871f6..1d9fdbf 100644
--- a/v13/api/current.txt
+++ b/v13/api/current.txt
@@ -5,44 +5,63 @@
     method public static android.support.v13.view.DragAndDropPermissionsCompat requestDragAndDropPermissions(android.app.Activity, android.view.DragEvent);
   }
 
-  public class FragmentCompat {
-    ctor public FragmentCompat();
-    method public static void requestPermissions(android.app.Fragment, java.lang.String[], int);
+  public deprecated class FragmentCompat {
+    ctor public deprecated FragmentCompat();
+    method public static deprecated void requestPermissions(android.app.Fragment, java.lang.String[], int);
     method public static deprecated void setMenuVisibility(android.app.Fragment, boolean);
-    method public static void setPermissionCompatDelegate(android.support.v13.app.FragmentCompat.PermissionCompatDelegate);
-    method public static void setUserVisibleHint(android.app.Fragment, boolean);
-    method public static boolean shouldShowRequestPermissionRationale(android.app.Fragment, java.lang.String);
+    method public static deprecated void setPermissionCompatDelegate(android.support.v13.app.FragmentCompat.PermissionCompatDelegate);
+    method public static deprecated void setUserVisibleHint(android.app.Fragment, boolean);
+    method public static deprecated boolean shouldShowRequestPermissionRationale(android.app.Fragment, java.lang.String);
   }
 
-  public static abstract interface FragmentCompat.OnRequestPermissionsResultCallback {
-    method public abstract void onRequestPermissionsResult(int, java.lang.String[], int[]);
+  public static abstract deprecated interface FragmentCompat.OnRequestPermissionsResultCallback {
+    method public abstract deprecated void onRequestPermissionsResult(int, java.lang.String[], int[]);
   }
 
-  public static abstract interface FragmentCompat.PermissionCompatDelegate {
-    method public abstract boolean requestPermissions(android.app.Fragment, java.lang.String[], int);
+  public static abstract deprecated interface FragmentCompat.PermissionCompatDelegate {
+    method public abstract deprecated boolean requestPermissions(android.app.Fragment, java.lang.String[], int);
   }
 
-  public abstract class FragmentPagerAdapter extends android.support.v4.view.PagerAdapter {
-    ctor public FragmentPagerAdapter(android.app.FragmentManager);
-    method public abstract android.app.Fragment getItem(int);
-    method public long getItemId(int);
-    method public boolean isViewFromObject(android.view.View, java.lang.Object);
+  public abstract deprecated class FragmentPagerAdapter extends android.support.v4.view.PagerAdapter {
+    ctor public deprecated FragmentPagerAdapter(android.app.FragmentManager);
+    method public deprecated void destroyItem(android.view.ViewGroup, int, java.lang.Object);
+    method public deprecated void finishUpdate(android.view.ViewGroup);
+    method public abstract deprecated android.app.Fragment getItem(int);
+    method public deprecated long getItemId(int);
+    method public deprecated java.lang.Object instantiateItem(android.view.ViewGroup, int);
+    method public deprecated boolean isViewFromObject(android.view.View, java.lang.Object);
+    method public deprecated void restoreState(android.os.Parcelable, java.lang.ClassLoader);
+    method public deprecated android.os.Parcelable saveState();
+    method public deprecated void setPrimaryItem(android.view.ViewGroup, int, java.lang.Object);
+    method public deprecated void startUpdate(android.view.ViewGroup);
   }
 
-  public abstract class FragmentStatePagerAdapter extends android.support.v4.view.PagerAdapter {
-    ctor public FragmentStatePagerAdapter(android.app.FragmentManager);
-    method public abstract android.app.Fragment getItem(int);
-    method public boolean isViewFromObject(android.view.View, java.lang.Object);
+  public abstract deprecated class FragmentStatePagerAdapter extends android.support.v4.view.PagerAdapter {
+    ctor public deprecated FragmentStatePagerAdapter(android.app.FragmentManager);
+    method public deprecated void destroyItem(android.view.ViewGroup, int, java.lang.Object);
+    method public deprecated void finishUpdate(android.view.ViewGroup);
+    method public abstract deprecated android.app.Fragment getItem(int);
+    method public deprecated java.lang.Object instantiateItem(android.view.ViewGroup, int);
+    method public deprecated boolean isViewFromObject(android.view.View, java.lang.Object);
+    method public deprecated void restoreState(android.os.Parcelable, java.lang.ClassLoader);
+    method public deprecated android.os.Parcelable saveState();
+    method public deprecated void setPrimaryItem(android.view.ViewGroup, int, java.lang.Object);
+    method public deprecated void startUpdate(android.view.ViewGroup);
   }
 
-  public class FragmentTabHost extends android.widget.TabHost implements android.widget.TabHost.OnTabChangeListener {
-    ctor public FragmentTabHost(android.content.Context);
-    ctor public FragmentTabHost(android.content.Context, android.util.AttributeSet);
-    method public void addTab(android.widget.TabHost.TabSpec, java.lang.Class<?>, android.os.Bundle);
-    method public void onTabChanged(java.lang.String);
+  public deprecated class FragmentTabHost extends android.widget.TabHost implements android.widget.TabHost.OnTabChangeListener {
+    ctor public deprecated FragmentTabHost(android.content.Context);
+    ctor public deprecated FragmentTabHost(android.content.Context, android.util.AttributeSet);
+    method public deprecated void addTab(android.widget.TabHost.TabSpec, java.lang.Class<?>, android.os.Bundle);
+    method protected deprecated void onAttachedToWindow();
+    method protected deprecated void onDetachedFromWindow();
+    method protected deprecated void onRestoreInstanceState(android.os.Parcelable);
+    method protected deprecated android.os.Parcelable onSaveInstanceState();
+    method public deprecated void onTabChanged(java.lang.String);
+    method public deprecated void setOnTabChangedListener(android.widget.TabHost.OnTabChangeListener);
     method public deprecated void setup();
-    method public void setup(android.content.Context, android.app.FragmentManager);
-    method public void setup(android.content.Context, android.app.FragmentManager, int);
+    method public deprecated void setup(android.content.Context, android.app.FragmentManager);
+    method public deprecated void setup(android.content.Context, android.app.FragmentManager, int);
   }
 
 }
diff --git a/v13/build.gradle b/v13/build.gradle
index 0f5c9b6..425a31f 100644
--- a/v13/build.gradle
+++ b/v13/build.gradle
@@ -10,8 +10,8 @@
     api(project(":support-annotations"))
     api(project(":support-v4"))
 
-    androidTestImplementation(TEST_RUNNER, libs.exclude_annotations)
-    androidTestImplementation(ESPRESSO_CORE, libs.exclude_annotations)
+    androidTestImplementation(TEST_RUNNER)
+    androidTestImplementation(ESPRESSO_CORE)
     androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
     androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
 }
diff --git a/v13/java/android/support/v13/app/FragmentCompat.java b/v13/java/android/support/v13/app/FragmentCompat.java
index 31c2343..e8915fb 100644
--- a/v13/java/android/support/v13/app/FragmentCompat.java
+++ b/v13/java/android/support/v13/app/FragmentCompat.java
@@ -30,8 +30,19 @@
 
 /**
  * Helper for accessing features in {@link Fragment} in a backwards compatible fashion.
+ *
+ * @deprecated Use {@link android.support.v4.app.Fragment} instead of the framework fragment.
  */
+@Deprecated
 public class FragmentCompat {
+
+    /**
+     * @deprecated Use {@link android.support.v4.app.Fragment} instead of the framework fragment.
+     */
+    @Deprecated
+    public FragmentCompat() {
+    }
+
     interface FragmentCompatImpl {
         void setUserVisibleHint(Fragment f, boolean deferStart);
         void requestPermissions(Fragment fragment, String[] permissions, int requestCode);
@@ -48,7 +59,11 @@
      *     to the compatibility methods in this class will first check whether the delegate can
      *     handle the method call, and invoke the corresponding method if it can.
      * </p>
+     *
+     * @deprecated Use {@link android.support.v4.app.Fragment} instead of the framework
+     * {@link Fragment}.
      */
+    @Deprecated
     public interface PermissionCompatDelegate {
 
         /**
@@ -66,7 +81,11 @@
          *
          * @return Whether the delegate has handled the permission request.
          * @see FragmentCompat#requestPermissions(Fragment, String[], int)
+         *
+         * @deprecated Use {@link android.support.v4.app.Fragment} instead of the framework
+         * {@link Fragment}.
          */
+        @Deprecated
         boolean requestPermissions(Fragment fragment, String[] permissions, int requestCode);
     }
 
@@ -157,22 +176,34 @@
      * delegate.
      *
      * @param delegate The delegate to be set. {@code null} to clear the set delegate.
+     *
+     * @deprecated Use {@link android.support.v4.app.Fragment} instead of the framework
+     * {@link Fragment}.
      */
+    @Deprecated
     public static void setPermissionCompatDelegate(PermissionCompatDelegate delegate) {
         sDelegate = delegate;
     }
 
     /**
      * @hide
+     *
+     * @deprecated Use {@link android.support.v4.app.Fragment} instead of the framework
+     * {@link Fragment}.
      */
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    @Deprecated
     public static PermissionCompatDelegate getPermissionCompatDelegate() {
         return sDelegate;
     }
 
     /**
      * This interface is the contract for receiving the results for permission requests.
+     *
+     * @deprecated Use {@link android.support.v4.app.Fragment} instead of the framework
+     * {@link Fragment}.
      */
+    @Deprecated
     public interface OnRequestPermissionsResultCallback {
 
         /**
@@ -188,7 +219,11 @@
          *     or {@link android.content.pm.PackageManager#PERMISSION_DENIED}. Never null.
          *
          * @see #requestPermissions(android.app.Fragment, String[], int)
+         *
+         * @deprecated Use {@link android.support.v4.app.Fragment} instead of the framework
+         * {@link Fragment}.
          */
+        @Deprecated
         public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                 @NonNull int[] grantResults);
     }
@@ -197,7 +232,8 @@
      * Call {@link Fragment#setMenuVisibility(boolean) Fragment.setMenuVisibility(boolean)}
      * if running on an appropriate version of the platform.
      *
-     * @deprecated Use {@link Fragment#setMenuVisibility(boolean)} directly.
+     * @deprecated Use {@link android.support.v4.app.Fragment} instead of the framework
+     * {@link Fragment}.
      */
     @Deprecated
     public static void setMenuVisibility(Fragment f, boolean visible) {
@@ -207,7 +243,11 @@
     /**
      * Call {@link Fragment#setUserVisibleHint(boolean) setUserVisibleHint(boolean)}
      * if running on an appropriate version of the platform.
+     *
+     * @deprecated Use {@link android.support.v4.app.Fragment} instead of the framework
+     * {@link Fragment}.
      */
+    @Deprecated
     public static void setUserVisibleHint(Fragment f, boolean deferStart) {
         IMPL.setUserVisibleHint(f, deferStart);
     }
@@ -262,7 +302,11 @@
      * @see android.support.v4.content.ContextCompat#checkSelfPermission(
      *     android.content.Context, String)
      * @see #shouldShowRequestPermissionRationale(android.app.Fragment, String)
+     *
+     * @deprecated Use {@link android.support.v4.app.Fragment} instead of the framework
+     * {@link Fragment}.
      */
+    @Deprecated
     public static void requestPermissions(@NonNull Fragment fragment,
             @NonNull String[] permissions, int requestCode) {
         if (sDelegate != null && sDelegate.requestPermissions(fragment, permissions, requestCode)) {
@@ -293,7 +337,11 @@
      * @see android.support.v4.content.ContextCompat#checkSelfPermission(
      *     android.content.Context, String)
      * @see #requestPermissions(android.app.Fragment, String[], int)
+     *
+     * @deprecated Use {@link android.support.v4.app.Fragment} instead of the framework
+     * {@link Fragment}.
      */
+    @Deprecated
     public static boolean shouldShowRequestPermissionRationale(@NonNull Fragment fragment,
             @NonNull String permission) {
         return IMPL.shouldShowRequestPermissionRationale(fragment, permission);
diff --git a/v13/java/android/support/v13/app/FragmentPagerAdapter.java b/v13/java/android/support/v13/app/FragmentPagerAdapter.java
index e0b788a..112ed02 100644
--- a/v13/java/android/support/v13/app/FragmentPagerAdapter.java
+++ b/v13/java/android/support/v13/app/FragmentPagerAdapter.java
@@ -61,7 +61,10 @@
  *
  * {@sample frameworks/support/samples/Support13Demos/src/main/res/layout/fragment_pager_list.xml
  *      complete}
+ *
+ * @deprecated Use {@link android.support.v4.app.FragmentPagerAdapter} instead.
  */
+@Deprecated
 public abstract class FragmentPagerAdapter extends PagerAdapter {
     private static final String TAG = "FragmentPagerAdapter";
     private static final boolean DEBUG = false;
@@ -70,15 +73,26 @@
     private FragmentTransaction mCurTransaction = null;
     private Fragment mCurrentPrimaryItem = null;
 
+    /**
+     * @deprecated Use {@link android.support.v4.app.FragmentPagerAdapter} instead.
+     */
+    @Deprecated
     public FragmentPagerAdapter(FragmentManager fm) {
         mFragmentManager = fm;
     }
 
     /**
      * Return the Fragment associated with a specified position.
+     *
+     * @deprecated Use {@link android.support.v4.app.FragmentPagerAdapter} instead.
      */
+    @Deprecated
     public abstract Fragment getItem(int position);
 
+    /**
+     * @deprecated Use {@link android.support.v4.app.FragmentPagerAdapter} instead.
+     */
+    @Deprecated
     @Override
     public void startUpdate(ViewGroup container) {
         if (container.getId() == View.NO_ID) {
@@ -87,6 +101,10 @@
         }
     }
 
+    /**
+     * @deprecated Use {@link android.support.v4.app.FragmentPagerAdapter} instead.
+     */
+    @Deprecated
     @SuppressWarnings("ReferenceEquality")
     @Override
     public Object instantiateItem(ViewGroup container, int position) {
@@ -116,6 +134,10 @@
         return fragment;
     }
 
+    /**
+     * @deprecated Use {@link android.support.v4.app.FragmentPagerAdapter} instead.
+     */
+    @Deprecated
     @Override
     public void destroyItem(ViewGroup container, int position, Object object) {
         if (mCurTransaction == null) {
@@ -126,6 +148,10 @@
         mCurTransaction.detach((Fragment)object);
     }
 
+    /**
+     * @deprecated Use {@link android.support.v4.app.FragmentPagerAdapter} instead.
+     */
+    @Deprecated
     @SuppressWarnings("ReferenceEquality")
     @Override
     public void setPrimaryItem(ViewGroup container, int position, Object object) {
@@ -143,6 +169,10 @@
         }
     }
 
+    /**
+     * @deprecated Use {@link android.support.v4.app.FragmentPagerAdapter} instead.
+     */
+    @Deprecated
     @Override
     public void finishUpdate(ViewGroup container) {
         if (mCurTransaction != null) {
@@ -152,16 +182,28 @@
         }
     }
 
+    /**
+     * @deprecated Use {@link android.support.v4.app.FragmentPagerAdapter} instead.
+     */
+    @Deprecated
     @Override
     public boolean isViewFromObject(View view, Object object) {
         return ((Fragment)object).getView() == view;
     }
 
+    /**
+     * @deprecated Use {@link android.support.v4.app.FragmentPagerAdapter} instead.
+     */
+    @Deprecated
     @Override
     public Parcelable saveState() {
         return null;
     }
 
+    /**
+     * @deprecated Use {@link android.support.v4.app.FragmentPagerAdapter} instead.
+     */
+    @Deprecated
     @Override
     public void restoreState(Parcelable state, ClassLoader loader) {
     }
@@ -174,7 +216,10 @@
      *
      * @param position Position within this adapter
      * @return Unique identifier for the item at position
+     *
+     * @deprecated Use {@link android.support.v4.app.FragmentPagerAdapter} instead.
      */
+    @Deprecated
     public long getItemId(int position) {
         return position;
     }
diff --git a/v13/java/android/support/v13/app/FragmentStatePagerAdapter.java b/v13/java/android/support/v13/app/FragmentStatePagerAdapter.java
index 45a6bf5..76a3224 100644
--- a/v13/java/android/support/v13/app/FragmentStatePagerAdapter.java
+++ b/v13/java/android/support/v13/app/FragmentStatePagerAdapter.java
@@ -64,7 +64,10 @@
  *
  * {@sample frameworks/support/samples/Support4Demos/src/main/res/layout/fragment_pager_list.xml
  *      complete}
+ *
+ * @deprecated Use {@link android.support.v4.app.FragmentStatePagerAdapter} instead.
  */
+@Deprecated
 public abstract class FragmentStatePagerAdapter extends PagerAdapter {
     private static final String TAG = "FragStatePagerAdapter";
     private static final boolean DEBUG = false;
@@ -76,15 +79,26 @@
     private ArrayList<Fragment> mFragments = new ArrayList<Fragment>();
     private Fragment mCurrentPrimaryItem = null;
 
+    /**
+     * @deprecated Use {@link android.support.v4.app.FragmentStatePagerAdapter} instead.
+     */
+    @Deprecated
     public FragmentStatePagerAdapter(FragmentManager fm) {
         mFragmentManager = fm;
     }
 
     /**
      * Return the Fragment associated with a specified position.
+     *
+     * @deprecated Use {@link android.support.v4.app.FragmentStatePagerAdapter} instead.
      */
+    @Deprecated
     public abstract Fragment getItem(int position);
 
+    /**
+     * @deprecated Use {@link android.support.v4.app.FragmentStatePagerAdapter} instead.
+     */
+    @Deprecated
     @Override
     public void startUpdate(ViewGroup container) {
         if (container.getId() == View.NO_ID) {
@@ -93,6 +107,10 @@
         }
     }
 
+    /**
+     * @deprecated Use {@link android.support.v4.app.FragmentStatePagerAdapter} instead.
+     */
+    @Deprecated
     @Override
     public Object instantiateItem(ViewGroup container, int position) {
         // If we already have this item instantiated, there is nothing
@@ -129,6 +147,10 @@
         return fragment;
     }
 
+    /**
+     * @deprecated Use {@link android.support.v4.app.FragmentStatePagerAdapter} instead.
+     */
+    @Deprecated
     @Override
     public void destroyItem(ViewGroup container, int position, Object object) {
         Fragment fragment = (Fragment) object;
@@ -148,6 +170,10 @@
         mCurTransaction.remove(fragment);
     }
 
+    /**
+     * @deprecated Use {@link android.support.v4.app.FragmentStatePagerAdapter} instead.
+     */
+    @Deprecated
     @SuppressWarnings("ReferenceEquality")
     @Override
     public void setPrimaryItem(ViewGroup container, int position, Object object) {
@@ -165,6 +191,10 @@
         }
     }
 
+    /**
+     * @deprecated Use {@link android.support.v4.app.FragmentStatePagerAdapter} instead.
+     */
+    @Deprecated
     @Override
     public void finishUpdate(ViewGroup container) {
         if (mCurTransaction != null) {
@@ -174,11 +204,19 @@
         }
     }
 
+    /**
+     * @deprecated Use {@link android.support.v4.app.FragmentStatePagerAdapter} instead.
+     */
+    @Deprecated
     @Override
     public boolean isViewFromObject(View view, Object object) {
         return ((Fragment)object).getView() == view;
     }
 
+    /**
+     * @deprecated Use {@link android.support.v4.app.FragmentStatePagerAdapter} instead.
+     */
+    @Deprecated
     @Override
     public Parcelable saveState() {
         Bundle state = null;
@@ -201,6 +239,10 @@
         return state;
     }
 
+    /**
+     * @deprecated Use {@link android.support.v4.app.FragmentStatePagerAdapter} instead.
+     */
+    @Deprecated
     @Override
     public void restoreState(Parcelable state, ClassLoader loader) {
         if (state != null) {
diff --git a/v13/java/android/support/v13/app/FragmentTabHost.java b/v13/java/android/support/v13/app/FragmentTabHost.java
index 2326ccb..5c34ab5 100644
--- a/v13/java/android/support/v13/app/FragmentTabHost.java
+++ b/v13/java/android/support/v13/app/FragmentTabHost.java
@@ -38,7 +38,10 @@
  * Version of {@link android.support.v4.app.FragmentTabHost} that can be
  * used with the platform {@link android.app.Fragment} APIs.  You will not
  * normally use this, instead using action bar tabs.
+ *
+ * @deprecated Use {@link android.support.v4.app.FragmentTabHost} instead.
  */
+@Deprecated
 public class FragmentTabHost extends TabHost implements TabHost.OnTabChangeListener {
     private final ArrayList<TabInfo> mTabs = new ArrayList<TabInfo>();
     private FrameLayout mRealTabContent;
@@ -117,6 +120,10 @@
         };
     }
 
+    /**
+     * @deprecated Use {@link android.support.v4.app.FragmentTabHost} instead.
+     */
+    @Deprecated
     public FragmentTabHost(Context context) {
         // Note that we call through to the version that takes an AttributeSet,
         // because the simple Context construct can result in a broken object!
@@ -124,6 +131,10 @@
         initFragmentTabHost(context, null);
     }
 
+    /**
+     * @deprecated Use {@link android.support.v4.app.FragmentTabHost} instead.
+     */
+    @Deprecated
     public FragmentTabHost(Context context, AttributeSet attrs) {
         super(context, attrs);
         initFragmentTabHost(context, attrs);
@@ -167,9 +178,7 @@
     }
 
     /**
-     * @deprecated Don't call the original TabHost setup, you must instead
-     * call {@link #setup(Context, FragmentManager)} or
-     * {@link #setup(Context, FragmentManager, int)}.
+     * @deprecated Use {@link android.support.v4.app.FragmentTabHost} instead.
      */
     @Override
     @Deprecated
@@ -178,6 +187,10 @@
                 "Must call setup() that takes a Context and FragmentManager");
     }
 
+    /**
+     * @deprecated Use {@link android.support.v4.app.FragmentTabHost} instead.
+     */
+    @Deprecated
     public void setup(Context context, FragmentManager manager) {
         ensureHierarchy(context);  // Ensure views required by super.setup()
         super.setup();
@@ -186,6 +199,10 @@
         ensureContent();
     }
 
+    /**
+     * @deprecated Use {@link android.support.v4.app.FragmentTabHost} instead.
+     */
+    @Deprecated
     public void setup(Context context, FragmentManager manager, int containerId) {
         ensureHierarchy(context);  // Ensure views required by super.setup()
         super.setup();
@@ -212,11 +229,19 @@
         }
     }
 
+    /**
+     * @deprecated Use {@link android.support.v4.app.FragmentTabHost} instead.
+     */
+    @Deprecated
     @Override
     public void setOnTabChangedListener(OnTabChangeListener l) {
         mOnTabChangeListener = l;
     }
 
+    /**
+     * @deprecated Use {@link android.support.v4.app.FragmentTabHost} instead.
+     */
+    @Deprecated
     public void addTab(TabHost.TabSpec tabSpec, Class<?> clss, Bundle args) {
         tabSpec.setContent(new DummyTabFactory(mContext));
         String tag = tabSpec.getTag();
@@ -239,6 +264,10 @@
         addTab(tabSpec);
     }
 
+    /**
+     * @deprecated Use {@link android.support.v4.app.FragmentTabHost} instead.
+     */
+    @Deprecated
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
@@ -278,12 +307,20 @@
         }
     }
 
+    /**
+     * @deprecated Use {@link android.support.v4.app.FragmentTabHost} instead.
+     */
+    @Deprecated
     @Override
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
         mAttached = false;
     }
 
+    /**
+     * @deprecated Use {@link android.support.v4.app.FragmentTabHost} instead.
+     */
+    @Deprecated
     @Override
     protected Parcelable onSaveInstanceState() {
         Parcelable superState = super.onSaveInstanceState();
@@ -292,6 +329,10 @@
         return ss;
     }
 
+    /**
+     * @deprecated Use {@link android.support.v4.app.FragmentTabHost} instead.
+     */
+    @Deprecated
     @Override
     protected void onRestoreInstanceState(Parcelable state) {
         if (!(state instanceof SavedState)) {
@@ -303,6 +344,10 @@
         setCurrentTabByTag(ss.curTab);
     }
 
+    /**
+     * @deprecated Use {@link android.support.v4.app.FragmentTabHost} instead.
+     */
+    @Deprecated
     @Override
     public void onTabChanged(String tabId) {
         if (mAttached) {
diff --git a/v13/java/android/support/v13/app/package.html b/v13/java/android/support/v13/app/package.html
deleted file mode 100755
index 3557ecb..0000000
--- a/v13/java/android/support/v13/app/package.html
+++ /dev/null
@@ -1,5 +0,0 @@
-<body>
-
-Support classes to access some of the android.app package features introduced after API level 13 in a backwards compatible fashion.
-
-</body>
diff --git a/v14/preference/api/current.txt b/v14/preference/api/current.txt
index b92ccf9..af3db09 100644
--- a/v14/preference/api/current.txt
+++ b/v14/preference/api/current.txt
@@ -46,7 +46,7 @@
     field protected static final java.lang.String ARG_KEY = "key";
   }
 
-  public abstract class PreferenceFragment extends android.app.Fragment implements android.support.v7.preference.PreferenceManager.OnDisplayPreferenceDialogListener android.support.v7.preference.PreferenceManager.OnNavigateToScreenListener android.support.v7.preference.PreferenceManager.OnPreferenceTreeClickListener {
+  public abstract class PreferenceFragment extends android.app.Fragment implements android.support.v7.preference.DialogPreference.TargetFragment android.support.v7.preference.PreferenceManager.OnDisplayPreferenceDialogListener android.support.v7.preference.PreferenceManager.OnNavigateToScreenListener android.support.v7.preference.PreferenceManager.OnPreferenceTreeClickListener {
     ctor public PreferenceFragment();
     method public void addPreferencesFromResource(int);
     method public android.support.v7.preference.Preference findPreference(java.lang.CharSequence);
diff --git a/v14/preference/res/layout-v21/preference_material.xml b/v14/preference/res/layout-v21/preference_material.xml
index da6b69f..c3000f9 100644
--- a/v14/preference/res/layout-v21/preference_material.xml
+++ b/v14/preference/res/layout-v21/preference_material.xml
@@ -31,11 +31,10 @@
         android:id="@+id/icon_frame"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_marginStart="-4dp"
-        android:minWidth="60dp"
+        android:minWidth="56dp"
         android:gravity="start|center_vertical"
         android:orientation="horizontal"
-        android:paddingEnd="12dp"
+        android:paddingEnd="8dp"
         android:paddingTop="4dp"
         android:paddingBottom="4dp">
         <android.support.v7.internal.widget.PreferenceImageView
@@ -66,6 +65,7 @@
             android:layout_below="@android:id/title"
             android:layout_alignStart="@android:id/title"
             android:textAppearance="?android:attr/textAppearanceListItemSecondary"
+            android:textAlignment="viewStart"
             android:textColor="?android:attr/textColorSecondary"
             android:maxLines="10" />
 
diff --git a/v17/Android.mk b/v17/Android.mk
deleted file mode 100644
index 14ff0aa..0000000
--- a/v17/Android.mk
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright (C) 2014 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH:= $(call my-dir)
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/v17/leanback/api/current.txt b/v17/leanback/api/current.txt
deleted file mode 100644
index 4ee4d94..0000000
--- a/v17/leanback/api/current.txt
+++ /dev/null
@@ -1,3129 +0,0 @@
-package android.support.v17.leanback.app {
-
-  public final class BackgroundManager {
-    method public void attach(android.view.Window);
-    method public void attachToView(android.view.View);
-    method public void clearDrawable();
-    method public final int getColor();
-    method public deprecated android.graphics.drawable.Drawable getDefaultDimLayer();
-    method public deprecated android.graphics.drawable.Drawable getDimLayer();
-    method public android.graphics.drawable.Drawable getDrawable();
-    method public static android.support.v17.leanback.app.BackgroundManager getInstance(android.app.Activity);
-    method public boolean isAttached();
-    method public boolean isAutoReleaseOnStop();
-    method public void release();
-    method public void setAutoReleaseOnStop(boolean);
-    method public void setBitmap(android.graphics.Bitmap);
-    method public void setColor(int);
-    method public deprecated void setDimLayer(android.graphics.drawable.Drawable);
-    method public void setDrawable(android.graphics.drawable.Drawable);
-    method public void setThemeDrawableResourceId(int);
-  }
-
-  public class BaseFragment extends android.support.v17.leanback.app.BrandedFragment {
-    method protected java.lang.Object createEntranceTransition();
-    method public final android.support.v17.leanback.app.ProgressBarManager getProgressBarManager();
-    method protected void onEntranceTransitionEnd();
-    method protected void onEntranceTransitionPrepare();
-    method protected void onEntranceTransitionStart();
-    method public void prepareEntranceTransition();
-    method protected void runEntranceTransition(java.lang.Object);
-    method public void startEntranceTransition();
-  }
-
-   abstract class BaseRowFragment extends android.app.Fragment {
-    method public final android.support.v17.leanback.widget.ObjectAdapter getAdapter();
-    method public final android.support.v17.leanback.widget.ItemBridgeAdapter getBridgeAdapter();
-    method public final android.support.v17.leanback.widget.PresenterSelector getPresenterSelector();
-    method public int getSelectedPosition();
-    method public final android.support.v17.leanback.widget.VerticalGridView getVerticalGridView();
-    method public void onTransitionEnd();
-    method public boolean onTransitionPrepare();
-    method public void onTransitionStart();
-    method public final void setAdapter(android.support.v17.leanback.widget.ObjectAdapter);
-    method public void setAlignment(int);
-    method public final void setPresenterSelector(android.support.v17.leanback.widget.PresenterSelector);
-    method public void setSelectedPosition(int);
-    method public void setSelectedPosition(int, boolean);
-  }
-
-   abstract class BaseRowSupportFragment extends android.support.v4.app.Fragment {
-    method public final android.support.v17.leanback.widget.ObjectAdapter getAdapter();
-    method public final android.support.v17.leanback.widget.ItemBridgeAdapter getBridgeAdapter();
-    method public final android.support.v17.leanback.widget.PresenterSelector getPresenterSelector();
-    method public int getSelectedPosition();
-    method public final android.support.v17.leanback.widget.VerticalGridView getVerticalGridView();
-    method public void onTransitionEnd();
-    method public boolean onTransitionPrepare();
-    method public void onTransitionStart();
-    method public final void setAdapter(android.support.v17.leanback.widget.ObjectAdapter);
-    method public void setAlignment(int);
-    method public final void setPresenterSelector(android.support.v17.leanback.widget.PresenterSelector);
-    method public void setSelectedPosition(int);
-    method public void setSelectedPosition(int, boolean);
-  }
-
-  public class BaseSupportFragment extends android.support.v17.leanback.app.BrandedSupportFragment {
-    method protected java.lang.Object createEntranceTransition();
-    method public final android.support.v17.leanback.app.ProgressBarManager getProgressBarManager();
-    method protected void onEntranceTransitionEnd();
-    method protected void onEntranceTransitionPrepare();
-    method protected void onEntranceTransitionStart();
-    method public void prepareEntranceTransition();
-    method protected void runEntranceTransition(java.lang.Object);
-    method public void startEntranceTransition();
-  }
-
-  public class BrandedFragment extends android.app.Fragment {
-    ctor public BrandedFragment();
-    method public android.graphics.drawable.Drawable getBadgeDrawable();
-    method public int getSearchAffordanceColor();
-    method public android.support.v17.leanback.widget.SearchOrbView.Colors getSearchAffordanceColors();
-    method public java.lang.CharSequence getTitle();
-    method public android.view.View getTitleView();
-    method public android.support.v17.leanback.widget.TitleViewAdapter getTitleViewAdapter();
-    method public void installTitleView(android.view.LayoutInflater, android.view.ViewGroup, android.os.Bundle);
-    method public final boolean isShowingTitle();
-    method public android.view.View onInflateTitleView(android.view.LayoutInflater, android.view.ViewGroup, android.os.Bundle);
-    method public void setBadgeDrawable(android.graphics.drawable.Drawable);
-    method public void setOnSearchClickedListener(android.view.View.OnClickListener);
-    method public void setSearchAffordanceColor(int);
-    method public void setSearchAffordanceColors(android.support.v17.leanback.widget.SearchOrbView.Colors);
-    method public void setTitle(java.lang.CharSequence);
-    method public void setTitleView(android.view.View);
-    method public void showTitle(boolean);
-    method public void showTitle(int);
-  }
-
-  public class BrandedSupportFragment extends android.support.v4.app.Fragment {
-    ctor public BrandedSupportFragment();
-    method public android.graphics.drawable.Drawable getBadgeDrawable();
-    method public int getSearchAffordanceColor();
-    method public android.support.v17.leanback.widget.SearchOrbView.Colors getSearchAffordanceColors();
-    method public java.lang.CharSequence getTitle();
-    method public android.view.View getTitleView();
-    method public android.support.v17.leanback.widget.TitleViewAdapter getTitleViewAdapter();
-    method public void installTitleView(android.view.LayoutInflater, android.view.ViewGroup, android.os.Bundle);
-    method public final boolean isShowingTitle();
-    method public android.view.View onInflateTitleView(android.view.LayoutInflater, android.view.ViewGroup, android.os.Bundle);
-    method public void setBadgeDrawable(android.graphics.drawable.Drawable);
-    method public void setOnSearchClickedListener(android.view.View.OnClickListener);
-    method public void setSearchAffordanceColor(int);
-    method public void setSearchAffordanceColors(android.support.v17.leanback.widget.SearchOrbView.Colors);
-    method public void setTitle(java.lang.CharSequence);
-    method public void setTitleView(android.view.View);
-    method public void showTitle(boolean);
-    method public void showTitle(int);
-  }
-
-  public class BrowseFragment extends android.support.v17.leanback.app.BaseFragment {
-    ctor public BrowseFragment();
-    method public static android.os.Bundle createArgs(android.os.Bundle, java.lang.String, int);
-    method public void enableMainFragmentScaling(boolean);
-    method public deprecated void enableRowScaling(boolean);
-    method public android.support.v17.leanback.widget.ObjectAdapter getAdapter();
-    method public int getBrandColor();
-    method public android.support.v17.leanback.app.HeadersFragment getHeadersFragment();
-    method public int getHeadersState();
-    method public android.app.Fragment getMainFragment();
-    method public final android.support.v17.leanback.app.BrowseFragment.MainFragmentAdapterRegistry getMainFragmentRegistry();
-    method public android.support.v17.leanback.widget.OnItemViewClickedListener getOnItemViewClickedListener();
-    method public android.support.v17.leanback.widget.OnItemViewSelectedListener getOnItemViewSelectedListener();
-    method public android.support.v17.leanback.app.RowsFragment getRowsFragment();
-    method public int getSelectedPosition();
-    method public android.support.v17.leanback.widget.RowPresenter.ViewHolder getSelectedRowViewHolder();
-    method public final boolean isHeadersTransitionOnBackEnabled();
-    method public boolean isInHeadersTransition();
-    method public boolean isShowingHeaders();
-    method public android.support.v17.leanback.app.HeadersFragment onCreateHeadersFragment();
-    method public void setAdapter(android.support.v17.leanback.widget.ObjectAdapter);
-    method public void setBrandColor(int);
-    method public void setBrowseTransitionListener(android.support.v17.leanback.app.BrowseFragment.BrowseTransitionListener);
-    method public void setHeaderPresenterSelector(android.support.v17.leanback.widget.PresenterSelector);
-    method public void setHeadersState(int);
-    method public final void setHeadersTransitionOnBackEnabled(boolean);
-    method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.OnItemViewClickedListener);
-    method public void setOnItemViewSelectedListener(android.support.v17.leanback.widget.OnItemViewSelectedListener);
-    method public void setSelectedPosition(int);
-    method public void setSelectedPosition(int, boolean);
-    method public void setSelectedPosition(int, boolean, android.support.v17.leanback.widget.Presenter.ViewHolderTask);
-    method public void startHeadersTransition(boolean);
-    field public static final int HEADERS_DISABLED = 3; // 0x3
-    field public static final int HEADERS_ENABLED = 1; // 0x1
-    field public static final int HEADERS_HIDDEN = 2; // 0x2
-  }
-
-  public static class BrowseFragment.BrowseTransitionListener {
-    ctor public BrowseFragment.BrowseTransitionListener();
-    method public void onHeadersTransitionStart(boolean);
-    method public void onHeadersTransitionStop(boolean);
-  }
-
-  public static abstract class BrowseFragment.FragmentFactory<T extends android.app.Fragment> {
-    ctor public BrowseFragment.FragmentFactory();
-    method public abstract T createFragment(java.lang.Object);
-  }
-
-  public static abstract interface BrowseFragment.FragmentHost {
-    method public abstract void notifyDataReady(android.support.v17.leanback.app.BrowseFragment.MainFragmentAdapter);
-    method public abstract void notifyViewCreated(android.support.v17.leanback.app.BrowseFragment.MainFragmentAdapter);
-    method public abstract void showTitleView(boolean);
-  }
-
-  public static class BrowseFragment.ListRowFragmentFactory extends android.support.v17.leanback.app.BrowseFragment.FragmentFactory {
-    ctor public BrowseFragment.ListRowFragmentFactory();
-    method public android.support.v17.leanback.app.RowsFragment createFragment(java.lang.Object);
-  }
-
-  public static class BrowseFragment.MainFragmentAdapter<T extends android.app.Fragment> {
-    ctor public BrowseFragment.MainFragmentAdapter(T);
-    method public final T getFragment();
-    method public final android.support.v17.leanback.app.BrowseFragment.FragmentHost getFragmentHost();
-    method public boolean isScalingEnabled();
-    method public boolean isScrolling();
-    method public void onTransitionEnd();
-    method public boolean onTransitionPrepare();
-    method public void onTransitionStart();
-    method public void setAlignment(int);
-    method public void setEntranceTransitionState(boolean);
-    method public void setExpand(boolean);
-    method public void setScalingEnabled(boolean);
-  }
-
-  public static abstract interface BrowseFragment.MainFragmentAdapterProvider {
-    method public abstract android.support.v17.leanback.app.BrowseFragment.MainFragmentAdapter getMainFragmentAdapter();
-  }
-
-  public static final class BrowseFragment.MainFragmentAdapterRegistry {
-    ctor public BrowseFragment.MainFragmentAdapterRegistry();
-    method public android.app.Fragment createFragment(java.lang.Object);
-    method public void registerFragment(java.lang.Class, android.support.v17.leanback.app.BrowseFragment.FragmentFactory);
-  }
-
-  public static class BrowseFragment.MainFragmentRowsAdapter<T extends android.app.Fragment> {
-    ctor public BrowseFragment.MainFragmentRowsAdapter(T);
-    method public android.support.v17.leanback.widget.RowPresenter.ViewHolder findRowViewHolderByPosition(int);
-    method public final T getFragment();
-    method public int getSelectedPosition();
-    method public void setAdapter(android.support.v17.leanback.widget.ObjectAdapter);
-    method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.OnItemViewClickedListener);
-    method public void setOnItemViewSelectedListener(android.support.v17.leanback.widget.OnItemViewSelectedListener);
-    method public void setSelectedPosition(int, boolean, android.support.v17.leanback.widget.Presenter.ViewHolderTask);
-    method public void setSelectedPosition(int, boolean);
-  }
-
-  public static abstract interface BrowseFragment.MainFragmentRowsAdapterProvider {
-    method public abstract android.support.v17.leanback.app.BrowseFragment.MainFragmentRowsAdapter getMainFragmentRowsAdapter();
-  }
-
-  public class BrowseSupportFragment extends android.support.v17.leanback.app.BaseSupportFragment {
-    ctor public BrowseSupportFragment();
-    method public static android.os.Bundle createArgs(android.os.Bundle, java.lang.String, int);
-    method public void enableMainFragmentScaling(boolean);
-    method public deprecated void enableRowScaling(boolean);
-    method public android.support.v17.leanback.widget.ObjectAdapter getAdapter();
-    method public int getBrandColor();
-    method public int getHeadersState();
-    method public android.support.v17.leanback.app.HeadersSupportFragment getHeadersSupportFragment();
-    method public android.support.v4.app.Fragment getMainFragment();
-    method public final android.support.v17.leanback.app.BrowseSupportFragment.MainFragmentAdapterRegistry getMainFragmentRegistry();
-    method public android.support.v17.leanback.widget.OnItemViewClickedListener getOnItemViewClickedListener();
-    method public android.support.v17.leanback.widget.OnItemViewSelectedListener getOnItemViewSelectedListener();
-    method public android.support.v17.leanback.app.RowsSupportFragment getRowsSupportFragment();
-    method public int getSelectedPosition();
-    method public android.support.v17.leanback.widget.RowPresenter.ViewHolder getSelectedRowViewHolder();
-    method public final boolean isHeadersTransitionOnBackEnabled();
-    method public boolean isInHeadersTransition();
-    method public boolean isShowingHeaders();
-    method public android.support.v17.leanback.app.HeadersSupportFragment onCreateHeadersSupportFragment();
-    method public void setAdapter(android.support.v17.leanback.widget.ObjectAdapter);
-    method public void setBrandColor(int);
-    method public void setBrowseTransitionListener(android.support.v17.leanback.app.BrowseSupportFragment.BrowseTransitionListener);
-    method public void setHeaderPresenterSelector(android.support.v17.leanback.widget.PresenterSelector);
-    method public void setHeadersState(int);
-    method public final void setHeadersTransitionOnBackEnabled(boolean);
-    method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.OnItemViewClickedListener);
-    method public void setOnItemViewSelectedListener(android.support.v17.leanback.widget.OnItemViewSelectedListener);
-    method public void setSelectedPosition(int);
-    method public void setSelectedPosition(int, boolean);
-    method public void setSelectedPosition(int, boolean, android.support.v17.leanback.widget.Presenter.ViewHolderTask);
-    method public void startHeadersTransition(boolean);
-    field public static final int HEADERS_DISABLED = 3; // 0x3
-    field public static final int HEADERS_ENABLED = 1; // 0x1
-    field public static final int HEADERS_HIDDEN = 2; // 0x2
-  }
-
-  public static class BrowseSupportFragment.BrowseTransitionListener {
-    ctor public BrowseSupportFragment.BrowseTransitionListener();
-    method public void onHeadersTransitionStart(boolean);
-    method public void onHeadersTransitionStop(boolean);
-  }
-
-  public static abstract class BrowseSupportFragment.FragmentFactory<T extends android.support.v4.app.Fragment> {
-    ctor public BrowseSupportFragment.FragmentFactory();
-    method public abstract T createFragment(java.lang.Object);
-  }
-
-  public static abstract interface BrowseSupportFragment.FragmentHost {
-    method public abstract void notifyDataReady(android.support.v17.leanback.app.BrowseSupportFragment.MainFragmentAdapter);
-    method public abstract void notifyViewCreated(android.support.v17.leanback.app.BrowseSupportFragment.MainFragmentAdapter);
-    method public abstract void showTitleView(boolean);
-  }
-
-  public static class BrowseSupportFragment.ListRowFragmentFactory extends android.support.v17.leanback.app.BrowseSupportFragment.FragmentFactory {
-    ctor public BrowseSupportFragment.ListRowFragmentFactory();
-    method public android.support.v17.leanback.app.RowsSupportFragment createFragment(java.lang.Object);
-  }
-
-  public static class BrowseSupportFragment.MainFragmentAdapter<T extends android.support.v4.app.Fragment> {
-    ctor public BrowseSupportFragment.MainFragmentAdapter(T);
-    method public final T getFragment();
-    method public final android.support.v17.leanback.app.BrowseSupportFragment.FragmentHost getFragmentHost();
-    method public boolean isScalingEnabled();
-    method public boolean isScrolling();
-    method public void onTransitionEnd();
-    method public boolean onTransitionPrepare();
-    method public void onTransitionStart();
-    method public void setAlignment(int);
-    method public void setEntranceTransitionState(boolean);
-    method public void setExpand(boolean);
-    method public void setScalingEnabled(boolean);
-  }
-
-  public static abstract interface BrowseSupportFragment.MainFragmentAdapterProvider {
-    method public abstract android.support.v17.leanback.app.BrowseSupportFragment.MainFragmentAdapter getMainFragmentAdapter();
-  }
-
-  public static final class BrowseSupportFragment.MainFragmentAdapterRegistry {
-    ctor public BrowseSupportFragment.MainFragmentAdapterRegistry();
-    method public android.support.v4.app.Fragment createFragment(java.lang.Object);
-    method public void registerFragment(java.lang.Class, android.support.v17.leanback.app.BrowseSupportFragment.FragmentFactory);
-  }
-
-  public static class BrowseSupportFragment.MainFragmentRowsAdapter<T extends android.support.v4.app.Fragment> {
-    ctor public BrowseSupportFragment.MainFragmentRowsAdapter(T);
-    method public android.support.v17.leanback.widget.RowPresenter.ViewHolder findRowViewHolderByPosition(int);
-    method public final T getFragment();
-    method public int getSelectedPosition();
-    method public void setAdapter(android.support.v17.leanback.widget.ObjectAdapter);
-    method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.OnItemViewClickedListener);
-    method public void setOnItemViewSelectedListener(android.support.v17.leanback.widget.OnItemViewSelectedListener);
-    method public void setSelectedPosition(int, boolean, android.support.v17.leanback.widget.Presenter.ViewHolderTask);
-    method public void setSelectedPosition(int, boolean);
-  }
-
-  public static abstract interface BrowseSupportFragment.MainFragmentRowsAdapterProvider {
-    method public abstract android.support.v17.leanback.app.BrowseSupportFragment.MainFragmentRowsAdapter getMainFragmentRowsAdapter();
-  }
-
-  public class DetailsFragment extends android.support.v17.leanback.app.BaseFragment {
-    ctor public DetailsFragment();
-    method public android.support.v17.leanback.widget.ObjectAdapter getAdapter();
-    method public android.support.v17.leanback.widget.BaseOnItemViewClickedListener getOnItemViewClickedListener();
-    method public android.support.v17.leanback.widget.DetailsParallax getParallax();
-    method public android.support.v17.leanback.app.RowsFragment getRowsFragment();
-    method protected deprecated android.view.View inflateTitle(android.view.LayoutInflater, android.view.ViewGroup, android.os.Bundle);
-    method protected void onSetDetailsOverviewRowStatus(android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter, android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter.ViewHolder, int, int, int);
-    method protected void onSetRowStatus(android.support.v17.leanback.widget.RowPresenter, android.support.v17.leanback.widget.RowPresenter.ViewHolder, int, int, int);
-    method public void setAdapter(android.support.v17.leanback.widget.ObjectAdapter);
-    method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.BaseOnItemViewClickedListener);
-    method public void setOnItemViewSelectedListener(android.support.v17.leanback.widget.BaseOnItemViewSelectedListener);
-    method public void setSelectedPosition(int);
-    method public void setSelectedPosition(int, boolean);
-    method protected void setupDetailsOverviewRowPresenter(android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter);
-    method protected void setupPresenter(android.support.v17.leanback.widget.Presenter);
-  }
-
-  public class DetailsFragmentBackgroundController {
-    ctor public DetailsFragmentBackgroundController(android.support.v17.leanback.app.DetailsFragment);
-    method public boolean canNavigateToVideoFragment();
-    method public void enableParallax();
-    method public void enableParallax(android.graphics.drawable.Drawable, android.graphics.drawable.Drawable, android.support.v17.leanback.widget.ParallaxTarget.PropertyValuesHolderTarget);
-    method public final android.app.Fragment findOrCreateVideoFragment();
-    method public final android.graphics.drawable.Drawable getBottomDrawable();
-    method public final android.graphics.Bitmap getCoverBitmap();
-    method public final android.graphics.drawable.Drawable getCoverDrawable();
-    method public final int getParallaxDrawableMaxOffset();
-    method public final android.support.v17.leanback.media.PlaybackGlue getPlaybackGlue();
-    method public final int getSolidColor();
-    method public android.support.v17.leanback.media.PlaybackGlueHost onCreateGlueHost();
-    method public android.app.Fragment onCreateVideoFragment();
-    method public final void setCoverBitmap(android.graphics.Bitmap);
-    method public final void setParallaxDrawableMaxOffset(int);
-    method public final void setSolidColor(int);
-    method public void setupVideoPlayback(android.support.v17.leanback.media.PlaybackGlue);
-    method public final void switchToRows();
-    method public final void switchToVideo();
-  }
-
-  public class DetailsSupportFragment extends android.support.v17.leanback.app.BaseSupportFragment {
-    ctor public DetailsSupportFragment();
-    method public android.support.v17.leanback.widget.ObjectAdapter getAdapter();
-    method public android.support.v17.leanback.widget.BaseOnItemViewClickedListener getOnItemViewClickedListener();
-    method public android.support.v17.leanback.widget.DetailsParallax getParallax();
-    method public android.support.v17.leanback.app.RowsSupportFragment getRowsSupportFragment();
-    method protected deprecated android.view.View inflateTitle(android.view.LayoutInflater, android.view.ViewGroup, android.os.Bundle);
-    method protected void onSetDetailsOverviewRowStatus(android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter, android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter.ViewHolder, int, int, int);
-    method protected void onSetRowStatus(android.support.v17.leanback.widget.RowPresenter, android.support.v17.leanback.widget.RowPresenter.ViewHolder, int, int, int);
-    method public void setAdapter(android.support.v17.leanback.widget.ObjectAdapter);
-    method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.BaseOnItemViewClickedListener);
-    method public void setOnItemViewSelectedListener(android.support.v17.leanback.widget.BaseOnItemViewSelectedListener);
-    method public void setSelectedPosition(int);
-    method public void setSelectedPosition(int, boolean);
-    method protected void setupDetailsOverviewRowPresenter(android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter);
-    method protected void setupPresenter(android.support.v17.leanback.widget.Presenter);
-  }
-
-  public class DetailsSupportFragmentBackgroundController {
-    ctor public DetailsSupportFragmentBackgroundController(android.support.v17.leanback.app.DetailsSupportFragment);
-    method public boolean canNavigateToVideoSupportFragment();
-    method public void enableParallax();
-    method public void enableParallax(android.graphics.drawable.Drawable, android.graphics.drawable.Drawable, android.support.v17.leanback.widget.ParallaxTarget.PropertyValuesHolderTarget);
-    method public final android.support.v4.app.Fragment findOrCreateVideoSupportFragment();
-    method public final android.graphics.drawable.Drawable getBottomDrawable();
-    method public final android.graphics.Bitmap getCoverBitmap();
-    method public final android.graphics.drawable.Drawable getCoverDrawable();
-    method public final int getParallaxDrawableMaxOffset();
-    method public final android.support.v17.leanback.media.PlaybackGlue getPlaybackGlue();
-    method public final int getSolidColor();
-    method public android.support.v17.leanback.media.PlaybackGlueHost onCreateGlueHost();
-    method public android.support.v4.app.Fragment onCreateVideoSupportFragment();
-    method public final void setCoverBitmap(android.graphics.Bitmap);
-    method public final void setParallaxDrawableMaxOffset(int);
-    method public final void setSolidColor(int);
-    method public void setupVideoPlayback(android.support.v17.leanback.media.PlaybackGlue);
-    method public final void switchToRows();
-    method public final void switchToVideo();
-  }
-
-  public class ErrorFragment extends android.support.v17.leanback.app.BrandedFragment {
-    ctor public ErrorFragment();
-    method public android.graphics.drawable.Drawable getBackgroundDrawable();
-    method public android.view.View.OnClickListener getButtonClickListener();
-    method public java.lang.String getButtonText();
-    method public android.graphics.drawable.Drawable getImageDrawable();
-    method public java.lang.CharSequence getMessage();
-    method public boolean isBackgroundTranslucent();
-    method public void setBackgroundDrawable(android.graphics.drawable.Drawable);
-    method public void setButtonClickListener(android.view.View.OnClickListener);
-    method public void setButtonText(java.lang.String);
-    method public void setDefaultBackground(boolean);
-    method public void setImageDrawable(android.graphics.drawable.Drawable);
-    method public void setMessage(java.lang.CharSequence);
-  }
-
-  public class ErrorSupportFragment extends android.support.v17.leanback.app.BrandedSupportFragment {
-    ctor public ErrorSupportFragment();
-    method public android.graphics.drawable.Drawable getBackgroundDrawable();
-    method public android.view.View.OnClickListener getButtonClickListener();
-    method public java.lang.String getButtonText();
-    method public android.graphics.drawable.Drawable getImageDrawable();
-    method public java.lang.CharSequence getMessage();
-    method public boolean isBackgroundTranslucent();
-    method public void setBackgroundDrawable(android.graphics.drawable.Drawable);
-    method public void setButtonClickListener(android.view.View.OnClickListener);
-    method public void setButtonText(java.lang.String);
-    method public void setDefaultBackground(boolean);
-    method public void setImageDrawable(android.graphics.drawable.Drawable);
-    method public void setMessage(java.lang.CharSequence);
-  }
-
-  public class GuidedStepFragment extends android.app.Fragment {
-    ctor public GuidedStepFragment();
-    method public static int add(android.app.FragmentManager, android.support.v17.leanback.app.GuidedStepFragment);
-    method public static int add(android.app.FragmentManager, android.support.v17.leanback.app.GuidedStepFragment, int);
-    method public static int addAsRoot(android.app.Activity, android.support.v17.leanback.app.GuidedStepFragment, int);
-    method public void collapseAction(boolean);
-    method public void collapseSubActions();
-    method public void expandAction(android.support.v17.leanback.widget.GuidedAction, boolean);
-    method public void expandSubActions(android.support.v17.leanback.widget.GuidedAction);
-    method public android.support.v17.leanback.widget.GuidedAction findActionById(long);
-    method public int findActionPositionById(long);
-    method public android.support.v17.leanback.widget.GuidedAction findButtonActionById(long);
-    method public int findButtonActionPositionById(long);
-    method public void finishGuidedStepFragments();
-    method public android.view.View getActionItemView(int);
-    method public java.util.List<android.support.v17.leanback.widget.GuidedAction> getActions();
-    method public android.view.View getButtonActionItemView(int);
-    method public java.util.List<android.support.v17.leanback.widget.GuidedAction> getButtonActions();
-    method public static android.support.v17.leanback.app.GuidedStepFragment getCurrentGuidedStepFragment(android.app.FragmentManager);
-    method public android.support.v17.leanback.widget.GuidanceStylist getGuidanceStylist();
-    method public android.support.v17.leanback.widget.GuidedActionsStylist getGuidedActionsStylist();
-    method public android.support.v17.leanback.widget.GuidedActionsStylist getGuidedButtonActionsStylist();
-    method public int getSelectedActionPosition();
-    method public int getSelectedButtonActionPosition();
-    method public int getUiStyle();
-    method public boolean isExpanded();
-    method public boolean isFocusOutEndAllowed();
-    method public boolean isFocusOutStartAllowed();
-    method public boolean isSubActionsExpanded();
-    method public void notifyActionChanged(int);
-    method public void notifyButtonActionChanged(int);
-    method protected void onAddSharedElementTransition(android.app.FragmentTransaction, android.support.v17.leanback.app.GuidedStepFragment);
-    method public void onCreateActions(java.util.List<android.support.v17.leanback.widget.GuidedAction>, android.os.Bundle);
-    method public android.support.v17.leanback.widget.GuidedActionsStylist onCreateActionsStylist();
-    method public android.view.View onCreateBackgroundView(android.view.LayoutInflater, android.view.ViewGroup, android.os.Bundle);
-    method public void onCreateButtonActions(java.util.List<android.support.v17.leanback.widget.GuidedAction>, android.os.Bundle);
-    method public android.support.v17.leanback.widget.GuidedActionsStylist onCreateButtonActionsStylist();
-    method public android.support.v17.leanback.widget.GuidanceStylist.Guidance onCreateGuidance(android.os.Bundle);
-    method public android.support.v17.leanback.widget.GuidanceStylist onCreateGuidanceStylist();
-    method public void onGuidedActionClicked(android.support.v17.leanback.widget.GuidedAction);
-    method public void onGuidedActionEditCanceled(android.support.v17.leanback.widget.GuidedAction);
-    method public deprecated void onGuidedActionEdited(android.support.v17.leanback.widget.GuidedAction);
-    method public long onGuidedActionEditedAndProceed(android.support.v17.leanback.widget.GuidedAction);
-    method public void onGuidedActionFocused(android.support.v17.leanback.widget.GuidedAction);
-    method protected void onProvideFragmentTransitions();
-    method public int onProvideTheme();
-    method public boolean onSubGuidedActionClicked(android.support.v17.leanback.widget.GuidedAction);
-    method public void openInEditMode(android.support.v17.leanback.widget.GuidedAction);
-    method public void popBackStackToGuidedStepFragment(java.lang.Class, int);
-    method public void setActions(java.util.List<android.support.v17.leanback.widget.GuidedAction>);
-    method public void setButtonActions(java.util.List<android.support.v17.leanback.widget.GuidedAction>);
-    method public void setSelectedActionPosition(int);
-    method public void setSelectedButtonActionPosition(int);
-    method public void setUiStyle(int);
-    field public static final java.lang.String EXTRA_UI_STYLE = "uiStyle";
-    field public static final int UI_STYLE_ACTIVITY_ROOT = 2; // 0x2
-    field public static final deprecated int UI_STYLE_DEFAULT = 0; // 0x0
-    field public static final int UI_STYLE_ENTRANCE = 1; // 0x1
-    field public static final int UI_STYLE_REPLACE = 0; // 0x0
-  }
-
-  public class GuidedStepSupportFragment extends android.support.v4.app.Fragment {
-    ctor public GuidedStepSupportFragment();
-    method public static int add(android.support.v4.app.FragmentManager, android.support.v17.leanback.app.GuidedStepSupportFragment);
-    method public static int add(android.support.v4.app.FragmentManager, android.support.v17.leanback.app.GuidedStepSupportFragment, int);
-    method public static int addAsRoot(android.support.v4.app.FragmentActivity, android.support.v17.leanback.app.GuidedStepSupportFragment, int);
-    method public void collapseAction(boolean);
-    method public void collapseSubActions();
-    method public void expandAction(android.support.v17.leanback.widget.GuidedAction, boolean);
-    method public void expandSubActions(android.support.v17.leanback.widget.GuidedAction);
-    method public android.support.v17.leanback.widget.GuidedAction findActionById(long);
-    method public int findActionPositionById(long);
-    method public android.support.v17.leanback.widget.GuidedAction findButtonActionById(long);
-    method public int findButtonActionPositionById(long);
-    method public void finishGuidedStepSupportFragments();
-    method public android.view.View getActionItemView(int);
-    method public java.util.List<android.support.v17.leanback.widget.GuidedAction> getActions();
-    method public android.view.View getButtonActionItemView(int);
-    method public java.util.List<android.support.v17.leanback.widget.GuidedAction> getButtonActions();
-    method public static android.support.v17.leanback.app.GuidedStepSupportFragment getCurrentGuidedStepSupportFragment(android.support.v4.app.FragmentManager);
-    method public android.support.v17.leanback.widget.GuidanceStylist getGuidanceStylist();
-    method public android.support.v17.leanback.widget.GuidedActionsStylist getGuidedActionsStylist();
-    method public android.support.v17.leanback.widget.GuidedActionsStylist getGuidedButtonActionsStylist();
-    method public int getSelectedActionPosition();
-    method public int getSelectedButtonActionPosition();
-    method public int getUiStyle();
-    method public boolean isExpanded();
-    method public boolean isFocusOutEndAllowed();
-    method public boolean isFocusOutStartAllowed();
-    method public boolean isSubActionsExpanded();
-    method public void notifyActionChanged(int);
-    method public void notifyButtonActionChanged(int);
-    method protected void onAddSharedElementTransition(android.support.v4.app.FragmentTransaction, android.support.v17.leanback.app.GuidedStepSupportFragment);
-    method public void onCreateActions(java.util.List<android.support.v17.leanback.widget.GuidedAction>, android.os.Bundle);
-    method public android.support.v17.leanback.widget.GuidedActionsStylist onCreateActionsStylist();
-    method public android.view.View onCreateBackgroundView(android.view.LayoutInflater, android.view.ViewGroup, android.os.Bundle);
-    method public void onCreateButtonActions(java.util.List<android.support.v17.leanback.widget.GuidedAction>, android.os.Bundle);
-    method public android.support.v17.leanback.widget.GuidedActionsStylist onCreateButtonActionsStylist();
-    method public android.support.v17.leanback.widget.GuidanceStylist.Guidance onCreateGuidance(android.os.Bundle);
-    method public android.support.v17.leanback.widget.GuidanceStylist onCreateGuidanceStylist();
-    method public void onGuidedActionClicked(android.support.v17.leanback.widget.GuidedAction);
-    method public void onGuidedActionEditCanceled(android.support.v17.leanback.widget.GuidedAction);
-    method public deprecated void onGuidedActionEdited(android.support.v17.leanback.widget.GuidedAction);
-    method public long onGuidedActionEditedAndProceed(android.support.v17.leanback.widget.GuidedAction);
-    method public void onGuidedActionFocused(android.support.v17.leanback.widget.GuidedAction);
-    method protected void onProvideFragmentTransitions();
-    method public int onProvideTheme();
-    method public boolean onSubGuidedActionClicked(android.support.v17.leanback.widget.GuidedAction);
-    method public void openInEditMode(android.support.v17.leanback.widget.GuidedAction);
-    method public void popBackStackToGuidedStepSupportFragment(java.lang.Class, int);
-    method public void setActions(java.util.List<android.support.v17.leanback.widget.GuidedAction>);
-    method public void setButtonActions(java.util.List<android.support.v17.leanback.widget.GuidedAction>);
-    method public void setSelectedActionPosition(int);
-    method public void setSelectedButtonActionPosition(int);
-    method public void setUiStyle(int);
-    field public static final java.lang.String EXTRA_UI_STYLE = "uiStyle";
-    field public static final int UI_STYLE_ACTIVITY_ROOT = 2; // 0x2
-    field public static final deprecated int UI_STYLE_DEFAULT = 0; // 0x0
-    field public static final int UI_STYLE_ENTRANCE = 1; // 0x1
-    field public static final int UI_STYLE_REPLACE = 0; // 0x0
-  }
-
-  public class HeadersFragment extends android.support.v17.leanback.app.BaseRowFragment {
-    ctor public HeadersFragment();
-    method public boolean isScrolling();
-    method public void setOnHeaderClickedListener(android.support.v17.leanback.app.HeadersFragment.OnHeaderClickedListener);
-    method public void setOnHeaderViewSelectedListener(android.support.v17.leanback.app.HeadersFragment.OnHeaderViewSelectedListener);
-  }
-
-  public static abstract interface HeadersFragment.OnHeaderClickedListener {
-    method public abstract void onHeaderClicked(android.support.v17.leanback.widget.RowHeaderPresenter.ViewHolder, android.support.v17.leanback.widget.Row);
-  }
-
-  public static abstract interface HeadersFragment.OnHeaderViewSelectedListener {
-    method public abstract void onHeaderSelected(android.support.v17.leanback.widget.RowHeaderPresenter.ViewHolder, android.support.v17.leanback.widget.Row);
-  }
-
-  public class HeadersSupportFragment extends android.support.v17.leanback.app.BaseRowSupportFragment {
-    ctor public HeadersSupportFragment();
-    method public boolean isScrolling();
-    method public void setOnHeaderClickedListener(android.support.v17.leanback.app.HeadersSupportFragment.OnHeaderClickedListener);
-    method public void setOnHeaderViewSelectedListener(android.support.v17.leanback.app.HeadersSupportFragment.OnHeaderViewSelectedListener);
-  }
-
-  public static abstract interface HeadersSupportFragment.OnHeaderClickedListener {
-    method public abstract void onHeaderClicked(android.support.v17.leanback.widget.RowHeaderPresenter.ViewHolder, android.support.v17.leanback.widget.Row);
-  }
-
-  public static abstract interface HeadersSupportFragment.OnHeaderViewSelectedListener {
-    method public abstract void onHeaderSelected(android.support.v17.leanback.widget.RowHeaderPresenter.ViewHolder, android.support.v17.leanback.widget.Row);
-  }
-
-  public abstract class OnboardingFragment extends android.app.Fragment {
-    ctor public OnboardingFragment();
-    method public final int getArrowBackgroundColor();
-    method public final int getArrowColor();
-    method protected final int getCurrentPageIndex();
-    method public final int getDescriptionViewTextColor();
-    method public final int getDotBackgroundColor();
-    method public final int getIconResourceId();
-    method public final int getLogoResourceId();
-    method protected abstract int getPageCount();
-    method protected abstract java.lang.CharSequence getPageDescription(int);
-    method protected abstract java.lang.CharSequence getPageTitle(int);
-    method public final java.lang.CharSequence getStartButtonText();
-    method public final int getTitleViewTextColor();
-    method protected final boolean isLogoAnimationFinished();
-    method protected void moveToNextPage();
-    method protected void moveToPreviousPage();
-    method protected abstract android.view.View onCreateBackgroundView(android.view.LayoutInflater, android.view.ViewGroup);
-    method protected abstract android.view.View onCreateContentView(android.view.LayoutInflater, android.view.ViewGroup);
-    method protected android.animation.Animator onCreateDescriptionAnimator();
-    method protected android.animation.Animator onCreateEnterAnimation();
-    method protected abstract android.view.View onCreateForegroundView(android.view.LayoutInflater, android.view.ViewGroup);
-    method protected android.animation.Animator onCreateLogoAnimation();
-    method protected android.animation.Animator onCreateTitleAnimator();
-    method protected void onFinishFragment();
-    method protected void onLogoAnimationFinished();
-    method protected void onPageChanged(int, int);
-    method public int onProvideTheme();
-    method public void setArrowBackgroundColor(int);
-    method public void setArrowColor(int);
-    method public void setDescriptionViewTextColor(int);
-    method public void setDotBackgroundColor(int);
-    method public final void setIconResouceId(int);
-    method public final void setLogoResourceId(int);
-    method public void setStartButtonText(java.lang.CharSequence);
-    method public void setTitleViewTextColor(int);
-    method protected final void startEnterAnimation(boolean);
-  }
-
-  public abstract class OnboardingSupportFragment extends android.support.v4.app.Fragment {
-    ctor public OnboardingSupportFragment();
-    method public final int getArrowBackgroundColor();
-    method public final int getArrowColor();
-    method protected final int getCurrentPageIndex();
-    method public final int getDescriptionViewTextColor();
-    method public final int getDotBackgroundColor();
-    method public final int getIconResourceId();
-    method public final int getLogoResourceId();
-    method protected abstract int getPageCount();
-    method protected abstract java.lang.CharSequence getPageDescription(int);
-    method protected abstract java.lang.CharSequence getPageTitle(int);
-    method public final java.lang.CharSequence getStartButtonText();
-    method public final int getTitleViewTextColor();
-    method protected final boolean isLogoAnimationFinished();
-    method protected void moveToNextPage();
-    method protected void moveToPreviousPage();
-    method protected abstract android.view.View onCreateBackgroundView(android.view.LayoutInflater, android.view.ViewGroup);
-    method protected abstract android.view.View onCreateContentView(android.view.LayoutInflater, android.view.ViewGroup);
-    method protected android.animation.Animator onCreateDescriptionAnimator();
-    method protected android.animation.Animator onCreateEnterAnimation();
-    method protected abstract android.view.View onCreateForegroundView(android.view.LayoutInflater, android.view.ViewGroup);
-    method protected android.animation.Animator onCreateLogoAnimation();
-    method protected android.animation.Animator onCreateTitleAnimator();
-    method protected void onFinishFragment();
-    method protected void onLogoAnimationFinished();
-    method protected void onPageChanged(int, int);
-    method public int onProvideTheme();
-    method public void setArrowBackgroundColor(int);
-    method public void setArrowColor(int);
-    method public void setDescriptionViewTextColor(int);
-    method public void setDotBackgroundColor(int);
-    method public final void setIconResouceId(int);
-    method public final void setLogoResourceId(int);
-    method public void setStartButtonText(java.lang.CharSequence);
-    method public void setTitleViewTextColor(int);
-    method protected final void startEnterAnimation(boolean);
-  }
-
-  public class PlaybackFragment extends android.app.Fragment {
-    ctor public PlaybackFragment();
-    method public deprecated void fadeOut();
-    method public android.support.v17.leanback.widget.ObjectAdapter getAdapter();
-    method public int getBackgroundType();
-    method public android.support.v17.leanback.app.ProgressBarManager getProgressBarManager();
-    method public void hideControlsOverlay(boolean);
-    method public boolean isControlsOverlayAutoHideEnabled();
-    method public boolean isControlsOverlayVisible();
-    method public deprecated boolean isFadingEnabled();
-    method public void notifyPlaybackRowChanged();
-    method protected void onBufferingStateChanged(boolean);
-    method protected void onError(int, java.lang.CharSequence);
-    method protected void onVideoSizeChanged(int, int);
-    method public void setAdapter(android.support.v17.leanback.widget.ObjectAdapter);
-    method public void setBackgroundType(int);
-    method public void setControlsOverlayAutoHideEnabled(boolean);
-    method public deprecated void setFadingEnabled(boolean);
-    method public void setHostCallback(android.support.v17.leanback.media.PlaybackGlueHost.HostCallback);
-    method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.BaseOnItemViewClickedListener);
-    method public void setOnItemViewSelectedListener(android.support.v17.leanback.widget.BaseOnItemViewSelectedListener);
-    method public final void setOnKeyInterceptListener(android.view.View.OnKeyListener);
-    method public void setOnPlaybackItemViewClickedListener(android.support.v17.leanback.widget.BaseOnItemViewClickedListener);
-    method public void setPlaybackRow(android.support.v17.leanback.widget.Row);
-    method public void setPlaybackRowPresenter(android.support.v17.leanback.widget.PlaybackRowPresenter);
-    method public void setPlaybackSeekUiClient(android.support.v17.leanback.widget.PlaybackSeekUi.Client);
-    method public void setSelectedPosition(int);
-    method public void setSelectedPosition(int, boolean);
-    method public void showControlsOverlay(boolean);
-    method public void tickle();
-    field public static final int BG_DARK = 1; // 0x1
-    field public static final int BG_LIGHT = 2; // 0x2
-    field public static final int BG_NONE = 0; // 0x0
-  }
-
-  public class PlaybackFragmentGlueHost extends android.support.v17.leanback.media.PlaybackGlueHost implements android.support.v17.leanback.widget.PlaybackSeekUi {
-    ctor public PlaybackFragmentGlueHost(android.support.v17.leanback.app.PlaybackFragment);
-    method public void fadeOut();
-    method public void setPlaybackSeekUiClient(android.support.v17.leanback.widget.PlaybackSeekUi.Client);
-  }
-
-  public class PlaybackSupportFragment extends android.support.v4.app.Fragment {
-    ctor public PlaybackSupportFragment();
-    method public deprecated void fadeOut();
-    method public android.support.v17.leanback.widget.ObjectAdapter getAdapter();
-    method public int getBackgroundType();
-    method public android.support.v17.leanback.app.ProgressBarManager getProgressBarManager();
-    method public void hideControlsOverlay(boolean);
-    method public boolean isControlsOverlayAutoHideEnabled();
-    method public boolean isControlsOverlayVisible();
-    method public deprecated boolean isFadingEnabled();
-    method public void notifyPlaybackRowChanged();
-    method protected void onBufferingStateChanged(boolean);
-    method protected void onError(int, java.lang.CharSequence);
-    method protected void onVideoSizeChanged(int, int);
-    method public void setAdapter(android.support.v17.leanback.widget.ObjectAdapter);
-    method public void setBackgroundType(int);
-    method public void setControlsOverlayAutoHideEnabled(boolean);
-    method public deprecated void setFadingEnabled(boolean);
-    method public void setHostCallback(android.support.v17.leanback.media.PlaybackGlueHost.HostCallback);
-    method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.BaseOnItemViewClickedListener);
-    method public void setOnItemViewSelectedListener(android.support.v17.leanback.widget.BaseOnItemViewSelectedListener);
-    method public final void setOnKeyInterceptListener(android.view.View.OnKeyListener);
-    method public void setOnPlaybackItemViewClickedListener(android.support.v17.leanback.widget.BaseOnItemViewClickedListener);
-    method public void setPlaybackRow(android.support.v17.leanback.widget.Row);
-    method public void setPlaybackRowPresenter(android.support.v17.leanback.widget.PlaybackRowPresenter);
-    method public void setPlaybackSeekUiClient(android.support.v17.leanback.widget.PlaybackSeekUi.Client);
-    method public void setSelectedPosition(int);
-    method public void setSelectedPosition(int, boolean);
-    method public void showControlsOverlay(boolean);
-    method public void tickle();
-    field public static final int BG_DARK = 1; // 0x1
-    field public static final int BG_LIGHT = 2; // 0x2
-    field public static final int BG_NONE = 0; // 0x0
-  }
-
-  public class PlaybackSupportFragmentGlueHost extends android.support.v17.leanback.media.PlaybackGlueHost implements android.support.v17.leanback.widget.PlaybackSeekUi {
-    ctor public PlaybackSupportFragmentGlueHost(android.support.v17.leanback.app.PlaybackSupportFragment);
-    method public void fadeOut();
-    method public void setPlaybackSeekUiClient(android.support.v17.leanback.widget.PlaybackSeekUi.Client);
-  }
-
-  public final class ProgressBarManager {
-    ctor public ProgressBarManager();
-    method public void disableProgressBar();
-    method public void enableProgressBar();
-    method public long getInitialDelay();
-    method public void hide();
-    method public void setInitialDelay(long);
-    method public void setProgressBarView(android.view.View);
-    method public void setRootView(android.view.ViewGroup);
-    method public void show();
-  }
-
-  public class RowsFragment extends android.support.v17.leanback.app.BaseRowFragment implements android.support.v17.leanback.app.BrowseFragment.MainFragmentAdapterProvider android.support.v17.leanback.app.BrowseFragment.MainFragmentRowsAdapterProvider {
-    ctor public RowsFragment();
-    method public deprecated void enableRowScaling(boolean);
-    method protected android.support.v17.leanback.widget.VerticalGridView findGridViewFromRoot(android.view.View);
-    method public android.support.v17.leanback.widget.RowPresenter.ViewHolder findRowViewHolderByPosition(int);
-    method public android.support.v17.leanback.app.BrowseFragment.MainFragmentAdapter getMainFragmentAdapter();
-    method public android.support.v17.leanback.app.BrowseFragment.MainFragmentRowsAdapter getMainFragmentRowsAdapter();
-    method public android.support.v17.leanback.widget.BaseOnItemViewClickedListener getOnItemViewClickedListener();
-    method public android.support.v17.leanback.widget.BaseOnItemViewSelectedListener getOnItemViewSelectedListener();
-    method public android.support.v17.leanback.widget.RowPresenter.ViewHolder getRowViewHolder(int);
-    method public boolean isScrolling();
-    method public void setEntranceTransitionState(boolean);
-    method public void setExpand(boolean);
-    method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.BaseOnItemViewClickedListener);
-    method public void setOnItemViewSelectedListener(android.support.v17.leanback.widget.BaseOnItemViewSelectedListener);
-    method public void setSelectedPosition(int, boolean, android.support.v17.leanback.widget.Presenter.ViewHolderTask);
-  }
-
-  public static class RowsFragment.MainFragmentAdapter extends android.support.v17.leanback.app.BrowseFragment.MainFragmentAdapter {
-    ctor public RowsFragment.MainFragmentAdapter(android.support.v17.leanback.app.RowsFragment);
-  }
-
-  public static class RowsFragment.MainFragmentRowsAdapter extends android.support.v17.leanback.app.BrowseFragment.MainFragmentRowsAdapter {
-    ctor public RowsFragment.MainFragmentRowsAdapter(android.support.v17.leanback.app.RowsFragment);
-  }
-
-  public class RowsSupportFragment extends android.support.v17.leanback.app.BaseRowSupportFragment implements android.support.v17.leanback.app.BrowseSupportFragment.MainFragmentAdapterProvider android.support.v17.leanback.app.BrowseSupportFragment.MainFragmentRowsAdapterProvider {
-    ctor public RowsSupportFragment();
-    method public deprecated void enableRowScaling(boolean);
-    method protected android.support.v17.leanback.widget.VerticalGridView findGridViewFromRoot(android.view.View);
-    method public android.support.v17.leanback.widget.RowPresenter.ViewHolder findRowViewHolderByPosition(int);
-    method public android.support.v17.leanback.app.BrowseSupportFragment.MainFragmentAdapter getMainFragmentAdapter();
-    method public android.support.v17.leanback.app.BrowseSupportFragment.MainFragmentRowsAdapter getMainFragmentRowsAdapter();
-    method public android.support.v17.leanback.widget.BaseOnItemViewClickedListener getOnItemViewClickedListener();
-    method public android.support.v17.leanback.widget.BaseOnItemViewSelectedListener getOnItemViewSelectedListener();
-    method public android.support.v17.leanback.widget.RowPresenter.ViewHolder getRowViewHolder(int);
-    method public boolean isScrolling();
-    method public void setEntranceTransitionState(boolean);
-    method public void setExpand(boolean);
-    method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.BaseOnItemViewClickedListener);
-    method public void setOnItemViewSelectedListener(android.support.v17.leanback.widget.BaseOnItemViewSelectedListener);
-    method public void setSelectedPosition(int, boolean, android.support.v17.leanback.widget.Presenter.ViewHolderTask);
-  }
-
-  public static class RowsSupportFragment.MainFragmentAdapter extends android.support.v17.leanback.app.BrowseSupportFragment.MainFragmentAdapter {
-    ctor public RowsSupportFragment.MainFragmentAdapter(android.support.v17.leanback.app.RowsSupportFragment);
-  }
-
-  public static class RowsSupportFragment.MainFragmentRowsAdapter extends android.support.v17.leanback.app.BrowseSupportFragment.MainFragmentRowsAdapter {
-    ctor public RowsSupportFragment.MainFragmentRowsAdapter(android.support.v17.leanback.app.RowsSupportFragment);
-  }
-
-  public class SearchFragment extends android.app.Fragment {
-    ctor public SearchFragment();
-    method public static android.os.Bundle createArgs(android.os.Bundle, java.lang.String);
-    method public static android.os.Bundle createArgs(android.os.Bundle, java.lang.String, java.lang.String);
-    method public void displayCompletions(java.util.List<java.lang.String>);
-    method public void displayCompletions(android.view.inputmethod.CompletionInfo[]);
-    method public android.graphics.drawable.Drawable getBadgeDrawable();
-    method public android.content.Intent getRecognizerIntent();
-    method public android.support.v17.leanback.app.RowsFragment getRowsFragment();
-    method public java.lang.String getTitle();
-    method public static android.support.v17.leanback.app.SearchFragment newInstance(java.lang.String);
-    method public void setBadgeDrawable(android.graphics.drawable.Drawable);
-    method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.OnItemViewClickedListener);
-    method public void setOnItemViewSelectedListener(android.support.v17.leanback.widget.OnItemViewSelectedListener);
-    method public void setSearchAffordanceColors(android.support.v17.leanback.widget.SearchOrbView.Colors);
-    method public void setSearchAffordanceColorsInListening(android.support.v17.leanback.widget.SearchOrbView.Colors);
-    method public void setSearchQuery(java.lang.String, boolean);
-    method public void setSearchQuery(android.content.Intent, boolean);
-    method public void setSearchResultProvider(android.support.v17.leanback.app.SearchFragment.SearchResultProvider);
-    method public deprecated void setSpeechRecognitionCallback(android.support.v17.leanback.widget.SpeechRecognitionCallback);
-    method public void setTitle(java.lang.String);
-    method public void startRecognition();
-  }
-
-  public static abstract interface SearchFragment.SearchResultProvider {
-    method public abstract android.support.v17.leanback.widget.ObjectAdapter getResultsAdapter();
-    method public abstract boolean onQueryTextChange(java.lang.String);
-    method public abstract boolean onQueryTextSubmit(java.lang.String);
-  }
-
-  public class SearchSupportFragment extends android.support.v4.app.Fragment {
-    ctor public SearchSupportFragment();
-    method public static android.os.Bundle createArgs(android.os.Bundle, java.lang.String);
-    method public static android.os.Bundle createArgs(android.os.Bundle, java.lang.String, java.lang.String);
-    method public void displayCompletions(java.util.List<java.lang.String>);
-    method public void displayCompletions(android.view.inputmethod.CompletionInfo[]);
-    method public android.graphics.drawable.Drawable getBadgeDrawable();
-    method public android.content.Intent getRecognizerIntent();
-    method public android.support.v17.leanback.app.RowsSupportFragment getRowsSupportFragment();
-    method public java.lang.String getTitle();
-    method public static android.support.v17.leanback.app.SearchSupportFragment newInstance(java.lang.String);
-    method public void setBadgeDrawable(android.graphics.drawable.Drawable);
-    method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.OnItemViewClickedListener);
-    method public void setOnItemViewSelectedListener(android.support.v17.leanback.widget.OnItemViewSelectedListener);
-    method public void setSearchAffordanceColors(android.support.v17.leanback.widget.SearchOrbView.Colors);
-    method public void setSearchAffordanceColorsInListening(android.support.v17.leanback.widget.SearchOrbView.Colors);
-    method public void setSearchQuery(java.lang.String, boolean);
-    method public void setSearchQuery(android.content.Intent, boolean);
-    method public void setSearchResultProvider(android.support.v17.leanback.app.SearchSupportFragment.SearchResultProvider);
-    method public deprecated void setSpeechRecognitionCallback(android.support.v17.leanback.widget.SpeechRecognitionCallback);
-    method public void setTitle(java.lang.String);
-    method public void startRecognition();
-  }
-
-  public static abstract interface SearchSupportFragment.SearchResultProvider {
-    method public abstract android.support.v17.leanback.widget.ObjectAdapter getResultsAdapter();
-    method public abstract boolean onQueryTextChange(java.lang.String);
-    method public abstract boolean onQueryTextSubmit(java.lang.String);
-  }
-
-  public class VerticalGridFragment extends android.support.v17.leanback.app.BaseFragment {
-    ctor public VerticalGridFragment();
-    method public android.support.v17.leanback.widget.ObjectAdapter getAdapter();
-    method public android.support.v17.leanback.widget.VerticalGridPresenter getGridPresenter();
-    method public android.support.v17.leanback.widget.OnItemViewClickedListener getOnItemViewClickedListener();
-    method public void setAdapter(android.support.v17.leanback.widget.ObjectAdapter);
-    method public void setGridPresenter(android.support.v17.leanback.widget.VerticalGridPresenter);
-    method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.OnItemViewClickedListener);
-    method public void setOnItemViewSelectedListener(android.support.v17.leanback.widget.OnItemViewSelectedListener);
-    method public void setSelectedPosition(int);
-  }
-
-  public class VerticalGridSupportFragment extends android.support.v17.leanback.app.BaseSupportFragment {
-    ctor public VerticalGridSupportFragment();
-    method public android.support.v17.leanback.widget.ObjectAdapter getAdapter();
-    method public android.support.v17.leanback.widget.VerticalGridPresenter getGridPresenter();
-    method public android.support.v17.leanback.widget.OnItemViewClickedListener getOnItemViewClickedListener();
-    method public void setAdapter(android.support.v17.leanback.widget.ObjectAdapter);
-    method public void setGridPresenter(android.support.v17.leanback.widget.VerticalGridPresenter);
-    method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.OnItemViewClickedListener);
-    method public void setOnItemViewSelectedListener(android.support.v17.leanback.widget.OnItemViewSelectedListener);
-    method public void setSelectedPosition(int);
-  }
-
-  public class VideoFragment extends android.support.v17.leanback.app.PlaybackFragment {
-    ctor public VideoFragment();
-    method public android.view.SurfaceView getSurfaceView();
-    method public void setSurfaceHolderCallback(android.view.SurfaceHolder.Callback);
-  }
-
-  public class VideoFragmentGlueHost extends android.support.v17.leanback.app.PlaybackFragmentGlueHost implements android.support.v17.leanback.media.SurfaceHolderGlueHost {
-    ctor public VideoFragmentGlueHost(android.support.v17.leanback.app.VideoFragment);
-    method public void setSurfaceHolderCallback(android.view.SurfaceHolder.Callback);
-  }
-
-  public class VideoSupportFragment extends android.support.v17.leanback.app.PlaybackSupportFragment {
-    ctor public VideoSupportFragment();
-    method public android.view.SurfaceView getSurfaceView();
-    method public void setSurfaceHolderCallback(android.view.SurfaceHolder.Callback);
-  }
-
-  public class VideoSupportFragmentGlueHost extends android.support.v17.leanback.app.PlaybackSupportFragmentGlueHost implements android.support.v17.leanback.media.SurfaceHolderGlueHost {
-    ctor public VideoSupportFragmentGlueHost(android.support.v17.leanback.app.VideoSupportFragment);
-    method public void setSurfaceHolderCallback(android.view.SurfaceHolder.Callback);
-  }
-
-}
-
-package android.support.v17.leanback.database {
-
-  public abstract class CursorMapper {
-    ctor public CursorMapper();
-    method protected abstract java.lang.Object bind(android.database.Cursor);
-    method protected abstract void bindColumns(android.database.Cursor);
-    method public java.lang.Object convert(android.database.Cursor);
-  }
-
-}
-
-package android.support.v17.leanback.graphics {
-
-  public class BoundsRule {
-    ctor public BoundsRule();
-    ctor public BoundsRule(android.support.v17.leanback.graphics.BoundsRule);
-    method public void calculateBounds(android.graphics.Rect, android.graphics.Rect);
-    field public android.support.v17.leanback.graphics.BoundsRule.ValueRule bottom;
-    field public android.support.v17.leanback.graphics.BoundsRule.ValueRule left;
-    field public android.support.v17.leanback.graphics.BoundsRule.ValueRule right;
-    field public android.support.v17.leanback.graphics.BoundsRule.ValueRule top;
-  }
-
-  public static final class BoundsRule.ValueRule {
-    method public static android.support.v17.leanback.graphics.BoundsRule.ValueRule absoluteValue(int);
-    method public int getAbsoluteValue();
-    method public float getFraction();
-    method public static android.support.v17.leanback.graphics.BoundsRule.ValueRule inheritFromParent(float);
-    method public static android.support.v17.leanback.graphics.BoundsRule.ValueRule inheritFromParentWithOffset(float, int);
-    method public void setAbsoluteValue(int);
-    method public void setFraction(float);
-  }
-
-  public final class ColorFilterCache {
-    method public static android.support.v17.leanback.graphics.ColorFilterCache getColorFilterCache(int);
-    method public android.graphics.ColorFilter getFilterForLevel(float);
-  }
-
-  public final class ColorFilterDimmer {
-    method public void applyFilterToView(android.view.View);
-    method public static android.support.v17.leanback.graphics.ColorFilterDimmer create(android.support.v17.leanback.graphics.ColorFilterCache, float, float);
-    method public static android.support.v17.leanback.graphics.ColorFilterDimmer createDefault(android.content.Context);
-    method public android.graphics.ColorFilter getColorFilter();
-    method public android.graphics.Paint getPaint();
-    method public void setActiveLevel(float);
-  }
-
-  public final class ColorOverlayDimmer {
-    method public int applyToColor(int);
-    method public static android.support.v17.leanback.graphics.ColorOverlayDimmer createColorOverlayDimmer(int, float, float);
-    method public static android.support.v17.leanback.graphics.ColorOverlayDimmer createDefault(android.content.Context);
-    method public void drawColorOverlay(android.graphics.Canvas, android.view.View, boolean);
-    method public int getAlpha();
-    method public float getAlphaFloat();
-    method public android.graphics.Paint getPaint();
-    method public boolean needsDraw();
-    method public void setActiveLevel(float);
-  }
-
-  public class CompositeDrawable extends android.graphics.drawable.Drawable implements android.graphics.drawable.Drawable.Callback {
-    ctor public CompositeDrawable();
-    method public void addChildDrawable(android.graphics.drawable.Drawable);
-    method public void draw(android.graphics.Canvas);
-    method public android.support.v17.leanback.graphics.CompositeDrawable.ChildDrawable getChildAt(int);
-    method public int getChildCount();
-    method public android.graphics.drawable.Drawable getDrawable(int);
-    method public int getOpacity();
-    method public void invalidateDrawable(android.graphics.drawable.Drawable);
-    method public void removeChild(int);
-    method public void removeDrawable(android.graphics.drawable.Drawable);
-    method public void scheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable, long);
-    method public void setAlpha(int);
-    method public void setChildDrawableAt(int, android.graphics.drawable.Drawable);
-    method public void setColorFilter(android.graphics.ColorFilter);
-    method public void unscheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable);
-  }
-
-  public static final class CompositeDrawable.ChildDrawable {
-    ctor public CompositeDrawable.ChildDrawable(android.graphics.drawable.Drawable, android.support.v17.leanback.graphics.CompositeDrawable);
-    method public android.support.v17.leanback.graphics.BoundsRule getBoundsRule();
-    method public android.graphics.drawable.Drawable getDrawable();
-    method public void recomputeBounds();
-    field public static final android.util.Property<android.support.v17.leanback.graphics.CompositeDrawable.ChildDrawable, java.lang.Integer> BOTTOM_ABSOLUTE;
-    field public static final android.util.Property<android.support.v17.leanback.graphics.CompositeDrawable.ChildDrawable, java.lang.Float> BOTTOM_FRACTION;
-    field public static final android.util.Property<android.support.v17.leanback.graphics.CompositeDrawable.ChildDrawable, java.lang.Integer> LEFT_ABSOLUTE;
-    field public static final android.util.Property<android.support.v17.leanback.graphics.CompositeDrawable.ChildDrawable, java.lang.Float> LEFT_FRACTION;
-    field public static final android.util.Property<android.support.v17.leanback.graphics.CompositeDrawable.ChildDrawable, java.lang.Integer> RIGHT_ABSOLUTE;
-    field public static final android.util.Property<android.support.v17.leanback.graphics.CompositeDrawable.ChildDrawable, java.lang.Float> RIGHT_FRACTION;
-    field public static final android.util.Property<android.support.v17.leanback.graphics.CompositeDrawable.ChildDrawable, java.lang.Integer> TOP_ABSOLUTE;
-    field public static final android.util.Property<android.support.v17.leanback.graphics.CompositeDrawable.ChildDrawable, java.lang.Float> TOP_FRACTION;
-  }
-
-  public class FitWidthBitmapDrawable extends android.graphics.drawable.Drawable {
-    ctor public FitWidthBitmapDrawable();
-    method public void draw(android.graphics.Canvas);
-    method public android.graphics.Bitmap getBitmap();
-    method public int getOpacity();
-    method public android.graphics.Rect getSource();
-    method public int getVerticalOffset();
-    method public void setAlpha(int);
-    method public void setBitmap(android.graphics.Bitmap);
-    method public void setColorFilter(android.graphics.ColorFilter);
-    method public void setSource(android.graphics.Rect);
-    method public void setVerticalOffset(int);
-    field public static final android.util.Property<android.support.v17.leanback.graphics.FitWidthBitmapDrawable, java.lang.Integer> PROPERTY_VERTICAL_OFFSET;
-  }
-
-}
-
-package android.support.v17.leanback.media {
-
-  public class MediaControllerAdapter extends android.support.v17.leanback.media.PlayerAdapter {
-    ctor public MediaControllerAdapter(android.support.v4.media.session.MediaControllerCompat);
-    method public android.graphics.drawable.Drawable getMediaArt(android.content.Context);
-    method public android.support.v4.media.session.MediaControllerCompat getMediaController();
-    method public java.lang.CharSequence getMediaSubtitle();
-    method public java.lang.CharSequence getMediaTitle();
-    method public void pause();
-    method public void play();
-  }
-
-  public abstract deprecated class MediaControllerGlue extends android.support.v17.leanback.media.PlaybackControlGlue {
-    ctor public MediaControllerGlue(android.content.Context, int[], int[]);
-    method public void attachToMediaController(android.support.v4.media.session.MediaControllerCompat);
-    method public void detach();
-    method public int getCurrentPosition();
-    method public int getCurrentSpeedId();
-    method public android.graphics.drawable.Drawable getMediaArt();
-    method public final android.support.v4.media.session.MediaControllerCompat getMediaController();
-    method public int getMediaDuration();
-    method public java.lang.CharSequence getMediaSubtitle();
-    method public java.lang.CharSequence getMediaTitle();
-    method public long getSupportedActions();
-    method public boolean hasValidMedia();
-    method public boolean isMediaPlaying();
-  }
-
-  public class MediaPlayerAdapter extends android.support.v17.leanback.media.PlayerAdapter {
-    ctor public MediaPlayerAdapter(android.content.Context);
-    method protected boolean onError(int, int);
-    method protected boolean onInfo(int, int);
-    method protected void onSeekComplete();
-    method public void pause();
-    method public void play();
-    method public void release();
-    method public void reset();
-    method public boolean setDataSource(android.net.Uri);
-  }
-
-  public class PlaybackBannerControlGlue<T extends android.support.v17.leanback.media.PlayerAdapter> extends android.support.v17.leanback.media.PlaybackBaseControlGlue {
-    ctor public PlaybackBannerControlGlue(android.content.Context, int[], T);
-    ctor public PlaybackBannerControlGlue(android.content.Context, int[], int[], T);
-    method public int[] getFastForwardSpeeds();
-    method public int[] getRewindSpeeds();
-    method public void onActionClicked(android.support.v17.leanback.widget.Action);
-    method protected android.support.v17.leanback.widget.PlaybackRowPresenter onCreateRowPresenter();
-    method public boolean onKey(android.view.View, int, android.view.KeyEvent);
-    field public static final int ACTION_CUSTOM_LEFT_FIRST = 1; // 0x1
-    field public static final int ACTION_CUSTOM_RIGHT_FIRST = 4096; // 0x1000
-    field public static final int ACTION_FAST_FORWARD = 128; // 0x80
-    field public static final int ACTION_PLAY_PAUSE = 64; // 0x40
-    field public static final int ACTION_REWIND = 32; // 0x20
-    field public static final int ACTION_SKIP_TO_NEXT = 256; // 0x100
-    field public static final int ACTION_SKIP_TO_PREVIOUS = 16; // 0x10
-    field public static final int PLAYBACK_SPEED_FAST_L0 = 10; // 0xa
-    field public static final int PLAYBACK_SPEED_FAST_L1 = 11; // 0xb
-    field public static final int PLAYBACK_SPEED_FAST_L2 = 12; // 0xc
-    field public static final int PLAYBACK_SPEED_FAST_L3 = 13; // 0xd
-    field public static final int PLAYBACK_SPEED_FAST_L4 = 14; // 0xe
-    field public static final int PLAYBACK_SPEED_INVALID = -1; // 0xffffffff
-    field public static final int PLAYBACK_SPEED_NORMAL = 1; // 0x1
-    field public static final int PLAYBACK_SPEED_PAUSED = 0; // 0x0
-  }
-
-  public abstract class PlaybackBaseControlGlue<T extends android.support.v17.leanback.media.PlayerAdapter> extends android.support.v17.leanback.media.PlaybackGlue implements android.support.v17.leanback.widget.OnActionClickedListener android.view.View.OnKeyListener {
-    ctor public PlaybackBaseControlGlue(android.content.Context, T);
-    method public android.graphics.drawable.Drawable getArt();
-    method public final long getBufferedPosition();
-    method public android.support.v17.leanback.widget.PlaybackControlsRow getControlsRow();
-    method public long getCurrentPosition();
-    method public final long getDuration();
-    method public android.support.v17.leanback.widget.PlaybackRowPresenter getPlaybackRowPresenter();
-    method public final T getPlayerAdapter();
-    method public java.lang.CharSequence getSubtitle();
-    method public long getSupportedActions();
-    method public java.lang.CharSequence getTitle();
-    method public boolean isControlsOverlayAutoHideEnabled();
-    method public final boolean isPlaying();
-    method public final boolean isPrepared();
-    method protected static void notifyItemChanged(android.support.v17.leanback.widget.ArrayObjectAdapter, java.lang.Object);
-    method public abstract void onActionClicked(android.support.v17.leanback.widget.Action);
-    method protected void onCreatePrimaryActions(android.support.v17.leanback.widget.ArrayObjectAdapter);
-    method protected abstract android.support.v17.leanback.widget.PlaybackRowPresenter onCreateRowPresenter();
-    method protected void onCreateSecondaryActions(android.support.v17.leanback.widget.ArrayObjectAdapter);
-    method public abstract boolean onKey(android.view.View, int, android.view.KeyEvent);
-    method protected void onMetadataChanged();
-    method protected void onPlayCompleted();
-    method protected void onPlayStateChanged();
-    method protected void onPreparedStateChanged();
-    method protected void onUpdateBufferedProgress();
-    method protected void onUpdateDuration();
-    method protected void onUpdateProgress();
-    method public final void seekTo(long);
-    method public void setArt(android.graphics.drawable.Drawable);
-    method public void setControlsOverlayAutoHideEnabled(boolean);
-    method public void setControlsRow(android.support.v17.leanback.widget.PlaybackControlsRow);
-    method public void setPlaybackRowPresenter(android.support.v17.leanback.widget.PlaybackRowPresenter);
-    method public void setSubtitle(java.lang.CharSequence);
-    method public void setTitle(java.lang.CharSequence);
-    field public static final int ACTION_CUSTOM_LEFT_FIRST = 1; // 0x1
-    field public static final int ACTION_CUSTOM_RIGHT_FIRST = 4096; // 0x1000
-    field public static final int ACTION_FAST_FORWARD = 128; // 0x80
-    field public static final int ACTION_PLAY_PAUSE = 64; // 0x40
-    field public static final int ACTION_REPEAT = 512; // 0x200
-    field public static final int ACTION_REWIND = 32; // 0x20
-    field public static final int ACTION_SHUFFLE = 1024; // 0x400
-    field public static final int ACTION_SKIP_TO_NEXT = 256; // 0x100
-    field public static final int ACTION_SKIP_TO_PREVIOUS = 16; // 0x10
-  }
-
-  public abstract class PlaybackControlGlue extends android.support.v17.leanback.media.PlaybackGlue implements android.support.v17.leanback.widget.OnActionClickedListener android.view.View.OnKeyListener {
-    ctor public PlaybackControlGlue(android.content.Context, int[]);
-    ctor public PlaybackControlGlue(android.content.Context, int[], int[]);
-    method public void enableProgressUpdating(boolean);
-    method public android.support.v17.leanback.widget.PlaybackControlsRow getControlsRow();
-    method public deprecated android.support.v17.leanback.widget.PlaybackControlsRowPresenter getControlsRowPresenter();
-    method public abstract int getCurrentPosition();
-    method public abstract int getCurrentSpeedId();
-    method public int[] getFastForwardSpeeds();
-    method public abstract android.graphics.drawable.Drawable getMediaArt();
-    method public abstract int getMediaDuration();
-    method public abstract java.lang.CharSequence getMediaSubtitle();
-    method public abstract java.lang.CharSequence getMediaTitle();
-    method public android.support.v17.leanback.widget.PlaybackRowPresenter getPlaybackRowPresenter();
-    method public int[] getRewindSpeeds();
-    method public abstract long getSupportedActions();
-    method public int getUpdatePeriod();
-    method public abstract boolean hasValidMedia();
-    method public boolean isFadingEnabled();
-    method public abstract boolean isMediaPlaying();
-    method public void onActionClicked(android.support.v17.leanback.widget.Action);
-    method protected void onCreateControlsRowAndPresenter();
-    method protected void onCreatePrimaryActions(android.support.v17.leanback.widget.SparseArrayObjectAdapter);
-    method protected void onCreateSecondaryActions(android.support.v17.leanback.widget.ArrayObjectAdapter);
-    method public boolean onKey(android.view.View, int, android.view.KeyEvent);
-    method protected void onMetadataChanged();
-    method protected void onStateChanged();
-    method public void play(int);
-    method public final void play();
-    method public void setControlsRow(android.support.v17.leanback.widget.PlaybackControlsRow);
-    method public deprecated void setControlsRowPresenter(android.support.v17.leanback.widget.PlaybackControlsRowPresenter);
-    method public void setFadingEnabled(boolean);
-    method public void setPlaybackRowPresenter(android.support.v17.leanback.widget.PlaybackRowPresenter);
-    method public void updateProgress();
-    field public static final int ACTION_CUSTOM_LEFT_FIRST = 1; // 0x1
-    field public static final int ACTION_CUSTOM_RIGHT_FIRST = 4096; // 0x1000
-    field public static final int ACTION_FAST_FORWARD = 128; // 0x80
-    field public static final int ACTION_PLAY_PAUSE = 64; // 0x40
-    field public static final int ACTION_REWIND = 32; // 0x20
-    field public static final int ACTION_SKIP_TO_NEXT = 256; // 0x100
-    field public static final int ACTION_SKIP_TO_PREVIOUS = 16; // 0x10
-    field public static final int PLAYBACK_SPEED_FAST_L0 = 10; // 0xa
-    field public static final int PLAYBACK_SPEED_FAST_L1 = 11; // 0xb
-    field public static final int PLAYBACK_SPEED_FAST_L2 = 12; // 0xc
-    field public static final int PLAYBACK_SPEED_FAST_L3 = 13; // 0xd
-    field public static final int PLAYBACK_SPEED_FAST_L4 = 14; // 0xe
-    field public static final int PLAYBACK_SPEED_INVALID = -1; // 0xffffffff
-    field public static final int PLAYBACK_SPEED_NORMAL = 1; // 0x1
-    field public static final int PLAYBACK_SPEED_PAUSED = 0; // 0x0
-  }
-
-  public abstract class PlaybackGlue {
-    ctor public PlaybackGlue(android.content.Context);
-    method public void addPlayerCallback(android.support.v17.leanback.media.PlaybackGlue.PlayerCallback);
-    method public android.content.Context getContext();
-    method public android.support.v17.leanback.media.PlaybackGlueHost getHost();
-    method protected java.util.List<android.support.v17.leanback.media.PlaybackGlue.PlayerCallback> getPlayerCallbacks();
-    method public boolean isPlaying();
-    method public boolean isPrepared();
-    method public void next();
-    method protected void onAttachedToHost(android.support.v17.leanback.media.PlaybackGlueHost);
-    method protected void onDetachedFromHost();
-    method protected void onHostPause();
-    method protected void onHostResume();
-    method protected void onHostStart();
-    method protected void onHostStop();
-    method public void pause();
-    method public void play();
-    method public void playWhenPrepared();
-    method public void previous();
-    method public void removePlayerCallback(android.support.v17.leanback.media.PlaybackGlue.PlayerCallback);
-    method public final void setHost(android.support.v17.leanback.media.PlaybackGlueHost);
-  }
-
-  public static abstract class PlaybackGlue.PlayerCallback {
-    ctor public PlaybackGlue.PlayerCallback();
-    method public void onPlayCompleted(android.support.v17.leanback.media.PlaybackGlue);
-    method public void onPlayStateChanged(android.support.v17.leanback.media.PlaybackGlue);
-    method public void onPreparedStateChanged(android.support.v17.leanback.media.PlaybackGlue);
-  }
-
-  public abstract class PlaybackGlueHost {
-    ctor public PlaybackGlueHost();
-    method public deprecated void fadeOut();
-    method public android.support.v17.leanback.media.PlaybackGlueHost.PlayerCallback getPlayerCallback();
-    method public void hideControlsOverlay(boolean);
-    method public boolean isControlsOverlayAutoHideEnabled();
-    method public boolean isControlsOverlayVisible();
-    method public void notifyPlaybackRowChanged();
-    method public void setControlsOverlayAutoHideEnabled(boolean);
-    method public deprecated void setFadingEnabled(boolean);
-    method public void setHostCallback(android.support.v17.leanback.media.PlaybackGlueHost.HostCallback);
-    method public void setOnActionClickedListener(android.support.v17.leanback.widget.OnActionClickedListener);
-    method public void setOnKeyInterceptListener(android.view.View.OnKeyListener);
-    method public void setPlaybackRow(android.support.v17.leanback.widget.Row);
-    method public void setPlaybackRowPresenter(android.support.v17.leanback.widget.PlaybackRowPresenter);
-    method public void showControlsOverlay(boolean);
-  }
-
-  public static abstract class PlaybackGlueHost.HostCallback {
-    ctor public PlaybackGlueHost.HostCallback();
-    method public void onHostDestroy();
-    method public void onHostPause();
-    method public void onHostResume();
-    method public void onHostStart();
-    method public void onHostStop();
-  }
-
-  public static class PlaybackGlueHost.PlayerCallback {
-    ctor public PlaybackGlueHost.PlayerCallback();
-    method public void onBufferingStateChanged(boolean);
-    method public void onError(int, java.lang.CharSequence);
-    method public void onVideoSizeChanged(int, int);
-  }
-
-  public class PlaybackTransportControlGlue<T extends android.support.v17.leanback.media.PlayerAdapter> extends android.support.v17.leanback.media.PlaybackBaseControlGlue {
-    ctor public PlaybackTransportControlGlue(android.content.Context, T);
-    method public final android.support.v17.leanback.widget.PlaybackSeekDataProvider getSeekProvider();
-    method public final boolean isSeekEnabled();
-    method public void onActionClicked(android.support.v17.leanback.widget.Action);
-    method protected android.support.v17.leanback.widget.PlaybackRowPresenter onCreateRowPresenter();
-    method public boolean onKey(android.view.View, int, android.view.KeyEvent);
-    method public final void setSeekEnabled(boolean);
-    method public final void setSeekProvider(android.support.v17.leanback.widget.PlaybackSeekDataProvider);
-  }
-
-  public abstract class PlayerAdapter {
-    ctor public PlayerAdapter();
-    method public void fastForward();
-    method public long getBufferedPosition();
-    method public final android.support.v17.leanback.media.PlayerAdapter.Callback getCallback();
-    method public long getCurrentPosition();
-    method public long getDuration();
-    method public long getSupportedActions();
-    method public boolean isPlaying();
-    method public boolean isPrepared();
-    method public void next();
-    method public void onAttachedToHost(android.support.v17.leanback.media.PlaybackGlueHost);
-    method public void onDetachedFromHost();
-    method public abstract void pause();
-    method public abstract void play();
-    method public void previous();
-    method public void rewind();
-    method public void seekTo(long);
-    method public final void setCallback(android.support.v17.leanback.media.PlayerAdapter.Callback);
-    method public void setProgressUpdatingEnabled(boolean);
-    method public void setRepeatAction(int);
-    method public void setShuffleAction(int);
-  }
-
-  public static class PlayerAdapter.Callback {
-    ctor public PlayerAdapter.Callback();
-    method public void onBufferedPositionChanged(android.support.v17.leanback.media.PlayerAdapter);
-    method public void onBufferingStateChanged(android.support.v17.leanback.media.PlayerAdapter, boolean);
-    method public void onCurrentPositionChanged(android.support.v17.leanback.media.PlayerAdapter);
-    method public void onDurationChanged(android.support.v17.leanback.media.PlayerAdapter);
-    method public void onError(android.support.v17.leanback.media.PlayerAdapter, int, java.lang.String);
-    method public void onMetadataChanged(android.support.v17.leanback.media.PlayerAdapter);
-    method public void onPlayCompleted(android.support.v17.leanback.media.PlayerAdapter);
-    method public void onPlayStateChanged(android.support.v17.leanback.media.PlayerAdapter);
-    method public void onPreparedStateChanged(android.support.v17.leanback.media.PlayerAdapter);
-    method public void onVideoSizeChanged(android.support.v17.leanback.media.PlayerAdapter, int, int);
-  }
-
-  public abstract interface SurfaceHolderGlueHost {
-    method public abstract void setSurfaceHolderCallback(android.view.SurfaceHolder.Callback);
-  }
-
-}
-
-package android.support.v17.leanback.system {
-
-  public class Settings {
-    method public boolean getBoolean(java.lang.String);
-    method public static android.support.v17.leanback.system.Settings getInstance(android.content.Context);
-    method public void setBoolean(java.lang.String, boolean);
-    field public static final java.lang.String OUTLINE_CLIPPING_DISABLED = "OUTLINE_CLIPPING_DISABLED";
-    field public static final java.lang.String PREFER_STATIC_SHADOWS = "PREFER_STATIC_SHADOWS";
-  }
-
-}
-
-package android.support.v17.leanback.widget {
-
-  public abstract class AbstractDetailsDescriptionPresenter extends android.support.v17.leanback.widget.Presenter {
-    ctor public AbstractDetailsDescriptionPresenter();
-    method protected abstract void onBindDescription(android.support.v17.leanback.widget.AbstractDetailsDescriptionPresenter.ViewHolder, java.lang.Object);
-    method public final void onBindViewHolder(android.support.v17.leanback.widget.Presenter.ViewHolder, java.lang.Object);
-    method public final android.support.v17.leanback.widget.AbstractDetailsDescriptionPresenter.ViewHolder onCreateViewHolder(android.view.ViewGroup);
-    method public void onUnbindViewHolder(android.support.v17.leanback.widget.Presenter.ViewHolder);
-  }
-
-  public static class AbstractDetailsDescriptionPresenter.ViewHolder extends android.support.v17.leanback.widget.Presenter.ViewHolder {
-    ctor public AbstractDetailsDescriptionPresenter.ViewHolder(android.view.View);
-    method public android.widget.TextView getBody();
-    method public android.widget.TextView getSubtitle();
-    method public android.widget.TextView getTitle();
-  }
-
-  public abstract class AbstractMediaItemPresenter extends android.support.v17.leanback.widget.RowPresenter {
-    ctor public AbstractMediaItemPresenter();
-    ctor public AbstractMediaItemPresenter(int);
-    method protected android.support.v17.leanback.widget.RowPresenter.ViewHolder createRowViewHolder(android.view.ViewGroup);
-    method public android.support.v17.leanback.widget.Presenter getActionPresenter();
-    method protected int getMediaPlayState(java.lang.Object);
-    method public int getThemeId();
-    method public boolean hasMediaRowSeparator();
-    method protected abstract void onBindMediaDetails(android.support.v17.leanback.widget.AbstractMediaItemPresenter.ViewHolder, java.lang.Object);
-    method public void onBindMediaPlayState(android.support.v17.leanback.widget.AbstractMediaItemPresenter.ViewHolder);
-    method protected void onBindRowActions(android.support.v17.leanback.widget.AbstractMediaItemPresenter.ViewHolder);
-    method protected void onUnbindMediaDetails(android.support.v17.leanback.widget.AbstractMediaItemPresenter.ViewHolder);
-    method public void onUnbindMediaPlayState(android.support.v17.leanback.widget.AbstractMediaItemPresenter.ViewHolder);
-    method public void setActionPresenter(android.support.v17.leanback.widget.Presenter);
-    method public void setBackgroundColor(int);
-    method public void setHasMediaRowSeparator(boolean);
-    method public void setThemeId(int);
-    field public static final int PLAY_STATE_INITIAL = 0; // 0x0
-    field public static final int PLAY_STATE_PAUSED = 1; // 0x1
-    field public static final int PLAY_STATE_PLAYING = 2; // 0x2
-  }
-
-  public static class AbstractMediaItemPresenter.ViewHolder extends android.support.v17.leanback.widget.RowPresenter.ViewHolder {
-    ctor public AbstractMediaItemPresenter.ViewHolder(android.view.View);
-    method public android.view.ViewGroup getMediaItemActionsContainer();
-    method public android.view.View getMediaItemDetailsView();
-    method public android.widget.TextView getMediaItemDurationView();
-    method public android.widget.TextView getMediaItemNameView();
-    method public android.widget.TextView getMediaItemNumberView();
-    method public android.widget.ViewFlipper getMediaItemNumberViewFlipper();
-    method public android.view.View getMediaItemPausedView();
-    method public android.view.View getMediaItemPlayingView();
-    method public android.support.v17.leanback.widget.MultiActionsProvider.MultiAction[] getMediaItemRowActions();
-    method public android.view.View getMediaItemRowSeparator();
-    method public android.view.View getSelectorView();
-    method public void notifyActionChanged(android.support.v17.leanback.widget.MultiActionsProvider.MultiAction);
-    method public void notifyDetailsChanged();
-    method public void notifyPlayStateChanged();
-    method public void onBindRowActions();
-    method public void setSelectedMediaItemNumberView(int);
-  }
-
-  public abstract class AbstractMediaListHeaderPresenter extends android.support.v17.leanback.widget.RowPresenter {
-    ctor public AbstractMediaListHeaderPresenter(android.content.Context, int);
-    ctor public AbstractMediaListHeaderPresenter();
-    method protected android.support.v17.leanback.widget.RowPresenter.ViewHolder createRowViewHolder(android.view.ViewGroup);
-    method protected abstract void onBindMediaListHeaderViewHolder(android.support.v17.leanback.widget.AbstractMediaListHeaderPresenter.ViewHolder, java.lang.Object);
-    method public void setBackgroundColor(int);
-  }
-
-  public static class AbstractMediaListHeaderPresenter.ViewHolder extends android.support.v17.leanback.widget.RowPresenter.ViewHolder {
-    ctor public AbstractMediaListHeaderPresenter.ViewHolder(android.view.View);
-    method public android.widget.TextView getHeaderView();
-  }
-
-  public class Action {
-    ctor public Action(long);
-    ctor public Action(long, java.lang.CharSequence);
-    ctor public Action(long, java.lang.CharSequence, java.lang.CharSequence);
-    ctor public Action(long, java.lang.CharSequence, java.lang.CharSequence, android.graphics.drawable.Drawable);
-    method public final void addKeyCode(int);
-    method public final android.graphics.drawable.Drawable getIcon();
-    method public final long getId();
-    method public final java.lang.CharSequence getLabel1();
-    method public final java.lang.CharSequence getLabel2();
-    method public final void removeKeyCode(int);
-    method public final boolean respondsToKeyCode(int);
-    method public final void setIcon(android.graphics.drawable.Drawable);
-    method public final void setId(long);
-    method public final void setLabel1(java.lang.CharSequence);
-    method public final void setLabel2(java.lang.CharSequence);
-    field public static final long NO_ID = -1L; // 0xffffffffffffffffL
-  }
-
-  public class ArrayObjectAdapter extends android.support.v17.leanback.widget.ObjectAdapter {
-    ctor public ArrayObjectAdapter(android.support.v17.leanback.widget.PresenterSelector);
-    ctor public ArrayObjectAdapter(android.support.v17.leanback.widget.Presenter);
-    ctor public ArrayObjectAdapter();
-    method public void add(java.lang.Object);
-    method public void add(int, java.lang.Object);
-    method public void addAll(int, java.util.Collection);
-    method public void clear();
-    method public java.lang.Object get(int);
-    method public int indexOf(java.lang.Object);
-    method public void move(int, int);
-    method public void notifyArrayItemRangeChanged(int, int);
-    method public boolean remove(java.lang.Object);
-    method public int removeItems(int, int);
-    method public void replace(int, java.lang.Object);
-    method public void setItems(java.util.List, android.support.v17.leanback.widget.DiffCallback);
-    method public int size();
-    method public <E> java.util.List<E> unmodifiableList();
-  }
-
-  public class BaseCardView extends android.widget.FrameLayout {
-    ctor public BaseCardView(android.content.Context);
-    ctor public BaseCardView(android.content.Context, android.util.AttributeSet);
-    ctor public BaseCardView(android.content.Context, android.util.AttributeSet, int);
-    method protected android.support.v17.leanback.widget.BaseCardView.LayoutParams generateDefaultLayoutParams();
-    method public android.support.v17.leanback.widget.BaseCardView.LayoutParams generateLayoutParams(android.util.AttributeSet);
-    method protected android.support.v17.leanback.widget.BaseCardView.LayoutParams generateLayoutParams(android.view.ViewGroup.LayoutParams);
-    method public int getCardType();
-    method public deprecated int getExtraVisibility();
-    method public int getInfoVisibility();
-    method public boolean isSelectedAnimationDelayed();
-    method public void setCardType(int);
-    method public deprecated void setExtraVisibility(int);
-    method public void setInfoVisibility(int);
-    method public void setSelectedAnimationDelayed(boolean);
-    field public static final int CARD_REGION_VISIBLE_ACTIVATED = 1; // 0x1
-    field public static final int CARD_REGION_VISIBLE_ALWAYS = 0; // 0x0
-    field public static final int CARD_REGION_VISIBLE_SELECTED = 2; // 0x2
-    field public static final int CARD_TYPE_INFO_OVER = 1; // 0x1
-    field public static final int CARD_TYPE_INFO_UNDER = 2; // 0x2
-    field public static final int CARD_TYPE_INFO_UNDER_WITH_EXTRA = 3; // 0x3
-    field public static final int CARD_TYPE_MAIN_ONLY = 0; // 0x0
-  }
-
-  public static class BaseCardView.LayoutParams extends android.widget.FrameLayout.LayoutParams {
-    ctor public BaseCardView.LayoutParams(android.content.Context, android.util.AttributeSet);
-    ctor public BaseCardView.LayoutParams(int, int);
-    ctor public BaseCardView.LayoutParams(android.view.ViewGroup.LayoutParams);
-    ctor public BaseCardView.LayoutParams(android.support.v17.leanback.widget.BaseCardView.LayoutParams);
-    field public static final int VIEW_TYPE_EXTRA = 2; // 0x2
-    field public static final int VIEW_TYPE_INFO = 1; // 0x1
-    field public static final int VIEW_TYPE_MAIN = 0; // 0x0
-    field public int viewType;
-  }
-
-  public abstract class BaseGridView extends android.support.v7.widget.RecyclerView {
-    method public void addOnChildViewHolderSelectedListener(android.support.v17.leanback.widget.OnChildViewHolderSelectedListener);
-    method public void animateIn();
-    method public void animateOut();
-    method public int getChildDrawingOrder(int, int);
-    method public deprecated int getHorizontalMargin();
-    method public int getHorizontalSpacing();
-    method public int getInitialPrefetchItemCount();
-    method public int getItemAlignmentOffset();
-    method public float getItemAlignmentOffsetPercent();
-    method public int getItemAlignmentViewId();
-    method public android.support.v17.leanback.widget.BaseGridView.OnUnhandledKeyListener getOnUnhandledKeyListener();
-    method public final int getSaveChildrenLimitNumber();
-    method public final int getSaveChildrenPolicy();
-    method public int getSelectedPosition();
-    method public deprecated int getVerticalMargin();
-    method public int getVerticalSpacing();
-    method public void getViewSelectedOffsets(android.view.View, int[]);
-    method public int getWindowAlignment();
-    method public int getWindowAlignmentOffset();
-    method public float getWindowAlignmentOffsetPercent();
-    method public boolean hasPreviousViewInSameRow(int);
-    method public boolean isChildLayoutAnimated();
-    method public boolean isFocusDrawingOrderEnabled();
-    method public final boolean isFocusSearchDisabled();
-    method public boolean isItemAlignmentOffsetWithPadding();
-    method public boolean isScrollEnabled();
-    method public boolean isWindowAlignmentPreferKeyLineOverHighEdge();
-    method public boolean isWindowAlignmentPreferKeyLineOverLowEdge();
-    method public boolean onRequestFocusInDescendants(int, android.graphics.Rect);
-    method public void removeOnChildViewHolderSelectedListener(android.support.v17.leanback.widget.OnChildViewHolderSelectedListener);
-    method public void setAnimateChildLayout(boolean);
-    method public void setChildrenVisibility(int);
-    method public void setFocusDrawingOrderEnabled(boolean);
-    method public final void setFocusSearchDisabled(boolean);
-    method public void setGravity(int);
-    method public void setHasOverlappingRendering(boolean);
-    method public deprecated void setHorizontalMargin(int);
-    method public void setHorizontalSpacing(int);
-    method public void setInitialPrefetchItemCount(int);
-    method public void setItemAlignmentOffset(int);
-    method public void setItemAlignmentOffsetPercent(float);
-    method public void setItemAlignmentOffsetWithPadding(boolean);
-    method public void setItemAlignmentViewId(int);
-    method public deprecated void setItemMargin(int);
-    method public void setItemSpacing(int);
-    method public void setLayoutEnabled(boolean);
-    method public void setOnChildLaidOutListener(android.support.v17.leanback.widget.OnChildLaidOutListener);
-    method public void setOnChildSelectedListener(android.support.v17.leanback.widget.OnChildSelectedListener);
-    method public void setOnChildViewHolderSelectedListener(android.support.v17.leanback.widget.OnChildViewHolderSelectedListener);
-    method public void setOnKeyInterceptListener(android.support.v17.leanback.widget.BaseGridView.OnKeyInterceptListener);
-    method public void setOnMotionInterceptListener(android.support.v17.leanback.widget.BaseGridView.OnMotionInterceptListener);
-    method public void setOnTouchInterceptListener(android.support.v17.leanback.widget.BaseGridView.OnTouchInterceptListener);
-    method public void setOnUnhandledKeyListener(android.support.v17.leanback.widget.BaseGridView.OnUnhandledKeyListener);
-    method public void setPruneChild(boolean);
-    method public final void setSaveChildrenLimitNumber(int);
-    method public final void setSaveChildrenPolicy(int);
-    method public void setScrollEnabled(boolean);
-    method public void setSelectedPosition(int);
-    method public void setSelectedPosition(int, int);
-    method public void setSelectedPosition(int, android.support.v17.leanback.widget.ViewHolderTask);
-    method public void setSelectedPositionSmooth(int);
-    method public void setSelectedPositionSmooth(int, android.support.v17.leanback.widget.ViewHolderTask);
-    method public deprecated void setVerticalMargin(int);
-    method public void setVerticalSpacing(int);
-    method public void setWindowAlignment(int);
-    method public void setWindowAlignmentOffset(int);
-    method public void setWindowAlignmentOffsetPercent(float);
-    method public void setWindowAlignmentPreferKeyLineOverHighEdge(boolean);
-    method public void setWindowAlignmentPreferKeyLineOverLowEdge(boolean);
-    field public static final float ITEM_ALIGN_OFFSET_PERCENT_DISABLED = -1.0f;
-    field public static final int SAVE_ALL_CHILD = 3; // 0x3
-    field public static final int SAVE_LIMITED_CHILD = 2; // 0x2
-    field public static final int SAVE_NO_CHILD = 0; // 0x0
-    field public static final int SAVE_ON_SCREEN_CHILD = 1; // 0x1
-    field public static final int WINDOW_ALIGN_BOTH_EDGE = 3; // 0x3
-    field public static final int WINDOW_ALIGN_HIGH_EDGE = 2; // 0x2
-    field public static final int WINDOW_ALIGN_LOW_EDGE = 1; // 0x1
-    field public static final int WINDOW_ALIGN_NO_EDGE = 0; // 0x0
-    field public static final float WINDOW_ALIGN_OFFSET_PERCENT_DISABLED = -1.0f;
-  }
-
-  public static abstract interface BaseGridView.OnKeyInterceptListener {
-    method public abstract boolean onInterceptKeyEvent(android.view.KeyEvent);
-  }
-
-  public static abstract interface BaseGridView.OnMotionInterceptListener {
-    method public abstract boolean onInterceptMotionEvent(android.view.MotionEvent);
-  }
-
-  public static abstract interface BaseGridView.OnTouchInterceptListener {
-    method public abstract boolean onInterceptTouchEvent(android.view.MotionEvent);
-  }
-
-  public static abstract interface BaseGridView.OnUnhandledKeyListener {
-    method public abstract boolean onUnhandledKey(android.view.KeyEvent);
-  }
-
-  public abstract interface BaseOnItemViewClickedListener<T> {
-    method public abstract void onItemClicked(android.support.v17.leanback.widget.Presenter.ViewHolder, java.lang.Object, android.support.v17.leanback.widget.RowPresenter.ViewHolder, T);
-  }
-
-  public abstract interface BaseOnItemViewSelectedListener<T> {
-    method public abstract void onItemSelected(android.support.v17.leanback.widget.Presenter.ViewHolder, java.lang.Object, android.support.v17.leanback.widget.RowPresenter.ViewHolder, T);
-  }
-
-  public class BrowseFrameLayout extends android.widget.FrameLayout {
-    ctor public BrowseFrameLayout(android.content.Context);
-    ctor public BrowseFrameLayout(android.content.Context, android.util.AttributeSet);
-    ctor public BrowseFrameLayout(android.content.Context, android.util.AttributeSet, int);
-    method public android.support.v17.leanback.widget.BrowseFrameLayout.OnChildFocusListener getOnChildFocusListener();
-    method public android.support.v17.leanback.widget.BrowseFrameLayout.OnFocusSearchListener getOnFocusSearchListener();
-    method public void setOnChildFocusListener(android.support.v17.leanback.widget.BrowseFrameLayout.OnChildFocusListener);
-    method public void setOnDispatchKeyListener(android.view.View.OnKeyListener);
-    method public void setOnFocusSearchListener(android.support.v17.leanback.widget.BrowseFrameLayout.OnFocusSearchListener);
-  }
-
-  public static abstract interface BrowseFrameLayout.OnChildFocusListener {
-    method public abstract void onRequestChildFocus(android.view.View, android.view.View);
-    method public abstract boolean onRequestFocusInDescendants(int, android.graphics.Rect);
-  }
-
-  public static abstract interface BrowseFrameLayout.OnFocusSearchListener {
-    method public abstract android.view.View onFocusSearch(android.view.View, int);
-  }
-
-  public final class ClassPresenterSelector extends android.support.v17.leanback.widget.PresenterSelector {
-    ctor public ClassPresenterSelector();
-    method public android.support.v17.leanback.widget.ClassPresenterSelector addClassPresenter(java.lang.Class<?>, android.support.v17.leanback.widget.Presenter);
-    method public android.support.v17.leanback.widget.ClassPresenterSelector addClassPresenterSelector(java.lang.Class<?>, android.support.v17.leanback.widget.PresenterSelector);
-    method public android.support.v17.leanback.widget.Presenter getPresenter(java.lang.Object);
-  }
-
-  public class ControlButtonPresenterSelector extends android.support.v17.leanback.widget.PresenterSelector {
-    ctor public ControlButtonPresenterSelector();
-    method public android.support.v17.leanback.widget.Presenter getPresenter(java.lang.Object);
-    method public android.support.v17.leanback.widget.Presenter getPrimaryPresenter();
-    method public android.support.v17.leanback.widget.Presenter getSecondaryPresenter();
-  }
-
-  public class CursorObjectAdapter extends android.support.v17.leanback.widget.ObjectAdapter {
-    ctor public CursorObjectAdapter(android.support.v17.leanback.widget.PresenterSelector);
-    ctor public CursorObjectAdapter(android.support.v17.leanback.widget.Presenter);
-    ctor public CursorObjectAdapter();
-    method public void changeCursor(android.database.Cursor);
-    method public void close();
-    method public java.lang.Object get(int);
-    method public final android.database.Cursor getCursor();
-    method public final android.support.v17.leanback.database.CursorMapper getMapper();
-    method protected final void invalidateCache(int);
-    method protected final void invalidateCache(int, int);
-    method public boolean isClosed();
-    method protected void onCursorChanged();
-    method protected void onMapperChanged();
-    method public final void setMapper(android.support.v17.leanback.database.CursorMapper);
-    method public int size();
-    method public android.database.Cursor swapCursor(android.database.Cursor);
-  }
-
-  public class DetailsOverviewLogoPresenter extends android.support.v17.leanback.widget.Presenter {
-    ctor public DetailsOverviewLogoPresenter();
-    method public boolean isBoundToImage(android.support.v17.leanback.widget.DetailsOverviewLogoPresenter.ViewHolder, android.support.v17.leanback.widget.DetailsOverviewRow);
-    method public void onBindViewHolder(android.support.v17.leanback.widget.Presenter.ViewHolder, java.lang.Object);
-    method public android.view.View onCreateView(android.view.ViewGroup);
-    method public android.support.v17.leanback.widget.Presenter.ViewHolder onCreateViewHolder(android.view.ViewGroup);
-    method public void onUnbindViewHolder(android.support.v17.leanback.widget.Presenter.ViewHolder);
-    method public void setContext(android.support.v17.leanback.widget.DetailsOverviewLogoPresenter.ViewHolder, android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter.ViewHolder, android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter);
-  }
-
-  public static class DetailsOverviewLogoPresenter.ViewHolder extends android.support.v17.leanback.widget.Presenter.ViewHolder {
-    ctor public DetailsOverviewLogoPresenter.ViewHolder(android.view.View);
-    method public android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter getParentPresenter();
-    method public android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter.ViewHolder getParentViewHolder();
-    method public boolean isSizeFromDrawableIntrinsic();
-    method public void setSizeFromDrawableIntrinsic(boolean);
-    field protected android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter mParentPresenter;
-    field protected android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter.ViewHolder mParentViewHolder;
-  }
-
-  public class DetailsOverviewRow extends android.support.v17.leanback.widget.Row {
-    ctor public DetailsOverviewRow(java.lang.Object);
-    method public final deprecated void addAction(android.support.v17.leanback.widget.Action);
-    method public final deprecated void addAction(int, android.support.v17.leanback.widget.Action);
-    method public android.support.v17.leanback.widget.Action getActionForKeyCode(int);
-    method public final deprecated java.util.List<android.support.v17.leanback.widget.Action> getActions();
-    method public final android.support.v17.leanback.widget.ObjectAdapter getActionsAdapter();
-    method public final android.graphics.drawable.Drawable getImageDrawable();
-    method public final java.lang.Object getItem();
-    method public boolean isImageScaleUpAllowed();
-    method public final deprecated boolean removeAction(android.support.v17.leanback.widget.Action);
-    method public final void setActionsAdapter(android.support.v17.leanback.widget.ObjectAdapter);
-    method public final void setImageBitmap(android.content.Context, android.graphics.Bitmap);
-    method public final void setImageDrawable(android.graphics.drawable.Drawable);
-    method public void setImageScaleUpAllowed(boolean);
-    method public final void setItem(java.lang.Object);
-  }
-
-  public static class DetailsOverviewRow.Listener {
-    ctor public DetailsOverviewRow.Listener();
-    method public void onActionsAdapterChanged(android.support.v17.leanback.widget.DetailsOverviewRow);
-    method public void onImageDrawableChanged(android.support.v17.leanback.widget.DetailsOverviewRow);
-    method public void onItemChanged(android.support.v17.leanback.widget.DetailsOverviewRow);
-  }
-
-  public deprecated class DetailsOverviewRowPresenter extends android.support.v17.leanback.widget.RowPresenter {
-    ctor public DetailsOverviewRowPresenter(android.support.v17.leanback.widget.Presenter);
-    method protected android.support.v17.leanback.widget.RowPresenter.ViewHolder createRowViewHolder(android.view.ViewGroup);
-    method public int getBackgroundColor();
-    method public android.support.v17.leanback.widget.OnActionClickedListener getOnActionClickedListener();
-    method public boolean isStyleLarge();
-    method public final boolean isUsingDefaultSelectEffect();
-    method public void setBackgroundColor(int);
-    method public void setOnActionClickedListener(android.support.v17.leanback.widget.OnActionClickedListener);
-    method public final void setSharedElementEnterTransition(android.app.Activity, java.lang.String, long);
-    method public final void setSharedElementEnterTransition(android.app.Activity, java.lang.String);
-    method public void setStyleLarge(boolean);
-  }
-
-  public final class DetailsOverviewRowPresenter.ViewHolder extends android.support.v17.leanback.widget.RowPresenter.ViewHolder {
-    ctor public DetailsOverviewRowPresenter.ViewHolder(android.view.View, android.support.v17.leanback.widget.Presenter);
-    field public final android.support.v17.leanback.widget.Presenter.ViewHolder mDetailsDescriptionViewHolder;
-  }
-
-  public class DetailsParallax extends android.support.v17.leanback.widget.RecyclerViewParallax {
-    ctor public DetailsParallax();
-    method public android.support.v17.leanback.widget.Parallax.IntProperty getOverviewRowBottom();
-    method public android.support.v17.leanback.widget.Parallax.IntProperty getOverviewRowTop();
-  }
-
-  public abstract class DiffCallback<Value> {
-    ctor public DiffCallback();
-    method public abstract boolean areContentsTheSame(Value, Value);
-    method public abstract boolean areItemsTheSame(Value, Value);
-    method public java.lang.Object getChangePayload(Value, Value);
-  }
-
-  public class DividerPresenter extends android.support.v17.leanback.widget.Presenter {
-    ctor public DividerPresenter();
-    method public void onBindViewHolder(android.support.v17.leanback.widget.Presenter.ViewHolder, java.lang.Object);
-    method public android.support.v17.leanback.widget.Presenter.ViewHolder onCreateViewHolder(android.view.ViewGroup);
-    method public void onUnbindViewHolder(android.support.v17.leanback.widget.Presenter.ViewHolder);
-  }
-
-  public class DividerRow extends android.support.v17.leanback.widget.Row {
-    ctor public DividerRow();
-    method public final boolean isRenderedAsRowView();
-  }
-
-  public abstract interface FacetProvider {
-    method public abstract java.lang.Object getFacet(java.lang.Class<?>);
-  }
-
-  public abstract interface FacetProviderAdapter {
-    method public abstract android.support.v17.leanback.widget.FacetProvider getFacetProvider(int);
-  }
-
-  public abstract interface FocusHighlight {
-    field public static final int ZOOM_FACTOR_LARGE = 3; // 0x3
-    field public static final int ZOOM_FACTOR_MEDIUM = 2; // 0x2
-    field public static final int ZOOM_FACTOR_NONE = 0; // 0x0
-    field public static final int ZOOM_FACTOR_SMALL = 1; // 0x1
-    field public static final int ZOOM_FACTOR_XSMALL = 4; // 0x4
-  }
-
-  public class FocusHighlightHelper {
-    ctor public FocusHighlightHelper();
-    method public static void setupBrowseItemFocusHighlight(android.support.v17.leanback.widget.ItemBridgeAdapter, int, boolean);
-    method public static deprecated void setupHeaderItemFocusHighlight(android.support.v17.leanback.widget.VerticalGridView);
-    method public static deprecated void setupHeaderItemFocusHighlight(android.support.v17.leanback.widget.VerticalGridView, boolean);
-    method public static void setupHeaderItemFocusHighlight(android.support.v17.leanback.widget.ItemBridgeAdapter);
-    method public static void setupHeaderItemFocusHighlight(android.support.v17.leanback.widget.ItemBridgeAdapter, boolean);
-  }
-
-  public abstract interface FragmentAnimationProvider {
-    method public abstract void onImeAppearing(java.util.List<android.animation.Animator>);
-    method public abstract void onImeDisappearing(java.util.List<android.animation.Animator>);
-  }
-
-  public class FullWidthDetailsOverviewRowPresenter extends android.support.v17.leanback.widget.RowPresenter {
-    ctor public FullWidthDetailsOverviewRowPresenter(android.support.v17.leanback.widget.Presenter);
-    ctor public FullWidthDetailsOverviewRowPresenter(android.support.v17.leanback.widget.Presenter, android.support.v17.leanback.widget.DetailsOverviewLogoPresenter);
-    method protected android.support.v17.leanback.widget.RowPresenter.ViewHolder createRowViewHolder(android.view.ViewGroup);
-    method public final int getActionsBackgroundColor();
-    method public final int getAlignmentMode();
-    method public final int getBackgroundColor();
-    method public final int getInitialState();
-    method protected int getLayoutResourceId();
-    method public android.support.v17.leanback.widget.OnActionClickedListener getOnActionClickedListener();
-    method public final boolean isParticipatingEntranceTransition();
-    method public final boolean isUsingDefaultSelectEffect();
-    method public final void notifyOnBindLogo(android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter.ViewHolder);
-    method protected void onLayoutLogo(android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter.ViewHolder, int, boolean);
-    method protected void onLayoutOverviewFrame(android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter.ViewHolder, int, boolean);
-    method protected void onStateChanged(android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter.ViewHolder, int);
-    method public final void setActionsBackgroundColor(int);
-    method public final void setAlignmentMode(int);
-    method public final void setBackgroundColor(int);
-    method public final void setInitialState(int);
-    method public final void setListener(android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter.Listener);
-    method public void setOnActionClickedListener(android.support.v17.leanback.widget.OnActionClickedListener);
-    method public final void setParticipatingEntranceTransition(boolean);
-    method public final void setState(android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter.ViewHolder, int);
-    field public static final int ALIGN_MODE_MIDDLE = 1; // 0x1
-    field public static final int ALIGN_MODE_START = 0; // 0x0
-    field public static final int STATE_FULL = 1; // 0x1
-    field public static final int STATE_HALF = 0; // 0x0
-    field public static final int STATE_SMALL = 2; // 0x2
-    field protected int mInitialState;
-  }
-
-  public static abstract class FullWidthDetailsOverviewRowPresenter.Listener {
-    ctor public FullWidthDetailsOverviewRowPresenter.Listener();
-    method public void onBindLogo(android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter.ViewHolder);
-  }
-
-  public class FullWidthDetailsOverviewRowPresenter.ViewHolder extends android.support.v17.leanback.widget.RowPresenter.ViewHolder {
-    ctor public FullWidthDetailsOverviewRowPresenter.ViewHolder(android.view.View, android.support.v17.leanback.widget.Presenter, android.support.v17.leanback.widget.DetailsOverviewLogoPresenter);
-    method protected android.support.v17.leanback.widget.DetailsOverviewRow.Listener createRowListener();
-    method public final android.view.ViewGroup getActionsRow();
-    method public final android.view.ViewGroup getDetailsDescriptionFrame();
-    method public final android.support.v17.leanback.widget.Presenter.ViewHolder getDetailsDescriptionViewHolder();
-    method public final android.support.v17.leanback.widget.DetailsOverviewLogoPresenter.ViewHolder getLogoViewHolder();
-    method public final android.view.ViewGroup getOverviewView();
-    method public final int getState();
-    field protected final android.support.v17.leanback.widget.DetailsOverviewRow.Listener mRowListener;
-  }
-
-  public class FullWidthDetailsOverviewRowPresenter.ViewHolder.DetailsOverviewRowListener extends android.support.v17.leanback.widget.DetailsOverviewRow.Listener {
-    ctor public FullWidthDetailsOverviewRowPresenter.ViewHolder.DetailsOverviewRowListener();
-  }
-
-  public class FullWidthDetailsOverviewSharedElementHelper extends android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter.Listener {
-    ctor public FullWidthDetailsOverviewSharedElementHelper();
-    method public boolean getAutoStartSharedElementTransition();
-    method public void setAutoStartSharedElementTransition(boolean);
-    method public void setSharedElementEnterTransition(android.app.Activity, java.lang.String);
-    method public void setSharedElementEnterTransition(android.app.Activity, java.lang.String, long);
-    method public void startPostponedEnterTransition();
-  }
-
-  public class GuidanceStylist implements android.support.v17.leanback.widget.FragmentAnimationProvider {
-    ctor public GuidanceStylist();
-    method public android.widget.TextView getBreadcrumbView();
-    method public android.widget.TextView getDescriptionView();
-    method public android.widget.ImageView getIconView();
-    method public android.widget.TextView getTitleView();
-    method public android.view.View onCreateView(android.view.LayoutInflater, android.view.ViewGroup, android.support.v17.leanback.widget.GuidanceStylist.Guidance);
-    method public void onDestroyView();
-    method public void onImeAppearing(java.util.List<android.animation.Animator>);
-    method public void onImeDisappearing(java.util.List<android.animation.Animator>);
-    method public int onProvideLayoutId();
-  }
-
-  public static class GuidanceStylist.Guidance {
-    ctor public GuidanceStylist.Guidance(java.lang.String, java.lang.String, java.lang.String, android.graphics.drawable.Drawable);
-    method public java.lang.String getBreadcrumb();
-    method public java.lang.String getDescription();
-    method public android.graphics.drawable.Drawable getIconDrawable();
-    method public java.lang.String getTitle();
-  }
-
-  public class GuidedAction extends android.support.v17.leanback.widget.Action {
-    ctor protected GuidedAction();
-    method public int getCheckSetId();
-    method public java.lang.CharSequence getDescription();
-    method public int getDescriptionEditInputType();
-    method public int getDescriptionInputType();
-    method public java.lang.CharSequence getEditDescription();
-    method public int getEditInputType();
-    method public java.lang.CharSequence getEditTitle();
-    method public int getInputType();
-    method public android.content.Intent getIntent();
-    method public java.util.List<android.support.v17.leanback.widget.GuidedAction> getSubActions();
-    method public java.lang.CharSequence getTitle();
-    method public boolean hasEditableActivatorView();
-    method public boolean hasMultilineDescription();
-    method public boolean hasNext();
-    method public boolean hasSubActions();
-    method public boolean hasTextEditable();
-    method public boolean infoOnly();
-    method public final boolean isAutoSaveRestoreEnabled();
-    method public boolean isChecked();
-    method public boolean isDescriptionEditable();
-    method public boolean isEditTitleUsed();
-    method public boolean isEditable();
-    method public boolean isEnabled();
-    method public boolean isFocusable();
-    method public void onRestoreInstanceState(android.os.Bundle, java.lang.String);
-    method public void onSaveInstanceState(android.os.Bundle, java.lang.String);
-    method public void setChecked(boolean);
-    method public void setDescription(java.lang.CharSequence);
-    method public void setEditDescription(java.lang.CharSequence);
-    method public void setEditTitle(java.lang.CharSequence);
-    method public void setEnabled(boolean);
-    method public void setFocusable(boolean);
-    method public void setIntent(android.content.Intent);
-    method public void setSubActions(java.util.List<android.support.v17.leanback.widget.GuidedAction>);
-    method public void setTitle(java.lang.CharSequence);
-    field public static final long ACTION_ID_CANCEL = -5L; // 0xfffffffffffffffbL
-    field public static final long ACTION_ID_CONTINUE = -7L; // 0xfffffffffffffff9L
-    field public static final long ACTION_ID_CURRENT = -3L; // 0xfffffffffffffffdL
-    field public static final long ACTION_ID_FINISH = -6L; // 0xfffffffffffffffaL
-    field public static final long ACTION_ID_NEXT = -2L; // 0xfffffffffffffffeL
-    field public static final long ACTION_ID_NO = -9L; // 0xfffffffffffffff7L
-    field public static final long ACTION_ID_OK = -4L; // 0xfffffffffffffffcL
-    field public static final long ACTION_ID_YES = -8L; // 0xfffffffffffffff8L
-    field public static final int CHECKBOX_CHECK_SET_ID = -1; // 0xffffffff
-    field public static final int DEFAULT_CHECK_SET_ID = 1; // 0x1
-    field public static final int NO_CHECK_SET = 0; // 0x0
-  }
-
-  public static class GuidedAction.Builder extends android.support.v17.leanback.widget.GuidedAction.BuilderBase {
-    ctor public deprecated GuidedAction.Builder();
-    ctor public GuidedAction.Builder(android.content.Context);
-    method public android.support.v17.leanback.widget.GuidedAction build();
-  }
-
-  public static abstract class GuidedAction.BuilderBase<B extends android.support.v17.leanback.widget.GuidedAction.BuilderBase> {
-    ctor public GuidedAction.BuilderBase(android.content.Context);
-    method protected final void applyValues(android.support.v17.leanback.widget.GuidedAction);
-    method public B autoSaveRestoreEnabled(boolean);
-    method public B checkSetId(int);
-    method public B checked(boolean);
-    method public B clickAction(long);
-    method public B description(java.lang.CharSequence);
-    method public B description(int);
-    method public B descriptionEditInputType(int);
-    method public B descriptionEditable(boolean);
-    method public B descriptionInputType(int);
-    method public B editDescription(java.lang.CharSequence);
-    method public B editDescription(int);
-    method public B editInputType(int);
-    method public B editTitle(java.lang.CharSequence);
-    method public B editTitle(int);
-    method public B editable(boolean);
-    method public B enabled(boolean);
-    method public B focusable(boolean);
-    method public android.content.Context getContext();
-    method public B hasEditableActivatorView(boolean);
-    method public B hasNext(boolean);
-    method public B icon(android.graphics.drawable.Drawable);
-    method public B icon(int);
-    method public deprecated B iconResourceId(int, android.content.Context);
-    method public B id(long);
-    method public B infoOnly(boolean);
-    method public B inputType(int);
-    method public B intent(android.content.Intent);
-    method public B multilineDescription(boolean);
-    method public B subActions(java.util.List<android.support.v17.leanback.widget.GuidedAction>);
-    method public B title(java.lang.CharSequence);
-    method public B title(int);
-  }
-
-  public class GuidedActionEditText extends android.widget.EditText implements android.support.v17.leanback.widget.ImeKeyMonitor {
-    ctor public GuidedActionEditText(android.content.Context);
-    ctor public GuidedActionEditText(android.content.Context, android.util.AttributeSet);
-    ctor public GuidedActionEditText(android.content.Context, android.util.AttributeSet, int);
-    method public void setImeKeyListener(android.support.v17.leanback.widget.ImeKeyMonitor.ImeKeyListener);
-  }
-
-  public class GuidedActionsStylist implements android.support.v17.leanback.widget.FragmentAnimationProvider {
-    ctor public GuidedActionsStylist();
-    method public void collapseAction(boolean);
-    method public void expandAction(android.support.v17.leanback.widget.GuidedAction, boolean);
-    method public android.support.v17.leanback.widget.VerticalGridView getActionsGridView();
-    method public android.support.v17.leanback.widget.GuidedAction getExpandedAction();
-    method public int getItemViewType(android.support.v17.leanback.widget.GuidedAction);
-    method public android.support.v17.leanback.widget.VerticalGridView getSubActionsGridView();
-    method public final boolean isBackKeyToCollapseActivatorView();
-    method public final boolean isBackKeyToCollapseSubActions();
-    method public boolean isButtonActions();
-    method public boolean isExpandTransitionSupported();
-    method public boolean isExpanded();
-    method public boolean isInExpandTransition();
-    method public boolean isSubActionsExpanded();
-    method public void onAnimateItemChecked(android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder, boolean);
-    method public void onAnimateItemFocused(android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder, boolean);
-    method public void onAnimateItemPressed(android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder, boolean);
-    method public void onAnimateItemPressedCancelled(android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder);
-    method public void onBindActivatorView(android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder, android.support.v17.leanback.widget.GuidedAction);
-    method public void onBindCheckMarkView(android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder, android.support.v17.leanback.widget.GuidedAction);
-    method public void onBindChevronView(android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder, android.support.v17.leanback.widget.GuidedAction);
-    method public void onBindViewHolder(android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder, android.support.v17.leanback.widget.GuidedAction);
-    method public android.view.View onCreateView(android.view.LayoutInflater, android.view.ViewGroup);
-    method public android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder onCreateViewHolder(android.view.ViewGroup);
-    method public android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder onCreateViewHolder(android.view.ViewGroup, int);
-    method public void onDestroyView();
-    method protected deprecated void onEditingModeChange(android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder, android.support.v17.leanback.widget.GuidedAction, boolean);
-    method protected void onEditingModeChange(android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder, boolean, boolean);
-    method public void onImeAppearing(java.util.List<android.animation.Animator>);
-    method public void onImeDisappearing(java.util.List<android.animation.Animator>);
-    method public int onProvideItemLayoutId();
-    method public int onProvideItemLayoutId(int);
-    method public int onProvideLayoutId();
-    method public boolean onUpdateActivatorView(android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder, android.support.v17.leanback.widget.GuidedAction);
-    method public void onUpdateExpandedViewHolder(android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder);
-    method public void openInEditMode(android.support.v17.leanback.widget.GuidedAction);
-    method public void setAsButtonActions();
-    method public final void setBackKeyToCollapseActivatorView(boolean);
-    method public final void setBackKeyToCollapseSubActions(boolean);
-    method public deprecated void setEditingMode(android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder, android.support.v17.leanback.widget.GuidedAction, boolean);
-    method public deprecated void setExpandedViewHolder(android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder);
-    method protected void setupImeOptions(android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder, android.support.v17.leanback.widget.GuidedAction);
-    method public deprecated void startExpandedTransition(android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder);
-    field public static final int VIEW_TYPE_DATE_PICKER = 1; // 0x1
-    field public static final int VIEW_TYPE_DEFAULT = 0; // 0x0
-  }
-
-  public static class GuidedActionsStylist.ViewHolder extends android.support.v7.widget.RecyclerView.ViewHolder implements android.support.v17.leanback.widget.FacetProvider {
-    ctor public GuidedActionsStylist.ViewHolder(android.view.View);
-    ctor public GuidedActionsStylist.ViewHolder(android.view.View, boolean);
-    method public android.support.v17.leanback.widget.GuidedAction getAction();
-    method public android.widget.ImageView getCheckmarkView();
-    method public android.widget.ImageView getChevronView();
-    method public android.view.View getContentView();
-    method public android.widget.TextView getDescriptionView();
-    method public android.widget.EditText getEditableDescriptionView();
-    method public android.widget.EditText getEditableTitleView();
-    method public android.view.View getEditingView();
-    method public java.lang.Object getFacet(java.lang.Class<?>);
-    method public android.widget.ImageView getIconView();
-    method public android.widget.TextView getTitleView();
-    method public boolean isInEditing();
-    method public boolean isInEditingActivatorView();
-    method public boolean isInEditingDescription();
-    method public boolean isInEditingText();
-    method public boolean isInEditingTitle();
-    method public boolean isSubAction();
-  }
-
-  public class GuidedDatePickerAction extends android.support.v17.leanback.widget.GuidedAction {
-    ctor public GuidedDatePickerAction();
-    method public long getDate();
-    method public java.lang.String getDatePickerFormat();
-    method public long getMaxDate();
-    method public long getMinDate();
-    method public void setDate(long);
-  }
-
-  public static final class GuidedDatePickerAction.Builder extends android.support.v17.leanback.widget.GuidedDatePickerAction.BuilderBase {
-    ctor public GuidedDatePickerAction.Builder(android.content.Context);
-    method public android.support.v17.leanback.widget.GuidedDatePickerAction build();
-  }
-
-  public static abstract class GuidedDatePickerAction.BuilderBase<B extends android.support.v17.leanback.widget.GuidedDatePickerAction.BuilderBase> extends android.support.v17.leanback.widget.GuidedAction.BuilderBase {
-    ctor public GuidedDatePickerAction.BuilderBase(android.content.Context);
-    method protected final void applyDatePickerValues(android.support.v17.leanback.widget.GuidedDatePickerAction);
-    method public B date(long);
-    method public B datePickerFormat(java.lang.String);
-    method public B maxDate(long);
-    method public B minDate(long);
-  }
-
-  public class HeaderItem {
-    ctor public HeaderItem(long, java.lang.String);
-    ctor public HeaderItem(java.lang.String);
-    method public java.lang.CharSequence getContentDescription();
-    method public java.lang.CharSequence getDescription();
-    method public final long getId();
-    method public final java.lang.String getName();
-    method public void setContentDescription(java.lang.CharSequence);
-    method public void setDescription(java.lang.CharSequence);
-  }
-
-  public class HorizontalGridView extends android.support.v17.leanback.widget.BaseGridView {
-    ctor public HorizontalGridView(android.content.Context);
-    ctor public HorizontalGridView(android.content.Context, android.util.AttributeSet);
-    ctor public HorizontalGridView(android.content.Context, android.util.AttributeSet, int);
-    method public final boolean getFadingLeftEdge();
-    method public final int getFadingLeftEdgeLength();
-    method public final int getFadingLeftEdgeOffset();
-    method public final boolean getFadingRightEdge();
-    method public final int getFadingRightEdgeLength();
-    method public final int getFadingRightEdgeOffset();
-    method protected void initAttributes(android.content.Context, android.util.AttributeSet);
-    method public final void setFadingLeftEdge(boolean);
-    method public final void setFadingLeftEdgeLength(int);
-    method public final void setFadingLeftEdgeOffset(int);
-    method public final void setFadingRightEdge(boolean);
-    method public final void setFadingRightEdgeLength(int);
-    method public final void setFadingRightEdgeOffset(int);
-    method public void setNumRows(int);
-    method public void setRowHeight(int);
-  }
-
-  public final class HorizontalHoverCardSwitcher extends android.support.v17.leanback.widget.PresenterSwitcher {
-    ctor public HorizontalHoverCardSwitcher();
-    method protected void insertView(android.view.View);
-    method public void select(android.support.v17.leanback.widget.HorizontalGridView, android.view.View, java.lang.Object);
-  }
-
-  public class ImageCardView extends android.support.v17.leanback.widget.BaseCardView {
-    ctor public deprecated ImageCardView(android.content.Context, int);
-    ctor public ImageCardView(android.content.Context, android.util.AttributeSet, int);
-    ctor public ImageCardView(android.content.Context);
-    ctor public ImageCardView(android.content.Context, android.util.AttributeSet);
-    method public android.graphics.drawable.Drawable getBadgeImage();
-    method public java.lang.CharSequence getContentText();
-    method public android.graphics.drawable.Drawable getInfoAreaBackground();
-    method public android.graphics.drawable.Drawable getMainImage();
-    method public final android.widget.ImageView getMainImageView();
-    method public java.lang.CharSequence getTitleText();
-    method public void setBadgeImage(android.graphics.drawable.Drawable);
-    method public void setContentText(java.lang.CharSequence);
-    method public void setInfoAreaBackground(android.graphics.drawable.Drawable);
-    method public void setInfoAreaBackgroundColor(int);
-    method public void setMainImage(android.graphics.drawable.Drawable);
-    method public void setMainImage(android.graphics.drawable.Drawable, boolean);
-    method public void setMainImageAdjustViewBounds(boolean);
-    method public void setMainImageDimensions(int, int);
-    method public void setMainImageScaleType(android.widget.ImageView.ScaleType);
-    method public void setTitleText(java.lang.CharSequence);
-    field public static final int CARD_TYPE_FLAG_CONTENT = 2; // 0x2
-    field public static final int CARD_TYPE_FLAG_ICON_LEFT = 8; // 0x8
-    field public static final int CARD_TYPE_FLAG_ICON_RIGHT = 4; // 0x4
-    field public static final int CARD_TYPE_FLAG_IMAGE_ONLY = 0; // 0x0
-    field public static final int CARD_TYPE_FLAG_TITLE = 1; // 0x1
-  }
-
-  public abstract interface ImeKeyMonitor {
-    method public abstract void setImeKeyListener(android.support.v17.leanback.widget.ImeKeyMonitor.ImeKeyListener);
-  }
-
-  public static abstract interface ImeKeyMonitor.ImeKeyListener {
-    method public abstract boolean onKeyPreIme(android.widget.EditText, int, android.view.KeyEvent);
-  }
-
-  public final class ItemAlignmentFacet {
-    ctor public ItemAlignmentFacet();
-    method public android.support.v17.leanback.widget.ItemAlignmentFacet.ItemAlignmentDef[] getAlignmentDefs();
-    method public boolean isMultiAlignment();
-    method public void setAlignmentDefs(android.support.v17.leanback.widget.ItemAlignmentFacet.ItemAlignmentDef[]);
-    field public static final float ITEM_ALIGN_OFFSET_PERCENT_DISABLED = -1.0f;
-  }
-
-  public static class ItemAlignmentFacet.ItemAlignmentDef {
-    ctor public ItemAlignmentFacet.ItemAlignmentDef();
-    method public final int getItemAlignmentFocusViewId();
-    method public final int getItemAlignmentOffset();
-    method public final float getItemAlignmentOffsetPercent();
-    method public final int getItemAlignmentViewId();
-    method public boolean isAlignedToTextViewBaseLine();
-    method public final boolean isItemAlignmentOffsetWithPadding();
-    method public final void setAlignedToTextViewBaseline(boolean);
-    method public final void setItemAlignmentFocusViewId(int);
-    method public final void setItemAlignmentOffset(int);
-    method public final void setItemAlignmentOffsetPercent(float);
-    method public final void setItemAlignmentOffsetWithPadding(boolean);
-    method public final void setItemAlignmentViewId(int);
-  }
-
-  public class ItemBridgeAdapter extends android.support.v7.widget.RecyclerView.Adapter implements android.support.v17.leanback.widget.FacetProviderAdapter {
-    ctor public ItemBridgeAdapter(android.support.v17.leanback.widget.ObjectAdapter, android.support.v17.leanback.widget.PresenterSelector);
-    ctor public ItemBridgeAdapter(android.support.v17.leanback.widget.ObjectAdapter);
-    ctor public ItemBridgeAdapter();
-    method public void clear();
-    method public android.support.v17.leanback.widget.FacetProvider getFacetProvider(int);
-    method public int getItemCount();
-    method public java.util.ArrayList<android.support.v17.leanback.widget.Presenter> getPresenterMapper();
-    method public android.support.v17.leanback.widget.ItemBridgeAdapter.Wrapper getWrapper();
-    method protected void onAddPresenter(android.support.v17.leanback.widget.Presenter, int);
-    method protected void onAttachedToWindow(android.support.v17.leanback.widget.ItemBridgeAdapter.ViewHolder);
-    method protected void onBind(android.support.v17.leanback.widget.ItemBridgeAdapter.ViewHolder);
-    method public final void onBindViewHolder(android.support.v7.widget.RecyclerView.ViewHolder, int);
-    method public final void onBindViewHolder(android.support.v7.widget.RecyclerView.ViewHolder, int, java.util.List);
-    method protected void onCreate(android.support.v17.leanback.widget.ItemBridgeAdapter.ViewHolder);
-    method public final android.support.v7.widget.RecyclerView.ViewHolder onCreateViewHolder(android.view.ViewGroup, int);
-    method protected void onDetachedFromWindow(android.support.v17.leanback.widget.ItemBridgeAdapter.ViewHolder);
-    method protected void onUnbind(android.support.v17.leanback.widget.ItemBridgeAdapter.ViewHolder);
-    method public final void onViewAttachedToWindow(android.support.v7.widget.RecyclerView.ViewHolder);
-    method public final void onViewDetachedFromWindow(android.support.v7.widget.RecyclerView.ViewHolder);
-    method public final void onViewRecycled(android.support.v7.widget.RecyclerView.ViewHolder);
-    method public void setAdapter(android.support.v17.leanback.widget.ObjectAdapter);
-    method public void setAdapterListener(android.support.v17.leanback.widget.ItemBridgeAdapter.AdapterListener);
-    method public void setPresenter(android.support.v17.leanback.widget.PresenterSelector);
-    method public void setPresenterMapper(java.util.ArrayList<android.support.v17.leanback.widget.Presenter>);
-    method public void setWrapper(android.support.v17.leanback.widget.ItemBridgeAdapter.Wrapper);
-  }
-
-  public static class ItemBridgeAdapter.AdapterListener {
-    ctor public ItemBridgeAdapter.AdapterListener();
-    method public void onAddPresenter(android.support.v17.leanback.widget.Presenter, int);
-    method public void onAttachedToWindow(android.support.v17.leanback.widget.ItemBridgeAdapter.ViewHolder);
-    method public void onBind(android.support.v17.leanback.widget.ItemBridgeAdapter.ViewHolder);
-    method public void onBind(android.support.v17.leanback.widget.ItemBridgeAdapter.ViewHolder, java.util.List);
-    method public void onCreate(android.support.v17.leanback.widget.ItemBridgeAdapter.ViewHolder);
-    method public void onDetachedFromWindow(android.support.v17.leanback.widget.ItemBridgeAdapter.ViewHolder);
-    method public void onUnbind(android.support.v17.leanback.widget.ItemBridgeAdapter.ViewHolder);
-  }
-
-  public class ItemBridgeAdapter.ViewHolder extends android.support.v7.widget.RecyclerView.ViewHolder implements android.support.v17.leanback.widget.FacetProvider {
-    method public final java.lang.Object getExtraObject();
-    method public java.lang.Object getFacet(java.lang.Class<?>);
-    method public final java.lang.Object getItem();
-    method public final android.support.v17.leanback.widget.Presenter getPresenter();
-    method public final android.support.v17.leanback.widget.Presenter.ViewHolder getViewHolder();
-    method public void setExtraObject(java.lang.Object);
-  }
-
-  public static abstract class ItemBridgeAdapter.Wrapper {
-    ctor public ItemBridgeAdapter.Wrapper();
-    method public abstract android.view.View createWrapper(android.view.View);
-    method public abstract void wrap(android.view.View, android.view.View);
-  }
-
-  public class ItemBridgeAdapterShadowOverlayWrapper extends android.support.v17.leanback.widget.ItemBridgeAdapter.Wrapper {
-    ctor public ItemBridgeAdapterShadowOverlayWrapper(android.support.v17.leanback.widget.ShadowOverlayHelper);
-    method public android.view.View createWrapper(android.view.View);
-    method public void wrap(android.view.View, android.view.View);
-  }
-
-  public class ListRow extends android.support.v17.leanback.widget.Row {
-    ctor public ListRow(android.support.v17.leanback.widget.HeaderItem, android.support.v17.leanback.widget.ObjectAdapter);
-    ctor public ListRow(long, android.support.v17.leanback.widget.HeaderItem, android.support.v17.leanback.widget.ObjectAdapter);
-    ctor public ListRow(android.support.v17.leanback.widget.ObjectAdapter);
-    method public final android.support.v17.leanback.widget.ObjectAdapter getAdapter();
-    method public java.lang.CharSequence getContentDescription();
-    method public void setContentDescription(java.lang.CharSequence);
-  }
-
-  public final class ListRowHoverCardView extends android.widget.LinearLayout {
-    ctor public ListRowHoverCardView(android.content.Context);
-    ctor public ListRowHoverCardView(android.content.Context, android.util.AttributeSet);
-    ctor public ListRowHoverCardView(android.content.Context, android.util.AttributeSet, int);
-    method public final java.lang.CharSequence getDescription();
-    method public final java.lang.CharSequence getTitle();
-    method public final void setDescription(java.lang.CharSequence);
-    method public final void setTitle(java.lang.CharSequence);
-  }
-
-  public class ListRowPresenter extends android.support.v17.leanback.widget.RowPresenter {
-    ctor public ListRowPresenter();
-    ctor public ListRowPresenter(int);
-    ctor public ListRowPresenter(int, boolean);
-    method protected void applySelectLevelToChild(android.support.v17.leanback.widget.ListRowPresenter.ViewHolder, android.view.View);
-    method public final boolean areChildRoundedCornersEnabled();
-    method protected android.support.v17.leanback.widget.RowPresenter.ViewHolder createRowViewHolder(android.view.ViewGroup);
-    method protected android.support.v17.leanback.widget.ShadowOverlayHelper.Options createShadowOverlayOptions();
-    method public final void enableChildRoundedCorners(boolean);
-    method public int getExpandedRowHeight();
-    method public final int getFocusZoomFactor();
-    method public final android.support.v17.leanback.widget.PresenterSelector getHoverCardPresenterSelector();
-    method public int getRecycledPoolSize(android.support.v17.leanback.widget.Presenter);
-    method public int getRowHeight();
-    method public final boolean getShadowEnabled();
-    method public final deprecated int getZoomFactor();
-    method public final boolean isFocusDimmerUsed();
-    method public final boolean isKeepChildForeground();
-    method public boolean isUsingDefaultListSelectEffect();
-    method public final boolean isUsingDefaultSelectEffect();
-    method public boolean isUsingDefaultShadow();
-    method public boolean isUsingOutlineClipping(android.content.Context);
-    method public boolean isUsingZOrder(android.content.Context);
-    method public void setExpandedRowHeight(int);
-    method public final void setHoverCardPresenterSelector(android.support.v17.leanback.widget.PresenterSelector);
-    method public final void setKeepChildForeground(boolean);
-    method public void setNumRows(int);
-    method public void setRecycledPoolSize(android.support.v17.leanback.widget.Presenter, int);
-    method public void setRowHeight(int);
-    method public final void setShadowEnabled(boolean);
-  }
-
-  public static class ListRowPresenter.SelectItemViewHolderTask extends android.support.v17.leanback.widget.Presenter.ViewHolderTask {
-    ctor public ListRowPresenter.SelectItemViewHolderTask(int);
-    method public int getItemPosition();
-    method public android.support.v17.leanback.widget.Presenter.ViewHolderTask getItemTask();
-    method public boolean isSmoothScroll();
-    method public void setItemPosition(int);
-    method public void setItemTask(android.support.v17.leanback.widget.Presenter.ViewHolderTask);
-    method public void setSmoothScroll(boolean);
-  }
-
-  public static class ListRowPresenter.ViewHolder extends android.support.v17.leanback.widget.RowPresenter.ViewHolder {
-    ctor public ListRowPresenter.ViewHolder(android.view.View, android.support.v17.leanback.widget.HorizontalGridView, android.support.v17.leanback.widget.ListRowPresenter);
-    method public final android.support.v17.leanback.widget.ItemBridgeAdapter getBridgeAdapter();
-    method public final android.support.v17.leanback.widget.HorizontalGridView getGridView();
-    method public android.support.v17.leanback.widget.Presenter.ViewHolder getItemViewHolder(int);
-    method public final android.support.v17.leanback.widget.ListRowPresenter getListRowPresenter();
-    method public int getSelectedPosition();
-  }
-
-  public final class ListRowView extends android.widget.LinearLayout {
-    ctor public ListRowView(android.content.Context);
-    ctor public ListRowView(android.content.Context, android.util.AttributeSet);
-    ctor public ListRowView(android.content.Context, android.util.AttributeSet, int);
-    method public android.support.v17.leanback.widget.HorizontalGridView getGridView();
-  }
-
-  public abstract interface MultiActionsProvider {
-    method public abstract android.support.v17.leanback.widget.MultiActionsProvider.MultiAction[] getActions();
-  }
-
-  public static class MultiActionsProvider.MultiAction {
-    ctor public MultiActionsProvider.MultiAction(long);
-    method public android.graphics.drawable.Drawable getCurrentDrawable();
-    method public android.graphics.drawable.Drawable[] getDrawables();
-    method public long getId();
-    method public int getIndex();
-    method public void incrementIndex();
-    method public void setDrawables(android.graphics.drawable.Drawable[]);
-    method public void setIndex(int);
-  }
-
-  public abstract class ObjectAdapter {
-    ctor public ObjectAdapter(android.support.v17.leanback.widget.PresenterSelector);
-    ctor public ObjectAdapter(android.support.v17.leanback.widget.Presenter);
-    ctor public ObjectAdapter();
-    method public abstract java.lang.Object get(int);
-    method public long getId(int);
-    method public final android.support.v17.leanback.widget.Presenter getPresenter(java.lang.Object);
-    method public final android.support.v17.leanback.widget.PresenterSelector getPresenterSelector();
-    method public final boolean hasStableIds();
-    method public boolean isImmediateNotifySupported();
-    method protected final void notifyChanged();
-    method protected final void notifyItemMoved(int, int);
-    method public final void notifyItemRangeChanged(int, int);
-    method public final void notifyItemRangeChanged(int, int, java.lang.Object);
-    method protected final void notifyItemRangeInserted(int, int);
-    method protected final void notifyItemRangeRemoved(int, int);
-    method protected void onHasStableIdsChanged();
-    method protected void onPresenterSelectorChanged();
-    method public final void registerObserver(android.support.v17.leanback.widget.ObjectAdapter.DataObserver);
-    method public final void setHasStableIds(boolean);
-    method public final void setPresenterSelector(android.support.v17.leanback.widget.PresenterSelector);
-    method public abstract int size();
-    method public final void unregisterAllObservers();
-    method public final void unregisterObserver(android.support.v17.leanback.widget.ObjectAdapter.DataObserver);
-    field public static final int NO_ID = -1; // 0xffffffff
-  }
-
-  public static abstract class ObjectAdapter.DataObserver {
-    ctor public ObjectAdapter.DataObserver();
-    method public void onChanged();
-    method public void onItemMoved(int, int);
-    method public void onItemRangeChanged(int, int);
-    method public void onItemRangeChanged(int, int, java.lang.Object);
-    method public void onItemRangeInserted(int, int);
-    method public void onItemRangeRemoved(int, int);
-  }
-
-  public abstract interface OnActionClickedListener {
-    method public abstract void onActionClicked(android.support.v17.leanback.widget.Action);
-  }
-
-  public abstract interface OnChildLaidOutListener {
-    method public abstract void onChildLaidOut(android.view.ViewGroup, android.view.View, int, long);
-  }
-
-  public abstract deprecated interface OnChildSelectedListener {
-    method public abstract void onChildSelected(android.view.ViewGroup, android.view.View, int, long);
-  }
-
-  public abstract class OnChildViewHolderSelectedListener {
-    ctor public OnChildViewHolderSelectedListener();
-    method public void onChildViewHolderSelected(android.support.v7.widget.RecyclerView, android.support.v7.widget.RecyclerView.ViewHolder, int, int);
-    method public void onChildViewHolderSelectedAndPositioned(android.support.v7.widget.RecyclerView, android.support.v7.widget.RecyclerView.ViewHolder, int, int);
-  }
-
-  public abstract interface OnItemViewClickedListener implements android.support.v17.leanback.widget.BaseOnItemViewClickedListener {
-  }
-
-  public abstract interface OnItemViewSelectedListener implements android.support.v17.leanback.widget.BaseOnItemViewSelectedListener {
-  }
-
-  public class PageRow extends android.support.v17.leanback.widget.Row {
-    ctor public PageRow(android.support.v17.leanback.widget.HeaderItem);
-    method public final boolean isRenderedAsRowView();
-  }
-
-  public abstract class Parallax<PropertyT extends android.util.Property> {
-    ctor public Parallax();
-    method public android.support.v17.leanback.widget.ParallaxEffect addEffect(android.support.v17.leanback.widget.Parallax.PropertyMarkerValue...);
-    method public final PropertyT addProperty(java.lang.String);
-    method public abstract PropertyT createProperty(java.lang.String, int);
-    method public java.util.List<android.support.v17.leanback.widget.ParallaxEffect> getEffects();
-    method public abstract float getMaxValue();
-    method public final java.util.List<PropertyT> getProperties();
-    method public void removeAllEffects();
-    method public void removeEffect(android.support.v17.leanback.widget.ParallaxEffect);
-    method public void updateValues();
-  }
-
-  public static class Parallax.FloatProperty extends android.util.Property {
-    ctor public Parallax.FloatProperty(java.lang.String, int);
-    method public final android.support.v17.leanback.widget.Parallax.PropertyMarkerValue at(float, float);
-    method public final android.support.v17.leanback.widget.Parallax.PropertyMarkerValue atAbsolute(float);
-    method public final android.support.v17.leanback.widget.Parallax.PropertyMarkerValue atFraction(float);
-    method public final android.support.v17.leanback.widget.Parallax.PropertyMarkerValue atMax();
-    method public final android.support.v17.leanback.widget.Parallax.PropertyMarkerValue atMin();
-    method public final java.lang.Float get(android.support.v17.leanback.widget.Parallax);
-    method public final int getIndex();
-    method public final float getValue(android.support.v17.leanback.widget.Parallax);
-    method public final void set(android.support.v17.leanback.widget.Parallax, java.lang.Float);
-    method public final void setValue(android.support.v17.leanback.widget.Parallax, float);
-    field public static final float UNKNOWN_AFTER = 3.4028235E38f;
-    field public static final float UNKNOWN_BEFORE = -3.4028235E38f;
-  }
-
-  public static class Parallax.IntProperty extends android.util.Property {
-    ctor public Parallax.IntProperty(java.lang.String, int);
-    method public final android.support.v17.leanback.widget.Parallax.PropertyMarkerValue at(int, float);
-    method public final android.support.v17.leanback.widget.Parallax.PropertyMarkerValue atAbsolute(int);
-    method public final android.support.v17.leanback.widget.Parallax.PropertyMarkerValue atFraction(float);
-    method public final android.support.v17.leanback.widget.Parallax.PropertyMarkerValue atMax();
-    method public final android.support.v17.leanback.widget.Parallax.PropertyMarkerValue atMin();
-    method public final java.lang.Integer get(android.support.v17.leanback.widget.Parallax);
-    method public final int getIndex();
-    method public final int getValue(android.support.v17.leanback.widget.Parallax);
-    method public final void set(android.support.v17.leanback.widget.Parallax, java.lang.Integer);
-    method public final void setValue(android.support.v17.leanback.widget.Parallax, int);
-    field public static final int UNKNOWN_AFTER = 2147483647; // 0x7fffffff
-    field public static final int UNKNOWN_BEFORE = -2147483648; // 0x80000000
-  }
-
-  public static class Parallax.PropertyMarkerValue<PropertyT> {
-    ctor public Parallax.PropertyMarkerValue(PropertyT);
-    method public PropertyT getProperty();
-  }
-
-  public abstract class ParallaxEffect {
-    method public final void addTarget(android.support.v17.leanback.widget.ParallaxTarget);
-    method public final java.util.List<android.support.v17.leanback.widget.Parallax.PropertyMarkerValue> getPropertyRanges();
-    method public final java.util.List<android.support.v17.leanback.widget.ParallaxTarget> getTargets();
-    method public final void performMapping(android.support.v17.leanback.widget.Parallax);
-    method public final void removeTarget(android.support.v17.leanback.widget.ParallaxTarget);
-    method public final void setPropertyRanges(android.support.v17.leanback.widget.Parallax.PropertyMarkerValue...);
-    method public final android.support.v17.leanback.widget.ParallaxEffect target(android.support.v17.leanback.widget.ParallaxTarget);
-    method public final android.support.v17.leanback.widget.ParallaxEffect target(java.lang.Object, android.animation.PropertyValuesHolder);
-    method public final <T, V extends java.lang.Number> android.support.v17.leanback.widget.ParallaxEffect target(T, android.util.Property<T, V>);
-  }
-
-  public abstract class ParallaxTarget {
-    ctor public ParallaxTarget();
-    method public void directUpdate(java.lang.Number);
-    method public boolean isDirectMapping();
-    method public void update(float);
-  }
-
-  public static final class ParallaxTarget.DirectPropertyTarget<T, V extends java.lang.Number> extends android.support.v17.leanback.widget.ParallaxTarget {
-    ctor public ParallaxTarget.DirectPropertyTarget(java.lang.Object, android.util.Property<T, V>);
-  }
-
-  public static final class ParallaxTarget.PropertyValuesHolderTarget extends android.support.v17.leanback.widget.ParallaxTarget {
-    ctor public ParallaxTarget.PropertyValuesHolderTarget(java.lang.Object, android.animation.PropertyValuesHolder);
-  }
-
-  public class PlaybackControlsRow extends android.support.v17.leanback.widget.Row {
-    ctor public PlaybackControlsRow(java.lang.Object);
-    ctor public PlaybackControlsRow();
-    method public android.support.v17.leanback.widget.Action getActionForKeyCode(int);
-    method public android.support.v17.leanback.widget.Action getActionForKeyCode(android.support.v17.leanback.widget.ObjectAdapter, int);
-    method public long getBufferedPosition();
-    method public deprecated int getBufferedProgress();
-    method public deprecated long getBufferedProgressLong();
-    method public long getCurrentPosition();
-    method public deprecated int getCurrentTime();
-    method public deprecated long getCurrentTimeLong();
-    method public long getDuration();
-    method public final android.graphics.drawable.Drawable getImageDrawable();
-    method public final java.lang.Object getItem();
-    method public final android.support.v17.leanback.widget.ObjectAdapter getPrimaryActionsAdapter();
-    method public final android.support.v17.leanback.widget.ObjectAdapter getSecondaryActionsAdapter();
-    method public deprecated int getTotalTime();
-    method public deprecated long getTotalTimeLong();
-    method public void setBufferedPosition(long);
-    method public deprecated void setBufferedProgress(int);
-    method public deprecated void setBufferedProgressLong(long);
-    method public void setCurrentPosition(long);
-    method public deprecated void setCurrentTime(int);
-    method public deprecated void setCurrentTimeLong(long);
-    method public void setDuration(long);
-    method public final void setImageBitmap(android.content.Context, android.graphics.Bitmap);
-    method public final void setImageDrawable(android.graphics.drawable.Drawable);
-    method public void setOnPlaybackProgressChangedListener(android.support.v17.leanback.widget.PlaybackControlsRow.OnPlaybackProgressCallback);
-    method public final void setPrimaryActionsAdapter(android.support.v17.leanback.widget.ObjectAdapter);
-    method public final void setSecondaryActionsAdapter(android.support.v17.leanback.widget.ObjectAdapter);
-    method public deprecated void setTotalTime(int);
-    method public deprecated void setTotalTimeLong(long);
-  }
-
-  public static class PlaybackControlsRow.ClosedCaptioningAction extends android.support.v17.leanback.widget.PlaybackControlsRow.MultiAction {
-    ctor public PlaybackControlsRow.ClosedCaptioningAction(android.content.Context);
-    ctor public PlaybackControlsRow.ClosedCaptioningAction(android.content.Context, int);
-    field public static final int INDEX_OFF = 0; // 0x0
-    field public static final int INDEX_ON = 1; // 0x1
-    field public static deprecated int OFF;
-    field public static deprecated int ON;
-  }
-
-  public static class PlaybackControlsRow.FastForwardAction extends android.support.v17.leanback.widget.PlaybackControlsRow.MultiAction {
-    ctor public PlaybackControlsRow.FastForwardAction(android.content.Context);
-    ctor public PlaybackControlsRow.FastForwardAction(android.content.Context, int);
-  }
-
-  public static class PlaybackControlsRow.HighQualityAction extends android.support.v17.leanback.widget.PlaybackControlsRow.MultiAction {
-    ctor public PlaybackControlsRow.HighQualityAction(android.content.Context);
-    ctor public PlaybackControlsRow.HighQualityAction(android.content.Context, int);
-    field public static final int INDEX_OFF = 0; // 0x0
-    field public static final int INDEX_ON = 1; // 0x1
-    field public static deprecated int OFF;
-    field public static deprecated int ON;
-  }
-
-  public static class PlaybackControlsRow.MoreActions extends android.support.v17.leanback.widget.Action {
-    ctor public PlaybackControlsRow.MoreActions(android.content.Context);
-  }
-
-  public static abstract class PlaybackControlsRow.MultiAction extends android.support.v17.leanback.widget.Action {
-    ctor public PlaybackControlsRow.MultiAction(int);
-    method public int getActionCount();
-    method public android.graphics.drawable.Drawable getDrawable(int);
-    method public int getIndex();
-    method public java.lang.String getLabel(int);
-    method public java.lang.String getSecondaryLabel(int);
-    method public void nextIndex();
-    method public void setDrawables(android.graphics.drawable.Drawable[]);
-    method public void setIndex(int);
-    method public void setLabels(java.lang.String[]);
-    method public void setSecondaryLabels(java.lang.String[]);
-  }
-
-  public static class PlaybackControlsRow.OnPlaybackProgressCallback {
-    ctor public PlaybackControlsRow.OnPlaybackProgressCallback();
-    method public void onBufferedPositionChanged(android.support.v17.leanback.widget.PlaybackControlsRow, long);
-    method public void onCurrentPositionChanged(android.support.v17.leanback.widget.PlaybackControlsRow, long);
-    method public void onDurationChanged(android.support.v17.leanback.widget.PlaybackControlsRow, long);
-  }
-
-  public static class PlaybackControlsRow.PictureInPictureAction extends android.support.v17.leanback.widget.Action {
-    ctor public PlaybackControlsRow.PictureInPictureAction(android.content.Context);
-  }
-
-  public static class PlaybackControlsRow.PlayPauseAction extends android.support.v17.leanback.widget.PlaybackControlsRow.MultiAction {
-    ctor public PlaybackControlsRow.PlayPauseAction(android.content.Context);
-    field public static final int INDEX_PAUSE = 1; // 0x1
-    field public static final int INDEX_PLAY = 0; // 0x0
-    field public static deprecated int PAUSE;
-    field public static deprecated int PLAY;
-  }
-
-  public static class PlaybackControlsRow.RepeatAction extends android.support.v17.leanback.widget.PlaybackControlsRow.MultiAction {
-    ctor public PlaybackControlsRow.RepeatAction(android.content.Context);
-    ctor public PlaybackControlsRow.RepeatAction(android.content.Context, int);
-    ctor public PlaybackControlsRow.RepeatAction(android.content.Context, int, int);
-    field public static deprecated int ALL;
-    field public static final int INDEX_ALL = 1; // 0x1
-    field public static final int INDEX_NONE = 0; // 0x0
-    field public static final int INDEX_ONE = 2; // 0x2
-    field public static deprecated int NONE;
-    field public static deprecated int ONE;
-  }
-
-  public static class PlaybackControlsRow.RewindAction extends android.support.v17.leanback.widget.PlaybackControlsRow.MultiAction {
-    ctor public PlaybackControlsRow.RewindAction(android.content.Context);
-    ctor public PlaybackControlsRow.RewindAction(android.content.Context, int);
-  }
-
-  public static class PlaybackControlsRow.ShuffleAction extends android.support.v17.leanback.widget.PlaybackControlsRow.MultiAction {
-    ctor public PlaybackControlsRow.ShuffleAction(android.content.Context);
-    ctor public PlaybackControlsRow.ShuffleAction(android.content.Context, int);
-    field public static final int INDEX_OFF = 0; // 0x0
-    field public static final int INDEX_ON = 1; // 0x1
-    field public static deprecated int OFF;
-    field public static deprecated int ON;
-  }
-
-  public static class PlaybackControlsRow.SkipNextAction extends android.support.v17.leanback.widget.Action {
-    ctor public PlaybackControlsRow.SkipNextAction(android.content.Context);
-  }
-
-  public static class PlaybackControlsRow.SkipPreviousAction extends android.support.v17.leanback.widget.Action {
-    ctor public PlaybackControlsRow.SkipPreviousAction(android.content.Context);
-  }
-
-  public static abstract class PlaybackControlsRow.ThumbsAction extends android.support.v17.leanback.widget.PlaybackControlsRow.MultiAction {
-    ctor public PlaybackControlsRow.ThumbsAction(int, android.content.Context, int, int);
-    field public static final int INDEX_OUTLINE = 1; // 0x1
-    field public static final int INDEX_SOLID = 0; // 0x0
-    field public static deprecated int OUTLINE;
-    field public static deprecated int SOLID;
-  }
-
-  public static class PlaybackControlsRow.ThumbsDownAction extends android.support.v17.leanback.widget.PlaybackControlsRow.ThumbsAction {
-    ctor public PlaybackControlsRow.ThumbsDownAction(android.content.Context);
-  }
-
-  public static class PlaybackControlsRow.ThumbsUpAction extends android.support.v17.leanback.widget.PlaybackControlsRow.ThumbsAction {
-    ctor public PlaybackControlsRow.ThumbsUpAction(android.content.Context);
-  }
-
-  public class PlaybackControlsRowPresenter extends android.support.v17.leanback.widget.PlaybackRowPresenter {
-    ctor public PlaybackControlsRowPresenter(android.support.v17.leanback.widget.Presenter);
-    ctor public PlaybackControlsRowPresenter();
-    method public boolean areSecondaryActionsHidden();
-    method protected android.support.v17.leanback.widget.RowPresenter.ViewHolder createRowViewHolder(android.view.ViewGroup);
-    method public int getBackgroundColor();
-    method public android.support.v17.leanback.widget.OnActionClickedListener getOnActionClickedListener();
-    method public int getProgressColor();
-    method public void setBackgroundColor(int);
-    method public void setOnActionClickedListener(android.support.v17.leanback.widget.OnActionClickedListener);
-    method public void setProgressColor(int);
-    method public void setSecondaryActionsHidden(boolean);
-    method public void showBottomSpace(android.support.v17.leanback.widget.PlaybackControlsRowPresenter.ViewHolder, boolean);
-    method public void showPrimaryActions(android.support.v17.leanback.widget.PlaybackControlsRowPresenter.ViewHolder);
-  }
-
-  public class PlaybackControlsRowPresenter.ViewHolder extends android.support.v17.leanback.widget.PlaybackRowPresenter.ViewHolder {
-    field public final android.support.v17.leanback.widget.Presenter.ViewHolder mDescriptionViewHolder;
-  }
-
-  public abstract class PlaybackRowPresenter extends android.support.v17.leanback.widget.RowPresenter {
-    ctor public PlaybackRowPresenter();
-    method public void onReappear(android.support.v17.leanback.widget.RowPresenter.ViewHolder);
-  }
-
-  public static class PlaybackRowPresenter.ViewHolder extends android.support.v17.leanback.widget.RowPresenter.ViewHolder {
-    ctor public PlaybackRowPresenter.ViewHolder(android.view.View);
-  }
-
-  public class PlaybackSeekDataProvider {
-    ctor public PlaybackSeekDataProvider();
-    method public long[] getSeekPositions();
-    method public void getThumbnail(int, android.support.v17.leanback.widget.PlaybackSeekDataProvider.ResultCallback);
-    method public void reset();
-  }
-
-  public static class PlaybackSeekDataProvider.ResultCallback {
-    ctor public PlaybackSeekDataProvider.ResultCallback();
-    method public void onThumbnailLoaded(android.graphics.Bitmap, int);
-  }
-
-  public abstract interface PlaybackSeekUi {
-    method public abstract void setPlaybackSeekUiClient(android.support.v17.leanback.widget.PlaybackSeekUi.Client);
-  }
-
-  public static class PlaybackSeekUi.Client {
-    ctor public PlaybackSeekUi.Client();
-    method public android.support.v17.leanback.widget.PlaybackSeekDataProvider getPlaybackSeekDataProvider();
-    method public boolean isSeekEnabled();
-    method public void onSeekFinished(boolean);
-    method public void onSeekPositionChanged(long);
-    method public void onSeekStarted();
-  }
-
-  public class PlaybackTransportRowPresenter extends android.support.v17.leanback.widget.PlaybackRowPresenter {
-    ctor public PlaybackTransportRowPresenter();
-    method protected android.support.v17.leanback.widget.RowPresenter.ViewHolder createRowViewHolder(android.view.ViewGroup);
-    method public float getDefaultSeekIncrement();
-    method public android.support.v17.leanback.widget.OnActionClickedListener getOnActionClickedListener();
-    method public int getProgressColor();
-    method protected void onProgressBarClicked(android.support.v17.leanback.widget.PlaybackTransportRowPresenter.ViewHolder);
-    method public void setDefaultSeekIncrement(float);
-    method public void setDescriptionPresenter(android.support.v17.leanback.widget.Presenter);
-    method public void setOnActionClickedListener(android.support.v17.leanback.widget.OnActionClickedListener);
-    method public void setProgressColor(int);
-  }
-
-  public class PlaybackTransportRowPresenter.ViewHolder extends android.support.v17.leanback.widget.PlaybackRowPresenter.ViewHolder implements android.support.v17.leanback.widget.PlaybackSeekUi {
-    ctor public PlaybackTransportRowPresenter.ViewHolder(android.view.View, android.support.v17.leanback.widget.Presenter);
-    method public final android.widget.TextView getCurrentPositionView();
-    method public final android.support.v17.leanback.widget.Presenter.ViewHolder getDescriptionViewHolder();
-    method public final android.widget.TextView getDurationView();
-    method protected void onSetCurrentPositionLabel(long);
-    method protected void onSetDurationLabel(long);
-    method public void setPlaybackSeekUiClient(android.support.v17.leanback.widget.PlaybackSeekUi.Client);
-  }
-
-  public abstract class Presenter implements android.support.v17.leanback.widget.FacetProvider {
-    ctor public Presenter();
-    method protected static void cancelAnimationsRecursive(android.view.View);
-    method public final java.lang.Object getFacet(java.lang.Class<?>);
-    method public abstract void onBindViewHolder(android.support.v17.leanback.widget.Presenter.ViewHolder, java.lang.Object);
-    method public void onBindViewHolder(android.support.v17.leanback.widget.Presenter.ViewHolder, java.lang.Object, java.util.List<java.lang.Object>);
-    method public abstract android.support.v17.leanback.widget.Presenter.ViewHolder onCreateViewHolder(android.view.ViewGroup);
-    method public abstract void onUnbindViewHolder(android.support.v17.leanback.widget.Presenter.ViewHolder);
-    method public void onViewAttachedToWindow(android.support.v17.leanback.widget.Presenter.ViewHolder);
-    method public void onViewDetachedFromWindow(android.support.v17.leanback.widget.Presenter.ViewHolder);
-    method public final void setFacet(java.lang.Class<?>, java.lang.Object);
-    method public void setOnClickListener(android.support.v17.leanback.widget.Presenter.ViewHolder, android.view.View.OnClickListener);
-  }
-
-  public static class Presenter.ViewHolder implements android.support.v17.leanback.widget.FacetProvider {
-    ctor public Presenter.ViewHolder(android.view.View);
-    method public final java.lang.Object getFacet(java.lang.Class<?>);
-    method public final void setFacet(java.lang.Class<?>, java.lang.Object);
-    field public final android.view.View view;
-  }
-
-  public static abstract class Presenter.ViewHolderTask {
-    ctor public Presenter.ViewHolderTask();
-    method public void run(android.support.v17.leanback.widget.Presenter.ViewHolder);
-  }
-
-  public abstract class PresenterSelector {
-    ctor public PresenterSelector();
-    method public abstract android.support.v17.leanback.widget.Presenter getPresenter(java.lang.Object);
-    method public android.support.v17.leanback.widget.Presenter[] getPresenters();
-  }
-
-  public abstract class PresenterSwitcher {
-    ctor public PresenterSwitcher();
-    method public void clear();
-    method public final android.view.ViewGroup getParentViewGroup();
-    method public void init(android.view.ViewGroup, android.support.v17.leanback.widget.PresenterSelector);
-    method protected abstract void insertView(android.view.View);
-    method protected void onViewSelected(android.view.View);
-    method public void select(java.lang.Object);
-    method protected void showView(android.view.View, boolean);
-    method public void unselect();
-  }
-
-  public class RecyclerViewParallax extends android.support.v17.leanback.widget.Parallax {
-    ctor public RecyclerViewParallax();
-    method public android.support.v17.leanback.widget.RecyclerViewParallax.ChildPositionProperty createProperty(java.lang.String, int);
-    method public float getMaxValue();
-    method public android.support.v7.widget.RecyclerView getRecyclerView();
-    method public void setRecyclerView(android.support.v7.widget.RecyclerView);
-  }
-
-  public static final class RecyclerViewParallax.ChildPositionProperty extends android.support.v17.leanback.widget.Parallax.IntProperty {
-    method public android.support.v17.leanback.widget.RecyclerViewParallax.ChildPositionProperty adapterPosition(int);
-    method public android.support.v17.leanback.widget.RecyclerViewParallax.ChildPositionProperty fraction(float);
-    method public int getAdapterPosition();
-    method public float getFraction();
-    method public int getOffset();
-    method public int getViewId();
-    method public android.support.v17.leanback.widget.RecyclerViewParallax.ChildPositionProperty offset(int);
-    method public android.support.v17.leanback.widget.RecyclerViewParallax.ChildPositionProperty viewId(int);
-  }
-
-  public class Row {
-    ctor public Row(long, android.support.v17.leanback.widget.HeaderItem);
-    ctor public Row(android.support.v17.leanback.widget.HeaderItem);
-    ctor public Row();
-    method public final android.support.v17.leanback.widget.HeaderItem getHeaderItem();
-    method public final long getId();
-    method public boolean isRenderedAsRowView();
-    method public final void setHeaderItem(android.support.v17.leanback.widget.HeaderItem);
-    method public final void setId(long);
-  }
-
-  public class RowHeaderPresenter extends android.support.v17.leanback.widget.Presenter {
-    ctor public RowHeaderPresenter();
-    method protected static float getFontDescent(android.widget.TextView, android.graphics.Paint);
-    method public int getSpaceUnderBaseline(android.support.v17.leanback.widget.RowHeaderPresenter.ViewHolder);
-    method public boolean isNullItemVisibilityGone();
-    method public void onBindViewHolder(android.support.v17.leanback.widget.Presenter.ViewHolder, java.lang.Object);
-    method public android.support.v17.leanback.widget.Presenter.ViewHolder onCreateViewHolder(android.view.ViewGroup);
-    method protected void onSelectLevelChanged(android.support.v17.leanback.widget.RowHeaderPresenter.ViewHolder);
-    method public void onUnbindViewHolder(android.support.v17.leanback.widget.Presenter.ViewHolder);
-    method public void setNullItemVisibilityGone(boolean);
-    method public final void setSelectLevel(android.support.v17.leanback.widget.RowHeaderPresenter.ViewHolder, float);
-  }
-
-  public static class RowHeaderPresenter.ViewHolder extends android.support.v17.leanback.widget.Presenter.ViewHolder {
-    ctor public RowHeaderPresenter.ViewHolder(android.view.View);
-    method public final float getSelectLevel();
-  }
-
-  public final class RowHeaderView extends android.widget.TextView {
-    ctor public RowHeaderView(android.content.Context);
-    ctor public RowHeaderView(android.content.Context, android.util.AttributeSet);
-    ctor public RowHeaderView(android.content.Context, android.util.AttributeSet, int);
-  }
-
-  public abstract class RowPresenter extends android.support.v17.leanback.widget.Presenter {
-    ctor public RowPresenter();
-    method protected abstract android.support.v17.leanback.widget.RowPresenter.ViewHolder createRowViewHolder(android.view.ViewGroup);
-    method protected void dispatchItemSelectedListener(android.support.v17.leanback.widget.RowPresenter.ViewHolder, boolean);
-    method public void freeze(android.support.v17.leanback.widget.RowPresenter.ViewHolder, boolean);
-    method public final android.support.v17.leanback.widget.RowHeaderPresenter getHeaderPresenter();
-    method public final android.support.v17.leanback.widget.RowPresenter.ViewHolder getRowViewHolder(android.support.v17.leanback.widget.Presenter.ViewHolder);
-    method public final boolean getSelectEffectEnabled();
-    method public final float getSelectLevel(android.support.v17.leanback.widget.Presenter.ViewHolder);
-    method public final int getSyncActivatePolicy();
-    method protected void initializeRowViewHolder(android.support.v17.leanback.widget.RowPresenter.ViewHolder);
-    method protected boolean isClippingChildren();
-    method public boolean isUsingDefaultSelectEffect();
-    method protected void onBindRowViewHolder(android.support.v17.leanback.widget.RowPresenter.ViewHolder, java.lang.Object);
-    method public final void onBindViewHolder(android.support.v17.leanback.widget.Presenter.ViewHolder, java.lang.Object);
-    method public final android.support.v17.leanback.widget.Presenter.ViewHolder onCreateViewHolder(android.view.ViewGroup);
-    method protected void onRowViewAttachedToWindow(android.support.v17.leanback.widget.RowPresenter.ViewHolder);
-    method protected void onRowViewDetachedFromWindow(android.support.v17.leanback.widget.RowPresenter.ViewHolder);
-    method protected void onRowViewExpanded(android.support.v17.leanback.widget.RowPresenter.ViewHolder, boolean);
-    method protected void onRowViewSelected(android.support.v17.leanback.widget.RowPresenter.ViewHolder, boolean);
-    method protected void onSelectLevelChanged(android.support.v17.leanback.widget.RowPresenter.ViewHolder);
-    method protected void onUnbindRowViewHolder(android.support.v17.leanback.widget.RowPresenter.ViewHolder);
-    method public final void onUnbindViewHolder(android.support.v17.leanback.widget.Presenter.ViewHolder);
-    method public final void onViewAttachedToWindow(android.support.v17.leanback.widget.Presenter.ViewHolder);
-    method public final void onViewDetachedFromWindow(android.support.v17.leanback.widget.Presenter.ViewHolder);
-    method public void setEntranceTransitionState(android.support.v17.leanback.widget.RowPresenter.ViewHolder, boolean);
-    method public final void setHeaderPresenter(android.support.v17.leanback.widget.RowHeaderPresenter);
-    method public final void setRowViewExpanded(android.support.v17.leanback.widget.Presenter.ViewHolder, boolean);
-    method public final void setRowViewSelected(android.support.v17.leanback.widget.Presenter.ViewHolder, boolean);
-    method public final void setSelectEffectEnabled(boolean);
-    method public final void setSelectLevel(android.support.v17.leanback.widget.Presenter.ViewHolder, float);
-    method public final void setSyncActivatePolicy(int);
-    field public static final int SYNC_ACTIVATED_CUSTOM = 0; // 0x0
-    field public static final int SYNC_ACTIVATED_TO_EXPANDED = 1; // 0x1
-    field public static final int SYNC_ACTIVATED_TO_EXPANDED_AND_SELECTED = 3; // 0x3
-    field public static final int SYNC_ACTIVATED_TO_SELECTED = 2; // 0x2
-  }
-
-  public static class RowPresenter.ViewHolder extends android.support.v17.leanback.widget.Presenter.ViewHolder {
-    ctor public RowPresenter.ViewHolder(android.view.View);
-    method public final android.support.v17.leanback.widget.RowHeaderPresenter.ViewHolder getHeaderViewHolder();
-    method public final android.support.v17.leanback.widget.BaseOnItemViewClickedListener getOnItemViewClickedListener();
-    method public final android.support.v17.leanback.widget.BaseOnItemViewSelectedListener getOnItemViewSelectedListener();
-    method public android.view.View.OnKeyListener getOnKeyListener();
-    method public final android.support.v17.leanback.widget.Row getRow();
-    method public final java.lang.Object getRowObject();
-    method public final float getSelectLevel();
-    method public java.lang.Object getSelectedItem();
-    method public android.support.v17.leanback.widget.Presenter.ViewHolder getSelectedItemViewHolder();
-    method public final boolean isExpanded();
-    method public final boolean isSelected();
-    method public final void setActivated(boolean);
-    method public final void setOnItemViewClickedListener(android.support.v17.leanback.widget.BaseOnItemViewClickedListener);
-    method public final void setOnItemViewSelectedListener(android.support.v17.leanback.widget.BaseOnItemViewSelectedListener);
-    method public void setOnKeyListener(android.view.View.OnKeyListener);
-    method public final void syncActivatedStatus(android.view.View);
-    field protected final android.support.v17.leanback.graphics.ColorOverlayDimmer mColorDimmer;
-  }
-
-  public class SearchBar extends android.widget.RelativeLayout {
-    ctor public SearchBar(android.content.Context);
-    ctor public SearchBar(android.content.Context, android.util.AttributeSet);
-    ctor public SearchBar(android.content.Context, android.util.AttributeSet, int);
-    method public void displayCompletions(java.util.List<java.lang.String>);
-    method public void displayCompletions(android.view.inputmethod.CompletionInfo[]);
-    method public android.graphics.drawable.Drawable getBadgeDrawable();
-    method public java.lang.CharSequence getHint();
-    method public java.lang.String getTitle();
-    method public boolean isRecognizing();
-    method public void setBadgeDrawable(android.graphics.drawable.Drawable);
-    method public void setPermissionListener(android.support.v17.leanback.widget.SearchBar.SearchBarPermissionListener);
-    method public void setSearchAffordanceColors(android.support.v17.leanback.widget.SearchOrbView.Colors);
-    method public void setSearchAffordanceColorsInListening(android.support.v17.leanback.widget.SearchOrbView.Colors);
-    method public void setSearchBarListener(android.support.v17.leanback.widget.SearchBar.SearchBarListener);
-    method public void setSearchQuery(java.lang.String);
-    method public deprecated void setSpeechRecognitionCallback(android.support.v17.leanback.widget.SpeechRecognitionCallback);
-    method public void setSpeechRecognizer(android.speech.SpeechRecognizer);
-    method public void setTitle(java.lang.String);
-    method public void startRecognition();
-    method public void stopRecognition();
-  }
-
-  public static abstract interface SearchBar.SearchBarListener {
-    method public abstract void onKeyboardDismiss(java.lang.String);
-    method public abstract void onSearchQueryChange(java.lang.String);
-    method public abstract void onSearchQuerySubmit(java.lang.String);
-  }
-
-  public static abstract interface SearchBar.SearchBarPermissionListener {
-    method public abstract void requestAudioPermission();
-  }
-
-  public class SearchEditText extends android.support.v17.leanback.widget.StreamingTextView {
-    ctor public SearchEditText(android.content.Context);
-    ctor public SearchEditText(android.content.Context, android.util.AttributeSet);
-    ctor public SearchEditText(android.content.Context, android.util.AttributeSet, int);
-    method public void setOnKeyboardDismissListener(android.support.v17.leanback.widget.SearchEditText.OnKeyboardDismissListener);
-  }
-
-  public static abstract interface SearchEditText.OnKeyboardDismissListener {
-    method public abstract void onKeyboardDismiss();
-  }
-
-  public class SearchOrbView extends android.widget.FrameLayout implements android.view.View.OnClickListener {
-    ctor public SearchOrbView(android.content.Context);
-    ctor public SearchOrbView(android.content.Context, android.util.AttributeSet);
-    ctor public SearchOrbView(android.content.Context, android.util.AttributeSet, int);
-    method public void enableOrbColorAnimation(boolean);
-    method public int getOrbColor();
-    method public android.support.v17.leanback.widget.SearchOrbView.Colors getOrbColors();
-    method public android.graphics.drawable.Drawable getOrbIcon();
-    method public void onClick(android.view.View);
-    method public void setOnOrbClickedListener(android.view.View.OnClickListener);
-    method public void setOrbColor(int);
-    method public deprecated void setOrbColor(int, int);
-    method public void setOrbColors(android.support.v17.leanback.widget.SearchOrbView.Colors);
-    method public void setOrbIcon(android.graphics.drawable.Drawable);
-  }
-
-  public static class SearchOrbView.Colors {
-    ctor public SearchOrbView.Colors(int);
-    ctor public SearchOrbView.Colors(int, int);
-    ctor public SearchOrbView.Colors(int, int, int);
-    method public static int getBrightColor(int);
-    field public int brightColor;
-    field public int color;
-    field public int iconColor;
-  }
-
-  public class SectionRow extends android.support.v17.leanback.widget.Row {
-    ctor public SectionRow(android.support.v17.leanback.widget.HeaderItem);
-    ctor public SectionRow(long, java.lang.String);
-    ctor public SectionRow(java.lang.String);
-    method public final boolean isRenderedAsRowView();
-  }
-
-  public class ShadowOverlayContainer extends android.widget.FrameLayout {
-    ctor public ShadowOverlayContainer(android.content.Context);
-    ctor public ShadowOverlayContainer(android.content.Context, android.util.AttributeSet);
-    ctor public ShadowOverlayContainer(android.content.Context, android.util.AttributeSet, int);
-    method public int getShadowType();
-    method public android.view.View getWrappedView();
-    method public deprecated void initialize(boolean, boolean);
-    method public deprecated void initialize(boolean, boolean, boolean);
-    method public static void prepareParentForShadow(android.view.ViewGroup);
-    method public void setOverlayColor(int);
-    method public void setShadowFocusLevel(float);
-    method public static boolean supportsDynamicShadow();
-    method public static boolean supportsShadow();
-    method public void useDynamicShadow();
-    method public void useDynamicShadow(float, float);
-    method public void useStaticShadow();
-    method public void wrap(android.view.View);
-    field public static final int SHADOW_DYNAMIC = 3; // 0x3
-    field public static final int SHADOW_NONE = 1; // 0x1
-    field public static final int SHADOW_STATIC = 2; // 0x2
-  }
-
-  public final class ShadowOverlayHelper {
-    method public android.support.v17.leanback.widget.ShadowOverlayContainer createShadowOverlayContainer(android.content.Context);
-    method public int getShadowType();
-    method public boolean needsOverlay();
-    method public boolean needsRoundedCorner();
-    method public boolean needsWrapper();
-    method public void onViewCreated(android.view.View);
-    method public void prepareParentForShadow(android.view.ViewGroup);
-    method public static void setNoneWrapperOverlayColor(android.view.View, int);
-    method public static void setNoneWrapperShadowFocusLevel(android.view.View, float);
-    method public void setOverlayColor(android.view.View, int);
-    method public void setShadowFocusLevel(android.view.View, float);
-    method public static boolean supportsDynamicShadow();
-    method public static boolean supportsForeground();
-    method public static boolean supportsRoundedCorner();
-    method public static boolean supportsShadow();
-    field public static final int SHADOW_DYNAMIC = 3; // 0x3
-    field public static final int SHADOW_NONE = 1; // 0x1
-    field public static final int SHADOW_STATIC = 2; // 0x2
-  }
-
-  public static final class ShadowOverlayHelper.Builder {
-    ctor public ShadowOverlayHelper.Builder();
-    method public android.support.v17.leanback.widget.ShadowOverlayHelper build(android.content.Context);
-    method public android.support.v17.leanback.widget.ShadowOverlayHelper.Builder keepForegroundDrawable(boolean);
-    method public android.support.v17.leanback.widget.ShadowOverlayHelper.Builder needsOverlay(boolean);
-    method public android.support.v17.leanback.widget.ShadowOverlayHelper.Builder needsRoundedCorner(boolean);
-    method public android.support.v17.leanback.widget.ShadowOverlayHelper.Builder needsShadow(boolean);
-    method public android.support.v17.leanback.widget.ShadowOverlayHelper.Builder options(android.support.v17.leanback.widget.ShadowOverlayHelper.Options);
-    method public android.support.v17.leanback.widget.ShadowOverlayHelper.Builder preferZOrder(boolean);
-  }
-
-  public static final class ShadowOverlayHelper.Options {
-    ctor public ShadowOverlayHelper.Options();
-    method public android.support.v17.leanback.widget.ShadowOverlayHelper.Options dynamicShadowZ(float, float);
-    method public final float getDynamicShadowFocusedZ();
-    method public final float getDynamicShadowUnfocusedZ();
-    method public final int getRoundedCornerRadius();
-    method public android.support.v17.leanback.widget.ShadowOverlayHelper.Options roundedCornerRadius(int);
-    field public static final android.support.v17.leanback.widget.ShadowOverlayHelper.Options DEFAULT;
-  }
-
-  public final class SinglePresenterSelector extends android.support.v17.leanback.widget.PresenterSelector {
-    ctor public SinglePresenterSelector(android.support.v17.leanback.widget.Presenter);
-    method public android.support.v17.leanback.widget.Presenter getPresenter(java.lang.Object);
-  }
-
-  public class SparseArrayObjectAdapter extends android.support.v17.leanback.widget.ObjectAdapter {
-    ctor public SparseArrayObjectAdapter(android.support.v17.leanback.widget.PresenterSelector);
-    ctor public SparseArrayObjectAdapter(android.support.v17.leanback.widget.Presenter);
-    ctor public SparseArrayObjectAdapter();
-    method public void clear(int);
-    method public void clear();
-    method public java.lang.Object get(int);
-    method public int indexOf(java.lang.Object);
-    method public int indexOf(int);
-    method public java.lang.Object lookup(int);
-    method public void notifyArrayItemRangeChanged(int, int);
-    method public void set(int, java.lang.Object);
-    method public int size();
-  }
-
-  public class SpeechOrbView extends android.support.v17.leanback.widget.SearchOrbView {
-    ctor public SpeechOrbView(android.content.Context);
-    ctor public SpeechOrbView(android.content.Context, android.util.AttributeSet);
-    ctor public SpeechOrbView(android.content.Context, android.util.AttributeSet, int);
-    method public void setListeningOrbColors(android.support.v17.leanback.widget.SearchOrbView.Colors);
-    method public void setNotListeningOrbColors(android.support.v17.leanback.widget.SearchOrbView.Colors);
-    method public void setSoundLevel(int);
-    method public void showListening();
-    method public void showNotListening();
-  }
-
-  public abstract deprecated interface SpeechRecognitionCallback {
-    method public abstract void recognizeSpeech();
-  }
-
-   class StreamingTextView extends android.widget.EditText {
-    ctor public StreamingTextView(android.content.Context, android.util.AttributeSet);
-    ctor public StreamingTextView(android.content.Context, android.util.AttributeSet, int);
-    method public static boolean isLayoutRtl(android.view.View);
-    method public void reset();
-    method public void setFinalRecognizedText(java.lang.CharSequence);
-    method public void updateRecognizedText(java.lang.String, java.lang.String);
-    method public void updateRecognizedText(java.lang.String, java.util.List<java.lang.Float>);
-  }
-
-  public class TitleHelper {
-    ctor public TitleHelper(android.view.ViewGroup, android.view.View);
-    method public android.support.v17.leanback.widget.BrowseFrameLayout.OnFocusSearchListener getOnFocusSearchListener();
-    method public android.view.ViewGroup getSceneRoot();
-    method public android.view.View getTitleView();
-    method public void showTitle(boolean);
-  }
-
-  public class TitleView extends android.widget.FrameLayout implements android.support.v17.leanback.widget.TitleViewAdapter.Provider {
-    ctor public TitleView(android.content.Context);
-    ctor public TitleView(android.content.Context, android.util.AttributeSet);
-    ctor public TitleView(android.content.Context, android.util.AttributeSet, int);
-    method public void enableAnimation(boolean);
-    method public android.graphics.drawable.Drawable getBadgeDrawable();
-    method public android.support.v17.leanback.widget.SearchOrbView.Colors getSearchAffordanceColors();
-    method public android.view.View getSearchAffordanceView();
-    method public java.lang.CharSequence getTitle();
-    method public android.support.v17.leanback.widget.TitleViewAdapter getTitleViewAdapter();
-    method public void setBadgeDrawable(android.graphics.drawable.Drawable);
-    method public void setOnSearchClickedListener(android.view.View.OnClickListener);
-    method public void setSearchAffordanceColors(android.support.v17.leanback.widget.SearchOrbView.Colors);
-    method public void setTitle(java.lang.CharSequence);
-    method public void updateComponentsVisibility(int);
-  }
-
-  public abstract class TitleViewAdapter {
-    ctor public TitleViewAdapter();
-    method public android.graphics.drawable.Drawable getBadgeDrawable();
-    method public android.support.v17.leanback.widget.SearchOrbView.Colors getSearchAffordanceColors();
-    method public abstract android.view.View getSearchAffordanceView();
-    method public java.lang.CharSequence getTitle();
-    method public void setAnimationEnabled(boolean);
-    method public void setBadgeDrawable(android.graphics.drawable.Drawable);
-    method public void setOnSearchClickedListener(android.view.View.OnClickListener);
-    method public void setSearchAffordanceColors(android.support.v17.leanback.widget.SearchOrbView.Colors);
-    method public void setTitle(java.lang.CharSequence);
-    method public void updateComponentsVisibility(int);
-    field public static final int BRANDING_VIEW_VISIBLE = 2; // 0x2
-    field public static final int FULL_VIEW_VISIBLE = 6; // 0x6
-    field public static final int SEARCH_VIEW_VISIBLE = 4; // 0x4
-  }
-
-  public static abstract interface TitleViewAdapter.Provider {
-    method public abstract android.support.v17.leanback.widget.TitleViewAdapter getTitleViewAdapter();
-  }
-
-  public class VerticalGridPresenter extends android.support.v17.leanback.widget.Presenter {
-    ctor public VerticalGridPresenter();
-    ctor public VerticalGridPresenter(int);
-    ctor public VerticalGridPresenter(int, boolean);
-    method public final boolean areChildRoundedCornersEnabled();
-    method protected android.support.v17.leanback.widget.VerticalGridPresenter.ViewHolder createGridViewHolder(android.view.ViewGroup);
-    method protected android.support.v17.leanback.widget.ShadowOverlayHelper.Options createShadowOverlayOptions();
-    method public final void enableChildRoundedCorners(boolean);
-    method public final int getFocusZoomFactor();
-    method public final boolean getKeepChildForeground();
-    method public int getNumberOfColumns();
-    method public final android.support.v17.leanback.widget.OnItemViewClickedListener getOnItemViewClickedListener();
-    method public final android.support.v17.leanback.widget.OnItemViewSelectedListener getOnItemViewSelectedListener();
-    method public final boolean getShadowEnabled();
-    method protected void initializeGridViewHolder(android.support.v17.leanback.widget.VerticalGridPresenter.ViewHolder);
-    method public final boolean isFocusDimmerUsed();
-    method public boolean isUsingDefaultShadow();
-    method public boolean isUsingZOrder(android.content.Context);
-    method public void onBindViewHolder(android.support.v17.leanback.widget.Presenter.ViewHolder, java.lang.Object);
-    method public final android.support.v17.leanback.widget.VerticalGridPresenter.ViewHolder onCreateViewHolder(android.view.ViewGroup);
-    method public void onUnbindViewHolder(android.support.v17.leanback.widget.Presenter.ViewHolder);
-    method public void setEntranceTransitionState(android.support.v17.leanback.widget.VerticalGridPresenter.ViewHolder, boolean);
-    method public final void setKeepChildForeground(boolean);
-    method public void setNumberOfColumns(int);
-    method public final void setOnItemViewClickedListener(android.support.v17.leanback.widget.OnItemViewClickedListener);
-    method public final void setOnItemViewSelectedListener(android.support.v17.leanback.widget.OnItemViewSelectedListener);
-    method public final void setShadowEnabled(boolean);
-  }
-
-  public static class VerticalGridPresenter.ViewHolder extends android.support.v17.leanback.widget.Presenter.ViewHolder {
-    ctor public VerticalGridPresenter.ViewHolder(android.support.v17.leanback.widget.VerticalGridView);
-    method public android.support.v17.leanback.widget.VerticalGridView getGridView();
-  }
-
-  public class VerticalGridView extends android.support.v17.leanback.widget.BaseGridView {
-    ctor public VerticalGridView(android.content.Context);
-    ctor public VerticalGridView(android.content.Context, android.util.AttributeSet);
-    ctor public VerticalGridView(android.content.Context, android.util.AttributeSet, int);
-    method protected void initAttributes(android.content.Context, android.util.AttributeSet);
-    method public void setColumnWidth(int);
-    method public void setNumColumns(int);
-  }
-
-  public abstract interface ViewHolderTask {
-    method public abstract void run(android.support.v7.widget.RecyclerView.ViewHolder);
-  }
-
-}
-
-package android.support.v17.leanback.widget.picker {
-
-  public class Picker extends android.widget.FrameLayout {
-    ctor public Picker(android.content.Context, android.util.AttributeSet, int);
-    method public void addOnValueChangedListener(android.support.v17.leanback.widget.picker.Picker.PickerValueListener);
-    method public float getActivatedVisibleItemCount();
-    method public android.support.v17.leanback.widget.picker.PickerColumn getColumnAt(int);
-    method public int getColumnsCount();
-    method protected int getPickerItemHeightPixels();
-    method public final int getPickerItemLayoutId();
-    method public final int getPickerItemTextViewId();
-    method public int getSelectedColumn();
-    method public final deprecated java.lang.CharSequence getSeparator();
-    method public final java.util.List<java.lang.CharSequence> getSeparators();
-    method public float getVisibleItemCount();
-    method public void onColumnValueChanged(int, int);
-    method public void removeOnValueChangedListener(android.support.v17.leanback.widget.picker.Picker.PickerValueListener);
-    method public void setActivatedVisibleItemCount(float);
-    method public void setColumnAt(int, android.support.v17.leanback.widget.picker.PickerColumn);
-    method public void setColumnValue(int, int, boolean);
-    method public void setColumns(java.util.List<android.support.v17.leanback.widget.picker.PickerColumn>);
-    method public final void setPickerItemTextViewId(int);
-    method public void setSelectedColumn(int);
-    method public final void setSeparator(java.lang.CharSequence);
-    method public final void setSeparators(java.util.List<java.lang.CharSequence>);
-    method public void setVisibleItemCount(float);
-  }
-
-  public static abstract interface Picker.PickerValueListener {
-    method public abstract void onValueChanged(android.support.v17.leanback.widget.picker.Picker, int);
-  }
-
-  public class PickerColumn {
-    ctor public PickerColumn();
-    method public int getCount();
-    method public int getCurrentValue();
-    method public java.lang.CharSequence getLabelFor(int);
-    method public java.lang.String getLabelFormat();
-    method public int getMaxValue();
-    method public int getMinValue();
-    method public java.lang.CharSequence[] getStaticLabels();
-    method public void setCurrentValue(int);
-    method public void setLabelFormat(java.lang.String);
-    method public void setMaxValue(int);
-    method public void setMinValue(int);
-    method public void setStaticLabels(java.lang.CharSequence[]);
-  }
-
-  public class TimePicker extends android.support.v17.leanback.widget.picker.Picker {
-    ctor public TimePicker(android.content.Context, android.util.AttributeSet);
-    ctor public TimePicker(android.content.Context, android.util.AttributeSet, int);
-    method public int getHour();
-    method public int getMinute();
-    method public boolean is24Hour();
-    method public boolean isPm();
-    method public void setHour(int);
-    method public void setIs24Hour(boolean);
-    method public void setMinute(int);
-  }
-
-}
-
diff --git a/v17/leanback/build.gradle b/v17/leanback/build.gradle
deleted file mode 100644
index c07bc76..0000000
--- a/v17/leanback/build.gradle
+++ /dev/null
@@ -1,47 +0,0 @@
-import static android.support.dependencies.DependenciesKt.*
-import android.support.LibraryGroups
-import android.support.LibraryVersions
-
-plugins {
-    id("SupportAndroidLibraryPlugin")
-}
-
-dependencies {
-    api(project(":support-compat"))
-    api(project(":support-core-ui"))
-    api(project(":support-media-compat"))
-    api(project(":support-fragment"))
-    api(project(":recyclerview-v7"))
-
-    androidTestImplementation(TEST_RUNNER, libs.exclude_annotations)
-    androidTestImplementation(ESPRESSO_CORE, libs.exclude_annotations)
-    androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
-    androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
-}
-
-android {
-    defaultConfig {
-        minSdkVersion 17
-    }
-
-    sourceSets {
-        main.java.srcDirs = [
-                'common',
-                'jbmr2',
-                'kitkat',
-                'api21',
-                'src'
-        ]
-        main.res.srcDir 'res'
-    }
-}
-
-supportLibrary {
-    name = "Android Support Leanback v17"
-    publish = true
-    mavenVersion = LibraryVersions.SUPPORT_LIBRARY
-    mavenGroup = LibraryGroups.SUPPORT
-    inceptionYear = "2014"
-    description = "Android Support Leanback v17"
-    legacySourceLocation = true
-}
diff --git a/v17/leanback/generatef.py b/v17/leanback/generatef.py
deleted file mode 100755
index 04e303a..0000000
--- a/v17/leanback/generatef.py
+++ /dev/null
@@ -1,108 +0,0 @@
-#!/usr/bin/python
-
-# 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.
-
-import os
-import sys
-import re
-
-print "Generate framework fragment related code for leanback"
-
-cls = ['Base', 'BaseRow', 'Browse', 'Details', 'Error', 'Headers',
-      'Playback', 'Rows', 'Search', 'VerticalGrid', 'Branded',
-      'GuidedStep', 'Onboarding', 'Video']
-
-for w in cls:
-    print "copy {}SupportFragment to {}Fragment".format(w, w)
-
-    file = open('src/android/support/v17/leanback/app/{}SupportFragment.java'.format(w), 'r')
-    content = "// CHECKSTYLE:OFF Generated code\n"
-    content = content + "/* This file is auto-generated from {}SupportFragment.java.  DO NOT MODIFY. */\n\n".format(w)
-
-    for line in file:
-        line = line.replace('IS_FRAMEWORK_FRAGMENT = false', 'IS_FRAMEWORK_FRAGMENT = true');
-        for w2 in cls:
-            line = line.replace('{}SupportFragment'.format(w2), '{}Fragment'.format(w2))
-        line = line.replace('android.support.v4.app.FragmentActivity', 'android.app.Activity')
-        line = line.replace('android.support.v4.app.Fragment', 'android.app.Fragment')
-        line = line.replace('activity.getSupportFragmentManager()', 'activity.getFragmentManager()')
-        line = line.replace('FragmentActivity activity', 'Activity activity')
-        line = line.replace('(FragmentActivity', '(Activity')
-        # replace getContext() with FragmentUtil.getContext(XXXFragment.this), but dont match the case "view.getContext()"
-        line = re.sub(r'([^\.])getContext\(\)', r'\1FragmentUtil.getContext({}Fragment.this)'.format(w), line);
-        content = content + line
-    file.close()
-    # add deprecated tag to fragment class and inner classes/interfaces
-    # content = re.sub(r'\*\/\n(@.*\n|)(public |abstract public |abstract |)class', '* @deprecated use {@link ' + w + 'SupportFragment}\n */\n@Deprecated\n\\1\\2class', content)
-    # content = re.sub(r'\*\/\n    public (static class|interface|final static class|abstract static class)', '* @deprecated use {@link ' + w + 'SupportFragment}\n     */\n    @Deprecated\n    public \\1', content)
-    outfile = open('src/android/support/v17/leanback/app/{}Fragment.java'.format(w), 'w')
-    outfile.write(content)
-    outfile.close()
-
-
-
-print "copy VideoSupportFragmentGlueHost to VideoFragmentGlueHost"
-file = open('src/android/support/v17/leanback/app/VideoSupportFragmentGlueHost.java', 'r')
-content = "// CHECKSTYLE:OFF Generated code\n"
-content = content + "/* This file is auto-generated from VideoSupportFragmentGlueHost.java.  DO NOT MODIFY. */\n\n"
-for line in file:
-    line = line.replace('android.support.v4.app.Fragment', 'android.app.Fragment')
-    line = line.replace('VideoSupportFragment', 'VideoFragment')
-    line = line.replace('PlaybackSupportFragment', 'PlaybackFragment')
-    content = content + line
-file.close()
-# add deprecated tag to class
-# content = re.sub(r'\*\/\npublic class', '* @deprecated use {@link VideoSupportFragmentGlueHost}\n */\n@Deprecated\npublic class', content)
-outfile = open('src/android/support/v17/leanback/app/VideoFragmentGlueHost.java', 'w')
-outfile.write(content)
-outfile.close()
-
-
-
-print "copy PlaybackSupportFragmentGlueHost to PlaybackFragmentGlueHost"
-file = open('src/android/support/v17/leanback/app/PlaybackSupportFragmentGlueHost.java', 'r')
-content = "// CHECKSTYLE:OFF Generated code\n"
-content = content + "/* This file is auto-generated from {}PlaybackSupportFragmentGlueHost.java.  DO NOT MODIFY. */\n\n"
-for line in file:
-    line = line.replace('VideoSupportFragment', 'VideoFragment')
-    line = line.replace('PlaybackSupportFragment', 'PlaybackFragment')
-    line = line.replace('android.support.v4.app.Fragment', 'android.app.Fragment')
-    content = content + line
-file.close()
-# add deprecated tag to class
-# content = re.sub(r'\*\/\npublic class', '* @deprecated use {@link PlaybackSupportFragmentGlueHost}\n */\n@Deprecated\npublic class', content)
-outfile = open('src/android/support/v17/leanback/app/PlaybackFragmentGlueHost.java', 'w')
-outfile.write(content)
-outfile.close()
-
-
-
-print "copy DetailsSupportFragmentBackgroundController to DetailsFragmentBackgroundController"
-file = open('src/android/support/v17/leanback/app/DetailsSupportFragmentBackgroundController.java', 'r')
-content = "// CHECKSTYLE:OFF Generated code\n"
-content = content + "/* This file is auto-generated from {}DetailsSupportFragmentBackgroundController.java.  DO NOT MODIFY. */\n\n"
-for line in file:
-    line = line.replace('VideoSupportFragment', 'VideoFragment')
-    line = line.replace('DetailsSupportFragment', 'DetailsFragment')
-    line = line.replace('RowsSupportFragment', 'RowsFragment')
-    line = line.replace('android.support.v4.app.Fragment', 'android.app.Fragment')
-    line = line.replace('mFragment.getContext()', 'FragmentUtil.getContext(mFragment)')
-    content = content + line
-file.close()
-# add deprecated tag to class
-# content = re.sub(r'\*\/\npublic class', '* @deprecated use {@link DetailsSupportFragmentBackgroundController}\n */\n@Deprecated\npublic class', content)
-outfile = open('src/android/support/v17/leanback/app/DetailsFragmentBackgroundController.java', 'w')
-outfile.write(content)
-outfile.close()
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BaseFragment.java b/v17/leanback/src/android/support/v17/leanback/app/BaseFragment.java
deleted file mode 100644
index bdb213f..0000000
--- a/v17/leanback/src/android/support/v17/leanback/app/BaseFragment.java
+++ /dev/null
@@ -1,321 +0,0 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from BaseSupportFragment.java.  DO NOT MODIFY. */
-
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-package android.support.v17.leanback.app;
-
-import android.annotation.SuppressLint;
-import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.v17.leanback.transition.TransitionHelper;
-import android.support.v17.leanback.transition.TransitionListener;
-import android.support.v17.leanback.util.StateMachine;
-import android.support.v17.leanback.util.StateMachine.Condition;
-import android.support.v17.leanback.util.StateMachine.Event;
-import android.support.v17.leanback.util.StateMachine.State;
-import android.view.View;
-import android.view.ViewTreeObserver;
-
-/**
- * Base class for leanback Fragments. This class is not intended to be subclassed by apps.
- */
-@SuppressWarnings("FragmentNotInstantiable")
-public class BaseFragment extends BrandedFragment {
-
-    /**
-     * The start state for all
-     */
-    final State STATE_START = new State("START", true, false);
-
-    /**
-     * Initial State for ENTRNACE transition.
-     */
-    final State STATE_ENTRANCE_INIT = new State("ENTRANCE_INIT");
-
-    /**
-     * prepareEntranceTransition is just called, but view not ready yet. We can enable the
-     * busy spinner.
-     */
-    final State STATE_ENTRANCE_ON_PREPARED = new State("ENTRANCE_ON_PREPARED", true, false) {
-        @Override
-        public void run() {
-            mProgressBarManager.show();
-        }
-    };
-
-    /**
-     * prepareEntranceTransition is called and main content view to slide in was created, so we can
-     * call {@link #onEntranceTransitionPrepare}. Note that we dont set initial content to invisible
-     * in this State, the process is very different in subclass, e.g. BrowseFragment hide header
-     * views and hide main fragment view in two steps.
-     */
-    final State STATE_ENTRANCE_ON_PREPARED_ON_CREATEVIEW = new State(
-            "ENTRANCE_ON_PREPARED_ON_CREATEVIEW") {
-        @Override
-        public void run() {
-            onEntranceTransitionPrepare();
-        }
-    };
-
-    /**
-     * execute the entrance transition.
-     */
-    final State STATE_ENTRANCE_PERFORM = new State("STATE_ENTRANCE_PERFORM") {
-        @Override
-        public void run() {
-            mProgressBarManager.hide();
-            onExecuteEntranceTransition();
-        }
-    };
-
-    /**
-     * execute onEntranceTransitionEnd.
-     */
-    final State STATE_ENTRANCE_ON_ENDED = new State("ENTRANCE_ON_ENDED") {
-        @Override
-        public void run() {
-            onEntranceTransitionEnd();
-        }
-    };
-
-    /**
-     * either entrance transition completed or skipped
-     */
-    final State STATE_ENTRANCE_COMPLETE = new State("ENTRANCE_COMPLETE", true, false);
-
-    /**
-     * Event fragment.onCreate()
-     */
-    final Event EVT_ON_CREATE = new Event("onCreate");
-
-    /**
-     * Event fragment.onViewCreated()
-     */
-    final Event EVT_ON_CREATEVIEW = new Event("onCreateView");
-
-    /**
-     * Event for {@link #prepareEntranceTransition()} is called.
-     */
-    final Event EVT_PREPARE_ENTRANCE = new Event("prepareEntranceTransition");
-
-    /**
-     * Event for {@link #startEntranceTransition()} is called.
-     */
-    final Event EVT_START_ENTRANCE = new Event("startEntranceTransition");
-
-    /**
-     * Event for entrance transition is ended through Transition listener.
-     */
-    final Event EVT_ENTRANCE_END = new Event("onEntranceTransitionEnd");
-
-    /**
-     * Event for skipping entrance transition if not supported.
-     */
-    final Condition COND_TRANSITION_NOT_SUPPORTED = new Condition("EntranceTransitionNotSupport") {
-        @Override
-        public boolean canProceed() {
-            return !TransitionHelper.systemSupportsEntranceTransitions();
-        }
-    };
-
-    final StateMachine mStateMachine = new StateMachine();
-
-    Object mEntranceTransition;
-    final ProgressBarManager mProgressBarManager = new ProgressBarManager();
-
-    @SuppressLint("ValidFragment")
-    BaseFragment() {
-    }
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        createStateMachineStates();
-        createStateMachineTransitions();
-        mStateMachine.start();
-        super.onCreate(savedInstanceState);
-        mStateMachine.fireEvent(EVT_ON_CREATE);
-    }
-
-    void createStateMachineStates() {
-        mStateMachine.addState(STATE_START);
-        mStateMachine.addState(STATE_ENTRANCE_INIT);
-        mStateMachine.addState(STATE_ENTRANCE_ON_PREPARED);
-        mStateMachine.addState(STATE_ENTRANCE_ON_PREPARED_ON_CREATEVIEW);
-        mStateMachine.addState(STATE_ENTRANCE_PERFORM);
-        mStateMachine.addState(STATE_ENTRANCE_ON_ENDED);
-        mStateMachine.addState(STATE_ENTRANCE_COMPLETE);
-    }
-
-    void createStateMachineTransitions() {
-        mStateMachine.addTransition(STATE_START, STATE_ENTRANCE_INIT, EVT_ON_CREATE);
-        mStateMachine.addTransition(STATE_ENTRANCE_INIT, STATE_ENTRANCE_COMPLETE,
-                COND_TRANSITION_NOT_SUPPORTED);
-        mStateMachine.addTransition(STATE_ENTRANCE_INIT, STATE_ENTRANCE_COMPLETE,
-                EVT_ON_CREATEVIEW);
-        mStateMachine.addTransition(STATE_ENTRANCE_INIT, STATE_ENTRANCE_ON_PREPARED,
-                EVT_PREPARE_ENTRANCE);
-        mStateMachine.addTransition(STATE_ENTRANCE_ON_PREPARED,
-                STATE_ENTRANCE_ON_PREPARED_ON_CREATEVIEW,
-                EVT_ON_CREATEVIEW);
-        mStateMachine.addTransition(STATE_ENTRANCE_ON_PREPARED,
-                STATE_ENTRANCE_PERFORM,
-                EVT_START_ENTRANCE);
-        mStateMachine.addTransition(STATE_ENTRANCE_ON_PREPARED_ON_CREATEVIEW,
-                STATE_ENTRANCE_PERFORM);
-        mStateMachine.addTransition(STATE_ENTRANCE_PERFORM,
-                STATE_ENTRANCE_ON_ENDED,
-                EVT_ENTRANCE_END);
-        mStateMachine.addTransition(STATE_ENTRANCE_ON_ENDED, STATE_ENTRANCE_COMPLETE);
-    }
-
-    @Override
-    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
-        super.onViewCreated(view, savedInstanceState);
-        mStateMachine.fireEvent(EVT_ON_CREATEVIEW);
-    }
-
-    /**
-     * Enables entrance transition.<p>
-     * Entrance transition is the standard slide-in transition that shows rows of data in
-     * browse screen and details screen.
-     * <p>
-     * The method is ignored before LOLLIPOP (API21).
-     * <p>
-     * This method must be called in or
-     * before onCreate().  Typically entrance transition should be enabled when savedInstance is
-     * null so that fragment restored from instanceState does not run an extra entrance transition.
-     * When the entrance transition is enabled, the fragment will make headers and content
-     * hidden initially.
-     * When data of rows are ready, app must call {@link #startEntranceTransition()} to kick off
-     * the transition, otherwise the rows will be invisible forever.
-     * <p>
-     * It is similar to android:windowsEnterTransition and can be considered a late-executed
-     * android:windowsEnterTransition controlled by app.  There are two reasons that app needs it:
-     * <li> Workaround the problem that activity transition is not available between launcher and
-     * app.  Browse activity must programmatically start the slide-in transition.</li>
-     * <li> Separates DetailsOverviewRow transition from other rows transition.  So that
-     * the DetailsOverviewRow transition can be executed earlier without waiting for all rows
-     * to be loaded.</li>
-     * <p>
-     * Transition object is returned by createEntranceTransition().  Typically the app does not need
-     * override the default transition that browse and details provides.
-     */
-    public void prepareEntranceTransition() {
-        mStateMachine.fireEvent(EVT_PREPARE_ENTRANCE);
-    }
-
-    /**
-     * Create entrance transition.  Subclass can override to load transition from
-     * resource or construct manually.  Typically app does not need to
-     * override the default transition that browse and details provides.
-     */
-    protected Object createEntranceTransition() {
-        return null;
-    }
-
-    /**
-     * Run entrance transition.  Subclass may use TransitionManager to perform
-     * go(Scene) or beginDelayedTransition().  App should not override the default
-     * implementation of browse and details fragment.
-     */
-    protected void runEntranceTransition(Object entranceTransition) {
-    }
-
-    /**
-     * Callback when entrance transition is prepared.  This is when fragment should
-     * stop user input and animations.
-     */
-    protected void onEntranceTransitionPrepare() {
-    }
-
-    /**
-     * Callback when entrance transition is started.  This is when fragment should
-     * stop processing layout.
-     */
-    protected void onEntranceTransitionStart() {
-    }
-
-    /**
-     * Callback when entrance transition is ended.
-     */
-    protected void onEntranceTransitionEnd() {
-    }
-
-    /**
-     * When fragment finishes loading data, it should call startEntranceTransition()
-     * to execute the entrance transition.
-     * startEntranceTransition() will start transition only if both two conditions
-     * are satisfied:
-     * <li> prepareEntranceTransition() was called.</li>
-     * <li> has not executed entrance transition yet.</li>
-     * <p>
-     * If startEntranceTransition() is called before onViewCreated(), it will be pending
-     * and executed when view is created.
-     */
-    public void startEntranceTransition() {
-        mStateMachine.fireEvent(EVT_START_ENTRANCE);
-    }
-
-    void onExecuteEntranceTransition() {
-        // wait till views get their initial position before start transition
-        final View view = getView();
-        if (view == null) {
-            // fragment view destroyed, transition not needed
-            return;
-        }
-        view.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
-            @Override
-            public boolean onPreDraw() {
-                view.getViewTreeObserver().removeOnPreDrawListener(this);
-                if (FragmentUtil.getContext(BaseFragment.this) == null || getView() == null) {
-                    // bail out if fragment is destroyed immediately after startEntranceTransition
-                    return true;
-                }
-                internalCreateEntranceTransition();
-                onEntranceTransitionStart();
-                if (mEntranceTransition != null) {
-                    runEntranceTransition(mEntranceTransition);
-                } else {
-                    mStateMachine.fireEvent(EVT_ENTRANCE_END);
-                }
-                return false;
-            }
-        });
-        view.invalidate();
-    }
-
-    void internalCreateEntranceTransition() {
-        mEntranceTransition = createEntranceTransition();
-        if (mEntranceTransition == null) {
-            return;
-        }
-        TransitionHelper.addTransitionListener(mEntranceTransition, new TransitionListener() {
-            @Override
-            public void onTransitionEnd(Object transition) {
-                mEntranceTransition = null;
-                mStateMachine.fireEvent(EVT_ENTRANCE_END);
-            }
-        });
-    }
-
-    /**
-     * Returns the {@link ProgressBarManager}.
-     * @return The {@link ProgressBarManager}.
-     */
-    public final ProgressBarManager getProgressBarManager() {
-        return mProgressBarManager;
-    }
-}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BaseRowFragment.java b/v17/leanback/src/android/support/v17/leanback/app/BaseRowFragment.java
deleted file mode 100644
index 2d79f3e..0000000
--- a/v17/leanback/src/android/support/v17/leanback/app/BaseRowFragment.java
+++ /dev/null
@@ -1,302 +0,0 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from BaseRowSupportFragment.java.  DO NOT MODIFY. */
-
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-package android.support.v17.leanback.app;
-
-import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.v17.leanback.widget.ItemBridgeAdapter;
-import android.support.v17.leanback.widget.ListRow;
-import android.support.v17.leanback.widget.ObjectAdapter;
-import android.support.v17.leanback.widget.OnChildViewHolderSelectedListener;
-import android.support.v17.leanback.widget.PresenterSelector;
-import android.support.v17.leanback.widget.Row;
-import android.support.v17.leanback.widget.VerticalGridView;
-import android.app.Fragment;
-import android.support.v7.widget.RecyclerView;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-/**
- * An internal base class for a fragment containing a list of rows.
- */
-abstract class BaseRowFragment extends Fragment {
-    private static final String CURRENT_SELECTED_POSITION = "currentSelectedPosition";
-    private ObjectAdapter mAdapter;
-    VerticalGridView mVerticalGridView;
-    private PresenterSelector mPresenterSelector;
-    final ItemBridgeAdapter mBridgeAdapter = new ItemBridgeAdapter();
-    int mSelectedPosition = -1;
-    private boolean mPendingTransitionPrepare;
-    private LateSelectionObserver mLateSelectionObserver = new LateSelectionObserver();
-
-    abstract int getLayoutResourceId();
-
-    private final OnChildViewHolderSelectedListener mRowSelectedListener =
-            new OnChildViewHolderSelectedListener() {
-                @Override
-                public void onChildViewHolderSelected(RecyclerView parent,
-                        RecyclerView.ViewHolder view, int position, int subposition) {
-                    if (!mLateSelectionObserver.mIsLateSelection) {
-                        mSelectedPosition = position;
-                        onRowSelected(parent, view, position, subposition);
-                    }
-                }
-            };
-
-    void onRowSelected(RecyclerView parent, RecyclerView.ViewHolder view,
-            int position, int subposition) {
-    }
-
-    @Override
-    public View onCreateView(LayoutInflater inflater, ViewGroup container,
-            Bundle savedInstanceState) {
-        View view = inflater.inflate(getLayoutResourceId(), container, false);
-        mVerticalGridView = findGridViewFromRoot(view);
-        if (mPendingTransitionPrepare) {
-            mPendingTransitionPrepare = false;
-            onTransitionPrepare();
-        }
-        return view;
-    }
-
-    VerticalGridView findGridViewFromRoot(View view) {
-        return (VerticalGridView) view;
-    }
-
-    @Override
-    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
-        if (savedInstanceState != null) {
-            mSelectedPosition = savedInstanceState.getInt(CURRENT_SELECTED_POSITION, -1);
-        }
-        setAdapterAndSelection();
-        mVerticalGridView.setOnChildViewHolderSelectedListener(mRowSelectedListener);
-    }
-
-    /**
-     * This class waits for the adapter to be updated before setting the selected
-     * row.
-     */
-    private class LateSelectionObserver extends RecyclerView.AdapterDataObserver {
-        boolean mIsLateSelection = false;
-
-        LateSelectionObserver() {
-        }
-
-        @Override
-        public void onChanged() {
-            performLateSelection();
-        }
-
-        @Override
-        public void onItemRangeInserted(int positionStart, int itemCount) {
-            performLateSelection();
-        }
-
-        void startLateSelection() {
-            mIsLateSelection = true;
-            mBridgeAdapter.registerAdapterDataObserver(this);
-        }
-
-        void performLateSelection() {
-            clear();
-            if (mVerticalGridView != null) {
-                mVerticalGridView.setSelectedPosition(mSelectedPosition);
-            }
-        }
-
-        void clear() {
-            if (mIsLateSelection) {
-                mIsLateSelection = false;
-                mBridgeAdapter.unregisterAdapterDataObserver(this);
-            }
-        }
-    }
-
-    void setAdapterAndSelection() {
-        if (mAdapter == null) {
-            // delay until ItemBridgeAdapter has wrappedAdapter. Once we assign ItemBridgeAdapter
-            // to RecyclerView, it will not be allowed to change "hasStableId" to true.
-            return;
-        }
-        if (mVerticalGridView.getAdapter() != mBridgeAdapter) {
-            // avoid extra layout if ItemBridgeAdapter was already set.
-            mVerticalGridView.setAdapter(mBridgeAdapter);
-        }
-        // We don't set the selected position unless we've data in the adapter.
-        boolean lateSelection = mBridgeAdapter.getItemCount() == 0 && mSelectedPosition >= 0;
-        if (lateSelection) {
-            mLateSelectionObserver.startLateSelection();
-        } else if (mSelectedPosition >= 0) {
-            mVerticalGridView.setSelectedPosition(mSelectedPosition);
-        }
-    }
-
-    @Override
-    public void onDestroyView() {
-        super.onDestroyView();
-        mLateSelectionObserver.clear();
-        mVerticalGridView = null;
-    }
-
-    @Override
-    public void onSaveInstanceState(Bundle outState) {
-        super.onSaveInstanceState(outState);
-        outState.putInt(CURRENT_SELECTED_POSITION, mSelectedPosition);
-    }
-
-    /**
-     * Set the presenter selector used to create and bind views.
-     */
-    public final void setPresenterSelector(PresenterSelector presenterSelector) {
-        mPresenterSelector = presenterSelector;
-        updateAdapter();
-    }
-
-    /**
-     * Get the presenter selector used to create and bind views.
-     */
-    public final PresenterSelector getPresenterSelector() {
-        return mPresenterSelector;
-    }
-
-    /**
-     * Sets the adapter that represents a list of rows.
-     * @param rowsAdapter Adapter that represents list of rows.
-     */
-    public final void setAdapter(ObjectAdapter rowsAdapter) {
-        mAdapter = rowsAdapter;
-        updateAdapter();
-    }
-
-    /**
-     * Returns the Adapter that represents list of rows.
-     * @return Adapter that represents list of rows.
-     */
-    public final ObjectAdapter getAdapter() {
-        return mAdapter;
-    }
-
-    /**
-     * Returns the RecyclerView.Adapter that wraps {@link #getAdapter()}.
-     * @return The RecyclerView.Adapter that wraps {@link #getAdapter()}.
-     */
-    public final ItemBridgeAdapter getBridgeAdapter() {
-        return mBridgeAdapter;
-    }
-
-    /**
-     * Sets the selected row position with smooth animation.
-     */
-    public void setSelectedPosition(int position) {
-        setSelectedPosition(position, true);
-    }
-
-    /**
-     * Gets position of currently selected row.
-     * @return Position of currently selected row.
-     */
-    public int getSelectedPosition() {
-        return mSelectedPosition;
-    }
-
-    /**
-     * Sets the selected row position.
-     */
-    public void setSelectedPosition(int position, boolean smooth) {
-        if (mSelectedPosition == position) {
-            return;
-        }
-        mSelectedPosition = position;
-        if (mVerticalGridView != null) {
-            if (mLateSelectionObserver.mIsLateSelection) {
-                return;
-            }
-            if (smooth) {
-                mVerticalGridView.setSelectedPositionSmooth(position);
-            } else {
-                mVerticalGridView.setSelectedPosition(position);
-            }
-        }
-    }
-
-    public final VerticalGridView getVerticalGridView() {
-        return mVerticalGridView;
-    }
-
-    void updateAdapter() {
-        mBridgeAdapter.setAdapter(mAdapter);
-        mBridgeAdapter.setPresenter(mPresenterSelector);
-
-        if (mVerticalGridView != null) {
-            setAdapterAndSelection();
-        }
-    }
-
-    Object getItem(Row row, int position) {
-        if (row instanceof ListRow) {
-            return ((ListRow) row).getAdapter().get(position);
-        } else {
-            return null;
-        }
-    }
-
-    public boolean onTransitionPrepare() {
-        if (mVerticalGridView != null) {
-            mVerticalGridView.setAnimateChildLayout(false);
-            mVerticalGridView.setScrollEnabled(false);
-            return true;
-        }
-        mPendingTransitionPrepare = true;
-        return false;
-    }
-
-    public void onTransitionStart() {
-        if (mVerticalGridView != null) {
-            mVerticalGridView.setPruneChild(false);
-            mVerticalGridView.setLayoutFrozen(true);
-            mVerticalGridView.setFocusSearchDisabled(true);
-        }
-    }
-
-    public void onTransitionEnd() {
-        // be careful that fragment might be destroyed before header transition ends.
-        if (mVerticalGridView != null) {
-            mVerticalGridView.setLayoutFrozen(false);
-            mVerticalGridView.setAnimateChildLayout(true);
-            mVerticalGridView.setPruneChild(true);
-            mVerticalGridView.setFocusSearchDisabled(false);
-            mVerticalGridView.setScrollEnabled(true);
-        }
-    }
-
-    public void setAlignment(int windowAlignOffsetTop) {
-        if (mVerticalGridView != null) {
-            // align the top edge of item
-            mVerticalGridView.setItemAlignmentOffset(0);
-            mVerticalGridView.setItemAlignmentOffsetPercent(
-                    VerticalGridView.ITEM_ALIGN_OFFSET_PERCENT_DISABLED);
-
-            // align to a fixed position from top
-            mVerticalGridView.setWindowAlignmentOffset(windowAlignOffsetTop);
-            mVerticalGridView.setWindowAlignmentOffsetPercent(
-                    VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
-            mVerticalGridView.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE);
-        }
-    }
-}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BaseRowSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/BaseRowSupportFragment.java
deleted file mode 100644
index dba78da..0000000
--- a/v17/leanback/src/android/support/v17/leanback/app/BaseRowSupportFragment.java
+++ /dev/null
@@ -1,299 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-package android.support.v17.leanback.app;
-
-import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.v17.leanback.widget.ItemBridgeAdapter;
-import android.support.v17.leanback.widget.ListRow;
-import android.support.v17.leanback.widget.ObjectAdapter;
-import android.support.v17.leanback.widget.OnChildViewHolderSelectedListener;
-import android.support.v17.leanback.widget.PresenterSelector;
-import android.support.v17.leanback.widget.Row;
-import android.support.v17.leanback.widget.VerticalGridView;
-import android.support.v4.app.Fragment;
-import android.support.v7.widget.RecyclerView;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-/**
- * An internal base class for a fragment containing a list of rows.
- */
-abstract class BaseRowSupportFragment extends Fragment {
-    private static final String CURRENT_SELECTED_POSITION = "currentSelectedPosition";
-    private ObjectAdapter mAdapter;
-    VerticalGridView mVerticalGridView;
-    private PresenterSelector mPresenterSelector;
-    final ItemBridgeAdapter mBridgeAdapter = new ItemBridgeAdapter();
-    int mSelectedPosition = -1;
-    private boolean mPendingTransitionPrepare;
-    private LateSelectionObserver mLateSelectionObserver = new LateSelectionObserver();
-
-    abstract int getLayoutResourceId();
-
-    private final OnChildViewHolderSelectedListener mRowSelectedListener =
-            new OnChildViewHolderSelectedListener() {
-                @Override
-                public void onChildViewHolderSelected(RecyclerView parent,
-                        RecyclerView.ViewHolder view, int position, int subposition) {
-                    if (!mLateSelectionObserver.mIsLateSelection) {
-                        mSelectedPosition = position;
-                        onRowSelected(parent, view, position, subposition);
-                    }
-                }
-            };
-
-    void onRowSelected(RecyclerView parent, RecyclerView.ViewHolder view,
-            int position, int subposition) {
-    }
-
-    @Override
-    public View onCreateView(LayoutInflater inflater, ViewGroup container,
-            Bundle savedInstanceState) {
-        View view = inflater.inflate(getLayoutResourceId(), container, false);
-        mVerticalGridView = findGridViewFromRoot(view);
-        if (mPendingTransitionPrepare) {
-            mPendingTransitionPrepare = false;
-            onTransitionPrepare();
-        }
-        return view;
-    }
-
-    VerticalGridView findGridViewFromRoot(View view) {
-        return (VerticalGridView) view;
-    }
-
-    @Override
-    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
-        if (savedInstanceState != null) {
-            mSelectedPosition = savedInstanceState.getInt(CURRENT_SELECTED_POSITION, -1);
-        }
-        setAdapterAndSelection();
-        mVerticalGridView.setOnChildViewHolderSelectedListener(mRowSelectedListener);
-    }
-
-    /**
-     * This class waits for the adapter to be updated before setting the selected
-     * row.
-     */
-    private class LateSelectionObserver extends RecyclerView.AdapterDataObserver {
-        boolean mIsLateSelection = false;
-
-        LateSelectionObserver() {
-        }
-
-        @Override
-        public void onChanged() {
-            performLateSelection();
-        }
-
-        @Override
-        public void onItemRangeInserted(int positionStart, int itemCount) {
-            performLateSelection();
-        }
-
-        void startLateSelection() {
-            mIsLateSelection = true;
-            mBridgeAdapter.registerAdapterDataObserver(this);
-        }
-
-        void performLateSelection() {
-            clear();
-            if (mVerticalGridView != null) {
-                mVerticalGridView.setSelectedPosition(mSelectedPosition);
-            }
-        }
-
-        void clear() {
-            if (mIsLateSelection) {
-                mIsLateSelection = false;
-                mBridgeAdapter.unregisterAdapterDataObserver(this);
-            }
-        }
-    }
-
-    void setAdapterAndSelection() {
-        if (mAdapter == null) {
-            // delay until ItemBridgeAdapter has wrappedAdapter. Once we assign ItemBridgeAdapter
-            // to RecyclerView, it will not be allowed to change "hasStableId" to true.
-            return;
-        }
-        if (mVerticalGridView.getAdapter() != mBridgeAdapter) {
-            // avoid extra layout if ItemBridgeAdapter was already set.
-            mVerticalGridView.setAdapter(mBridgeAdapter);
-        }
-        // We don't set the selected position unless we've data in the adapter.
-        boolean lateSelection = mBridgeAdapter.getItemCount() == 0 && mSelectedPosition >= 0;
-        if (lateSelection) {
-            mLateSelectionObserver.startLateSelection();
-        } else if (mSelectedPosition >= 0) {
-            mVerticalGridView.setSelectedPosition(mSelectedPosition);
-        }
-    }
-
-    @Override
-    public void onDestroyView() {
-        super.onDestroyView();
-        mLateSelectionObserver.clear();
-        mVerticalGridView = null;
-    }
-
-    @Override
-    public void onSaveInstanceState(Bundle outState) {
-        super.onSaveInstanceState(outState);
-        outState.putInt(CURRENT_SELECTED_POSITION, mSelectedPosition);
-    }
-
-    /**
-     * Set the presenter selector used to create and bind views.
-     */
-    public final void setPresenterSelector(PresenterSelector presenterSelector) {
-        mPresenterSelector = presenterSelector;
-        updateAdapter();
-    }
-
-    /**
-     * Get the presenter selector used to create and bind views.
-     */
-    public final PresenterSelector getPresenterSelector() {
-        return mPresenterSelector;
-    }
-
-    /**
-     * Sets the adapter that represents a list of rows.
-     * @param rowsAdapter Adapter that represents list of rows.
-     */
-    public final void setAdapter(ObjectAdapter rowsAdapter) {
-        mAdapter = rowsAdapter;
-        updateAdapter();
-    }
-
-    /**
-     * Returns the Adapter that represents list of rows.
-     * @return Adapter that represents list of rows.
-     */
-    public final ObjectAdapter getAdapter() {
-        return mAdapter;
-    }
-
-    /**
-     * Returns the RecyclerView.Adapter that wraps {@link #getAdapter()}.
-     * @return The RecyclerView.Adapter that wraps {@link #getAdapter()}.
-     */
-    public final ItemBridgeAdapter getBridgeAdapter() {
-        return mBridgeAdapter;
-    }
-
-    /**
-     * Sets the selected row position with smooth animation.
-     */
-    public void setSelectedPosition(int position) {
-        setSelectedPosition(position, true);
-    }
-
-    /**
-     * Gets position of currently selected row.
-     * @return Position of currently selected row.
-     */
-    public int getSelectedPosition() {
-        return mSelectedPosition;
-    }
-
-    /**
-     * Sets the selected row position.
-     */
-    public void setSelectedPosition(int position, boolean smooth) {
-        if (mSelectedPosition == position) {
-            return;
-        }
-        mSelectedPosition = position;
-        if (mVerticalGridView != null) {
-            if (mLateSelectionObserver.mIsLateSelection) {
-                return;
-            }
-            if (smooth) {
-                mVerticalGridView.setSelectedPositionSmooth(position);
-            } else {
-                mVerticalGridView.setSelectedPosition(position);
-            }
-        }
-    }
-
-    public final VerticalGridView getVerticalGridView() {
-        return mVerticalGridView;
-    }
-
-    void updateAdapter() {
-        mBridgeAdapter.setAdapter(mAdapter);
-        mBridgeAdapter.setPresenter(mPresenterSelector);
-
-        if (mVerticalGridView != null) {
-            setAdapterAndSelection();
-        }
-    }
-
-    Object getItem(Row row, int position) {
-        if (row instanceof ListRow) {
-            return ((ListRow) row).getAdapter().get(position);
-        } else {
-            return null;
-        }
-    }
-
-    public boolean onTransitionPrepare() {
-        if (mVerticalGridView != null) {
-            mVerticalGridView.setAnimateChildLayout(false);
-            mVerticalGridView.setScrollEnabled(false);
-            return true;
-        }
-        mPendingTransitionPrepare = true;
-        return false;
-    }
-
-    public void onTransitionStart() {
-        if (mVerticalGridView != null) {
-            mVerticalGridView.setPruneChild(false);
-            mVerticalGridView.setLayoutFrozen(true);
-            mVerticalGridView.setFocusSearchDisabled(true);
-        }
-    }
-
-    public void onTransitionEnd() {
-        // be careful that fragment might be destroyed before header transition ends.
-        if (mVerticalGridView != null) {
-            mVerticalGridView.setLayoutFrozen(false);
-            mVerticalGridView.setAnimateChildLayout(true);
-            mVerticalGridView.setPruneChild(true);
-            mVerticalGridView.setFocusSearchDisabled(false);
-            mVerticalGridView.setScrollEnabled(true);
-        }
-    }
-
-    public void setAlignment(int windowAlignOffsetTop) {
-        if (mVerticalGridView != null) {
-            // align the top edge of item
-            mVerticalGridView.setItemAlignmentOffset(0);
-            mVerticalGridView.setItemAlignmentOffsetPercent(
-                    VerticalGridView.ITEM_ALIGN_OFFSET_PERCENT_DISABLED);
-
-            // align to a fixed position from top
-            mVerticalGridView.setWindowAlignmentOffset(windowAlignOffsetTop);
-            mVerticalGridView.setWindowAlignmentOffsetPercent(
-                    VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
-            mVerticalGridView.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE);
-        }
-    }
-}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BrandedFragment.java b/v17/leanback/src/android/support/v17/leanback/app/BrandedFragment.java
deleted file mode 100644
index 1f6ad29..0000000
--- a/v17/leanback/src/android/support/v17/leanback/app/BrandedFragment.java
+++ /dev/null
@@ -1,338 +0,0 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from BrandedSupportFragment.java.  DO NOT MODIFY. */
-
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-package android.support.v17.leanback.app;
-
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.v17.leanback.R;
-import android.support.v17.leanback.widget.SearchOrbView;
-import android.support.v17.leanback.widget.TitleHelper;
-import android.support.v17.leanback.widget.TitleViewAdapter;
-import android.app.Fragment;
-import android.util.TypedValue;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-/**
- * Fragment class for managing search and branding using a view that implements
- * {@link TitleViewAdapter.Provider}.
- */
-public class BrandedFragment extends Fragment {
-
-    // BUNDLE attribute for title is showing
-    private static final String TITLE_SHOW = "titleShow";
-
-    private boolean mShowingTitle = true;
-    private CharSequence mTitle;
-    private Drawable mBadgeDrawable;
-    private View mTitleView;
-    private TitleViewAdapter mTitleViewAdapter;
-    private SearchOrbView.Colors mSearchAffordanceColors;
-    private boolean mSearchAffordanceColorSet;
-    private View.OnClickListener mExternalOnSearchClickedListener;
-    private TitleHelper mTitleHelper;
-
-    /**
-     * Called by {@link #installTitleView(LayoutInflater, ViewGroup, Bundle)} to inflate
-     * title view.  Default implementation uses layout file lb_browse_title.
-     * Subclass may override and use its own layout, the layout must have a descendant with id
-     * browse_title_group that implements {@link TitleViewAdapter.Provider}. Subclass may return
-     * null if no title is needed.
-     *
-     * @param inflater           The LayoutInflater object that can be used to inflate
-     *                           any views in the fragment,
-     * @param parent             Parent of title view.
-     * @param savedInstanceState If non-null, this fragment is being re-constructed
-     *                           from a previous saved state as given here.
-     * @return Title view which must have a descendant with id browse_title_group that implements
-     *         {@link TitleViewAdapter.Provider}, or null for no title view.
-     */
-    public View onInflateTitleView(LayoutInflater inflater, ViewGroup parent,
-                                Bundle savedInstanceState) {
-        TypedValue typedValue = new TypedValue();
-        boolean found = parent.getContext().getTheme().resolveAttribute(
-                R.attr.browseTitleViewLayout, typedValue, true);
-        return inflater.inflate(found ? typedValue.resourceId : R.layout.lb_browse_title,
-                parent, false);
-    }
-
-    /**
-     * Inflate title view and add to parent.  This method should be called in
-     * {@link Fragment#onCreateView(LayoutInflater, ViewGroup, Bundle)}.
-     * @param inflater The LayoutInflater object that can be used to inflate
-     * any views in the fragment,
-     * @param parent Parent of title view.
-     * @param savedInstanceState If non-null, this fragment is being re-constructed
-     * from a previous saved state as given here.
-     */
-    public void installTitleView(LayoutInflater inflater, ViewGroup parent,
-                            Bundle savedInstanceState) {
-        View titleLayoutRoot = onInflateTitleView(inflater, parent, savedInstanceState);
-        if (titleLayoutRoot != null) {
-            parent.addView(titleLayoutRoot);
-            setTitleView(titleLayoutRoot.findViewById(R.id.browse_title_group));
-        } else {
-            setTitleView(null);
-        }
-    }
-
-    /**
-     * Sets the view that implemented {@link TitleViewAdapter}.
-     * @param titleView The view that implemented {@link TitleViewAdapter.Provider}.
-     */
-    public void setTitleView(View titleView) {
-        mTitleView = titleView;
-        if (mTitleView == null) {
-            mTitleViewAdapter = null;
-            mTitleHelper = null;
-        } else {
-            mTitleViewAdapter = ((TitleViewAdapter.Provider) mTitleView).getTitleViewAdapter();
-            mTitleViewAdapter.setTitle(mTitle);
-            mTitleViewAdapter.setBadgeDrawable(mBadgeDrawable);
-            if (mSearchAffordanceColorSet) {
-                mTitleViewAdapter.setSearchAffordanceColors(mSearchAffordanceColors);
-            }
-            if (mExternalOnSearchClickedListener != null) {
-                setOnSearchClickedListener(mExternalOnSearchClickedListener);
-            }
-            if (getView() instanceof ViewGroup) {
-                mTitleHelper = new TitleHelper((ViewGroup) getView(), mTitleView);
-            }
-        }
-    }
-
-    /**
-     * Returns the view that implements {@link TitleViewAdapter.Provider}.
-     * @return The view that implements {@link TitleViewAdapter.Provider}.
-     */
-    public View getTitleView() {
-        return mTitleView;
-    }
-
-    /**
-     * Returns the {@link TitleViewAdapter} implemented by title view.
-     * @return The {@link TitleViewAdapter} implemented by title view.
-     */
-    public TitleViewAdapter getTitleViewAdapter() {
-        return mTitleViewAdapter;
-    }
-
-    /**
-     * Returns the {@link TitleHelper}.
-     */
-    TitleHelper getTitleHelper() {
-        return mTitleHelper;
-    }
-
-    @Override
-    public void onSaveInstanceState(Bundle outState) {
-        super.onSaveInstanceState(outState);
-        outState.putBoolean(TITLE_SHOW, mShowingTitle);
-    }
-
-    @Override
-    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
-        super.onViewCreated(view, savedInstanceState);
-        if (savedInstanceState != null) {
-            mShowingTitle = savedInstanceState.getBoolean(TITLE_SHOW);
-        }
-        if (mTitleView != null && view instanceof ViewGroup) {
-            mTitleHelper = new TitleHelper((ViewGroup) view, mTitleView);
-            mTitleHelper.showTitle(mShowingTitle);
-        }
-    }
-
-    @Override
-    public void onDestroyView() {
-        super.onDestroyView();
-        mTitleHelper = null;
-    }
-
-    /**
-     * Shows or hides the title view.
-     * @param show True to show title view, false to hide title view.
-     */
-    public void showTitle(boolean show) {
-        // TODO: handle interruptions?
-        if (show == mShowingTitle) {
-            return;
-        }
-        mShowingTitle = show;
-        if (mTitleHelper != null) {
-            mTitleHelper.showTitle(show);
-        }
-    }
-
-    /**
-     * Changes title view's components visibility and shows title.
-     * @param flags Flags representing the visibility of components inside title view.
-     * @see TitleViewAdapter#SEARCH_VIEW_VISIBLE
-     * @see TitleViewAdapter#BRANDING_VIEW_VISIBLE
-     * @see TitleViewAdapter#FULL_VIEW_VISIBLE
-     * @see TitleViewAdapter#updateComponentsVisibility(int)
-     */
-    public void showTitle(int flags) {
-        if (mTitleViewAdapter != null) {
-            mTitleViewAdapter.updateComponentsVisibility(flags);
-        }
-        showTitle(true);
-    }
-
-    /**
-     * Sets the drawable displayed in the fragment title.
-     *
-     * @param drawable The Drawable to display in the fragment title.
-     */
-    public void setBadgeDrawable(Drawable drawable) {
-        if (mBadgeDrawable != drawable) {
-            mBadgeDrawable = drawable;
-            if (mTitleViewAdapter != null) {
-                mTitleViewAdapter.setBadgeDrawable(drawable);
-            }
-        }
-    }
-
-    /**
-     * Returns the badge drawable used in the fragment title.
-     * @return The badge drawable used in the fragment title.
-     */
-    public Drawable getBadgeDrawable() {
-        return mBadgeDrawable;
-    }
-
-    /**
-     * Sets title text for the fragment.
-     *
-     * @param title The title text of the fragment.
-     */
-    public void setTitle(CharSequence title) {
-        mTitle = title;
-        if (mTitleViewAdapter != null) {
-            mTitleViewAdapter.setTitle(title);
-        }
-    }
-
-    /**
-     * Returns the title text for the fragment.
-     * @return Title text for the fragment.
-     */
-    public CharSequence getTitle() {
-        return mTitle;
-    }
-
-    /**
-     * Sets a click listener for the search affordance.
-     *
-     * <p>The presence of a listener will change the visibility of the search
-     * affordance in the fragment title. When set to non-null, the title will
-     * contain an element that a user may click to begin a search.
-     *
-     * <p>The listener's {@link View.OnClickListener#onClick onClick} method
-     * will be invoked when the user clicks on the search element.
-     *
-     * @param listener The listener to call when the search element is clicked.
-     */
-    public void setOnSearchClickedListener(View.OnClickListener listener) {
-        mExternalOnSearchClickedListener = listener;
-        if (mTitleViewAdapter != null) {
-            mTitleViewAdapter.setOnSearchClickedListener(listener);
-        }
-    }
-
-    /**
-     * Sets the {@link android.support.v17.leanback.widget.SearchOrbView.Colors} used to draw the
-     * search affordance.
-     *
-     * @param colors Colors used to draw search affordance.
-     */
-    public void setSearchAffordanceColors(SearchOrbView.Colors colors) {
-        mSearchAffordanceColors = colors;
-        mSearchAffordanceColorSet = true;
-        if (mTitleViewAdapter != null) {
-            mTitleViewAdapter.setSearchAffordanceColors(mSearchAffordanceColors);
-        }
-    }
-
-    /**
-     * Returns the {@link android.support.v17.leanback.widget.SearchOrbView.Colors}
-     * used to draw the search affordance.
-     */
-    public SearchOrbView.Colors getSearchAffordanceColors() {
-        if (mSearchAffordanceColorSet) {
-            return mSearchAffordanceColors;
-        }
-        if (mTitleViewAdapter == null) {
-            throw new IllegalStateException("Fragment views not yet created");
-        }
-        return mTitleViewAdapter.getSearchAffordanceColors();
-    }
-
-    /**
-     * Sets the color used to draw the search affordance.
-     * A default brighter color will be set by the framework.
-     *
-     * @param color The color to use for the search affordance.
-     */
-    public void setSearchAffordanceColor(int color) {
-        setSearchAffordanceColors(new SearchOrbView.Colors(color));
-    }
-
-    /**
-     * Returns the color used to draw the search affordance.
-     */
-    public int getSearchAffordanceColor() {
-        return getSearchAffordanceColors().color;
-    }
-
-    @Override
-    public void onStart() {
-        super.onStart();
-        if (mTitleViewAdapter != null) {
-            showTitle(mShowingTitle);
-            mTitleViewAdapter.setAnimationEnabled(true);
-        }
-    }
-
-    @Override
-    public void onPause() {
-        if (mTitleViewAdapter != null) {
-            mTitleViewAdapter.setAnimationEnabled(false);
-        }
-        super.onPause();
-    }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-        if (mTitleViewAdapter != null) {
-            mTitleViewAdapter.setAnimationEnabled(true);
-        }
-    }
-
-    /**
-     * Returns true/false to indicate the visibility of TitleView.
-     *
-     * @return boolean to indicate whether or not it's showing the title.
-     */
-    public final boolean isShowingTitle() {
-        return mShowingTitle;
-    }
-
-}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BrowseFragment.java b/v17/leanback/src/android/support/v17/leanback/app/BrowseFragment.java
deleted file mode 100644
index f377389..0000000
--- a/v17/leanback/src/android/support/v17/leanback/app/BrowseFragment.java
+++ /dev/null
@@ -1,1816 +0,0 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from BrowseSupportFragment.java.  DO NOT MODIFY. */
-
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-package android.support.v17.leanback.app;
-
-import static android.support.v7.widget.RecyclerView.NO_POSITION;
-
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Color;
-import android.graphics.Rect;
-import android.os.Bundle;
-import android.support.annotation.ColorInt;
-import android.support.v17.leanback.R;
-import android.support.v17.leanback.transition.TransitionHelper;
-import android.support.v17.leanback.transition.TransitionListener;
-import android.support.v17.leanback.util.StateMachine.Event;
-import android.support.v17.leanback.util.StateMachine.State;
-import android.support.v17.leanback.widget.BrowseFrameLayout;
-import android.support.v17.leanback.widget.InvisibleRowPresenter;
-import android.support.v17.leanback.widget.ListRow;
-import android.support.v17.leanback.widget.ObjectAdapter;
-import android.support.v17.leanback.widget.OnItemViewClickedListener;
-import android.support.v17.leanback.widget.OnItemViewSelectedListener;
-import android.support.v17.leanback.widget.PageRow;
-import android.support.v17.leanback.widget.Presenter;
-import android.support.v17.leanback.widget.PresenterSelector;
-import android.support.v17.leanback.widget.Row;
-import android.support.v17.leanback.widget.RowHeaderPresenter;
-import android.support.v17.leanback.widget.RowPresenter;
-import android.support.v17.leanback.widget.ScaleFrameLayout;
-import android.support.v17.leanback.widget.TitleViewAdapter;
-import android.support.v17.leanback.widget.VerticalGridView;
-import android.app.Fragment;
-import android.app.FragmentManager;
-import android.app.FragmentManager.BackStackEntry;
-import android.app.FragmentTransaction;
-import android.support.v4.view.ViewCompat;
-import android.support.v7.widget.RecyclerView;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewGroup.MarginLayoutParams;
-import android.view.ViewTreeObserver;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * A fragment for creating Leanback browse screens. It is composed of a
- * RowsFragment and a HeadersFragment.
- * <p>
- * A BrowseFragment renders the elements of its {@link ObjectAdapter} as a set
- * of rows in a vertical list. The elements in this adapter must be subclasses
- * of {@link Row}.
- * <p>
- * The HeadersFragment can be set to be either shown or hidden by default, or
- * may be disabled entirely. See {@link #setHeadersState} for details.
- * <p>
- * By default the BrowseFragment includes support for returning to the headers
- * when the user presses Back. For Activities that customize {@link
- * android.app.Activity#onBackPressed()}, you must disable this default Back key support by
- * calling {@link #setHeadersTransitionOnBackEnabled(boolean)} with false and
- * use {@link BrowseFragment.BrowseTransitionListener} and
- * {@link #startHeadersTransition(boolean)}.
- * <p>
- * The recommended theme to use with a BrowseFragment is
- * {@link android.support.v17.leanback.R.style#Theme_Leanback_Browse}.
- * </p>
- */
-public class BrowseFragment extends BaseFragment {
-
-    // BUNDLE attribute for saving header show/hide status when backstack is used:
-    static final String HEADER_STACK_INDEX = "headerStackIndex";
-    // BUNDLE attribute for saving header show/hide status when backstack is not used:
-    static final String HEADER_SHOW = "headerShow";
-    private static final String IS_PAGE_ROW = "isPageRow";
-    private static final String CURRENT_SELECTED_POSITION = "currentSelectedPosition";
-
-    /**
-     * State to hide headers fragment.
-     */
-    final State STATE_SET_ENTRANCE_START_STATE = new State("SET_ENTRANCE_START_STATE") {
-        @Override
-        public void run() {
-            setEntranceTransitionStartState();
-        }
-    };
-
-    /**
-     * Event for Header fragment view is created, we could perform
-     * {@link #setEntranceTransitionStartState()} to hide headers fragment initially.
-     */
-    final Event EVT_HEADER_VIEW_CREATED = new Event("headerFragmentViewCreated");
-
-    /**
-     * Event for {@link #getMainFragment()} view is created, it's additional requirement to execute
-     * {@link #onEntranceTransitionPrepare()}.
-     */
-    final Event EVT_MAIN_FRAGMENT_VIEW_CREATED = new Event("mainFragmentViewCreated");
-
-    /**
-     * Event that data for the screen is ready, this is additional requirement to launch entrance
-     * transition.
-     */
-    final Event EVT_SCREEN_DATA_READY = new Event("screenDataReady");
-
-    @Override
-    void createStateMachineStates() {
-        super.createStateMachineStates();
-        mStateMachine.addState(STATE_SET_ENTRANCE_START_STATE);
-    }
-
-    @Override
-    void createStateMachineTransitions() {
-        super.createStateMachineTransitions();
-        // when headers fragment view is created we could setEntranceTransitionStartState()
-        mStateMachine.addTransition(STATE_ENTRANCE_ON_PREPARED, STATE_SET_ENTRANCE_START_STATE,
-                EVT_HEADER_VIEW_CREATED);
-
-        // add additional requirement for onEntranceTransitionPrepare()
-        mStateMachine.addTransition(STATE_ENTRANCE_ON_PREPARED,
-                STATE_ENTRANCE_ON_PREPARED_ON_CREATEVIEW,
-                EVT_MAIN_FRAGMENT_VIEW_CREATED);
-        // add additional requirement to launch entrance transition.
-        mStateMachine.addTransition(STATE_ENTRANCE_ON_PREPARED,  STATE_ENTRANCE_PERFORM,
-                EVT_SCREEN_DATA_READY);
-    }
-
-    final class BackStackListener implements FragmentManager.OnBackStackChangedListener {
-        int mLastEntryCount;
-        int mIndexOfHeadersBackStack;
-
-        BackStackListener() {
-            mLastEntryCount = getFragmentManager().getBackStackEntryCount();
-            mIndexOfHeadersBackStack = -1;
-        }
-
-        void load(Bundle savedInstanceState) {
-            if (savedInstanceState != null) {
-                mIndexOfHeadersBackStack = savedInstanceState.getInt(HEADER_STACK_INDEX, -1);
-                mShowingHeaders = mIndexOfHeadersBackStack == -1;
-            } else {
-                if (!mShowingHeaders) {
-                    getFragmentManager().beginTransaction()
-                            .addToBackStack(mWithHeadersBackStackName).commit();
-                }
-            }
-        }
-
-        void save(Bundle outState) {
-            outState.putInt(HEADER_STACK_INDEX, mIndexOfHeadersBackStack);
-        }
-
-
-        @Override
-        public void onBackStackChanged() {
-            if (getFragmentManager() == null) {
-                Log.w(TAG, "getFragmentManager() is null, stack:", new Exception());
-                return;
-            }
-            int count = getFragmentManager().getBackStackEntryCount();
-            // if backstack is growing and last pushed entry is "headers" backstack,
-            // remember the index of the entry.
-            if (count > mLastEntryCount) {
-                BackStackEntry entry = getFragmentManager().getBackStackEntryAt(count - 1);
-                if (mWithHeadersBackStackName.equals(entry.getName())) {
-                    mIndexOfHeadersBackStack = count - 1;
-                }
-            } else if (count < mLastEntryCount) {
-                // if popped "headers" backstack, initiate the show header transition if needed
-                if (mIndexOfHeadersBackStack >= count) {
-                    if (!isHeadersDataReady()) {
-                        // if main fragment was restored first before BrowseFragment's adapter gets
-                        // restored: don't start header transition, but add the entry back.
-                        getFragmentManager().beginTransaction()
-                                .addToBackStack(mWithHeadersBackStackName).commit();
-                        return;
-                    }
-                    mIndexOfHeadersBackStack = -1;
-                    if (!mShowingHeaders) {
-                        startHeadersTransitionInternal(true);
-                    }
-                }
-            }
-            mLastEntryCount = count;
-        }
-    }
-
-    /**
-     * Listener for transitions between browse headers and rows.
-     */
-    public static class BrowseTransitionListener {
-        /**
-         * Callback when headers transition starts.
-         *
-         * @param withHeaders True if the transition will result in headers
-         *        being shown, false otherwise.
-         */
-        public void onHeadersTransitionStart(boolean withHeaders) {
-        }
-        /**
-         * Callback when headers transition stops.
-         *
-         * @param withHeaders True if the transition will result in headers
-         *        being shown, false otherwise.
-         */
-        public void onHeadersTransitionStop(boolean withHeaders) {
-        }
-    }
-
-    private class SetSelectionRunnable implements Runnable {
-        static final int TYPE_INVALID = -1;
-        static final int TYPE_INTERNAL_SYNC = 0;
-        static final int TYPE_USER_REQUEST = 1;
-
-        private int mPosition;
-        private int mType;
-        private boolean mSmooth;
-
-        SetSelectionRunnable() {
-            reset();
-        }
-
-        void post(int position, int type, boolean smooth) {
-            // Posting the set selection, rather than calling it immediately, prevents an issue
-            // with adapter changes.  Example: a row is added before the current selected row;
-            // first the fast lane view updates its selection, then the rows fragment has that
-            // new selection propagated immediately; THEN the rows view processes the same adapter
-            // change and moves the selection again.
-            if (type >= mType) {
-                mPosition = position;
-                mType = type;
-                mSmooth = smooth;
-                mBrowseFrame.removeCallbacks(this);
-                mBrowseFrame.post(this);
-            }
-        }
-
-        @Override
-        public void run() {
-            setSelection(mPosition, mSmooth);
-            reset();
-        }
-
-        private void reset() {
-            mPosition = -1;
-            mType = TYPE_INVALID;
-            mSmooth = false;
-        }
-    }
-
-    /**
-     * Possible set of actions that {@link BrowseFragment} exposes to clients. Custom
-     * fragments can interact with {@link BrowseFragment} using this interface.
-     */
-    public interface FragmentHost {
-        /**
-         * Fragments are required to invoke this callback once their view is created
-         * inside {@link Fragment#onViewCreated} method. {@link BrowseFragment} starts the entrance
-         * animation only after receiving this callback. Failure to invoke this method
-         * will lead to fragment not showing up.
-         *
-         * @param fragmentAdapter {@link MainFragmentAdapter} used by the current fragment.
-         */
-        void notifyViewCreated(MainFragmentAdapter fragmentAdapter);
-
-        /**
-         * Fragments mapped to {@link PageRow} are required to invoke this callback once their data
-         * is created for transition, the entrance animation only after receiving this callback.
-         * Failure to invoke this method will lead to fragment not showing up.
-         *
-         * @param fragmentAdapter {@link MainFragmentAdapter} used by the current fragment.
-         */
-        void notifyDataReady(MainFragmentAdapter fragmentAdapter);
-
-        /**
-         * Show or hide title view in {@link BrowseFragment} for fragments mapped to
-         * {@link PageRow}.  Otherwise the request is ignored, in that case BrowseFragment is fully
-         * in control of showing/hiding title view.
-         * <p>
-         * When HeadersFragment is visible, BrowseFragment will hide search affordance view if
-         * there are other focusable rows above currently focused row.
-         *
-         * @param show Boolean indicating whether or not to show the title view.
-         */
-        void showTitleView(boolean show);
-    }
-
-    /**
-     * Default implementation of {@link FragmentHost} that is used only by
-     * {@link BrowseFragment}.
-     */
-    private final class FragmentHostImpl implements FragmentHost {
-        boolean mShowTitleView = true;
-
-        FragmentHostImpl() {
-        }
-
-        @Override
-        public void notifyViewCreated(MainFragmentAdapter fragmentAdapter) {
-            mStateMachine.fireEvent(EVT_MAIN_FRAGMENT_VIEW_CREATED);
-            if (!mIsPageRow) {
-                // If it's not a PageRow: it's a ListRow, so we already have data ready.
-                mStateMachine.fireEvent(EVT_SCREEN_DATA_READY);
-            }
-        }
-
-        @Override
-        public void notifyDataReady(MainFragmentAdapter fragmentAdapter) {
-            // If fragment host is not the currently active fragment (in BrowseFragment), then
-            // ignore the request.
-            if (mMainFragmentAdapter == null || mMainFragmentAdapter.getFragmentHost() != this) {
-                return;
-            }
-
-            // We only honor showTitle request for PageRows.
-            if (!mIsPageRow) {
-                return;
-            }
-
-            mStateMachine.fireEvent(EVT_SCREEN_DATA_READY);
-        }
-
-        @Override
-        public void showTitleView(boolean show) {
-            mShowTitleView = show;
-
-            // If fragment host is not the currently active fragment (in BrowseFragment), then
-            // ignore the request.
-            if (mMainFragmentAdapter == null || mMainFragmentAdapter.getFragmentHost() != this) {
-                return;
-            }
-
-            // We only honor showTitle request for PageRows.
-            if (!mIsPageRow) {
-                return;
-            }
-
-            updateTitleViewVisibility();
-        }
-    }
-
-    /**
-     * Interface that defines the interaction between {@link BrowseFragment} and its main
-     * content fragment. The key method is {@link MainFragmentAdapter#getFragment()},
-     * it will be used to get the fragment to be shown in the content section. Clients can
-     * provide any implementation of fragment and customize its interaction with
-     * {@link BrowseFragment} by overriding the necessary methods.
-     *
-     * <p>
-     * Clients are expected to provide
-     * an instance of {@link MainFragmentAdapterRegistry} which will be responsible for providing
-     * implementations of {@link MainFragmentAdapter} for given content types. Currently
-     * we support different types of content - {@link ListRow}, {@link PageRow} or any subtype
-     * of {@link Row}. We provide an out of the box adapter implementation for any rows other than
-     * {@link PageRow} - {@link android.support.v17.leanback.app.RowsFragment.MainFragmentAdapter}.
-     *
-     * <p>
-     * {@link PageRow} is intended to give full flexibility to developers in terms of Fragment
-     * design. Users will have to provide an implementation of {@link MainFragmentAdapter}
-     * and provide that through {@link MainFragmentAdapterRegistry}.
-     * {@link MainFragmentAdapter} implementation can supply any fragment and override
-     * just those interactions that makes sense.
-     */
-    public static class MainFragmentAdapter<T extends Fragment> {
-        private boolean mScalingEnabled;
-        private final T mFragment;
-        FragmentHostImpl mFragmentHost;
-
-        public MainFragmentAdapter(T fragment) {
-            this.mFragment = fragment;
-        }
-
-        public final T getFragment() {
-            return mFragment;
-        }
-
-        /**
-         * Returns whether its scrolling.
-         */
-        public boolean isScrolling() {
-            return false;
-        }
-
-        /**
-         * Set the visibility of titles/hover card of browse rows.
-         */
-        public void setExpand(boolean expand) {
-        }
-
-        /**
-         * For rows that willing to participate entrance transition,  this function
-         * hide views if afterTransition is true,  show views if afterTransition is false.
-         */
-        public void setEntranceTransitionState(boolean state) {
-        }
-
-        /**
-         * Sets the window alignment and also the pivots for scale operation.
-         */
-        public void setAlignment(int windowAlignOffsetFromTop) {
-        }
-
-        /**
-         * Callback indicating transition prepare start.
-         */
-        public boolean onTransitionPrepare() {
-            return false;
-        }
-
-        /**
-         * Callback indicating transition start.
-         */
-        public void onTransitionStart() {
-        }
-
-        /**
-         * Callback indicating transition end.
-         */
-        public void onTransitionEnd() {
-        }
-
-        /**
-         * Returns whether row scaling is enabled.
-         */
-        public boolean isScalingEnabled() {
-            return mScalingEnabled;
-        }
-
-        /**
-         * Sets the row scaling property.
-         */
-        public void setScalingEnabled(boolean scalingEnabled) {
-            this.mScalingEnabled = scalingEnabled;
-        }
-
-        /**
-         * Returns the current host interface so that main fragment can interact with
-         * {@link BrowseFragment}.
-         */
-        public final FragmentHost getFragmentHost() {
-            return mFragmentHost;
-        }
-
-        void setFragmentHost(FragmentHostImpl fragmentHost) {
-            this.mFragmentHost = fragmentHost;
-        }
-    }
-
-    /**
-     * Interface to be implemented by all fragments for providing an instance of
-     * {@link MainFragmentAdapter}. Both {@link RowsFragment} and custom fragment provided
-     * against {@link PageRow} will need to implement this interface.
-     */
-    public interface MainFragmentAdapterProvider {
-        /**
-         * Returns an instance of {@link MainFragmentAdapter} that {@link BrowseFragment}
-         * would use to communicate with the target fragment.
-         */
-        MainFragmentAdapter getMainFragmentAdapter();
-    }
-
-    /**
-     * Interface to be implemented by {@link RowsFragment} and its subclasses for providing
-     * an instance of {@link MainFragmentRowsAdapter}.
-     */
-    public interface MainFragmentRowsAdapterProvider {
-        /**
-         * Returns an instance of {@link MainFragmentRowsAdapter} that {@link BrowseFragment}
-         * would use to communicate with the target fragment.
-         */
-        MainFragmentRowsAdapter getMainFragmentRowsAdapter();
-    }
-
-    /**
-     * This is used to pass information to {@link RowsFragment} or its subclasses.
-     * {@link BrowseFragment} uses this interface to pass row based interaction events to
-     * the target fragment.
-     */
-    public static class MainFragmentRowsAdapter<T extends Fragment> {
-        private final T mFragment;
-
-        public MainFragmentRowsAdapter(T fragment) {
-            if (fragment == null) {
-                throw new IllegalArgumentException("Fragment can't be null");
-            }
-            this.mFragment = fragment;
-        }
-
-        public final T getFragment() {
-            return mFragment;
-        }
-        /**
-         * Set the visibility titles/hover of browse rows.
-         */
-        public void setAdapter(ObjectAdapter adapter) {
-        }
-
-        /**
-         * Sets an item clicked listener on the fragment.
-         */
-        public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
-        }
-
-        /**
-         * Sets an item selection listener.
-         */
-        public void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
-        }
-
-        /**
-         * Selects a Row and perform an optional task on the Row.
-         */
-        public void setSelectedPosition(int rowPosition,
-                                        boolean smooth,
-                                        final Presenter.ViewHolderTask rowHolderTask) {
-        }
-
-        /**
-         * Selects a Row.
-         */
-        public void setSelectedPosition(int rowPosition, boolean smooth) {
-        }
-
-        /**
-         * @return The position of selected row.
-         */
-        public int getSelectedPosition() {
-            return 0;
-        }
-
-        /**
-         * @param position Position of Row.
-         * @return Row ViewHolder.
-         */
-        public RowPresenter.ViewHolder findRowViewHolderByPosition(int position) {
-            return null;
-        }
-    }
-
-    private boolean createMainFragment(ObjectAdapter adapter, int position) {
-        Object item = null;
-        if (!mCanShowHeaders) {
-            // when header is disabled, we can decide to use RowsFragment even no data.
-        } else if (adapter == null || adapter.size() == 0) {
-            return false;
-        } else {
-            if (position < 0) {
-                position = 0;
-            } else if (position >= adapter.size()) {
-                throw new IllegalArgumentException(
-                        String.format("Invalid position %d requested", position));
-            }
-            item = adapter.get(position);
-        }
-
-        boolean oldIsPageRow = mIsPageRow;
-        mIsPageRow = mCanShowHeaders && item instanceof PageRow;
-        boolean swap;
-
-        if (mMainFragment == null) {
-            swap = true;
-        } else {
-            if (oldIsPageRow) {
-                swap = true;
-            } else {
-                swap = mIsPageRow;
-            }
-        }
-
-        if (swap) {
-            mMainFragment = mMainFragmentAdapterRegistry.createFragment(item);
-            if (!(mMainFragment instanceof MainFragmentAdapterProvider)) {
-                throw new IllegalArgumentException(
-                        "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;
-            }
-        }
-
-        return swap;
-    }
-
-    /**
-     * Factory class responsible for creating fragment given the current item. {@link ListRow}
-     * should return {@link RowsFragment} or its subclass whereas {@link PageRow}
-     * can return any fragment class.
-     */
-    public abstract static class FragmentFactory<T extends Fragment> {
-        public abstract T createFragment(Object row);
-    }
-
-    /**
-     * FragmentFactory implementation for {@link ListRow}.
-     */
-    public static class ListRowFragmentFactory extends FragmentFactory<RowsFragment> {
-        @Override
-        public RowsFragment createFragment(Object row) {
-            return new RowsFragment();
-        }
-    }
-
-    /**
-     * Registry class maintaining the mapping of {@link Row} subclasses to {@link FragmentFactory}.
-     * BrowseRowFragment automatically registers {@link ListRowFragmentFactory} for
-     * handling {@link ListRow}. Developers can override that and also if they want to
-     * use custom fragment, they can register a custom {@link FragmentFactory}
-     * against {@link PageRow}.
-     */
-    public final static class MainFragmentAdapterRegistry {
-        private final Map<Class, FragmentFactory> mItemToFragmentFactoryMapping = new HashMap<>();
-        private final static FragmentFactory sDefaultFragmentFactory = new ListRowFragmentFactory();
-
-        public MainFragmentAdapterRegistry() {
-            registerFragment(ListRow.class, sDefaultFragmentFactory);
-        }
-
-        public void registerFragment(Class rowClass, FragmentFactory factory) {
-            mItemToFragmentFactoryMapping.put(rowClass, factory);
-        }
-
-        public Fragment createFragment(Object item) {
-            FragmentFactory fragmentFactory = item == null ? sDefaultFragmentFactory :
-                    mItemToFragmentFactoryMapping.get(item.getClass());
-            if (fragmentFactory == null && !(item instanceof PageRow)) {
-                fragmentFactory = sDefaultFragmentFactory;
-            }
-
-            return fragmentFactory.createFragment(item);
-        }
-    }
-
-    static final String TAG = "BrowseFragment";
-
-    private static final String LB_HEADERS_BACKSTACK = "lbHeadersBackStack_";
-
-    static boolean DEBUG = false;
-
-    /** The headers fragment is enabled and shown by default. */
-    public static final int HEADERS_ENABLED = 1;
-
-    /** The headers fragment is enabled and hidden by default. */
-    public static final int HEADERS_HIDDEN = 2;
-
-    /** The headers fragment is disabled and will never be shown. */
-    public static final int HEADERS_DISABLED = 3;
-
-    private MainFragmentAdapterRegistry mMainFragmentAdapterRegistry =
-            new MainFragmentAdapterRegistry();
-    MainFragmentAdapter mMainFragmentAdapter;
-    Fragment mMainFragment;
-    HeadersFragment mHeadersFragment;
-    private MainFragmentRowsAdapter mMainFragmentRowsAdapter;
-
-    private ObjectAdapter mAdapter;
-    private PresenterSelector mAdapterPresenter;
-    private PresenterSelector mWrappingPresenterSelector;
-
-    private int mHeadersState = HEADERS_ENABLED;
-    private int mBrandColor = Color.TRANSPARENT;
-    private boolean mBrandColorSet;
-
-    BrowseFrameLayout mBrowseFrame;
-    private ScaleFrameLayout mScaleFrameLayout;
-    boolean mHeadersBackStackEnabled = true;
-    String mWithHeadersBackStackName;
-    boolean mShowingHeaders = true;
-    boolean mCanShowHeaders = true;
-    private int mContainerListMarginStart;
-    private int mContainerListAlignTop;
-    private boolean mMainFragmentScaleEnabled = true;
-    OnItemViewSelectedListener mExternalOnItemViewSelectedListener;
-    private OnItemViewClickedListener mOnItemViewClickedListener;
-    private int mSelectedPosition = -1;
-    private float mScaleFactor;
-    boolean mIsPageRow;
-
-    private PresenterSelector mHeaderPresenterSelector;
-    private final SetSelectionRunnable mSetSelectionRunnable = new SetSelectionRunnable();
-
-    // transition related:
-    Object mSceneWithHeaders;
-    Object mSceneWithoutHeaders;
-    private Object mSceneAfterEntranceTransition;
-    Object mHeadersTransition;
-    BackStackListener mBackStackChangedListener;
-    BrowseTransitionListener mBrowseTransitionListener;
-
-    private static final String ARG_TITLE = BrowseFragment.class.getCanonicalName() + ".title";
-    private static final String ARG_HEADERS_STATE =
-        BrowseFragment.class.getCanonicalName() + ".headersState";
-
-    /**
-     * Creates arguments for a browse fragment.
-     *
-     * @param args The Bundle to place arguments into, or null if the method
-     *        should return a new Bundle.
-     * @param title The title of the BrowseFragment.
-     * @param headersState The initial state of the headers of the
-     *        BrowseFragment. Must be one of {@link #HEADERS_ENABLED}, {@link
-     *        #HEADERS_HIDDEN}, or {@link #HEADERS_DISABLED}.
-     * @return A Bundle with the given arguments for creating a BrowseFragment.
-     */
-    public static Bundle createArgs(Bundle args, String title, int headersState) {
-        if (args == null) {
-            args = new Bundle();
-        }
-        args.putString(ARG_TITLE, title);
-        args.putInt(ARG_HEADERS_STATE, headersState);
-        return args;
-    }
-
-    /**
-     * Sets the brand color for the browse fragment. The brand color is used as
-     * the primary color for UI elements in the browse fragment. For example,
-     * the background color of the headers fragment uses the brand color.
-     *
-     * @param color The color to use as the brand color of the fragment.
-     */
-    public void setBrandColor(@ColorInt int color) {
-        mBrandColor = color;
-        mBrandColorSet = true;
-
-        if (mHeadersFragment != null) {
-            mHeadersFragment.setBackgroundColor(mBrandColor);
-        }
-    }
-
-    /**
-     * Returns the brand color for the browse fragment.
-     * The default is transparent.
-     */
-    @ColorInt
-    public int getBrandColor() {
-        return mBrandColor;
-    }
-
-    /**
-     * Wrapping app provided PresenterSelector to support InvisibleRowPresenter for SectionRow
-     * DividerRow and PageRow.
-     */
-    private void createAndSetWrapperPresenter() {
-        final PresenterSelector adapterPresenter = mAdapter.getPresenterSelector();
-        if (adapterPresenter == null) {
-            throw new IllegalArgumentException("Adapter.getPresenterSelector() is null");
-        }
-        if (adapterPresenter == mAdapterPresenter) {
-            return;
-        }
-        mAdapterPresenter = adapterPresenter;
-
-        Presenter[] presenters = adapterPresenter.getPresenters();
-        final Presenter invisibleRowPresenter = new InvisibleRowPresenter();
-        final Presenter[] allPresenters = new Presenter[presenters.length + 1];
-        System.arraycopy(allPresenters, 0, presenters, 0, presenters.length);
-        allPresenters[allPresenters.length - 1] = invisibleRowPresenter;
-        mAdapter.setPresenterSelector(new PresenterSelector() {
-            @Override
-            public Presenter getPresenter(Object item) {
-                Row row = (Row) item;
-                if (row.isRenderedAsRowView()) {
-                    return adapterPresenter.getPresenter(item);
-                } else {
-                    return invisibleRowPresenter;
-                }
-            }
-
-            @Override
-            public Presenter[] getPresenters() {
-                return allPresenters;
-            }
-        });
-    }
-
-    /**
-     * Sets the adapter containing the rows for the fragment.
-     *
-     * <p>The items referenced by the adapter must be be derived from
-     * {@link Row}. These rows will be used by the rows fragment and the headers
-     * fragment (if not disabled) to render the browse rows.
-     *
-     * @param adapter An ObjectAdapter for the browse rows. All items must
-     *        derive from {@link Row}.
-     */
-    public void setAdapter(ObjectAdapter adapter) {
-        mAdapter = adapter;
-        createAndSetWrapperPresenter();
-        if (getView() == null) {
-            return;
-        }
-        replaceMainFragment(mSelectedPosition);
-
-        if (adapter != null) {
-            if (mMainFragmentRowsAdapter != null) {
-                mMainFragmentRowsAdapter.setAdapter(new ListRowDataAdapter(adapter));
-            }
-            mHeadersFragment.setAdapter(adapter);
-        }
-    }
-
-    public final MainFragmentAdapterRegistry getMainFragmentRegistry() {
-        return mMainFragmentAdapterRegistry;
-    }
-
-    /**
-     * Returns the adapter containing the rows for the fragment.
-     */
-    public ObjectAdapter getAdapter() {
-        return mAdapter;
-    }
-
-    /**
-     * Sets an item selection listener.
-     */
-    public void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
-        mExternalOnItemViewSelectedListener = listener;
-    }
-
-    /**
-     * Returns an item selection listener.
-     */
-    public OnItemViewSelectedListener getOnItemViewSelectedListener() {
-        return mExternalOnItemViewSelectedListener;
-    }
-
-    /**
-     * Get RowsFragment if it's bound to BrowseFragment or null if either BrowseFragment has
-     * not been created yet or a different fragment is bound to it.
-     *
-     * @return RowsFragment if it's bound to BrowseFragment or null otherwise.
-     */
-    public RowsFragment getRowsFragment() {
-        if (mMainFragment instanceof RowsFragment) {
-            return (RowsFragment) mMainFragment;
-        }
-
-        return null;
-    }
-
-    /**
-     * @return Current main fragment or null if not created.
-     */
-    public Fragment getMainFragment() {
-        return mMainFragment;
-    }
-
-    /**
-     * Get currently bound HeadersFragment or null if HeadersFragment has not been created yet.
-     * @return Currently bound HeadersFragment or null if HeadersFragment has not been created yet.
-     */
-    public HeadersFragment getHeadersFragment() {
-        return mHeadersFragment;
-    }
-
-    /**
-     * Sets an item clicked listener on the fragment.
-     * OnItemViewClickedListener will override {@link View.OnClickListener} that
-     * item presenter sets during {@link Presenter#onCreateViewHolder(ViewGroup)}.
-     * So in general, developer should choose one of the listeners but not both.
-     */
-    public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
-        mOnItemViewClickedListener = listener;
-        if (mMainFragmentRowsAdapter != null) {
-            mMainFragmentRowsAdapter.setOnItemViewClickedListener(listener);
-        }
-    }
-
-    /**
-     * Returns the item Clicked listener.
-     */
-    public OnItemViewClickedListener getOnItemViewClickedListener() {
-        return mOnItemViewClickedListener;
-    }
-
-    /**
-     * Starts a headers transition.
-     *
-     * <p>This method will begin a transition to either show or hide the
-     * headers, depending on the value of withHeaders. If headers are disabled
-     * for this browse fragment, this method will throw an exception.
-     *
-     * @param withHeaders True if the headers should transition to being shown,
-     *        false if the transition should result in headers being hidden.
-     */
-    public void startHeadersTransition(boolean withHeaders) {
-        if (!mCanShowHeaders) {
-            throw new IllegalStateException("Cannot start headers transition");
-        }
-        if (isInHeadersTransition() || mShowingHeaders == withHeaders) {
-            return;
-        }
-        startHeadersTransitionInternal(withHeaders);
-    }
-
-    /**
-     * Returns true if the headers transition is currently running.
-     */
-    public boolean isInHeadersTransition() {
-        return mHeadersTransition != null;
-    }
-
-    /**
-     * Returns true if headers are shown.
-     */
-    public boolean isShowingHeaders() {
-        return mShowingHeaders;
-    }
-
-    /**
-     * Sets a listener for browse fragment transitions.
-     *
-     * @param listener The listener to call when a browse headers transition
-     *        begins or ends.
-     */
-    public void setBrowseTransitionListener(BrowseTransitionListener listener) {
-        mBrowseTransitionListener = listener;
-    }
-
-    /**
-     * @deprecated use {@link BrowseFragment#enableMainFragmentScaling(boolean)} instead.
-     *
-     * @param enable true to enable row scaling
-     */
-    @Deprecated
-    public void enableRowScaling(boolean enable) {
-        enableMainFragmentScaling(enable);
-    }
-
-    /**
-     * Enables scaling of main fragment when headers are present. For the page/row fragment,
-     * scaling is enabled only when both this method and
-     * {@link MainFragmentAdapter#isScalingEnabled()} are enabled.
-     *
-     * @param enable true to enable row scaling
-     */
-    public void enableMainFragmentScaling(boolean enable) {
-        mMainFragmentScaleEnabled = enable;
-    }
-
-    void startHeadersTransitionInternal(final boolean withHeaders) {
-        if (getFragmentManager().isDestroyed()) {
-            return;
-        }
-        if (!isHeadersDataReady()) {
-            return;
-        }
-        mShowingHeaders = withHeaders;
-        mMainFragmentAdapter.onTransitionPrepare();
-        mMainFragmentAdapter.onTransitionStart();
-        onExpandTransitionStart(!withHeaders, new Runnable() {
-            @Override
-            public void run() {
-                mHeadersFragment.onTransitionPrepare();
-                mHeadersFragment.onTransitionStart();
-                createHeadersTransition();
-                if (mBrowseTransitionListener != null) {
-                    mBrowseTransitionListener.onHeadersTransitionStart(withHeaders);
-                }
-                TransitionHelper.runTransition(
-                        withHeaders ? mSceneWithHeaders : mSceneWithoutHeaders, mHeadersTransition);
-                if (mHeadersBackStackEnabled) {
-                    if (!withHeaders) {
-                        getFragmentManager().beginTransaction()
-                                .addToBackStack(mWithHeadersBackStackName).commit();
-                    } else {
-                        int index = mBackStackChangedListener.mIndexOfHeadersBackStack;
-                        if (index >= 0) {
-                            BackStackEntry entry = getFragmentManager().getBackStackEntryAt(index);
-                            getFragmentManager().popBackStackImmediate(entry.getId(),
-                                    FragmentManager.POP_BACK_STACK_INCLUSIVE);
-                        }
-                    }
-                }
-            }
-        });
-    }
-
-    boolean isVerticalScrolling() {
-        // don't run transition
-        return mHeadersFragment.isScrolling() || mMainFragmentAdapter.isScrolling();
-    }
-
-
-    private final BrowseFrameLayout.OnFocusSearchListener mOnFocusSearchListener =
-            new BrowseFrameLayout.OnFocusSearchListener() {
-        @Override
-        public View onFocusSearch(View focused, int direction) {
-            // if headers is running transition,  focus stays
-            if (mCanShowHeaders && isInHeadersTransition()) {
-                return focused;
-            }
-            if (DEBUG) Log.v(TAG, "onFocusSearch focused " + focused + " + direction " + direction);
-
-            if (getTitleView() != null && focused != getTitleView()
-                    && direction == View.FOCUS_UP) {
-                return getTitleView();
-            }
-            if (getTitleView() != null && getTitleView().hasFocus()
-                    && direction == View.FOCUS_DOWN) {
-                return mCanShowHeaders && mShowingHeaders
-                        ? mHeadersFragment.getVerticalGridView() : mMainFragment.getView();
-            }
-
-            boolean isRtl = ViewCompat.getLayoutDirection(focused)
-                    == ViewCompat.LAYOUT_DIRECTION_RTL;
-            int towardStart = isRtl ? View.FOCUS_RIGHT : View.FOCUS_LEFT;
-            int towardEnd = isRtl ? View.FOCUS_LEFT : View.FOCUS_RIGHT;
-            if (mCanShowHeaders && direction == towardStart) {
-                if (isVerticalScrolling() || mShowingHeaders || !isHeadersDataReady()) {
-                    return focused;
-                }
-                return mHeadersFragment.getVerticalGridView();
-            } else if (direction == towardEnd) {
-                if (isVerticalScrolling()) {
-                    return focused;
-                } else if (mMainFragment != null && mMainFragment.getView() != null) {
-                    return mMainFragment.getView();
-                }
-                return focused;
-            } else if (direction == View.FOCUS_DOWN && mShowingHeaders) {
-                // disable focus_down moving into PageFragment.
-                return focused;
-            } else {
-                return null;
-            }
-        }
-    };
-
-    final boolean isHeadersDataReady() {
-        return mAdapter != null && mAdapter.size() != 0;
-    }
-
-    private final BrowseFrameLayout.OnChildFocusListener mOnChildFocusListener =
-            new BrowseFrameLayout.OnChildFocusListener() {
-
-        @Override
-        public boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
-            if (getChildFragmentManager().isDestroyed()) {
-                return true;
-            }
-            // Make sure not changing focus when requestFocus() is called.
-            if (mCanShowHeaders && mShowingHeaders) {
-                if (mHeadersFragment != null && mHeadersFragment.getView() != null
-                        && mHeadersFragment.getView().requestFocus(
-                                direction, previouslyFocusedRect)) {
-                    return true;
-                }
-            }
-            if (mMainFragment != null && mMainFragment.getView() != null
-                    && mMainFragment.getView().requestFocus(direction, previouslyFocusedRect)) {
-                return true;
-            }
-            return getTitleView() != null
-                    && getTitleView().requestFocus(direction, previouslyFocusedRect);
-        }
-
-        @Override
-        public void onRequestChildFocus(View child, View focused) {
-            if (getChildFragmentManager().isDestroyed()) {
-                return;
-            }
-            if (!mCanShowHeaders || isInHeadersTransition()) return;
-            int childId = child.getId();
-            if (childId == R.id.browse_container_dock && mShowingHeaders) {
-                startHeadersTransitionInternal(false);
-            } else if (childId == R.id.browse_headers_dock && !mShowingHeaders) {
-                startHeadersTransitionInternal(true);
-            }
-        }
-    };
-
-    @Override
-    public void onSaveInstanceState(Bundle outState) {
-        super.onSaveInstanceState(outState);
-        outState.putInt(CURRENT_SELECTED_POSITION, mSelectedPosition);
-        outState.putBoolean(IS_PAGE_ROW, mIsPageRow);
-
-        if (mBackStackChangedListener != null) {
-            mBackStackChangedListener.save(outState);
-        } else {
-            outState.putBoolean(HEADER_SHOW, mShowingHeaders);
-        }
-    }
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        final Context context = FragmentUtil.getContext(BrowseFragment.this);
-        TypedArray ta = context.obtainStyledAttributes(R.styleable.LeanbackTheme);
-        mContainerListMarginStart = (int) ta.getDimension(
-                R.styleable.LeanbackTheme_browseRowsMarginStart, context.getResources()
-                .getDimensionPixelSize(R.dimen.lb_browse_rows_margin_start));
-        mContainerListAlignTop = (int) ta.getDimension(
-                R.styleable.LeanbackTheme_browseRowsMarginTop, context.getResources()
-                .getDimensionPixelSize(R.dimen.lb_browse_rows_margin_top));
-        ta.recycle();
-
-        readArguments(getArguments());
-
-        if (mCanShowHeaders) {
-            if (mHeadersBackStackEnabled) {
-                mWithHeadersBackStackName = LB_HEADERS_BACKSTACK + this;
-                mBackStackChangedListener = new BackStackListener();
-                getFragmentManager().addOnBackStackChangedListener(mBackStackChangedListener);
-                mBackStackChangedListener.load(savedInstanceState);
-            } else {
-                if (savedInstanceState != null) {
-                    mShowingHeaders = savedInstanceState.getBoolean(HEADER_SHOW);
-                }
-            }
-        }
-
-        mScaleFactor = getResources().getFraction(R.fraction.lb_browse_rows_scale, 1, 1);
-    }
-
-    @Override
-    public void onDestroyView() {
-        mMainFragmentRowsAdapter = null;
-        mMainFragmentAdapter = null;
-        mMainFragment = null;
-        mHeadersFragment = null;
-        super.onDestroyView();
-    }
-
-    @Override
-    public void onDestroy() {
-        if (mBackStackChangedListener != null) {
-            getFragmentManager().removeOnBackStackChangedListener(mBackStackChangedListener);
-        }
-        super.onDestroy();
-    }
-
-    /**
-     * Creates a new {@link HeadersFragment} instance. Subclass of BrowseFragment may override and
-     * return an instance of subclass of HeadersFragment, e.g. when app wants to replace presenter
-     * to render HeaderItem.
-     *
-     * @return A new instance of {@link HeadersFragment} or its subclass.
-     */
-    public HeadersFragment onCreateHeadersFragment() {
-        return new HeadersFragment();
-    }
-
-    @Override
-    public View onCreateView(LayoutInflater inflater, ViewGroup container,
-            Bundle savedInstanceState) {
-
-        if (getChildFragmentManager().findFragmentById(R.id.scale_frame) == null) {
-            mHeadersFragment = onCreateHeadersFragment();
-
-            createMainFragment(mAdapter, mSelectedPosition);
-            FragmentTransaction ft = getChildFragmentManager().beginTransaction()
-                    .replace(R.id.browse_headers_dock, mHeadersFragment);
-
-            if (mMainFragment != null) {
-                ft.replace(R.id.scale_frame, mMainFragment);
-            } else {
-                // Empty adapter used to guard against lazy adapter loading. When this
-                // fragment is instantiated, mAdapter might not have the data or might not
-                // have been set. In either of those cases mFragmentAdapter will be null.
-                // This way we can maintain the invariant that mMainFragmentAdapter is never
-                // null and it avoids doing null checks all over the code.
-                mMainFragmentAdapter = new MainFragmentAdapter(null);
-                mMainFragmentAdapter.setFragmentHost(new FragmentHostImpl());
-            }
-
-            ft.commit();
-        } else {
-            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);
-
-            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;
-            }
-        }
-
-        mHeadersFragment.setHeadersGone(!mCanShowHeaders);
-        if (mHeaderPresenterSelector != null) {
-            mHeadersFragment.setPresenterSelector(mHeaderPresenterSelector);
-        }
-        mHeadersFragment.setAdapter(mAdapter);
-        mHeadersFragment.setOnHeaderViewSelectedListener(mHeaderViewSelectedListener);
-        mHeadersFragment.setOnHeaderClickedListener(mHeaderClickedListener);
-
-        View root = inflater.inflate(R.layout.lb_browse_fragment, container, false);
-
-        getProgressBarManager().setRootView((ViewGroup)root);
-
-        mBrowseFrame = (BrowseFrameLayout) root.findViewById(R.id.browse_frame);
-        mBrowseFrame.setOnChildFocusListener(mOnChildFocusListener);
-        mBrowseFrame.setOnFocusSearchListener(mOnFocusSearchListener);
-
-        installTitleView(inflater, mBrowseFrame, savedInstanceState);
-
-        mScaleFrameLayout = (ScaleFrameLayout) root.findViewById(R.id.scale_frame);
-        mScaleFrameLayout.setPivotX(0);
-        mScaleFrameLayout.setPivotY(mContainerListAlignTop);
-
-        setupMainFragment();
-
-        if (mBrandColorSet) {
-            mHeadersFragment.setBackgroundColor(mBrandColor);
-        }
-
-        mSceneWithHeaders = TransitionHelper.createScene(mBrowseFrame, new Runnable() {
-            @Override
-            public void run() {
-                showHeaders(true);
-            }
-        });
-        mSceneWithoutHeaders =  TransitionHelper.createScene(mBrowseFrame, new Runnable() {
-            @Override
-            public void run() {
-                showHeaders(false);
-            }
-        });
-        mSceneAfterEntranceTransition = TransitionHelper.createScene(mBrowseFrame, new Runnable() {
-            @Override
-            public void run() {
-                setEntranceTransitionEndState();
-            }
-        });
-
-        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
-                        ? R.transition.lb_browse_headers_in : R.transition.lb_browse_headers_out);
-
-        TransitionHelper.addTransitionListener(mHeadersTransition, new TransitionListener() {
-            @Override
-            public void onTransitionStart(Object transition) {
-            }
-            @Override
-            public void onTransitionEnd(Object transition) {
-                mHeadersTransition = null;
-                if (mMainFragmentAdapter != null) {
-                    mMainFragmentAdapter.onTransitionEnd();
-                    if (!mShowingHeaders && mMainFragment != null) {
-                        View mainFragmentView = mMainFragment.getView();
-                        if (mainFragmentView != null && !mainFragmentView.hasFocus()) {
-                            mainFragmentView.requestFocus();
-                        }
-                    }
-                }
-                if (mHeadersFragment != null) {
-                    mHeadersFragment.onTransitionEnd();
-                    if (mShowingHeaders) {
-                        VerticalGridView headerGridView = mHeadersFragment.getVerticalGridView();
-                        if (headerGridView != null && !headerGridView.hasFocus()) {
-                            headerGridView.requestFocus();
-                        }
-                    }
-                }
-
-                // Animate TitleView once header animation is complete.
-                updateTitleViewVisibility();
-
-                if (mBrowseTransitionListener != null) {
-                    mBrowseTransitionListener.onHeadersTransitionStop(mShowingHeaders);
-                }
-            }
-        });
-    }
-
-    void updateTitleViewVisibility() {
-        if (!mShowingHeaders) {
-            boolean showTitleView;
-            if (mIsPageRow && mMainFragmentAdapter != null) {
-                // page fragment case:
-                showTitleView = mMainFragmentAdapter.mFragmentHost.mShowTitleView;
-            } else {
-                // regular row view case:
-                showTitleView = isFirstRowWithContent(mSelectedPosition);
-            }
-            if (showTitleView) {
-                showTitle(TitleViewAdapter.FULL_VIEW_VISIBLE);
-            } else {
-                showTitle(false);
-            }
-        } else {
-            // when HeaderFragment is showing,  showBranding and showSearch are slightly different
-            boolean showBranding;
-            boolean showSearch;
-            if (mIsPageRow && mMainFragmentAdapter != null) {
-                showBranding = mMainFragmentAdapter.mFragmentHost.mShowTitleView;
-            } else {
-                showBranding = isFirstRowWithContent(mSelectedPosition);
-            }
-            showSearch = isFirstRowWithContentOrPageRow(mSelectedPosition);
-            int flags = 0;
-            if (showBranding) flags |= TitleViewAdapter.BRANDING_VIEW_VISIBLE;
-            if (showSearch) flags |= TitleViewAdapter.SEARCH_VIEW_VISIBLE;
-            if (flags != 0) {
-                showTitle(flags);
-            } else {
-                showTitle(false);
-            }
-        }
-    }
-
-    boolean isFirstRowWithContentOrPageRow(int rowPosition) {
-        if (mAdapter == null || mAdapter.size() == 0) {
-            return true;
-        }
-        for (int i = 0; i < mAdapter.size(); i++) {
-            final Row row = (Row) mAdapter.get(i);
-            if (row.isRenderedAsRowView() || row instanceof PageRow) {
-                return rowPosition == i;
-            }
-        }
-        return true;
-    }
-
-    boolean isFirstRowWithContent(int rowPosition) {
-        if (mAdapter == null || mAdapter.size() == 0) {
-            return true;
-        }
-        for (int i = 0; i < mAdapter.size(); i++) {
-            final Row row = (Row) mAdapter.get(i);
-            if (row.isRenderedAsRowView()) {
-                return rowPosition == i;
-            }
-        }
-        return true;
-    }
-
-    /**
-     * Sets the {@link PresenterSelector} used to render the row headers.
-     *
-     * @param headerPresenterSelector The PresenterSelector that will determine
-     *        the Presenter for each row header.
-     */
-    public void setHeaderPresenterSelector(PresenterSelector headerPresenterSelector) {
-        mHeaderPresenterSelector = headerPresenterSelector;
-        if (mHeadersFragment != null) {
-            mHeadersFragment.setPresenterSelector(mHeaderPresenterSelector);
-        }
-    }
-
-    private void setHeadersOnScreen(boolean onScreen) {
-        MarginLayoutParams lp;
-        View containerList;
-        containerList = mHeadersFragment.getView();
-        lp = (MarginLayoutParams) containerList.getLayoutParams();
-        lp.setMarginStart(onScreen ? 0 : -mContainerListMarginStart);
-        containerList.setLayoutParams(lp);
-    }
-
-    void showHeaders(boolean show) {
-        if (DEBUG) Log.v(TAG, "showHeaders " + show);
-        mHeadersFragment.setHeadersEnabled(show);
-        setHeadersOnScreen(show);
-        expandMainFragment(!show);
-    }
-
-    private void expandMainFragment(boolean expand) {
-        MarginLayoutParams params = (MarginLayoutParams) mScaleFrameLayout.getLayoutParams();
-        params.setMarginStart(!expand ? mContainerListMarginStart : 0);
-        mScaleFrameLayout.setLayoutParams(params);
-        mMainFragmentAdapter.setExpand(expand);
-
-        setMainFragmentAlignment();
-        final float scaleFactor = !expand
-                && mMainFragmentScaleEnabled
-                && mMainFragmentAdapter.isScalingEnabled() ? mScaleFactor : 1;
-        mScaleFrameLayout.setLayoutScaleY(scaleFactor);
-        mScaleFrameLayout.setChildScale(scaleFactor);
-    }
-
-    private HeadersFragment.OnHeaderClickedListener mHeaderClickedListener =
-        new HeadersFragment.OnHeaderClickedListener() {
-            @Override
-            public void onHeaderClicked(RowHeaderPresenter.ViewHolder viewHolder, Row row) {
-                if (!mCanShowHeaders || !mShowingHeaders || isInHeadersTransition()) {
-                    return;
-                }
-                startHeadersTransitionInternal(false);
-                mMainFragment.getView().requestFocus();
-            }
-        };
-
-    class MainFragmentItemViewSelectedListener implements OnItemViewSelectedListener {
-        MainFragmentRowsAdapter mMainFragmentRowsAdapter;
-
-        public MainFragmentItemViewSelectedListener(MainFragmentRowsAdapter fragmentRowsAdapter) {
-            mMainFragmentRowsAdapter = fragmentRowsAdapter;
-        }
-
-        @Override
-        public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
-                RowPresenter.ViewHolder rowViewHolder, Row row) {
-            int position = mMainFragmentRowsAdapter.getSelectedPosition();
-            if (DEBUG) Log.v(TAG, "row selected position " + position);
-            onRowSelected(position);
-            if (mExternalOnItemViewSelectedListener != null) {
-                mExternalOnItemViewSelectedListener.onItemSelected(itemViewHolder, item,
-                        rowViewHolder, row);
-            }
-        }
-    };
-
-    private HeadersFragment.OnHeaderViewSelectedListener mHeaderViewSelectedListener =
-            new HeadersFragment.OnHeaderViewSelectedListener() {
-        @Override
-        public void onHeaderSelected(RowHeaderPresenter.ViewHolder viewHolder, Row row) {
-            int position = mHeadersFragment.getSelectedPosition();
-            if (DEBUG) Log.v(TAG, "header selected position " + position);
-            onRowSelected(position);
-        }
-    };
-
-    void onRowSelected(int position) {
-        if (position != mSelectedPosition) {
-            mSetSelectionRunnable.post(
-                    position, SetSelectionRunnable.TYPE_INTERNAL_SYNC, true);
-        }
-    }
-
-    void setSelection(int position, boolean smooth) {
-        if (position == NO_POSITION) {
-            return;
-        }
-
-        mSelectedPosition = position;
-        if (mHeadersFragment == null || mMainFragmentAdapter == null) {
-            // onDestroyView() called
-            return;
-        }
-        mHeadersFragment.setSelectedPosition(position, smooth);
-        replaceMainFragment(position);
-
-        if (mMainFragmentRowsAdapter != null) {
-            mMainFragmentRowsAdapter.setSelectedPosition(position, smooth);
-        }
-
-        updateTitleViewVisibility();
-    }
-
-    private void replaceMainFragment(int position) {
-        if (createMainFragment(mAdapter, position)) {
-            swapToMainFragment();
-            expandMainFragment(!(mCanShowHeaders && mShowingHeaders));
-            setupMainFragment();
-        }
-    }
-
-    private void swapToMainFragment() {
-        final VerticalGridView gridView = mHeadersFragment.getVerticalGridView();
-        if (isShowingHeaders() && gridView != null
-                && gridView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) {
-            // if user is scrolling HeadersFragment,  swap to empty fragment and wait scrolling
-            // finishes.
-            getChildFragmentManager().beginTransaction()
-                    .replace(R.id.scale_frame, new Fragment()).commit();
-            gridView.addOnScrollListener(new RecyclerView.OnScrollListener() {
-                @SuppressWarnings("ReferenceEquality")
-                @Override
-                public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
-                    if (newState == RecyclerView.SCROLL_STATE_IDLE) {
-                        gridView.removeOnScrollListener(this);
-                        FragmentManager fm = getChildFragmentManager();
-                        Fragment currentFragment = fm.findFragmentById(R.id.scale_frame);
-                        if (currentFragment != mMainFragment) {
-                            fm.beginTransaction().replace(R.id.scale_frame, mMainFragment).commit();
-                        }
-                    }
-                }
-            });
-        } else {
-            // Otherwise swap immediately
-            getChildFragmentManager().beginTransaction()
-                    .replace(R.id.scale_frame, mMainFragment).commit();
-        }
-    }
-
-    /**
-     * Sets the selected row position with smooth animation.
-     */
-    public void setSelectedPosition(int position) {
-        setSelectedPosition(position, true);
-    }
-
-    /**
-     * Gets position of currently selected row.
-     * @return Position of currently selected row.
-     */
-    public int getSelectedPosition() {
-        return mSelectedPosition;
-    }
-
-    /**
-     * @return selected row ViewHolder inside fragment created by {@link MainFragmentRowsAdapter}.
-     */
-    public RowPresenter.ViewHolder getSelectedRowViewHolder() {
-        if (mMainFragmentRowsAdapter != null) {
-            int rowPos = mMainFragmentRowsAdapter.getSelectedPosition();
-            return mMainFragmentRowsAdapter.findRowViewHolderByPosition(rowPos);
-        }
-        return null;
-    }
-
-    /**
-     * Sets the selected row position.
-     */
-    public void setSelectedPosition(int position, boolean smooth) {
-        mSetSelectionRunnable.post(
-                position, SetSelectionRunnable.TYPE_USER_REQUEST, smooth);
-    }
-
-    /**
-     * Selects a Row and perform an optional task on the Row. For example
-     * <code>setSelectedPosition(10, true, new ListRowPresenterSelectItemViewHolderTask(5))</code>
-     * scrolls to 11th row and selects 6th item on that row.  The method will be ignored if
-     * RowsFragment has not been created (i.e. before {@link #onCreateView(LayoutInflater,
-     * ViewGroup, Bundle)}).
-     *
-     * @param rowPosition Which row to select.
-     * @param smooth True to scroll to the row, false for no animation.
-     * @param rowHolderTask Optional task to perform on the Row.  When the task is not null, headers
-     * fragment will be collapsed.
-     */
-    public void setSelectedPosition(int rowPosition, boolean smooth,
-            final Presenter.ViewHolderTask rowHolderTask) {
-        if (mMainFragmentAdapterRegistry == null) {
-            return;
-        }
-        if (rowHolderTask != null) {
-            startHeadersTransition(false);
-        }
-        if (mMainFragmentRowsAdapter != null) {
-            mMainFragmentRowsAdapter.setSelectedPosition(rowPosition, smooth, rowHolderTask);
-        }
-    }
-
-    @Override
-    public void onStart() {
-        super.onStart();
-        mHeadersFragment.setAlignment(mContainerListAlignTop);
-        setMainFragmentAlignment();
-
-        if (mCanShowHeaders && mShowingHeaders && mHeadersFragment != null
-                && mHeadersFragment.getView() != null) {
-            mHeadersFragment.getView().requestFocus();
-        } else if ((!mCanShowHeaders || !mShowingHeaders) && mMainFragment != null
-                && mMainFragment.getView() != null) {
-            mMainFragment.getView().requestFocus();
-        }
-
-        if (mCanShowHeaders) {
-            showHeaders(mShowingHeaders);
-        }
-
-        mStateMachine.fireEvent(EVT_HEADER_VIEW_CREATED);
-    }
-
-    private void onExpandTransitionStart(boolean expand, final Runnable callback) {
-        if (expand) {
-            callback.run();
-            return;
-        }
-        // Run a "pre" layout when we go non-expand, in order to get the initial
-        // positions of added rows.
-        new ExpandPreLayout(callback, mMainFragmentAdapter, getView()).execute();
-    }
-
-    private void setMainFragmentAlignment() {
-        int alignOffset = mContainerListAlignTop;
-        if (mMainFragmentScaleEnabled
-                && mMainFragmentAdapter.isScalingEnabled()
-                && mShowingHeaders) {
-            alignOffset = (int) (alignOffset / mScaleFactor + 0.5f);
-        }
-        mMainFragmentAdapter.setAlignment(alignOffset);
-    }
-
-    /**
-     * Enables/disables headers transition on back key support. This is enabled by
-     * default. The BrowseFragment will add a back stack entry when headers are
-     * showing. Running a headers transition when the back key is pressed only
-     * works when the headers state is {@link #HEADERS_ENABLED} or
-     * {@link #HEADERS_HIDDEN}.
-     * <p>
-     * NOTE: If an Activity has its own onBackPressed() handling, you must
-     * disable this feature. You may use {@link #startHeadersTransition(boolean)}
-     * and {@link BrowseTransitionListener} in your own back stack handling.
-     */
-    public final void setHeadersTransitionOnBackEnabled(boolean headersBackStackEnabled) {
-        mHeadersBackStackEnabled = headersBackStackEnabled;
-    }
-
-    /**
-     * Returns true if headers transition on back key support is enabled.
-     */
-    public final boolean isHeadersTransitionOnBackEnabled() {
-        return mHeadersBackStackEnabled;
-    }
-
-    private void readArguments(Bundle args) {
-        if (args == null) {
-            return;
-        }
-        if (args.containsKey(ARG_TITLE)) {
-            setTitle(args.getString(ARG_TITLE));
-        }
-        if (args.containsKey(ARG_HEADERS_STATE)) {
-            setHeadersState(args.getInt(ARG_HEADERS_STATE));
-        }
-    }
-
-    /**
-     * Sets the state for the headers column in the browse fragment. Must be one
-     * of {@link #HEADERS_ENABLED}, {@link #HEADERS_HIDDEN}, or
-     * {@link #HEADERS_DISABLED}.
-     *
-     * @param headersState The state of the headers for the browse fragment.
-     */
-    public void setHeadersState(int headersState) {
-        if (headersState < HEADERS_ENABLED || headersState > HEADERS_DISABLED) {
-            throw new IllegalArgumentException("Invalid headers state: " + headersState);
-        }
-        if (DEBUG) Log.v(TAG, "setHeadersState " + headersState);
-
-        if (headersState != mHeadersState) {
-            mHeadersState = headersState;
-            switch (headersState) {
-                case HEADERS_ENABLED:
-                    mCanShowHeaders = true;
-                    mShowingHeaders = true;
-                    break;
-                case HEADERS_HIDDEN:
-                    mCanShowHeaders = true;
-                    mShowingHeaders = false;
-                    break;
-                case HEADERS_DISABLED:
-                    mCanShowHeaders = false;
-                    mShowingHeaders = false;
-                    break;
-                default:
-                    Log.w(TAG, "Unknown headers state: " + headersState);
-                    break;
-            }
-            if (mHeadersFragment != null) {
-                mHeadersFragment.setHeadersGone(!mCanShowHeaders);
-            }
-        }
-    }
-
-    /**
-     * Returns the state of the headers column in the browse fragment.
-     */
-    public int getHeadersState() {
-        return mHeadersState;
-    }
-
-    @Override
-    protected Object createEntranceTransition() {
-        return TransitionHelper.loadTransition(FragmentUtil.getContext(BrowseFragment.this),
-                R.transition.lb_browse_entrance_transition);
-    }
-
-    @Override
-    protected void runEntranceTransition(Object entranceTransition) {
-        TransitionHelper.runTransition(mSceneAfterEntranceTransition, entranceTransition);
-    }
-
-    @Override
-    protected void onEntranceTransitionPrepare() {
-        mHeadersFragment.onTransitionPrepare();
-        mMainFragmentAdapter.setEntranceTransitionState(false);
-        mMainFragmentAdapter.onTransitionPrepare();
-    }
-
-    @Override
-    protected void onEntranceTransitionStart() {
-        mHeadersFragment.onTransitionStart();
-        mMainFragmentAdapter.onTransitionStart();
-    }
-
-    @Override
-    protected void onEntranceTransitionEnd() {
-        if (mMainFragmentAdapter != null) {
-            mMainFragmentAdapter.onTransitionEnd();
-        }
-
-        if (mHeadersFragment != null) {
-            mHeadersFragment.onTransitionEnd();
-        }
-    }
-
-    void setSearchOrbViewOnScreen(boolean onScreen) {
-        View searchOrbView = getTitleViewAdapter().getSearchAffordanceView();
-        if (searchOrbView != null) {
-            MarginLayoutParams lp = (MarginLayoutParams) searchOrbView.getLayoutParams();
-            lp.setMarginStart(onScreen ? 0 : -mContainerListMarginStart);
-            searchOrbView.setLayoutParams(lp);
-        }
-    }
-
-    void setEntranceTransitionStartState() {
-        setHeadersOnScreen(false);
-        setSearchOrbViewOnScreen(false);
-        // NOTE that mMainFragmentAdapter.setEntranceTransitionState(false) will be called
-        // in onEntranceTransitionPrepare() because mMainFragmentAdapter is still the dummy
-        // one when setEntranceTransitionStartState() is called.
-    }
-
-    void setEntranceTransitionEndState() {
-        setHeadersOnScreen(mShowingHeaders);
-        setSearchOrbViewOnScreen(true);
-        mMainFragmentAdapter.setEntranceTransitionState(true);
-    }
-
-    private class ExpandPreLayout implements ViewTreeObserver.OnPreDrawListener {
-
-        private final View mView;
-        private final Runnable mCallback;
-        private int mState;
-        private MainFragmentAdapter mainFragmentAdapter;
-
-        final static int STATE_INIT = 0;
-        final static int STATE_FIRST_DRAW = 1;
-        final static int STATE_SECOND_DRAW = 2;
-
-        ExpandPreLayout(Runnable callback, MainFragmentAdapter adapter, View view) {
-            mView = view;
-            mCallback = callback;
-            mainFragmentAdapter = adapter;
-        }
-
-        void execute() {
-            mView.getViewTreeObserver().addOnPreDrawListener(this);
-            mainFragmentAdapter.setExpand(false);
-            // always trigger onPreDraw even adapter setExpand() does nothing.
-            mView.invalidate();
-            mState = STATE_INIT;
-        }
-
-        @Override
-        public boolean onPreDraw() {
-            if (getView() == null || FragmentUtil.getContext(BrowseFragment.this) == null) {
-                mView.getViewTreeObserver().removeOnPreDrawListener(this);
-                return true;
-            }
-            if (mState == STATE_INIT) {
-                mainFragmentAdapter.setExpand(true);
-                // always trigger onPreDraw even adapter setExpand() does nothing.
-                mView.invalidate();
-                mState = STATE_FIRST_DRAW;
-            } else if (mState == STATE_FIRST_DRAW) {
-                mCallback.run();
-                mView.getViewTreeObserver().removeOnPreDrawListener(this);
-                mState = STATE_SECOND_DRAW;
-            }
-            return false;
-        }
-    }
-}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BrowseSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/BrowseSupportFragment.java
deleted file mode 100644
index 03b3c8a..0000000
--- a/v17/leanback/src/android/support/v17/leanback/app/BrowseSupportFragment.java
+++ /dev/null
@@ -1,1813 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-package android.support.v17.leanback.app;
-
-import static android.support.v7.widget.RecyclerView.NO_POSITION;
-
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Color;
-import android.graphics.Rect;
-import android.os.Bundle;
-import android.support.annotation.ColorInt;
-import android.support.v17.leanback.R;
-import android.support.v17.leanback.transition.TransitionHelper;
-import android.support.v17.leanback.transition.TransitionListener;
-import android.support.v17.leanback.util.StateMachine.Event;
-import android.support.v17.leanback.util.StateMachine.State;
-import android.support.v17.leanback.widget.BrowseFrameLayout;
-import android.support.v17.leanback.widget.InvisibleRowPresenter;
-import android.support.v17.leanback.widget.ListRow;
-import android.support.v17.leanback.widget.ObjectAdapter;
-import android.support.v17.leanback.widget.OnItemViewClickedListener;
-import android.support.v17.leanback.widget.OnItemViewSelectedListener;
-import android.support.v17.leanback.widget.PageRow;
-import android.support.v17.leanback.widget.Presenter;
-import android.support.v17.leanback.widget.PresenterSelector;
-import android.support.v17.leanback.widget.Row;
-import android.support.v17.leanback.widget.RowHeaderPresenter;
-import android.support.v17.leanback.widget.RowPresenter;
-import android.support.v17.leanback.widget.ScaleFrameLayout;
-import android.support.v17.leanback.widget.TitleViewAdapter;
-import android.support.v17.leanback.widget.VerticalGridView;
-import android.support.v4.app.Fragment;
-import android.support.v4.app.FragmentManager;
-import android.support.v4.app.FragmentManager.BackStackEntry;
-import android.support.v4.app.FragmentTransaction;
-import android.support.v4.view.ViewCompat;
-import android.support.v7.widget.RecyclerView;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewGroup.MarginLayoutParams;
-import android.view.ViewTreeObserver;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * A fragment for creating Leanback browse screens. It is composed of a
- * RowsSupportFragment and a HeadersSupportFragment.
- * <p>
- * A BrowseSupportFragment renders the elements of its {@link ObjectAdapter} as a set
- * of rows in a vertical list. The elements in this adapter must be subclasses
- * of {@link Row}.
- * <p>
- * The HeadersSupportFragment can be set to be either shown or hidden by default, or
- * may be disabled entirely. See {@link #setHeadersState} for details.
- * <p>
- * By default the BrowseSupportFragment includes support for returning to the headers
- * when the user presses Back. For Activities that customize {@link
- * android.support.v4.app.FragmentActivity#onBackPressed()}, you must disable this default Back key support by
- * calling {@link #setHeadersTransitionOnBackEnabled(boolean)} with false and
- * use {@link BrowseSupportFragment.BrowseTransitionListener} and
- * {@link #startHeadersTransition(boolean)}.
- * <p>
- * The recommended theme to use with a BrowseSupportFragment is
- * {@link android.support.v17.leanback.R.style#Theme_Leanback_Browse}.
- * </p>
- */
-public class BrowseSupportFragment extends BaseSupportFragment {
-
-    // BUNDLE attribute for saving header show/hide status when backstack is used:
-    static final String HEADER_STACK_INDEX = "headerStackIndex";
-    // BUNDLE attribute for saving header show/hide status when backstack is not used:
-    static final String HEADER_SHOW = "headerShow";
-    private static final String IS_PAGE_ROW = "isPageRow";
-    private static final String CURRENT_SELECTED_POSITION = "currentSelectedPosition";
-
-    /**
-     * State to hide headers fragment.
-     */
-    final State STATE_SET_ENTRANCE_START_STATE = new State("SET_ENTRANCE_START_STATE") {
-        @Override
-        public void run() {
-            setEntranceTransitionStartState();
-        }
-    };
-
-    /**
-     * Event for Header fragment view is created, we could perform
-     * {@link #setEntranceTransitionStartState()} to hide headers fragment initially.
-     */
-    final Event EVT_HEADER_VIEW_CREATED = new Event("headerFragmentViewCreated");
-
-    /**
-     * Event for {@link #getMainFragment()} view is created, it's additional requirement to execute
-     * {@link #onEntranceTransitionPrepare()}.
-     */
-    final Event EVT_MAIN_FRAGMENT_VIEW_CREATED = new Event("mainFragmentViewCreated");
-
-    /**
-     * Event that data for the screen is ready, this is additional requirement to launch entrance
-     * transition.
-     */
-    final Event EVT_SCREEN_DATA_READY = new Event("screenDataReady");
-
-    @Override
-    void createStateMachineStates() {
-        super.createStateMachineStates();
-        mStateMachine.addState(STATE_SET_ENTRANCE_START_STATE);
-    }
-
-    @Override
-    void createStateMachineTransitions() {
-        super.createStateMachineTransitions();
-        // when headers fragment view is created we could setEntranceTransitionStartState()
-        mStateMachine.addTransition(STATE_ENTRANCE_ON_PREPARED, STATE_SET_ENTRANCE_START_STATE,
-                EVT_HEADER_VIEW_CREATED);
-
-        // add additional requirement for onEntranceTransitionPrepare()
-        mStateMachine.addTransition(STATE_ENTRANCE_ON_PREPARED,
-                STATE_ENTRANCE_ON_PREPARED_ON_CREATEVIEW,
-                EVT_MAIN_FRAGMENT_VIEW_CREATED);
-        // add additional requirement to launch entrance transition.
-        mStateMachine.addTransition(STATE_ENTRANCE_ON_PREPARED,  STATE_ENTRANCE_PERFORM,
-                EVT_SCREEN_DATA_READY);
-    }
-
-    final class BackStackListener implements FragmentManager.OnBackStackChangedListener {
-        int mLastEntryCount;
-        int mIndexOfHeadersBackStack;
-
-        BackStackListener() {
-            mLastEntryCount = getFragmentManager().getBackStackEntryCount();
-            mIndexOfHeadersBackStack = -1;
-        }
-
-        void load(Bundle savedInstanceState) {
-            if (savedInstanceState != null) {
-                mIndexOfHeadersBackStack = savedInstanceState.getInt(HEADER_STACK_INDEX, -1);
-                mShowingHeaders = mIndexOfHeadersBackStack == -1;
-            } else {
-                if (!mShowingHeaders) {
-                    getFragmentManager().beginTransaction()
-                            .addToBackStack(mWithHeadersBackStackName).commit();
-                }
-            }
-        }
-
-        void save(Bundle outState) {
-            outState.putInt(HEADER_STACK_INDEX, mIndexOfHeadersBackStack);
-        }
-
-
-        @Override
-        public void onBackStackChanged() {
-            if (getFragmentManager() == null) {
-                Log.w(TAG, "getFragmentManager() is null, stack:", new Exception());
-                return;
-            }
-            int count = getFragmentManager().getBackStackEntryCount();
-            // if backstack is growing and last pushed entry is "headers" backstack,
-            // remember the index of the entry.
-            if (count > mLastEntryCount) {
-                BackStackEntry entry = getFragmentManager().getBackStackEntryAt(count - 1);
-                if (mWithHeadersBackStackName.equals(entry.getName())) {
-                    mIndexOfHeadersBackStack = count - 1;
-                }
-            } else if (count < mLastEntryCount) {
-                // if popped "headers" backstack, initiate the show header transition if needed
-                if (mIndexOfHeadersBackStack >= count) {
-                    if (!isHeadersDataReady()) {
-                        // if main fragment was restored first before BrowseSupportFragment's adapter gets
-                        // restored: don't start header transition, but add the entry back.
-                        getFragmentManager().beginTransaction()
-                                .addToBackStack(mWithHeadersBackStackName).commit();
-                        return;
-                    }
-                    mIndexOfHeadersBackStack = -1;
-                    if (!mShowingHeaders) {
-                        startHeadersTransitionInternal(true);
-                    }
-                }
-            }
-            mLastEntryCount = count;
-        }
-    }
-
-    /**
-     * Listener for transitions between browse headers and rows.
-     */
-    public static class BrowseTransitionListener {
-        /**
-         * Callback when headers transition starts.
-         *
-         * @param withHeaders True if the transition will result in headers
-         *        being shown, false otherwise.
-         */
-        public void onHeadersTransitionStart(boolean withHeaders) {
-        }
-        /**
-         * Callback when headers transition stops.
-         *
-         * @param withHeaders True if the transition will result in headers
-         *        being shown, false otherwise.
-         */
-        public void onHeadersTransitionStop(boolean withHeaders) {
-        }
-    }
-
-    private class SetSelectionRunnable implements Runnable {
-        static final int TYPE_INVALID = -1;
-        static final int TYPE_INTERNAL_SYNC = 0;
-        static final int TYPE_USER_REQUEST = 1;
-
-        private int mPosition;
-        private int mType;
-        private boolean mSmooth;
-
-        SetSelectionRunnable() {
-            reset();
-        }
-
-        void post(int position, int type, boolean smooth) {
-            // Posting the set selection, rather than calling it immediately, prevents an issue
-            // with adapter changes.  Example: a row is added before the current selected row;
-            // first the fast lane view updates its selection, then the rows fragment has that
-            // new selection propagated immediately; THEN the rows view processes the same adapter
-            // change and moves the selection again.
-            if (type >= mType) {
-                mPosition = position;
-                mType = type;
-                mSmooth = smooth;
-                mBrowseFrame.removeCallbacks(this);
-                mBrowseFrame.post(this);
-            }
-        }
-
-        @Override
-        public void run() {
-            setSelection(mPosition, mSmooth);
-            reset();
-        }
-
-        private void reset() {
-            mPosition = -1;
-            mType = TYPE_INVALID;
-            mSmooth = false;
-        }
-    }
-
-    /**
-     * Possible set of actions that {@link BrowseSupportFragment} exposes to clients. Custom
-     * fragments can interact with {@link BrowseSupportFragment} using this interface.
-     */
-    public interface FragmentHost {
-        /**
-         * Fragments are required to invoke this callback once their view is created
-         * inside {@link Fragment#onViewCreated} method. {@link BrowseSupportFragment} starts the entrance
-         * animation only after receiving this callback. Failure to invoke this method
-         * will lead to fragment not showing up.
-         *
-         * @param fragmentAdapter {@link MainFragmentAdapter} used by the current fragment.
-         */
-        void notifyViewCreated(MainFragmentAdapter fragmentAdapter);
-
-        /**
-         * Fragments mapped to {@link PageRow} are required to invoke this callback once their data
-         * is created for transition, the entrance animation only after receiving this callback.
-         * Failure to invoke this method will lead to fragment not showing up.
-         *
-         * @param fragmentAdapter {@link MainFragmentAdapter} used by the current fragment.
-         */
-        void notifyDataReady(MainFragmentAdapter fragmentAdapter);
-
-        /**
-         * Show or hide title view in {@link BrowseSupportFragment} for fragments mapped to
-         * {@link PageRow}.  Otherwise the request is ignored, in that case BrowseSupportFragment is fully
-         * in control of showing/hiding title view.
-         * <p>
-         * When HeadersSupportFragment is visible, BrowseSupportFragment will hide search affordance view if
-         * there are other focusable rows above currently focused row.
-         *
-         * @param show Boolean indicating whether or not to show the title view.
-         */
-        void showTitleView(boolean show);
-    }
-
-    /**
-     * Default implementation of {@link FragmentHost} that is used only by
-     * {@link BrowseSupportFragment}.
-     */
-    private final class FragmentHostImpl implements FragmentHost {
-        boolean mShowTitleView = true;
-
-        FragmentHostImpl() {
-        }
-
-        @Override
-        public void notifyViewCreated(MainFragmentAdapter fragmentAdapter) {
-            mStateMachine.fireEvent(EVT_MAIN_FRAGMENT_VIEW_CREATED);
-            if (!mIsPageRow) {
-                // If it's not a PageRow: it's a ListRow, so we already have data ready.
-                mStateMachine.fireEvent(EVT_SCREEN_DATA_READY);
-            }
-        }
-
-        @Override
-        public void notifyDataReady(MainFragmentAdapter fragmentAdapter) {
-            // If fragment host is not the currently active fragment (in BrowseSupportFragment), then
-            // ignore the request.
-            if (mMainFragmentAdapter == null || mMainFragmentAdapter.getFragmentHost() != this) {
-                return;
-            }
-
-            // We only honor showTitle request for PageRows.
-            if (!mIsPageRow) {
-                return;
-            }
-
-            mStateMachine.fireEvent(EVT_SCREEN_DATA_READY);
-        }
-
-        @Override
-        public void showTitleView(boolean show) {
-            mShowTitleView = show;
-
-            // If fragment host is not the currently active fragment (in BrowseSupportFragment), then
-            // ignore the request.
-            if (mMainFragmentAdapter == null || mMainFragmentAdapter.getFragmentHost() != this) {
-                return;
-            }
-
-            // We only honor showTitle request for PageRows.
-            if (!mIsPageRow) {
-                return;
-            }
-
-            updateTitleViewVisibility();
-        }
-    }
-
-    /**
-     * Interface that defines the interaction between {@link BrowseSupportFragment} and its main
-     * content fragment. The key method is {@link MainFragmentAdapter#getFragment()},
-     * it will be used to get the fragment to be shown in the content section. Clients can
-     * provide any implementation of fragment and customize its interaction with
-     * {@link BrowseSupportFragment} by overriding the necessary methods.
-     *
-     * <p>
-     * Clients are expected to provide
-     * an instance of {@link MainFragmentAdapterRegistry} which will be responsible for providing
-     * implementations of {@link MainFragmentAdapter} for given content types. Currently
-     * we support different types of content - {@link ListRow}, {@link PageRow} or any subtype
-     * of {@link Row}. We provide an out of the box adapter implementation for any rows other than
-     * {@link PageRow} - {@link android.support.v17.leanback.app.RowsSupportFragment.MainFragmentAdapter}.
-     *
-     * <p>
-     * {@link PageRow} is intended to give full flexibility to developers in terms of Fragment
-     * design. Users will have to provide an implementation of {@link MainFragmentAdapter}
-     * and provide that through {@link MainFragmentAdapterRegistry}.
-     * {@link MainFragmentAdapter} implementation can supply any fragment and override
-     * just those interactions that makes sense.
-     */
-    public static class MainFragmentAdapter<T extends Fragment> {
-        private boolean mScalingEnabled;
-        private final T mFragment;
-        FragmentHostImpl mFragmentHost;
-
-        public MainFragmentAdapter(T fragment) {
-            this.mFragment = fragment;
-        }
-
-        public final T getFragment() {
-            return mFragment;
-        }
-
-        /**
-         * Returns whether its scrolling.
-         */
-        public boolean isScrolling() {
-            return false;
-        }
-
-        /**
-         * Set the visibility of titles/hover card of browse rows.
-         */
-        public void setExpand(boolean expand) {
-        }
-
-        /**
-         * For rows that willing to participate entrance transition,  this function
-         * hide views if afterTransition is true,  show views if afterTransition is false.
-         */
-        public void setEntranceTransitionState(boolean state) {
-        }
-
-        /**
-         * Sets the window alignment and also the pivots for scale operation.
-         */
-        public void setAlignment(int windowAlignOffsetFromTop) {
-        }
-
-        /**
-         * Callback indicating transition prepare start.
-         */
-        public boolean onTransitionPrepare() {
-            return false;
-        }
-
-        /**
-         * Callback indicating transition start.
-         */
-        public void onTransitionStart() {
-        }
-
-        /**
-         * Callback indicating transition end.
-         */
-        public void onTransitionEnd() {
-        }
-
-        /**
-         * Returns whether row scaling is enabled.
-         */
-        public boolean isScalingEnabled() {
-            return mScalingEnabled;
-        }
-
-        /**
-         * Sets the row scaling property.
-         */
-        public void setScalingEnabled(boolean scalingEnabled) {
-            this.mScalingEnabled = scalingEnabled;
-        }
-
-        /**
-         * Returns the current host interface so that main fragment can interact with
-         * {@link BrowseSupportFragment}.
-         */
-        public final FragmentHost getFragmentHost() {
-            return mFragmentHost;
-        }
-
-        void setFragmentHost(FragmentHostImpl fragmentHost) {
-            this.mFragmentHost = fragmentHost;
-        }
-    }
-
-    /**
-     * Interface to be implemented by all fragments for providing an instance of
-     * {@link MainFragmentAdapter}. Both {@link RowsSupportFragment} and custom fragment provided
-     * against {@link PageRow} will need to implement this interface.
-     */
-    public interface MainFragmentAdapterProvider {
-        /**
-         * Returns an instance of {@link MainFragmentAdapter} that {@link BrowseSupportFragment}
-         * would use to communicate with the target fragment.
-         */
-        MainFragmentAdapter getMainFragmentAdapter();
-    }
-
-    /**
-     * Interface to be implemented by {@link RowsSupportFragment} and its subclasses for providing
-     * an instance of {@link MainFragmentRowsAdapter}.
-     */
-    public interface MainFragmentRowsAdapterProvider {
-        /**
-         * Returns an instance of {@link MainFragmentRowsAdapter} that {@link BrowseSupportFragment}
-         * would use to communicate with the target fragment.
-         */
-        MainFragmentRowsAdapter getMainFragmentRowsAdapter();
-    }
-
-    /**
-     * This is used to pass information to {@link RowsSupportFragment} or its subclasses.
-     * {@link BrowseSupportFragment} uses this interface to pass row based interaction events to
-     * the target fragment.
-     */
-    public static class MainFragmentRowsAdapter<T extends Fragment> {
-        private final T mFragment;
-
-        public MainFragmentRowsAdapter(T fragment) {
-            if (fragment == null) {
-                throw new IllegalArgumentException("Fragment can't be null");
-            }
-            this.mFragment = fragment;
-        }
-
-        public final T getFragment() {
-            return mFragment;
-        }
-        /**
-         * Set the visibility titles/hover of browse rows.
-         */
-        public void setAdapter(ObjectAdapter adapter) {
-        }
-
-        /**
-         * Sets an item clicked listener on the fragment.
-         */
-        public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
-        }
-
-        /**
-         * Sets an item selection listener.
-         */
-        public void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
-        }
-
-        /**
-         * Selects a Row and perform an optional task on the Row.
-         */
-        public void setSelectedPosition(int rowPosition,
-                                        boolean smooth,
-                                        final Presenter.ViewHolderTask rowHolderTask) {
-        }
-
-        /**
-         * Selects a Row.
-         */
-        public void setSelectedPosition(int rowPosition, boolean smooth) {
-        }
-
-        /**
-         * @return The position of selected row.
-         */
-        public int getSelectedPosition() {
-            return 0;
-        }
-
-        /**
-         * @param position Position of Row.
-         * @return Row ViewHolder.
-         */
-        public RowPresenter.ViewHolder findRowViewHolderByPosition(int position) {
-            return null;
-        }
-    }
-
-    private boolean createMainFragment(ObjectAdapter adapter, int position) {
-        Object item = null;
-        if (!mCanShowHeaders) {
-            // when header is disabled, we can decide to use RowsSupportFragment even no data.
-        } else if (adapter == null || adapter.size() == 0) {
-            return false;
-        } else {
-            if (position < 0) {
-                position = 0;
-            } else if (position >= adapter.size()) {
-                throw new IllegalArgumentException(
-                        String.format("Invalid position %d requested", position));
-            }
-            item = adapter.get(position);
-        }
-
-        boolean oldIsPageRow = mIsPageRow;
-        mIsPageRow = mCanShowHeaders && item instanceof PageRow;
-        boolean swap;
-
-        if (mMainFragment == null) {
-            swap = true;
-        } else {
-            if (oldIsPageRow) {
-                swap = true;
-            } else {
-                swap = mIsPageRow;
-            }
-        }
-
-        if (swap) {
-            mMainFragment = mMainFragmentAdapterRegistry.createFragment(item);
-            if (!(mMainFragment instanceof MainFragmentAdapterProvider)) {
-                throw new IllegalArgumentException(
-                        "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;
-            }
-        }
-
-        return swap;
-    }
-
-    /**
-     * Factory class responsible for creating fragment given the current item. {@link ListRow}
-     * should return {@link RowsSupportFragment} or its subclass whereas {@link PageRow}
-     * can return any fragment class.
-     */
-    public abstract static class FragmentFactory<T extends Fragment> {
-        public abstract T createFragment(Object row);
-    }
-
-    /**
-     * FragmentFactory implementation for {@link ListRow}.
-     */
-    public static class ListRowFragmentFactory extends FragmentFactory<RowsSupportFragment> {
-        @Override
-        public RowsSupportFragment createFragment(Object row) {
-            return new RowsSupportFragment();
-        }
-    }
-
-    /**
-     * Registry class maintaining the mapping of {@link Row} subclasses to {@link FragmentFactory}.
-     * BrowseRowFragment automatically registers {@link ListRowFragmentFactory} for
-     * handling {@link ListRow}. Developers can override that and also if they want to
-     * use custom fragment, they can register a custom {@link FragmentFactory}
-     * against {@link PageRow}.
-     */
-    public final static class MainFragmentAdapterRegistry {
-        private final Map<Class, FragmentFactory> mItemToFragmentFactoryMapping = new HashMap<>();
-        private final static FragmentFactory sDefaultFragmentFactory = new ListRowFragmentFactory();
-
-        public MainFragmentAdapterRegistry() {
-            registerFragment(ListRow.class, sDefaultFragmentFactory);
-        }
-
-        public void registerFragment(Class rowClass, FragmentFactory factory) {
-            mItemToFragmentFactoryMapping.put(rowClass, factory);
-        }
-
-        public Fragment createFragment(Object item) {
-            FragmentFactory fragmentFactory = item == null ? sDefaultFragmentFactory :
-                    mItemToFragmentFactoryMapping.get(item.getClass());
-            if (fragmentFactory == null && !(item instanceof PageRow)) {
-                fragmentFactory = sDefaultFragmentFactory;
-            }
-
-            return fragmentFactory.createFragment(item);
-        }
-    }
-
-    static final String TAG = "BrowseSupportFragment";
-
-    private static final String LB_HEADERS_BACKSTACK = "lbHeadersBackStack_";
-
-    static boolean DEBUG = false;
-
-    /** The headers fragment is enabled and shown by default. */
-    public static final int HEADERS_ENABLED = 1;
-
-    /** The headers fragment is enabled and hidden by default. */
-    public static final int HEADERS_HIDDEN = 2;
-
-    /** The headers fragment is disabled and will never be shown. */
-    public static final int HEADERS_DISABLED = 3;
-
-    private MainFragmentAdapterRegistry mMainFragmentAdapterRegistry =
-            new MainFragmentAdapterRegistry();
-    MainFragmentAdapter mMainFragmentAdapter;
-    Fragment mMainFragment;
-    HeadersSupportFragment mHeadersSupportFragment;
-    private MainFragmentRowsAdapter mMainFragmentRowsAdapter;
-
-    private ObjectAdapter mAdapter;
-    private PresenterSelector mAdapterPresenter;
-    private PresenterSelector mWrappingPresenterSelector;
-
-    private int mHeadersState = HEADERS_ENABLED;
-    private int mBrandColor = Color.TRANSPARENT;
-    private boolean mBrandColorSet;
-
-    BrowseFrameLayout mBrowseFrame;
-    private ScaleFrameLayout mScaleFrameLayout;
-    boolean mHeadersBackStackEnabled = true;
-    String mWithHeadersBackStackName;
-    boolean mShowingHeaders = true;
-    boolean mCanShowHeaders = true;
-    private int mContainerListMarginStart;
-    private int mContainerListAlignTop;
-    private boolean mMainFragmentScaleEnabled = true;
-    OnItemViewSelectedListener mExternalOnItemViewSelectedListener;
-    private OnItemViewClickedListener mOnItemViewClickedListener;
-    private int mSelectedPosition = -1;
-    private float mScaleFactor;
-    boolean mIsPageRow;
-
-    private PresenterSelector mHeaderPresenterSelector;
-    private final SetSelectionRunnable mSetSelectionRunnable = new SetSelectionRunnable();
-
-    // transition related:
-    Object mSceneWithHeaders;
-    Object mSceneWithoutHeaders;
-    private Object mSceneAfterEntranceTransition;
-    Object mHeadersTransition;
-    BackStackListener mBackStackChangedListener;
-    BrowseTransitionListener mBrowseTransitionListener;
-
-    private static final String ARG_TITLE = BrowseSupportFragment.class.getCanonicalName() + ".title";
-    private static final String ARG_HEADERS_STATE =
-        BrowseSupportFragment.class.getCanonicalName() + ".headersState";
-
-    /**
-     * Creates arguments for a browse fragment.
-     *
-     * @param args The Bundle to place arguments into, or null if the method
-     *        should return a new Bundle.
-     * @param title The title of the BrowseSupportFragment.
-     * @param headersState The initial state of the headers of the
-     *        BrowseSupportFragment. Must be one of {@link #HEADERS_ENABLED}, {@link
-     *        #HEADERS_HIDDEN}, or {@link #HEADERS_DISABLED}.
-     * @return A Bundle with the given arguments for creating a BrowseSupportFragment.
-     */
-    public static Bundle createArgs(Bundle args, String title, int headersState) {
-        if (args == null) {
-            args = new Bundle();
-        }
-        args.putString(ARG_TITLE, title);
-        args.putInt(ARG_HEADERS_STATE, headersState);
-        return args;
-    }
-
-    /**
-     * Sets the brand color for the browse fragment. The brand color is used as
-     * the primary color for UI elements in the browse fragment. For example,
-     * the background color of the headers fragment uses the brand color.
-     *
-     * @param color The color to use as the brand color of the fragment.
-     */
-    public void setBrandColor(@ColorInt int color) {
-        mBrandColor = color;
-        mBrandColorSet = true;
-
-        if (mHeadersSupportFragment != null) {
-            mHeadersSupportFragment.setBackgroundColor(mBrandColor);
-        }
-    }
-
-    /**
-     * Returns the brand color for the browse fragment.
-     * The default is transparent.
-     */
-    @ColorInt
-    public int getBrandColor() {
-        return mBrandColor;
-    }
-
-    /**
-     * Wrapping app provided PresenterSelector to support InvisibleRowPresenter for SectionRow
-     * DividerRow and PageRow.
-     */
-    private void createAndSetWrapperPresenter() {
-        final PresenterSelector adapterPresenter = mAdapter.getPresenterSelector();
-        if (adapterPresenter == null) {
-            throw new IllegalArgumentException("Adapter.getPresenterSelector() is null");
-        }
-        if (adapterPresenter == mAdapterPresenter) {
-            return;
-        }
-        mAdapterPresenter = adapterPresenter;
-
-        Presenter[] presenters = adapterPresenter.getPresenters();
-        final Presenter invisibleRowPresenter = new InvisibleRowPresenter();
-        final Presenter[] allPresenters = new Presenter[presenters.length + 1];
-        System.arraycopy(allPresenters, 0, presenters, 0, presenters.length);
-        allPresenters[allPresenters.length - 1] = invisibleRowPresenter;
-        mAdapter.setPresenterSelector(new PresenterSelector() {
-            @Override
-            public Presenter getPresenter(Object item) {
-                Row row = (Row) item;
-                if (row.isRenderedAsRowView()) {
-                    return adapterPresenter.getPresenter(item);
-                } else {
-                    return invisibleRowPresenter;
-                }
-            }
-
-            @Override
-            public Presenter[] getPresenters() {
-                return allPresenters;
-            }
-        });
-    }
-
-    /**
-     * Sets the adapter containing the rows for the fragment.
-     *
-     * <p>The items referenced by the adapter must be be derived from
-     * {@link Row}. These rows will be used by the rows fragment and the headers
-     * fragment (if not disabled) to render the browse rows.
-     *
-     * @param adapter An ObjectAdapter for the browse rows. All items must
-     *        derive from {@link Row}.
-     */
-    public void setAdapter(ObjectAdapter adapter) {
-        mAdapter = adapter;
-        createAndSetWrapperPresenter();
-        if (getView() == null) {
-            return;
-        }
-        replaceMainFragment(mSelectedPosition);
-
-        if (adapter != null) {
-            if (mMainFragmentRowsAdapter != null) {
-                mMainFragmentRowsAdapter.setAdapter(new ListRowDataAdapter(adapter));
-            }
-            mHeadersSupportFragment.setAdapter(adapter);
-        }
-    }
-
-    public final MainFragmentAdapterRegistry getMainFragmentRegistry() {
-        return mMainFragmentAdapterRegistry;
-    }
-
-    /**
-     * Returns the adapter containing the rows for the fragment.
-     */
-    public ObjectAdapter getAdapter() {
-        return mAdapter;
-    }
-
-    /**
-     * Sets an item selection listener.
-     */
-    public void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
-        mExternalOnItemViewSelectedListener = listener;
-    }
-
-    /**
-     * Returns an item selection listener.
-     */
-    public OnItemViewSelectedListener getOnItemViewSelectedListener() {
-        return mExternalOnItemViewSelectedListener;
-    }
-
-    /**
-     * Get RowsSupportFragment if it's bound to BrowseSupportFragment or null if either BrowseSupportFragment has
-     * not been created yet or a different fragment is bound to it.
-     *
-     * @return RowsSupportFragment if it's bound to BrowseSupportFragment or null otherwise.
-     */
-    public RowsSupportFragment getRowsSupportFragment() {
-        if (mMainFragment instanceof RowsSupportFragment) {
-            return (RowsSupportFragment) mMainFragment;
-        }
-
-        return null;
-    }
-
-    /**
-     * @return Current main fragment or null if not created.
-     */
-    public Fragment getMainFragment() {
-        return mMainFragment;
-    }
-
-    /**
-     * Get currently bound HeadersSupportFragment or null if HeadersSupportFragment has not been created yet.
-     * @return Currently bound HeadersSupportFragment or null if HeadersSupportFragment has not been created yet.
-     */
-    public HeadersSupportFragment getHeadersSupportFragment() {
-        return mHeadersSupportFragment;
-    }
-
-    /**
-     * Sets an item clicked listener on the fragment.
-     * OnItemViewClickedListener will override {@link View.OnClickListener} that
-     * item presenter sets during {@link Presenter#onCreateViewHolder(ViewGroup)}.
-     * So in general, developer should choose one of the listeners but not both.
-     */
-    public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
-        mOnItemViewClickedListener = listener;
-        if (mMainFragmentRowsAdapter != null) {
-            mMainFragmentRowsAdapter.setOnItemViewClickedListener(listener);
-        }
-    }
-
-    /**
-     * Returns the item Clicked listener.
-     */
-    public OnItemViewClickedListener getOnItemViewClickedListener() {
-        return mOnItemViewClickedListener;
-    }
-
-    /**
-     * Starts a headers transition.
-     *
-     * <p>This method will begin a transition to either show or hide the
-     * headers, depending on the value of withHeaders. If headers are disabled
-     * for this browse fragment, this method will throw an exception.
-     *
-     * @param withHeaders True if the headers should transition to being shown,
-     *        false if the transition should result in headers being hidden.
-     */
-    public void startHeadersTransition(boolean withHeaders) {
-        if (!mCanShowHeaders) {
-            throw new IllegalStateException("Cannot start headers transition");
-        }
-        if (isInHeadersTransition() || mShowingHeaders == withHeaders) {
-            return;
-        }
-        startHeadersTransitionInternal(withHeaders);
-    }
-
-    /**
-     * Returns true if the headers transition is currently running.
-     */
-    public boolean isInHeadersTransition() {
-        return mHeadersTransition != null;
-    }
-
-    /**
-     * Returns true if headers are shown.
-     */
-    public boolean isShowingHeaders() {
-        return mShowingHeaders;
-    }
-
-    /**
-     * Sets a listener for browse fragment transitions.
-     *
-     * @param listener The listener to call when a browse headers transition
-     *        begins or ends.
-     */
-    public void setBrowseTransitionListener(BrowseTransitionListener listener) {
-        mBrowseTransitionListener = listener;
-    }
-
-    /**
-     * @deprecated use {@link BrowseSupportFragment#enableMainFragmentScaling(boolean)} instead.
-     *
-     * @param enable true to enable row scaling
-     */
-    @Deprecated
-    public void enableRowScaling(boolean enable) {
-        enableMainFragmentScaling(enable);
-    }
-
-    /**
-     * Enables scaling of main fragment when headers are present. For the page/row fragment,
-     * scaling is enabled only when both this method and
-     * {@link MainFragmentAdapter#isScalingEnabled()} are enabled.
-     *
-     * @param enable true to enable row scaling
-     */
-    public void enableMainFragmentScaling(boolean enable) {
-        mMainFragmentScaleEnabled = enable;
-    }
-
-    void startHeadersTransitionInternal(final boolean withHeaders) {
-        if (getFragmentManager().isDestroyed()) {
-            return;
-        }
-        if (!isHeadersDataReady()) {
-            return;
-        }
-        mShowingHeaders = withHeaders;
-        mMainFragmentAdapter.onTransitionPrepare();
-        mMainFragmentAdapter.onTransitionStart();
-        onExpandTransitionStart(!withHeaders, new Runnable() {
-            @Override
-            public void run() {
-                mHeadersSupportFragment.onTransitionPrepare();
-                mHeadersSupportFragment.onTransitionStart();
-                createHeadersTransition();
-                if (mBrowseTransitionListener != null) {
-                    mBrowseTransitionListener.onHeadersTransitionStart(withHeaders);
-                }
-                TransitionHelper.runTransition(
-                        withHeaders ? mSceneWithHeaders : mSceneWithoutHeaders, mHeadersTransition);
-                if (mHeadersBackStackEnabled) {
-                    if (!withHeaders) {
-                        getFragmentManager().beginTransaction()
-                                .addToBackStack(mWithHeadersBackStackName).commit();
-                    } else {
-                        int index = mBackStackChangedListener.mIndexOfHeadersBackStack;
-                        if (index >= 0) {
-                            BackStackEntry entry = getFragmentManager().getBackStackEntryAt(index);
-                            getFragmentManager().popBackStackImmediate(entry.getId(),
-                                    FragmentManager.POP_BACK_STACK_INCLUSIVE);
-                        }
-                    }
-                }
-            }
-        });
-    }
-
-    boolean isVerticalScrolling() {
-        // don't run transition
-        return mHeadersSupportFragment.isScrolling() || mMainFragmentAdapter.isScrolling();
-    }
-
-
-    private final BrowseFrameLayout.OnFocusSearchListener mOnFocusSearchListener =
-            new BrowseFrameLayout.OnFocusSearchListener() {
-        @Override
-        public View onFocusSearch(View focused, int direction) {
-            // if headers is running transition,  focus stays
-            if (mCanShowHeaders && isInHeadersTransition()) {
-                return focused;
-            }
-            if (DEBUG) Log.v(TAG, "onFocusSearch focused " + focused + " + direction " + direction);
-
-            if (getTitleView() != null && focused != getTitleView()
-                    && direction == View.FOCUS_UP) {
-                return getTitleView();
-            }
-            if (getTitleView() != null && getTitleView().hasFocus()
-                    && direction == View.FOCUS_DOWN) {
-                return mCanShowHeaders && mShowingHeaders
-                        ? mHeadersSupportFragment.getVerticalGridView() : mMainFragment.getView();
-            }
-
-            boolean isRtl = ViewCompat.getLayoutDirection(focused)
-                    == ViewCompat.LAYOUT_DIRECTION_RTL;
-            int towardStart = isRtl ? View.FOCUS_RIGHT : View.FOCUS_LEFT;
-            int towardEnd = isRtl ? View.FOCUS_LEFT : View.FOCUS_RIGHT;
-            if (mCanShowHeaders && direction == towardStart) {
-                if (isVerticalScrolling() || mShowingHeaders || !isHeadersDataReady()) {
-                    return focused;
-                }
-                return mHeadersSupportFragment.getVerticalGridView();
-            } else if (direction == towardEnd) {
-                if (isVerticalScrolling()) {
-                    return focused;
-                } else if (mMainFragment != null && mMainFragment.getView() != null) {
-                    return mMainFragment.getView();
-                }
-                return focused;
-            } else if (direction == View.FOCUS_DOWN && mShowingHeaders) {
-                // disable focus_down moving into PageFragment.
-                return focused;
-            } else {
-                return null;
-            }
-        }
-    };
-
-    final boolean isHeadersDataReady() {
-        return mAdapter != null && mAdapter.size() != 0;
-    }
-
-    private final BrowseFrameLayout.OnChildFocusListener mOnChildFocusListener =
-            new BrowseFrameLayout.OnChildFocusListener() {
-
-        @Override
-        public boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
-            if (getChildFragmentManager().isDestroyed()) {
-                return true;
-            }
-            // Make sure not changing focus when requestFocus() is called.
-            if (mCanShowHeaders && mShowingHeaders) {
-                if (mHeadersSupportFragment != null && mHeadersSupportFragment.getView() != null
-                        && mHeadersSupportFragment.getView().requestFocus(
-                                direction, previouslyFocusedRect)) {
-                    return true;
-                }
-            }
-            if (mMainFragment != null && mMainFragment.getView() != null
-                    && mMainFragment.getView().requestFocus(direction, previouslyFocusedRect)) {
-                return true;
-            }
-            return getTitleView() != null
-                    && getTitleView().requestFocus(direction, previouslyFocusedRect);
-        }
-
-        @Override
-        public void onRequestChildFocus(View child, View focused) {
-            if (getChildFragmentManager().isDestroyed()) {
-                return;
-            }
-            if (!mCanShowHeaders || isInHeadersTransition()) return;
-            int childId = child.getId();
-            if (childId == R.id.browse_container_dock && mShowingHeaders) {
-                startHeadersTransitionInternal(false);
-            } else if (childId == R.id.browse_headers_dock && !mShowingHeaders) {
-                startHeadersTransitionInternal(true);
-            }
-        }
-    };
-
-    @Override
-    public void onSaveInstanceState(Bundle outState) {
-        super.onSaveInstanceState(outState);
-        outState.putInt(CURRENT_SELECTED_POSITION, mSelectedPosition);
-        outState.putBoolean(IS_PAGE_ROW, mIsPageRow);
-
-        if (mBackStackChangedListener != null) {
-            mBackStackChangedListener.save(outState);
-        } else {
-            outState.putBoolean(HEADER_SHOW, mShowingHeaders);
-        }
-    }
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        final Context context = getContext();
-        TypedArray ta = context.obtainStyledAttributes(R.styleable.LeanbackTheme);
-        mContainerListMarginStart = (int) ta.getDimension(
-                R.styleable.LeanbackTheme_browseRowsMarginStart, context.getResources()
-                .getDimensionPixelSize(R.dimen.lb_browse_rows_margin_start));
-        mContainerListAlignTop = (int) ta.getDimension(
-                R.styleable.LeanbackTheme_browseRowsMarginTop, context.getResources()
-                .getDimensionPixelSize(R.dimen.lb_browse_rows_margin_top));
-        ta.recycle();
-
-        readArguments(getArguments());
-
-        if (mCanShowHeaders) {
-            if (mHeadersBackStackEnabled) {
-                mWithHeadersBackStackName = LB_HEADERS_BACKSTACK + this;
-                mBackStackChangedListener = new BackStackListener();
-                getFragmentManager().addOnBackStackChangedListener(mBackStackChangedListener);
-                mBackStackChangedListener.load(savedInstanceState);
-            } else {
-                if (savedInstanceState != null) {
-                    mShowingHeaders = savedInstanceState.getBoolean(HEADER_SHOW);
-                }
-            }
-        }
-
-        mScaleFactor = getResources().getFraction(R.fraction.lb_browse_rows_scale, 1, 1);
-    }
-
-    @Override
-    public void onDestroyView() {
-        mMainFragmentRowsAdapter = null;
-        mMainFragmentAdapter = null;
-        mMainFragment = null;
-        mHeadersSupportFragment = null;
-        super.onDestroyView();
-    }
-
-    @Override
-    public void onDestroy() {
-        if (mBackStackChangedListener != null) {
-            getFragmentManager().removeOnBackStackChangedListener(mBackStackChangedListener);
-        }
-        super.onDestroy();
-    }
-
-    /**
-     * Creates a new {@link HeadersSupportFragment} instance. Subclass of BrowseSupportFragment may override and
-     * return an instance of subclass of HeadersSupportFragment, e.g. when app wants to replace presenter
-     * to render HeaderItem.
-     *
-     * @return A new instance of {@link HeadersSupportFragment} or its subclass.
-     */
-    public HeadersSupportFragment onCreateHeadersSupportFragment() {
-        return new HeadersSupportFragment();
-    }
-
-    @Override
-    public View onCreateView(LayoutInflater inflater, ViewGroup container,
-            Bundle savedInstanceState) {
-
-        if (getChildFragmentManager().findFragmentById(R.id.scale_frame) == null) {
-            mHeadersSupportFragment = onCreateHeadersSupportFragment();
-
-            createMainFragment(mAdapter, mSelectedPosition);
-            FragmentTransaction ft = getChildFragmentManager().beginTransaction()
-                    .replace(R.id.browse_headers_dock, mHeadersSupportFragment);
-
-            if (mMainFragment != null) {
-                ft.replace(R.id.scale_frame, mMainFragment);
-            } else {
-                // Empty adapter used to guard against lazy adapter loading. When this
-                // fragment is instantiated, mAdapter might not have the data or might not
-                // have been set. In either of those cases mFragmentAdapter will be null.
-                // This way we can maintain the invariant that mMainFragmentAdapter is never
-                // null and it avoids doing null checks all over the code.
-                mMainFragmentAdapter = new MainFragmentAdapter(null);
-                mMainFragmentAdapter.setFragmentHost(new FragmentHostImpl());
-            }
-
-            ft.commit();
-        } else {
-            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);
-
-            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;
-            }
-        }
-
-        mHeadersSupportFragment.setHeadersGone(!mCanShowHeaders);
-        if (mHeaderPresenterSelector != null) {
-            mHeadersSupportFragment.setPresenterSelector(mHeaderPresenterSelector);
-        }
-        mHeadersSupportFragment.setAdapter(mAdapter);
-        mHeadersSupportFragment.setOnHeaderViewSelectedListener(mHeaderViewSelectedListener);
-        mHeadersSupportFragment.setOnHeaderClickedListener(mHeaderClickedListener);
-
-        View root = inflater.inflate(R.layout.lb_browse_fragment, container, false);
-
-        getProgressBarManager().setRootView((ViewGroup)root);
-
-        mBrowseFrame = (BrowseFrameLayout) root.findViewById(R.id.browse_frame);
-        mBrowseFrame.setOnChildFocusListener(mOnChildFocusListener);
-        mBrowseFrame.setOnFocusSearchListener(mOnFocusSearchListener);
-
-        installTitleView(inflater, mBrowseFrame, savedInstanceState);
-
-        mScaleFrameLayout = (ScaleFrameLayout) root.findViewById(R.id.scale_frame);
-        mScaleFrameLayout.setPivotX(0);
-        mScaleFrameLayout.setPivotY(mContainerListAlignTop);
-
-        setupMainFragment();
-
-        if (mBrandColorSet) {
-            mHeadersSupportFragment.setBackgroundColor(mBrandColor);
-        }
-
-        mSceneWithHeaders = TransitionHelper.createScene(mBrowseFrame, new Runnable() {
-            @Override
-            public void run() {
-                showHeaders(true);
-            }
-        });
-        mSceneWithoutHeaders =  TransitionHelper.createScene(mBrowseFrame, new Runnable() {
-            @Override
-            public void run() {
-                showHeaders(false);
-            }
-        });
-        mSceneAfterEntranceTransition = TransitionHelper.createScene(mBrowseFrame, new Runnable() {
-            @Override
-            public void run() {
-                setEntranceTransitionEndState();
-            }
-        });
-
-        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
-                        ? R.transition.lb_browse_headers_in : R.transition.lb_browse_headers_out);
-
-        TransitionHelper.addTransitionListener(mHeadersTransition, new TransitionListener() {
-            @Override
-            public void onTransitionStart(Object transition) {
-            }
-            @Override
-            public void onTransitionEnd(Object transition) {
-                mHeadersTransition = null;
-                if (mMainFragmentAdapter != null) {
-                    mMainFragmentAdapter.onTransitionEnd();
-                    if (!mShowingHeaders && mMainFragment != null) {
-                        View mainFragmentView = mMainFragment.getView();
-                        if (mainFragmentView != null && !mainFragmentView.hasFocus()) {
-                            mainFragmentView.requestFocus();
-                        }
-                    }
-                }
-                if (mHeadersSupportFragment != null) {
-                    mHeadersSupportFragment.onTransitionEnd();
-                    if (mShowingHeaders) {
-                        VerticalGridView headerGridView = mHeadersSupportFragment.getVerticalGridView();
-                        if (headerGridView != null && !headerGridView.hasFocus()) {
-                            headerGridView.requestFocus();
-                        }
-                    }
-                }
-
-                // Animate TitleView once header animation is complete.
-                updateTitleViewVisibility();
-
-                if (mBrowseTransitionListener != null) {
-                    mBrowseTransitionListener.onHeadersTransitionStop(mShowingHeaders);
-                }
-            }
-        });
-    }
-
-    void updateTitleViewVisibility() {
-        if (!mShowingHeaders) {
-            boolean showTitleView;
-            if (mIsPageRow && mMainFragmentAdapter != null) {
-                // page fragment case:
-                showTitleView = mMainFragmentAdapter.mFragmentHost.mShowTitleView;
-            } else {
-                // regular row view case:
-                showTitleView = isFirstRowWithContent(mSelectedPosition);
-            }
-            if (showTitleView) {
-                showTitle(TitleViewAdapter.FULL_VIEW_VISIBLE);
-            } else {
-                showTitle(false);
-            }
-        } else {
-            // when HeaderFragment is showing,  showBranding and showSearch are slightly different
-            boolean showBranding;
-            boolean showSearch;
-            if (mIsPageRow && mMainFragmentAdapter != null) {
-                showBranding = mMainFragmentAdapter.mFragmentHost.mShowTitleView;
-            } else {
-                showBranding = isFirstRowWithContent(mSelectedPosition);
-            }
-            showSearch = isFirstRowWithContentOrPageRow(mSelectedPosition);
-            int flags = 0;
-            if (showBranding) flags |= TitleViewAdapter.BRANDING_VIEW_VISIBLE;
-            if (showSearch) flags |= TitleViewAdapter.SEARCH_VIEW_VISIBLE;
-            if (flags != 0) {
-                showTitle(flags);
-            } else {
-                showTitle(false);
-            }
-        }
-    }
-
-    boolean isFirstRowWithContentOrPageRow(int rowPosition) {
-        if (mAdapter == null || mAdapter.size() == 0) {
-            return true;
-        }
-        for (int i = 0; i < mAdapter.size(); i++) {
-            final Row row = (Row) mAdapter.get(i);
-            if (row.isRenderedAsRowView() || row instanceof PageRow) {
-                return rowPosition == i;
-            }
-        }
-        return true;
-    }
-
-    boolean isFirstRowWithContent(int rowPosition) {
-        if (mAdapter == null || mAdapter.size() == 0) {
-            return true;
-        }
-        for (int i = 0; i < mAdapter.size(); i++) {
-            final Row row = (Row) mAdapter.get(i);
-            if (row.isRenderedAsRowView()) {
-                return rowPosition == i;
-            }
-        }
-        return true;
-    }
-
-    /**
-     * Sets the {@link PresenterSelector} used to render the row headers.
-     *
-     * @param headerPresenterSelector The PresenterSelector that will determine
-     *        the Presenter for each row header.
-     */
-    public void setHeaderPresenterSelector(PresenterSelector headerPresenterSelector) {
-        mHeaderPresenterSelector = headerPresenterSelector;
-        if (mHeadersSupportFragment != null) {
-            mHeadersSupportFragment.setPresenterSelector(mHeaderPresenterSelector);
-        }
-    }
-
-    private void setHeadersOnScreen(boolean onScreen) {
-        MarginLayoutParams lp;
-        View containerList;
-        containerList = mHeadersSupportFragment.getView();
-        lp = (MarginLayoutParams) containerList.getLayoutParams();
-        lp.setMarginStart(onScreen ? 0 : -mContainerListMarginStart);
-        containerList.setLayoutParams(lp);
-    }
-
-    void showHeaders(boolean show) {
-        if (DEBUG) Log.v(TAG, "showHeaders " + show);
-        mHeadersSupportFragment.setHeadersEnabled(show);
-        setHeadersOnScreen(show);
-        expandMainFragment(!show);
-    }
-
-    private void expandMainFragment(boolean expand) {
-        MarginLayoutParams params = (MarginLayoutParams) mScaleFrameLayout.getLayoutParams();
-        params.setMarginStart(!expand ? mContainerListMarginStart : 0);
-        mScaleFrameLayout.setLayoutParams(params);
-        mMainFragmentAdapter.setExpand(expand);
-
-        setMainFragmentAlignment();
-        final float scaleFactor = !expand
-                && mMainFragmentScaleEnabled
-                && mMainFragmentAdapter.isScalingEnabled() ? mScaleFactor : 1;
-        mScaleFrameLayout.setLayoutScaleY(scaleFactor);
-        mScaleFrameLayout.setChildScale(scaleFactor);
-    }
-
-    private HeadersSupportFragment.OnHeaderClickedListener mHeaderClickedListener =
-        new HeadersSupportFragment.OnHeaderClickedListener() {
-            @Override
-            public void onHeaderClicked(RowHeaderPresenter.ViewHolder viewHolder, Row row) {
-                if (!mCanShowHeaders || !mShowingHeaders || isInHeadersTransition()) {
-                    return;
-                }
-                startHeadersTransitionInternal(false);
-                mMainFragment.getView().requestFocus();
-            }
-        };
-
-    class MainFragmentItemViewSelectedListener implements OnItemViewSelectedListener {
-        MainFragmentRowsAdapter mMainFragmentRowsAdapter;
-
-        public MainFragmentItemViewSelectedListener(MainFragmentRowsAdapter fragmentRowsAdapter) {
-            mMainFragmentRowsAdapter = fragmentRowsAdapter;
-        }
-
-        @Override
-        public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
-                RowPresenter.ViewHolder rowViewHolder, Row row) {
-            int position = mMainFragmentRowsAdapter.getSelectedPosition();
-            if (DEBUG) Log.v(TAG, "row selected position " + position);
-            onRowSelected(position);
-            if (mExternalOnItemViewSelectedListener != null) {
-                mExternalOnItemViewSelectedListener.onItemSelected(itemViewHolder, item,
-                        rowViewHolder, row);
-            }
-        }
-    };
-
-    private HeadersSupportFragment.OnHeaderViewSelectedListener mHeaderViewSelectedListener =
-            new HeadersSupportFragment.OnHeaderViewSelectedListener() {
-        @Override
-        public void onHeaderSelected(RowHeaderPresenter.ViewHolder viewHolder, Row row) {
-            int position = mHeadersSupportFragment.getSelectedPosition();
-            if (DEBUG) Log.v(TAG, "header selected position " + position);
-            onRowSelected(position);
-        }
-    };
-
-    void onRowSelected(int position) {
-        if (position != mSelectedPosition) {
-            mSetSelectionRunnable.post(
-                    position, SetSelectionRunnable.TYPE_INTERNAL_SYNC, true);
-        }
-    }
-
-    void setSelection(int position, boolean smooth) {
-        if (position == NO_POSITION) {
-            return;
-        }
-
-        mSelectedPosition = position;
-        if (mHeadersSupportFragment == null || mMainFragmentAdapter == null) {
-            // onDestroyView() called
-            return;
-        }
-        mHeadersSupportFragment.setSelectedPosition(position, smooth);
-        replaceMainFragment(position);
-
-        if (mMainFragmentRowsAdapter != null) {
-            mMainFragmentRowsAdapter.setSelectedPosition(position, smooth);
-        }
-
-        updateTitleViewVisibility();
-    }
-
-    private void replaceMainFragment(int position) {
-        if (createMainFragment(mAdapter, position)) {
-            swapToMainFragment();
-            expandMainFragment(!(mCanShowHeaders && mShowingHeaders));
-            setupMainFragment();
-        }
-    }
-
-    private void swapToMainFragment() {
-        final VerticalGridView gridView = mHeadersSupportFragment.getVerticalGridView();
-        if (isShowingHeaders() && gridView != null
-                && gridView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) {
-            // if user is scrolling HeadersSupportFragment,  swap to empty fragment and wait scrolling
-            // finishes.
-            getChildFragmentManager().beginTransaction()
-                    .replace(R.id.scale_frame, new Fragment()).commit();
-            gridView.addOnScrollListener(new RecyclerView.OnScrollListener() {
-                @SuppressWarnings("ReferenceEquality")
-                @Override
-                public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
-                    if (newState == RecyclerView.SCROLL_STATE_IDLE) {
-                        gridView.removeOnScrollListener(this);
-                        FragmentManager fm = getChildFragmentManager();
-                        Fragment currentFragment = fm.findFragmentById(R.id.scale_frame);
-                        if (currentFragment != mMainFragment) {
-                            fm.beginTransaction().replace(R.id.scale_frame, mMainFragment).commit();
-                        }
-                    }
-                }
-            });
-        } else {
-            // Otherwise swap immediately
-            getChildFragmentManager().beginTransaction()
-                    .replace(R.id.scale_frame, mMainFragment).commit();
-        }
-    }
-
-    /**
-     * Sets the selected row position with smooth animation.
-     */
-    public void setSelectedPosition(int position) {
-        setSelectedPosition(position, true);
-    }
-
-    /**
-     * Gets position of currently selected row.
-     * @return Position of currently selected row.
-     */
-    public int getSelectedPosition() {
-        return mSelectedPosition;
-    }
-
-    /**
-     * @return selected row ViewHolder inside fragment created by {@link MainFragmentRowsAdapter}.
-     */
-    public RowPresenter.ViewHolder getSelectedRowViewHolder() {
-        if (mMainFragmentRowsAdapter != null) {
-            int rowPos = mMainFragmentRowsAdapter.getSelectedPosition();
-            return mMainFragmentRowsAdapter.findRowViewHolderByPosition(rowPos);
-        }
-        return null;
-    }
-
-    /**
-     * Sets the selected row position.
-     */
-    public void setSelectedPosition(int position, boolean smooth) {
-        mSetSelectionRunnable.post(
-                position, SetSelectionRunnable.TYPE_USER_REQUEST, smooth);
-    }
-
-    /**
-     * Selects a Row and perform an optional task on the Row. For example
-     * <code>setSelectedPosition(10, true, new ListRowPresenterSelectItemViewHolderTask(5))</code>
-     * scrolls to 11th row and selects 6th item on that row.  The method will be ignored if
-     * RowsSupportFragment has not been created (i.e. before {@link #onCreateView(LayoutInflater,
-     * ViewGroup, Bundle)}).
-     *
-     * @param rowPosition Which row to select.
-     * @param smooth True to scroll to the row, false for no animation.
-     * @param rowHolderTask Optional task to perform on the Row.  When the task is not null, headers
-     * fragment will be collapsed.
-     */
-    public void setSelectedPosition(int rowPosition, boolean smooth,
-            final Presenter.ViewHolderTask rowHolderTask) {
-        if (mMainFragmentAdapterRegistry == null) {
-            return;
-        }
-        if (rowHolderTask != null) {
-            startHeadersTransition(false);
-        }
-        if (mMainFragmentRowsAdapter != null) {
-            mMainFragmentRowsAdapter.setSelectedPosition(rowPosition, smooth, rowHolderTask);
-        }
-    }
-
-    @Override
-    public void onStart() {
-        super.onStart();
-        mHeadersSupportFragment.setAlignment(mContainerListAlignTop);
-        setMainFragmentAlignment();
-
-        if (mCanShowHeaders && mShowingHeaders && mHeadersSupportFragment != null
-                && mHeadersSupportFragment.getView() != null) {
-            mHeadersSupportFragment.getView().requestFocus();
-        } else if ((!mCanShowHeaders || !mShowingHeaders) && mMainFragment != null
-                && mMainFragment.getView() != null) {
-            mMainFragment.getView().requestFocus();
-        }
-
-        if (mCanShowHeaders) {
-            showHeaders(mShowingHeaders);
-        }
-
-        mStateMachine.fireEvent(EVT_HEADER_VIEW_CREATED);
-    }
-
-    private void onExpandTransitionStart(boolean expand, final Runnable callback) {
-        if (expand) {
-            callback.run();
-            return;
-        }
-        // Run a "pre" layout when we go non-expand, in order to get the initial
-        // positions of added rows.
-        new ExpandPreLayout(callback, mMainFragmentAdapter, getView()).execute();
-    }
-
-    private void setMainFragmentAlignment() {
-        int alignOffset = mContainerListAlignTop;
-        if (mMainFragmentScaleEnabled
-                && mMainFragmentAdapter.isScalingEnabled()
-                && mShowingHeaders) {
-            alignOffset = (int) (alignOffset / mScaleFactor + 0.5f);
-        }
-        mMainFragmentAdapter.setAlignment(alignOffset);
-    }
-
-    /**
-     * Enables/disables headers transition on back key support. This is enabled by
-     * default. The BrowseSupportFragment will add a back stack entry when headers are
-     * showing. Running a headers transition when the back key is pressed only
-     * works when the headers state is {@link #HEADERS_ENABLED} or
-     * {@link #HEADERS_HIDDEN}.
-     * <p>
-     * NOTE: If an Activity has its own onBackPressed() handling, you must
-     * disable this feature. You may use {@link #startHeadersTransition(boolean)}
-     * and {@link BrowseTransitionListener} in your own back stack handling.
-     */
-    public final void setHeadersTransitionOnBackEnabled(boolean headersBackStackEnabled) {
-        mHeadersBackStackEnabled = headersBackStackEnabled;
-    }
-
-    /**
-     * Returns true if headers transition on back key support is enabled.
-     */
-    public final boolean isHeadersTransitionOnBackEnabled() {
-        return mHeadersBackStackEnabled;
-    }
-
-    private void readArguments(Bundle args) {
-        if (args == null) {
-            return;
-        }
-        if (args.containsKey(ARG_TITLE)) {
-            setTitle(args.getString(ARG_TITLE));
-        }
-        if (args.containsKey(ARG_HEADERS_STATE)) {
-            setHeadersState(args.getInt(ARG_HEADERS_STATE));
-        }
-    }
-
-    /**
-     * Sets the state for the headers column in the browse fragment. Must be one
-     * of {@link #HEADERS_ENABLED}, {@link #HEADERS_HIDDEN}, or
-     * {@link #HEADERS_DISABLED}.
-     *
-     * @param headersState The state of the headers for the browse fragment.
-     */
-    public void setHeadersState(int headersState) {
-        if (headersState < HEADERS_ENABLED || headersState > HEADERS_DISABLED) {
-            throw new IllegalArgumentException("Invalid headers state: " + headersState);
-        }
-        if (DEBUG) Log.v(TAG, "setHeadersState " + headersState);
-
-        if (headersState != mHeadersState) {
-            mHeadersState = headersState;
-            switch (headersState) {
-                case HEADERS_ENABLED:
-                    mCanShowHeaders = true;
-                    mShowingHeaders = true;
-                    break;
-                case HEADERS_HIDDEN:
-                    mCanShowHeaders = true;
-                    mShowingHeaders = false;
-                    break;
-                case HEADERS_DISABLED:
-                    mCanShowHeaders = false;
-                    mShowingHeaders = false;
-                    break;
-                default:
-                    Log.w(TAG, "Unknown headers state: " + headersState);
-                    break;
-            }
-            if (mHeadersSupportFragment != null) {
-                mHeadersSupportFragment.setHeadersGone(!mCanShowHeaders);
-            }
-        }
-    }
-
-    /**
-     * Returns the state of the headers column in the browse fragment.
-     */
-    public int getHeadersState() {
-        return mHeadersState;
-    }
-
-    @Override
-    protected Object createEntranceTransition() {
-        return TransitionHelper.loadTransition(getContext(),
-                R.transition.lb_browse_entrance_transition);
-    }
-
-    @Override
-    protected void runEntranceTransition(Object entranceTransition) {
-        TransitionHelper.runTransition(mSceneAfterEntranceTransition, entranceTransition);
-    }
-
-    @Override
-    protected void onEntranceTransitionPrepare() {
-        mHeadersSupportFragment.onTransitionPrepare();
-        mMainFragmentAdapter.setEntranceTransitionState(false);
-        mMainFragmentAdapter.onTransitionPrepare();
-    }
-
-    @Override
-    protected void onEntranceTransitionStart() {
-        mHeadersSupportFragment.onTransitionStart();
-        mMainFragmentAdapter.onTransitionStart();
-    }
-
-    @Override
-    protected void onEntranceTransitionEnd() {
-        if (mMainFragmentAdapter != null) {
-            mMainFragmentAdapter.onTransitionEnd();
-        }
-
-        if (mHeadersSupportFragment != null) {
-            mHeadersSupportFragment.onTransitionEnd();
-        }
-    }
-
-    void setSearchOrbViewOnScreen(boolean onScreen) {
-        View searchOrbView = getTitleViewAdapter().getSearchAffordanceView();
-        if (searchOrbView != null) {
-            MarginLayoutParams lp = (MarginLayoutParams) searchOrbView.getLayoutParams();
-            lp.setMarginStart(onScreen ? 0 : -mContainerListMarginStart);
-            searchOrbView.setLayoutParams(lp);
-        }
-    }
-
-    void setEntranceTransitionStartState() {
-        setHeadersOnScreen(false);
-        setSearchOrbViewOnScreen(false);
-        // NOTE that mMainFragmentAdapter.setEntranceTransitionState(false) will be called
-        // in onEntranceTransitionPrepare() because mMainFragmentAdapter is still the dummy
-        // one when setEntranceTransitionStartState() is called.
-    }
-
-    void setEntranceTransitionEndState() {
-        setHeadersOnScreen(mShowingHeaders);
-        setSearchOrbViewOnScreen(true);
-        mMainFragmentAdapter.setEntranceTransitionState(true);
-    }
-
-    private class ExpandPreLayout implements ViewTreeObserver.OnPreDrawListener {
-
-        private final View mView;
-        private final Runnable mCallback;
-        private int mState;
-        private MainFragmentAdapter mainFragmentAdapter;
-
-        final static int STATE_INIT = 0;
-        final static int STATE_FIRST_DRAW = 1;
-        final static int STATE_SECOND_DRAW = 2;
-
-        ExpandPreLayout(Runnable callback, MainFragmentAdapter adapter, View view) {
-            mView = view;
-            mCallback = callback;
-            mainFragmentAdapter = adapter;
-        }
-
-        void execute() {
-            mView.getViewTreeObserver().addOnPreDrawListener(this);
-            mainFragmentAdapter.setExpand(false);
-            // always trigger onPreDraw even adapter setExpand() does nothing.
-            mView.invalidate();
-            mState = STATE_INIT;
-        }
-
-        @Override
-        public boolean onPreDraw() {
-            if (getView() == null || getContext() == null) {
-                mView.getViewTreeObserver().removeOnPreDrawListener(this);
-                return true;
-            }
-            if (mState == STATE_INIT) {
-                mainFragmentAdapter.setExpand(true);
-                // always trigger onPreDraw even adapter setExpand() does nothing.
-                mView.invalidate();
-                mState = STATE_FIRST_DRAW;
-            } else if (mState == STATE_FIRST_DRAW) {
-                mCallback.run();
-                mView.getViewTreeObserver().removeOnPreDrawListener(this);
-                mState = STATE_SECOND_DRAW;
-            }
-            return false;
-        }
-    }
-}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/DetailsFragment.java b/v17/leanback/src/android/support/v17/leanback/app/DetailsFragment.java
deleted file mode 100644
index 3655963..0000000
--- a/v17/leanback/src/android/support/v17/leanback/app/DetailsFragment.java
+++ /dev/null
@@ -1,932 +0,0 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from DetailsSupportFragment.java.  DO NOT MODIFY. */
-
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from DetailsFragment.java.  DO NOT MODIFY. */
-
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-package android.support.v17.leanback.app;
-
-import android.app.Activity;
-import android.app.Fragment;
-import android.app.FragmentTransaction;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.os.Build;
-import android.os.Bundle;
-import android.support.annotation.CallSuper;
-import android.support.v17.leanback.R;
-import android.support.v17.leanback.transition.TransitionHelper;
-import android.support.v17.leanback.transition.TransitionListener;
-import android.support.v17.leanback.util.StateMachine.Event;
-import android.support.v17.leanback.util.StateMachine.State;
-import android.support.v17.leanback.widget.BaseOnItemViewClickedListener;
-import android.support.v17.leanback.widget.BaseOnItemViewSelectedListener;
-import android.support.v17.leanback.widget.BrowseFrameLayout;
-import android.support.v17.leanback.widget.DetailsParallax;
-import android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter;
-import android.support.v17.leanback.widget.ItemAlignmentFacet;
-import android.support.v17.leanback.widget.ItemBridgeAdapter;
-import android.support.v17.leanback.widget.ObjectAdapter;
-import android.support.v17.leanback.widget.Presenter;
-import android.support.v17.leanback.widget.PresenterSelector;
-import android.support.v17.leanback.widget.RowPresenter;
-import android.support.v17.leanback.widget.VerticalGridView;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.Window;
-
-import java.lang.ref.WeakReference;
-
-/**
- * A fragment for creating Leanback details screens.
- *
- * <p>
- * A DetailsFragment renders the elements of its {@link ObjectAdapter} as a set
- * of rows in a vertical list.The Adapter's {@link PresenterSelector} must maintain subclasses
- * of {@link RowPresenter}.
- * </p>
- *
- * When {@link FullWidthDetailsOverviewRowPresenter} is found in adapter,  DetailsFragment will
- * setup default behavior of the DetailsOverviewRow:
- * <li>
- * The alignment of FullWidthDetailsOverviewRowPresenter is setup in
- * {@link #setupDetailsOverviewRowPresenter(FullWidthDetailsOverviewRowPresenter)}.
- * </li>
- * <li>
- * The view status switching of FullWidthDetailsOverviewRowPresenter is done in
- * {@link #onSetDetailsOverviewRowStatus(FullWidthDetailsOverviewRowPresenter,
- * FullWidthDetailsOverviewRowPresenter.ViewHolder, int, int, int)}.
- * </li>
- *
- * <p>
- * The recommended activity themes to use with a DetailsFragment are
- * <li>
- * {@link android.support.v17.leanback.R.style#Theme_Leanback_Details} with activity
- * shared element transition for {@link FullWidthDetailsOverviewRowPresenter}.
- * </li>
- * <li>
- * {@link android.support.v17.leanback.R.style#Theme_Leanback_Details_NoSharedElementTransition}
- * if shared element transition is not needed, for example if first row is not rendered by
- * {@link FullWidthDetailsOverviewRowPresenter}.
- * </li>
- * </p>
- *
- * <p>
- * DetailsFragment can use {@link DetailsFragmentBackgroundController} to add a parallax drawable
- * background and embedded video playing fragment.
- * </p>
- */
-public class DetailsFragment extends BaseFragment {
-    static final String TAG = "DetailsFragment";
-    static boolean DEBUG = false;
-
-    final State STATE_SET_ENTRANCE_START_STATE = new State("STATE_SET_ENTRANCE_START_STATE") {
-        @Override
-        public void run() {
-            mRowsFragment.setEntranceTransitionState(false);
-        }
-    };
-
-    final State STATE_ENTER_TRANSITION_INIT = new State("STATE_ENTER_TRANSIITON_INIT");
-
-    void switchToVideoBeforeVideoFragmentCreated() {
-        // if the video fragment is not ready: immediately fade out covering drawable,
-        // hide title and mark mPendingFocusOnVideo and set focus on it later.
-        mDetailsBackgroundController.switchToVideoBeforeCreate();
-        showTitle(false);
-        mPendingFocusOnVideo = true;
-        slideOutGridView();
-    }
-
-    final State STATE_SWITCH_TO_VIDEO_IN_ON_CREATE = new State("STATE_SWITCH_TO_VIDEO_IN_ON_CREATE",
-            false, false) {
-        @Override
-        public void run() {
-            switchToVideoBeforeVideoFragmentCreated();
-        }
-    };
-
-    final State STATE_ENTER_TRANSITION_CANCEL = new State("STATE_ENTER_TRANSITION_CANCEL",
-            false, false) {
-        @Override
-        public void run() {
-            if (mWaitEnterTransitionTimeout != null) {
-                mWaitEnterTransitionTimeout.mRef.clear();
-            }
-            // clear the activity enter/sharedElement transition, return transitions are kept.
-            // keep the return transitions and clear enter transition
-            if (getActivity() != null) {
-                Window window = getActivity().getWindow();
-                Object returnTransition = TransitionHelper.getReturnTransition(window);
-                Object sharedReturnTransition = TransitionHelper
-                        .getSharedElementReturnTransition(window);
-                TransitionHelper.setEnterTransition(window, null);
-                TransitionHelper.setSharedElementEnterTransition(window, null);
-                TransitionHelper.setReturnTransition(window, returnTransition);
-                TransitionHelper.setSharedElementReturnTransition(window, sharedReturnTransition);
-            }
-        }
-    };
-
-    final State STATE_ENTER_TRANSITION_COMPLETE = new State("STATE_ENTER_TRANSIITON_COMPLETE",
-            true, false);
-
-    final State STATE_ENTER_TRANSITION_ADDLISTENER = new State("STATE_ENTER_TRANSITION_PENDING") {
-        @Override
-        public void run() {
-            Object transition = TransitionHelper.getEnterTransition(getActivity().getWindow());
-            TransitionHelper.addTransitionListener(transition, mEnterTransitionListener);
-        }
-    };
-
-    final State STATE_ENTER_TRANSITION_PENDING = new State("STATE_ENTER_TRANSITION_PENDING") {
-        @Override
-        public void run() {
-            if (mWaitEnterTransitionTimeout == null) {
-                new WaitEnterTransitionTimeout(DetailsFragment.this);
-            }
-        }
-    };
-
-    /**
-     * Start this task when first DetailsOverviewRow is created, if there is no entrance transition
-     * started, it will clear PF_ENTRANCE_TRANSITION_PENDING.
-     */
-    static class WaitEnterTransitionTimeout implements Runnable {
-        static final long WAIT_ENTERTRANSITION_START = 200;
-
-        final WeakReference<DetailsFragment> mRef;
-
-        WaitEnterTransitionTimeout(DetailsFragment f) {
-            mRef = new WeakReference<>(f);
-            f.getView().postDelayed(this, WAIT_ENTERTRANSITION_START);
-        }
-
-        @Override
-        public void run() {
-            DetailsFragment f = mRef.get();
-            if (f != null) {
-                f.mStateMachine.fireEvent(f.EVT_ENTER_TRANSIITON_DONE);
-            }
-        }
-    }
-
-    final State STATE_ON_SAFE_START = new State("STATE_ON_SAFE_START") {
-        @Override
-        public void run() {
-            onSafeStart();
-        }
-    };
-
-    final Event EVT_ONSTART = new Event("onStart");
-
-    final Event EVT_NO_ENTER_TRANSITION = new Event("EVT_NO_ENTER_TRANSITION");
-
-    final Event EVT_DETAILS_ROW_LOADED = new Event("onFirstRowLoaded");
-
-    final Event EVT_ENTER_TRANSIITON_DONE = new Event("onEnterTransitionDone");
-
-    final Event EVT_SWITCH_TO_VIDEO = new Event("switchToVideo");
-
-    @Override
-    void createStateMachineStates() {
-        super.createStateMachineStates();
-        mStateMachine.addState(STATE_SET_ENTRANCE_START_STATE);
-        mStateMachine.addState(STATE_ON_SAFE_START);
-        mStateMachine.addState(STATE_SWITCH_TO_VIDEO_IN_ON_CREATE);
-        mStateMachine.addState(STATE_ENTER_TRANSITION_INIT);
-        mStateMachine.addState(STATE_ENTER_TRANSITION_ADDLISTENER);
-        mStateMachine.addState(STATE_ENTER_TRANSITION_CANCEL);
-        mStateMachine.addState(STATE_ENTER_TRANSITION_PENDING);
-        mStateMachine.addState(STATE_ENTER_TRANSITION_COMPLETE);
-    }
-
-    @Override
-    void createStateMachineTransitions() {
-        super.createStateMachineTransitions();
-        /**
-         * Part 1: Processing enter transitions after fragment.onCreate
-         */
-        mStateMachine.addTransition(STATE_START, STATE_ENTER_TRANSITION_INIT, EVT_ON_CREATE);
-        // if transition is not supported, skip to complete
-        mStateMachine.addTransition(STATE_ENTER_TRANSITION_INIT, STATE_ENTER_TRANSITION_COMPLETE,
-                COND_TRANSITION_NOT_SUPPORTED);
-        // if transition is not set on Activity, skip to complete
-        mStateMachine.addTransition(STATE_ENTER_TRANSITION_INIT, STATE_ENTER_TRANSITION_COMPLETE,
-                EVT_NO_ENTER_TRANSITION);
-        // if switchToVideo is called before EVT_ON_CREATEVIEW, clear enter transition and skip to
-        // complete.
-        mStateMachine.addTransition(STATE_ENTER_TRANSITION_INIT, STATE_ENTER_TRANSITION_CANCEL,
-                EVT_SWITCH_TO_VIDEO);
-        mStateMachine.addTransition(STATE_ENTER_TRANSITION_CANCEL, STATE_ENTER_TRANSITION_COMPLETE);
-        // once after onCreateView, we cannot skip the enter transition, add a listener and wait
-        // it to finish
-        mStateMachine.addTransition(STATE_ENTER_TRANSITION_INIT, STATE_ENTER_TRANSITION_ADDLISTENER,
-                EVT_ON_CREATEVIEW);
-        // when enter transition finishes, go to complete, however this might never happen if
-        // the activity is not giving transition options in startActivity, there is no API to query
-        // if this activity is started in a enter transition mode. So we rely on a timer below:
-        mStateMachine.addTransition(STATE_ENTER_TRANSITION_ADDLISTENER,
-                STATE_ENTER_TRANSITION_COMPLETE, EVT_ENTER_TRANSIITON_DONE);
-        // we are expecting app to start delayed enter transition shortly after details row is
-        // loaded, so create a timer and wait for enter transition start.
-        mStateMachine.addTransition(STATE_ENTER_TRANSITION_ADDLISTENER,
-                STATE_ENTER_TRANSITION_PENDING, EVT_DETAILS_ROW_LOADED);
-        // if enter transition not started in the timer, skip to DONE, this can be also true when
-        // startActivity is not giving transition option.
-        mStateMachine.addTransition(STATE_ENTER_TRANSITION_PENDING, STATE_ENTER_TRANSITION_COMPLETE,
-                EVT_ENTER_TRANSIITON_DONE);
-
-        /**
-         * Part 2: modification to the entrance transition defined in BaseFragment
-         */
-        // Must finish enter transition before perform entrance transition.
-        mStateMachine.addTransition(STATE_ENTER_TRANSITION_COMPLETE, STATE_ENTRANCE_PERFORM);
-        // Calling switch to video would hide immediately and skip entrance transition
-        mStateMachine.addTransition(STATE_ENTRANCE_INIT, STATE_SWITCH_TO_VIDEO_IN_ON_CREATE,
-                EVT_SWITCH_TO_VIDEO);
-        mStateMachine.addTransition(STATE_SWITCH_TO_VIDEO_IN_ON_CREATE, STATE_ENTRANCE_COMPLETE);
-        // if the entrance transition is skipped to complete by COND_TRANSITION_NOT_SUPPORTED, we
-        // still need to do the switchToVideo.
-        mStateMachine.addTransition(STATE_ENTRANCE_COMPLETE, STATE_SWITCH_TO_VIDEO_IN_ON_CREATE,
-                EVT_SWITCH_TO_VIDEO);
-
-        // for once the view is created in onStart and prepareEntranceTransition was called, we
-        // could setEntranceStartState:
-        mStateMachine.addTransition(STATE_ENTRANCE_ON_PREPARED,
-                STATE_SET_ENTRANCE_START_STATE, EVT_ONSTART);
-
-        /**
-         * Part 3: onSafeStart()
-         */
-        // for onSafeStart: the condition is onStart called, entrance transition complete
-        mStateMachine.addTransition(STATE_START, STATE_ON_SAFE_START, EVT_ONSTART);
-        mStateMachine.addTransition(STATE_ENTRANCE_COMPLETE, STATE_ON_SAFE_START);
-        mStateMachine.addTransition(STATE_ENTER_TRANSITION_COMPLETE, STATE_ON_SAFE_START);
-    }
-
-    private class SetSelectionRunnable implements Runnable {
-        int mPosition;
-        boolean mSmooth = true;
-
-        SetSelectionRunnable() {
-        }
-
-        @Override
-        public void run() {
-            if (mRowsFragment == null) {
-                return;
-            }
-            mRowsFragment.setSelectedPosition(mPosition, mSmooth);
-        }
-    }
-
-    TransitionListener mEnterTransitionListener = new TransitionListener() {
-        @Override
-        public void onTransitionStart(Object transition) {
-            if (mWaitEnterTransitionTimeout != null) {
-                // cancel task of WaitEnterTransitionTimeout, we will clearPendingEnterTransition
-                // when transition finishes.
-                mWaitEnterTransitionTimeout.mRef.clear();
-            }
-        }
-
-        @Override
-        public void onTransitionCancel(Object transition) {
-            mStateMachine.fireEvent(EVT_ENTER_TRANSIITON_DONE);
-        }
-
-        @Override
-        public void onTransitionEnd(Object transition) {
-            mStateMachine.fireEvent(EVT_ENTER_TRANSIITON_DONE);
-        }
-    };
-
-    TransitionListener mReturnTransitionListener = new TransitionListener() {
-        @Override
-        public void onTransitionStart(Object transition) {
-            onReturnTransitionStart();
-        }
-    };
-
-    BrowseFrameLayout mRootView;
-    View mBackgroundView;
-    Drawable mBackgroundDrawable;
-    Fragment mVideoFragment;
-    DetailsParallax mDetailsParallax;
-    RowsFragment mRowsFragment;
-    ObjectAdapter mAdapter;
-    int mContainerListAlignTop;
-    BaseOnItemViewSelectedListener mExternalOnItemViewSelectedListener;
-    BaseOnItemViewClickedListener mOnItemViewClickedListener;
-    DetailsFragmentBackgroundController mDetailsBackgroundController;
-
-    // A temporarily flag when switchToVideo() is called in onCreate(), if mPendingFocusOnVideo is
-    // true, we will focus to VideoFragment immediately after video fragment's view is created.
-    boolean mPendingFocusOnVideo = false;
-
-    WaitEnterTransitionTimeout mWaitEnterTransitionTimeout;
-
-    Object mSceneAfterEntranceTransition;
-
-    final SetSelectionRunnable mSetSelectionRunnable = new SetSelectionRunnable();
-
-    final BaseOnItemViewSelectedListener<Object> mOnItemViewSelectedListener =
-            new BaseOnItemViewSelectedListener<Object>() {
-        @Override
-        public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
-                                   RowPresenter.ViewHolder rowViewHolder, Object row) {
-            int position = mRowsFragment.getVerticalGridView().getSelectedPosition();
-            int subposition = mRowsFragment.getVerticalGridView().getSelectedSubPosition();
-            if (DEBUG) Log.v(TAG, "row selected position " + position
-                    + " subposition " + subposition);
-            onRowSelected(position, subposition);
-            if (mExternalOnItemViewSelectedListener != null) {
-                mExternalOnItemViewSelectedListener.onItemSelected(itemViewHolder, item,
-                        rowViewHolder, row);
-            }
-        }
-    };
-
-    /**
-     * Sets the list of rows for the fragment.
-     */
-    public void setAdapter(ObjectAdapter adapter) {
-        mAdapter = adapter;
-        Presenter[] presenters = adapter.getPresenterSelector().getPresenters();
-        if (presenters != null) {
-            for (int i = 0; i < presenters.length; i++) {
-                setupPresenter(presenters[i]);
-            }
-        } else {
-            Log.e(TAG, "PresenterSelector.getPresenters() not implemented");
-        }
-        if (mRowsFragment != null) {
-            mRowsFragment.setAdapter(adapter);
-        }
-    }
-
-    /**
-     * Returns the list of rows.
-     */
-    public ObjectAdapter getAdapter() {
-        return mAdapter;
-    }
-
-    /**
-     * Sets an item selection listener.
-     */
-    public void setOnItemViewSelectedListener(BaseOnItemViewSelectedListener listener) {
-        mExternalOnItemViewSelectedListener = listener;
-    }
-
-    /**
-     * Sets an item clicked listener.
-     */
-    public void setOnItemViewClickedListener(BaseOnItemViewClickedListener listener) {
-        if (mOnItemViewClickedListener != listener) {
-            mOnItemViewClickedListener = listener;
-            if (mRowsFragment != null) {
-                mRowsFragment.setOnItemViewClickedListener(listener);
-            }
-        }
-    }
-
-    /**
-     * Returns the item clicked listener.
-     */
-    public BaseOnItemViewClickedListener getOnItemViewClickedListener() {
-        return mOnItemViewClickedListener;
-    }
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        mContainerListAlignTop =
-            getResources().getDimensionPixelSize(R.dimen.lb_details_rows_align_top);
-
-        Activity activity = getActivity();
-        if (activity != null) {
-            Object transition = TransitionHelper.getEnterTransition(activity.getWindow());
-            if (transition == null) {
-                mStateMachine.fireEvent(EVT_NO_ENTER_TRANSITION);
-            }
-            transition = TransitionHelper.getReturnTransition(activity.getWindow());
-            if (transition != null) {
-                TransitionHelper.addTransitionListener(transition, mReturnTransitionListener);
-            }
-        } else {
-            mStateMachine.fireEvent(EVT_NO_ENTER_TRANSITION);
-        }
-    }
-
-    @Override
-    public View onCreateView(LayoutInflater inflater, ViewGroup container,
-            Bundle savedInstanceState) {
-        mRootView = (BrowseFrameLayout) inflater.inflate(
-                R.layout.lb_details_fragment, container, false);
-        mBackgroundView = mRootView.findViewById(R.id.details_background_view);
-        if (mBackgroundView != null) {
-            mBackgroundView.setBackground(mBackgroundDrawable);
-        }
-        mRowsFragment = (RowsFragment) getChildFragmentManager().findFragmentById(
-                R.id.details_rows_dock);
-        if (mRowsFragment == null) {
-            mRowsFragment = new RowsFragment();
-            getChildFragmentManager().beginTransaction()
-                    .replace(R.id.details_rows_dock, mRowsFragment).commit();
-        }
-        installTitleView(inflater, mRootView, savedInstanceState);
-        mRowsFragment.setAdapter(mAdapter);
-        mRowsFragment.setOnItemViewSelectedListener(mOnItemViewSelectedListener);
-        mRowsFragment.setOnItemViewClickedListener(mOnItemViewClickedListener);
-
-        mSceneAfterEntranceTransition = TransitionHelper.createScene(mRootView, new Runnable() {
-            @Override
-            public void run() {
-                mRowsFragment.setEntranceTransitionState(true);
-            }
-        });
-
-        setupDpadNavigation();
-
-        if (Build.VERSION.SDK_INT >= 21) {
-            // Setup adapter listener to work with ParallaxTransition (>= API 21).
-            mRowsFragment.setExternalAdapterListener(new ItemBridgeAdapter.AdapterListener() {
-                @Override
-                public void onCreate(ItemBridgeAdapter.ViewHolder vh) {
-                    if (mDetailsParallax != null && vh.getViewHolder()
-                            instanceof FullWidthDetailsOverviewRowPresenter.ViewHolder) {
-                        FullWidthDetailsOverviewRowPresenter.ViewHolder rowVh =
-                                (FullWidthDetailsOverviewRowPresenter.ViewHolder)
-                                        vh.getViewHolder();
-                        rowVh.getOverviewView().setTag(R.id.lb_parallax_source,
-                                mDetailsParallax);
-                    }
-                }
-            });
-        }
-        return mRootView;
-    }
-
-    /**
-     * @deprecated override {@link #onInflateTitleView(LayoutInflater,ViewGroup,Bundle)} instead.
-     */
-    @Deprecated
-    protected View inflateTitle(LayoutInflater inflater, ViewGroup parent,
-            Bundle savedInstanceState) {
-        return super.onInflateTitleView(inflater, parent, savedInstanceState);
-    }
-
-    @Override
-    public View onInflateTitleView(LayoutInflater inflater, ViewGroup parent,
-                                   Bundle savedInstanceState) {
-        return inflateTitle(inflater, parent, savedInstanceState);
-    }
-
-    void setVerticalGridViewLayout(VerticalGridView listview) {
-        // align the top edge of item to a fixed position
-        listview.setItemAlignmentOffset(-mContainerListAlignTop);
-        listview.setItemAlignmentOffsetPercent(VerticalGridView.ITEM_ALIGN_OFFSET_PERCENT_DISABLED);
-        listview.setWindowAlignmentOffset(0);
-        listview.setWindowAlignmentOffsetPercent(VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
-        listview.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE);
-    }
-
-    /**
-     * Called to setup each Presenter of Adapter passed in {@link #setAdapter(ObjectAdapter)}.Note
-     * that setup should only change the Presenter behavior that is meaningful in DetailsFragment.
-     * For example how a row is aligned in details Fragment.   The default implementation invokes
-     * {@link #setupDetailsOverviewRowPresenter(FullWidthDetailsOverviewRowPresenter)}
-     *
-     */
-    protected void setupPresenter(Presenter rowPresenter) {
-        if (rowPresenter instanceof FullWidthDetailsOverviewRowPresenter) {
-            setupDetailsOverviewRowPresenter((FullWidthDetailsOverviewRowPresenter) rowPresenter);
-        }
-    }
-
-    /**
-     * Called to setup {@link FullWidthDetailsOverviewRowPresenter}.  The default implementation
-     * adds two alignment positions({@link ItemAlignmentFacet}) for ViewHolder of
-     * FullWidthDetailsOverviewRowPresenter to align in fragment.
-     */
-    protected void setupDetailsOverviewRowPresenter(FullWidthDetailsOverviewRowPresenter presenter) {
-        ItemAlignmentFacet facet = new ItemAlignmentFacet();
-        // by default align details_frame to half window height
-        ItemAlignmentFacet.ItemAlignmentDef alignDef1 = new ItemAlignmentFacet.ItemAlignmentDef();
-        alignDef1.setItemAlignmentViewId(R.id.details_frame);
-        alignDef1.setItemAlignmentOffset(- getResources()
-                .getDimensionPixelSize(R.dimen.lb_details_v2_align_pos_for_actions));
-        alignDef1.setItemAlignmentOffsetPercent(0);
-        // when description is selected, align details_frame to top edge
-        ItemAlignmentFacet.ItemAlignmentDef alignDef2 = new ItemAlignmentFacet.ItemAlignmentDef();
-        alignDef2.setItemAlignmentViewId(R.id.details_frame);
-        alignDef2.setItemAlignmentFocusViewId(R.id.details_overview_description);
-        alignDef2.setItemAlignmentOffset(- getResources()
-                .getDimensionPixelSize(R.dimen.lb_details_v2_align_pos_for_description));
-        alignDef2.setItemAlignmentOffsetPercent(0);
-        ItemAlignmentFacet.ItemAlignmentDef[] defs =
-                new ItemAlignmentFacet.ItemAlignmentDef[] {alignDef1, alignDef2};
-        facet.setAlignmentDefs(defs);
-        presenter.setFacet(ItemAlignmentFacet.class, facet);
-    }
-
-    VerticalGridView getVerticalGridView() {
-        return mRowsFragment == null ? null : mRowsFragment.getVerticalGridView();
-    }
-
-    /**
-     * Gets embedded RowsFragment showing multiple rows for DetailsFragment.  If view of
-     * DetailsFragment is not created, the method returns null.
-     * @return Embedded RowsFragment showing multiple rows for DetailsFragment.
-     */
-    public RowsFragment getRowsFragment() {
-        return mRowsFragment;
-    }
-
-    /**
-     * Setup dimensions that are only meaningful when the child Fragments are inside
-     * DetailsFragment.
-     */
-    private void setupChildFragmentLayout() {
-        setVerticalGridViewLayout(mRowsFragment.getVerticalGridView());
-    }
-
-    /**
-     * Sets the selected row position with smooth animation.
-     */
-    public void setSelectedPosition(int position) {
-        setSelectedPosition(position, true);
-    }
-
-    /**
-     * Sets the selected row position.
-     */
-    public void setSelectedPosition(int position, boolean smooth) {
-        mSetSelectionRunnable.mPosition = position;
-        mSetSelectionRunnable.mSmooth = smooth;
-        if (getView() != null && getView().getHandler() != null) {
-            getView().getHandler().post(mSetSelectionRunnable);
-        }
-    }
-
-    void switchToVideo() {
-        if (mVideoFragment != null && mVideoFragment.getView() != null) {
-            mVideoFragment.getView().requestFocus();
-        } else {
-            mStateMachine.fireEvent(EVT_SWITCH_TO_VIDEO);
-        }
-    }
-
-    void switchToRows() {
-        mPendingFocusOnVideo = false;
-        VerticalGridView verticalGridView = getVerticalGridView();
-        if (verticalGridView != null && verticalGridView.getChildCount() > 0) {
-            verticalGridView.requestFocus();
-        }
-    }
-
-    /**
-     * This method asks DetailsFragmentBackgroundController to add a fragment for rendering video.
-     * In case the fragment is already there, it will return the existing one. The method must be
-     * called after calling super.onCreate(). App usually does not call this method directly.
-     *
-     * @return Fragment the added or restored fragment responsible for rendering video.
-     * @see DetailsFragmentBackgroundController#onCreateVideoFragment()
-     */
-    final Fragment findOrCreateVideoFragment() {
-        if (mVideoFragment != null) {
-            return mVideoFragment;
-        }
-        Fragment fragment = getChildFragmentManager()
-                .findFragmentById(R.id.video_surface_container);
-        if (fragment == null && mDetailsBackgroundController != null) {
-            FragmentTransaction ft2 = getChildFragmentManager().beginTransaction();
-            ft2.add(android.support.v17.leanback.R.id.video_surface_container,
-                    fragment = mDetailsBackgroundController.onCreateVideoFragment());
-            ft2.commit();
-            if (mPendingFocusOnVideo) {
-                // wait next cycle for Fragment view created so we can focus on it.
-                // This is a bit hack eventually we will do commitNow() which get view immediately.
-                getView().post(new Runnable() {
-                    @Override
-                    public void run() {
-                        if (getView() != null) {
-                            switchToVideo();
-                        }
-                        mPendingFocusOnVideo = false;
-                    }
-                });
-            }
-        }
-        mVideoFragment = fragment;
-        return mVideoFragment;
-    }
-
-    void onRowSelected(int selectedPosition, int selectedSubPosition) {
-        ObjectAdapter adapter = getAdapter();
-        if (( mRowsFragment != null && mRowsFragment.getView() != null
-                && mRowsFragment.getView().hasFocus() && !mPendingFocusOnVideo)
-                && (adapter == null || adapter.size() == 0
-                || (getVerticalGridView().getSelectedPosition() == 0
-                && getVerticalGridView().getSelectedSubPosition() == 0))) {
-            showTitle(true);
-        } else {
-            showTitle(false);
-        }
-        if (adapter != null && adapter.size() > selectedPosition) {
-            final VerticalGridView gridView = getVerticalGridView();
-            final int count = gridView.getChildCount();
-            if (count > 0) {
-                mStateMachine.fireEvent(EVT_DETAILS_ROW_LOADED);
-            }
-            for (int i = 0; i < count; i++) {
-                ItemBridgeAdapter.ViewHolder bridgeViewHolder = (ItemBridgeAdapter.ViewHolder)
-                        gridView.getChildViewHolder(gridView.getChildAt(i));
-                RowPresenter rowPresenter = (RowPresenter) bridgeViewHolder.getPresenter();
-                onSetRowStatus(rowPresenter,
-                        rowPresenter.getRowViewHolder(bridgeViewHolder.getViewHolder()),
-                        bridgeViewHolder.getAdapterPosition(),
-                        selectedPosition, selectedSubPosition);
-            }
-        }
-    }
-
-    /**
-     * Called when onStart and enter transition (postponed/none postponed) and entrance transition
-     * are all finished.
-     */
-    @CallSuper
-    void onSafeStart() {
-        if (mDetailsBackgroundController != null) {
-            mDetailsBackgroundController.onStart();
-        }
-    }
-
-    @CallSuper
-    void onReturnTransitionStart() {
-        if (mDetailsBackgroundController != null) {
-            // first disable parallax effect that auto-start PlaybackGlue.
-            boolean isVideoVisible = mDetailsBackgroundController.disableVideoParallax();
-            // if video is not visible we can safely remove VideoFragment,
-            // otherwise let video playing during return transition.
-            if (!isVideoVisible && mVideoFragment != null) {
-                FragmentTransaction ft2 = getChildFragmentManager().beginTransaction();
-                ft2.remove(mVideoFragment);
-                ft2.commit();
-                mVideoFragment = null;
-            }
-        }
-    }
-
-    @Override
-    public void onStop() {
-        if (mDetailsBackgroundController != null) {
-            mDetailsBackgroundController.onStop();
-        }
-        super.onStop();
-    }
-
-    /**
-     * Called on every visible row to change view status when current selected row position
-     * or selected sub position changed.  Subclass may override.   The default
-     * implementation calls {@link #onSetDetailsOverviewRowStatus(FullWidthDetailsOverviewRowPresenter,
-     * FullWidthDetailsOverviewRowPresenter.ViewHolder, int, int, int)} if presenter is
-     * instance of {@link FullWidthDetailsOverviewRowPresenter}.
-     *
-     * @param presenter   The presenter used to create row ViewHolder.
-     * @param viewHolder  The visible (attached) row ViewHolder, note that it may or may not
-     *                    be selected.
-     * @param adapterPosition  The adapter position of viewHolder inside adapter.
-     * @param selectedPosition The adapter position of currently selected row.
-     * @param selectedSubPosition The sub position within currently selected row.  This is used
-     *                            When a row has multiple alignment positions.
-     */
-    protected void onSetRowStatus(RowPresenter presenter, RowPresenter.ViewHolder viewHolder, int
-            adapterPosition, int selectedPosition, int selectedSubPosition) {
-        if (presenter instanceof FullWidthDetailsOverviewRowPresenter) {
-            onSetDetailsOverviewRowStatus((FullWidthDetailsOverviewRowPresenter) presenter,
-                    (FullWidthDetailsOverviewRowPresenter.ViewHolder) viewHolder,
-                    adapterPosition, selectedPosition, selectedSubPosition);
-        }
-    }
-
-    /**
-     * Called to change DetailsOverviewRow view status when current selected row position
-     * or selected sub position changed.  Subclass may override.   The default
-     * implementation switches between three states based on the positions:
-     * {@link FullWidthDetailsOverviewRowPresenter#STATE_HALF},
-     * {@link FullWidthDetailsOverviewRowPresenter#STATE_FULL} and
-     * {@link FullWidthDetailsOverviewRowPresenter#STATE_SMALL}.
-     *
-     * @param presenter   The presenter used to create row ViewHolder.
-     * @param viewHolder  The visible (attached) row ViewHolder, note that it may or may not
-     *                    be selected.
-     * @param adapterPosition  The adapter position of viewHolder inside adapter.
-     * @param selectedPosition The adapter position of currently selected row.
-     * @param selectedSubPosition The sub position within currently selected row.  This is used
-     *                            When a row has multiple alignment positions.
-     */
-    protected void onSetDetailsOverviewRowStatus(FullWidthDetailsOverviewRowPresenter presenter,
-            FullWidthDetailsOverviewRowPresenter.ViewHolder viewHolder, int adapterPosition,
-            int selectedPosition, int selectedSubPosition) {
-        if (selectedPosition > adapterPosition) {
-            presenter.setState(viewHolder, FullWidthDetailsOverviewRowPresenter.STATE_HALF);
-        } else if (selectedPosition == adapterPosition && selectedSubPosition == 1) {
-            presenter.setState(viewHolder, FullWidthDetailsOverviewRowPresenter.STATE_HALF);
-        } else if (selectedPosition == adapterPosition && selectedSubPosition == 0){
-            presenter.setState(viewHolder, FullWidthDetailsOverviewRowPresenter.STATE_FULL);
-        } else {
-            presenter.setState(viewHolder,
-                    FullWidthDetailsOverviewRowPresenter.STATE_SMALL);
-        }
-    }
-
-    @Override
-    public void onStart() {
-        super.onStart();
-
-        setupChildFragmentLayout();
-        mStateMachine.fireEvent(EVT_ONSTART);
-        if (mDetailsParallax != null) {
-            mDetailsParallax.setRecyclerView(mRowsFragment.getVerticalGridView());
-        }
-        if (mPendingFocusOnVideo) {
-            slideOutGridView();
-        } else if (!getView().hasFocus()) {
-            mRowsFragment.getVerticalGridView().requestFocus();
-        }
-    }
-
-    @Override
-    protected Object createEntranceTransition() {
-        return TransitionHelper.loadTransition(FragmentUtil.getContext(DetailsFragment.this),
-                R.transition.lb_details_enter_transition);
-    }
-
-    @Override
-    protected void runEntranceTransition(Object entranceTransition) {
-        TransitionHelper.runTransition(mSceneAfterEntranceTransition, entranceTransition);
-    }
-
-    @Override
-    protected void onEntranceTransitionEnd() {
-        mRowsFragment.onTransitionEnd();
-    }
-
-    @Override
-    protected void onEntranceTransitionPrepare() {
-        mRowsFragment.onTransitionPrepare();
-    }
-
-    @Override
-    protected void onEntranceTransitionStart() {
-        mRowsFragment.onTransitionStart();
-    }
-
-    /**
-     * Returns the {@link DetailsParallax} instance used by
-     * {@link DetailsFragmentBackgroundController} to configure parallax effect of background and
-     * control embedded video playback. App usually does not use this method directly.
-     * App may use this method for other custom parallax tasks.
-     *
-     * @return The DetailsParallax instance attached to the DetailsFragment.
-     */
-    public DetailsParallax getParallax() {
-        if (mDetailsParallax == null) {
-            mDetailsParallax = new DetailsParallax();
-            if (mRowsFragment != null && mRowsFragment.getView() != null) {
-                mDetailsParallax.setRecyclerView(mRowsFragment.getVerticalGridView());
-            }
-        }
-        return mDetailsParallax;
-    }
-
-    /**
-     * Set background drawable shown below foreground rows UI and above
-     * {@link #findOrCreateVideoFragment()}.
-     *
-     * @see DetailsFragmentBackgroundController
-     */
-    void setBackgroundDrawable(Drawable drawable) {
-        if (mBackgroundView != null) {
-            mBackgroundView.setBackground(drawable);
-        }
-        mBackgroundDrawable = drawable;
-    }
-
-    /**
-     * This method does the following
-     * <ul>
-     * <li>sets up focus search handling logic in the root view to enable transitioning between
-     * half screen/full screen/no video mode.</li>
-     *
-     * <li>Sets up the key listener in the root view to intercept events like UP/DOWN and
-     * transition to appropriate mode like half/full screen video.</li>
-     * </ul>
-     */
-    void setupDpadNavigation() {
-        mRootView.setOnChildFocusListener(new BrowseFrameLayout.OnChildFocusListener() {
-
-            @Override
-            public boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
-                return false;
-            }
-
-            @Override
-            public void onRequestChildFocus(View child, View focused) {
-                if (child != mRootView.getFocusedChild()) {
-                    if (child.getId() == R.id.details_fragment_root) {
-                        if (!mPendingFocusOnVideo) {
-                            slideInGridView();
-                            showTitle(true);
-                        }
-                    } else if (child.getId() == R.id.video_surface_container) {
-                        slideOutGridView();
-                        showTitle(false);
-                    } else {
-                        showTitle(true);
-                    }
-                }
-            }
-        });
-        mRootView.setOnFocusSearchListener(new BrowseFrameLayout.OnFocusSearchListener() {
-            @Override
-            public View onFocusSearch(View focused, int direction) {
-                if (mRowsFragment.getVerticalGridView() != null
-                        && mRowsFragment.getVerticalGridView().hasFocus()) {
-                    if (direction == View.FOCUS_UP) {
-                        if (mDetailsBackgroundController != null
-                                && mDetailsBackgroundController.canNavigateToVideoFragment()
-                                && mVideoFragment != null && mVideoFragment.getView() != null) {
-                            return mVideoFragment.getView();
-                        } else if (getTitleView() != null && getTitleView().hasFocusable()) {
-                            return getTitleView();
-                        }
-                    }
-                } else if (getTitleView() != null && getTitleView().hasFocus()) {
-                    if (direction == View.FOCUS_DOWN) {
-                        if (mRowsFragment.getVerticalGridView() != null) {
-                            return mRowsFragment.getVerticalGridView();
-                        }
-                    }
-                }
-                return focused;
-            }
-        });
-
-        // If we press BACK on remote while in full screen video mode, we should
-        // transition back to half screen video playback mode.
-        mRootView.setOnDispatchKeyListener(new View.OnKeyListener() {
-            @Override
-            public boolean onKey(View v, int keyCode, KeyEvent event) {
-                // This is used to check if we are in full screen video mode. This is somewhat
-                // hacky and relies on the behavior of the video helper class to update the
-                // focusability of the video surface view.
-                if (mVideoFragment != null && mVideoFragment.getView() != null
-                        && mVideoFragment.getView().hasFocus()) {
-                    if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE) {
-                        if (getVerticalGridView().getChildCount() > 0) {
-                            getVerticalGridView().requestFocus();
-                            return true;
-                        }
-                    }
-                }
-
-                return false;
-            }
-        });
-    }
-
-    /**
-     * Slides vertical grid view (displaying media item details) out of the screen from below.
-     */
-    void slideOutGridView() {
-        if (getVerticalGridView() != null) {
-            getVerticalGridView().animateOut();
-        }
-    }
-
-    void slideInGridView() {
-        if (getVerticalGridView() != null) {
-            getVerticalGridView().animateIn();
-        }
-    }
-}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/DetailsFragmentBackgroundController.java b/v17/leanback/src/android/support/v17/leanback/app/DetailsFragmentBackgroundController.java
deleted file mode 100644
index 223b8ef..0000000
--- a/v17/leanback/src/android/support/v17/leanback/app/DetailsFragmentBackgroundController.java
+++ /dev/null
@@ -1,495 +0,0 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from {}DetailsSupportFragmentBackgroundController.java.  DO NOT MODIFY. */
-
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.support.v17.leanback.app;
-
-import android.animation.PropertyValuesHolder;
-import android.graphics.Bitmap;
-import android.graphics.Color;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
-import android.support.annotation.ColorInt;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.v17.leanback.R;
-import android.support.v17.leanback.graphics.FitWidthBitmapDrawable;
-import android.support.v17.leanback.media.PlaybackGlue;
-import android.support.v17.leanback.media.PlaybackGlueHost;
-import android.support.v17.leanback.widget.DetailsParallaxDrawable;
-import android.support.v17.leanback.widget.ParallaxTarget;
-import android.app.Fragment;
-
-/**
- * Controller for DetailsFragment parallax background and embedded video play.
- * <p>
- * The parallax background drawable is made of two parts: cover drawable (by default
- * {@link FitWidthBitmapDrawable}) above the details overview row and bottom drawable (by default
- * {@link ColorDrawable}) below the details overview row. While vertically scrolling rows, the size
- * of cover drawable and bottom drawable will be updated and the cover drawable will by default
- * perform a parallax shift using {@link FitWidthBitmapDrawable#PROPERTY_VERTICAL_OFFSET}.
- * </p>
- * <pre>
- *        ***************************
- *        *      Cover Drawable     *
- *        * (FitWidthBitmapDrawable)*
- *        *                         *
- *        ***************************
- *        *    DetailsOverviewRow   *
- *        *                         *
- *        ***************************
- *        *     Bottom Drawable     *
- *        *      (ColorDrawable)    *
- *        *         Related         *
- *        *         Content         *
- *        ***************************
- * </pre>
- * Both parallax background drawable and embedded video play are optional. App must call
- * {@link #enableParallax()} and/or {@link #setupVideoPlayback(PlaybackGlue)} explicitly.
- * The PlaybackGlue is automatically {@link PlaybackGlue#play()} when fragment starts and
- * {@link PlaybackGlue#pause()} when fragment stops. When video is ready to play, cover drawable
- * will be faded out.
- * Example:
- * <pre>
- * DetailsFragmentBackgroundController mController = new DetailsFragmentBackgroundController(this);
- *
- * public void onCreate(Bundle savedInstance) {
- *     super.onCreate(savedInstance);
- *     MediaPlayerGlue player = new MediaPlayerGlue(..);
- *     player.setUrl(...);
- *     mController.enableParallax();
- *     mController.setupVideoPlayback(player);
- * }
- *
- * static class MyLoadBitmapTask extends ... {
- *     WeakReference<MyFragment> mFragmentRef;
- *     MyLoadBitmapTask(MyFragment fragment) {
- *         mFragmentRef = new WeakReference(fragment);
- *     }
- *     protected void onPostExecute(Bitmap bitmap) {
- *         MyFragment fragment = mFragmentRef.get();
- *         if (fragment != null) {
- *             fragment.mController.setCoverBitmap(bitmap);
- *         }
- *     }
- * }
- *
- * public void onStart() {
- *     new MyLoadBitmapTask(this).execute(url);
- * }
- *
- * public void onStop() {
- *     mController.setCoverBitmap(null);
- * }
- * </pre>
- * <p>
- * To customize cover drawable and/or bottom drawable, app should call
- * {@link #enableParallax(Drawable, Drawable, ParallaxTarget.PropertyValuesHolderTarget)}.
- * If app supplies a custom cover Drawable, it should not call {@link #setCoverBitmap(Bitmap)}.
- * If app supplies a custom bottom Drawable, it should not call {@link #setSolidColor(int)}.
- * </p>
- * <p>
- * To customize playback fragment, app should override {@link #onCreateVideoFragment()} and
- * {@link #onCreateGlueHost()}.
- * </p>
- *
- */
-public class DetailsFragmentBackgroundController {
-
-    final DetailsFragment mFragment;
-    DetailsParallaxDrawable mParallaxDrawable;
-    int mParallaxDrawableMaxOffset;
-    PlaybackGlue mPlaybackGlue;
-    DetailsBackgroundVideoHelper mVideoHelper;
-    Bitmap mCoverBitmap;
-    int mSolidColor;
-    boolean mCanUseHost = false;
-    boolean mInitialControlVisible = false;
-
-    private Fragment mLastVideoFragmentForGlueHost;
-
-    /**
-     * Creates a DetailsFragmentBackgroundController for a DetailsFragment. Note that
-     * each DetailsFragment can only associate with one DetailsFragmentBackgroundController.
-     *
-     * @param fragment The DetailsFragment to control background and embedded video playing.
-     * @throws IllegalStateException If fragment was already associated with another controller.
-     */
-    public DetailsFragmentBackgroundController(DetailsFragment fragment) {
-        if (fragment.mDetailsBackgroundController != null) {
-            throw new IllegalStateException("Each DetailsFragment is allowed to initialize "
-                    + "DetailsFragmentBackgroundController once");
-        }
-        fragment.mDetailsBackgroundController = this;
-        mFragment = fragment;
-    }
-
-    /**
-     * Enables default parallax background using a {@link FitWidthBitmapDrawable} as cover drawable
-     * and {@link ColorDrawable} as bottom drawable. A vertical parallax movement will be applied
-     * to the FitWidthBitmapDrawable. App may use {@link #setSolidColor(int)} and
-     * {@link #setCoverBitmap(Bitmap)} to change the content of bottom drawable and cover drawable.
-     * This method must be called before {@link #setupVideoPlayback(PlaybackGlue)}.
-     *
-     * @see #setCoverBitmap(Bitmap)
-     * @see #setSolidColor(int)
-     * @throws IllegalStateException If {@link #setupVideoPlayback(PlaybackGlue)} was called.
-     */
-    public void enableParallax() {
-        int offset = mParallaxDrawableMaxOffset;
-        if (offset == 0) {
-            offset = FragmentUtil.getContext(mFragment).getResources()
-                    .getDimensionPixelSize(R.dimen.lb_details_cover_drawable_parallax_movement);
-        }
-        Drawable coverDrawable = new FitWidthBitmapDrawable();
-        ColorDrawable colorDrawable = new ColorDrawable();
-        enableParallax(coverDrawable, colorDrawable,
-                new ParallaxTarget.PropertyValuesHolderTarget(
-                        coverDrawable,
-                        PropertyValuesHolder.ofInt(FitWidthBitmapDrawable.PROPERTY_VERTICAL_OFFSET,
-                                0, -offset)
-                ));
-    }
-
-    /**
-     * Enables parallax background using a custom cover drawable at top and a custom bottom
-     * drawable. This method must be called before {@link #setupVideoPlayback(PlaybackGlue)}.
-     *
-     * @param coverDrawable Custom cover drawable shown at top. {@link #setCoverBitmap(Bitmap)}
-     *                      will not work if coverDrawable is not {@link FitWidthBitmapDrawable};
-     *                      in that case it's app's responsibility to set content into
-     *                      coverDrawable.
-     * @param bottomDrawable Drawable shown at bottom. {@link #setSolidColor(int)} will not work
-     *                       if bottomDrawable is not {@link ColorDrawable}; in that case it's app's
-     *                       responsibility to set content of bottomDrawable.
-     * @param coverDrawableParallaxTarget Target to perform parallax effect within coverDrawable.
-     *                                    Use null for no parallax movement effect.
-     *                                    Example to move bitmap within FitWidthBitmapDrawable:
-     *                                    new ParallaxTarget.PropertyValuesHolderTarget(
-     *                                        coverDrawable, PropertyValuesHolder.ofInt(
-     *                                            FitWidthBitmapDrawable.PROPERTY_VERTICAL_OFFSET,
-     *                                            0, -120))
-     * @throws IllegalStateException If {@link #setupVideoPlayback(PlaybackGlue)} was called.
-     */
-    public void enableParallax(@NonNull Drawable coverDrawable, @NonNull Drawable bottomDrawable,
-                               @Nullable ParallaxTarget.PropertyValuesHolderTarget
-                                       coverDrawableParallaxTarget) {
-        if (mParallaxDrawable != null) {
-            return;
-        }
-        // if bitmap is set before enableParallax, use it as initial value.
-        if (mCoverBitmap != null && coverDrawable instanceof FitWidthBitmapDrawable) {
-            ((FitWidthBitmapDrawable) coverDrawable).setBitmap(mCoverBitmap);
-        }
-        // if solid color is set before enableParallax, use it as initial value.
-        if (mSolidColor != Color.TRANSPARENT && bottomDrawable instanceof ColorDrawable) {
-            ((ColorDrawable) bottomDrawable).setColor(mSolidColor);
-        }
-        if (mPlaybackGlue != null) {
-            throw new IllegalStateException("enableParallaxDrawable must be called before "
-                    + "enableVideoPlayback");
-        }
-        mParallaxDrawable = new DetailsParallaxDrawable(
-                FragmentUtil.getContext(mFragment),
-                mFragment.getParallax(),
-                coverDrawable,
-                bottomDrawable,
-                coverDrawableParallaxTarget);
-        mFragment.setBackgroundDrawable(mParallaxDrawable);
-        // create a VideoHelper with null PlaybackGlue for changing CoverDrawable visibility
-        // before PlaybackGlue is ready.
-        mVideoHelper = new DetailsBackgroundVideoHelper(null,
-                mFragment.getParallax(), mParallaxDrawable.getCoverDrawable());
-    }
-
-    /**
-     * Enable video playback and set proper {@link PlaybackGlueHost}. This method by default
-     * creates a VideoFragment and VideoFragmentGlueHost to host the PlaybackGlue.
-     * This method must be called after calling details Fragment super.onCreate(). This method
-     * can be called multiple times to replace existing PlaybackGlue or calling
-     * setupVideoPlayback(null) to clear. Note a typical {@link PlaybackGlue} subclass releases
-     * resources in {@link PlaybackGlue#onDetachedFromHost()}, when the {@link PlaybackGlue}
-     * subclass is not doing that, it's app's responsibility to release the resources.
-     *
-     * @param playbackGlue The new PlaybackGlue to set as background or null to clear existing one.
-     * @see #onCreateVideoFragment()
-     * @see #onCreateGlueHost().
-     */
-    @SuppressWarnings("ReferenceEquality")
-    public void setupVideoPlayback(@NonNull PlaybackGlue playbackGlue) {
-        if (mPlaybackGlue == playbackGlue) {
-            return;
-        }
-
-        PlaybackGlueHost playbackGlueHost = null;
-        if (mPlaybackGlue != null) {
-            playbackGlueHost = mPlaybackGlue.getHost();
-            mPlaybackGlue.setHost(null);
-        }
-
-        mPlaybackGlue = playbackGlue;
-        mVideoHelper.setPlaybackGlue(mPlaybackGlue);
-        if (mCanUseHost && mPlaybackGlue != null) {
-            if (playbackGlueHost == null
-                    || mLastVideoFragmentForGlueHost != findOrCreateVideoFragment()) {
-                mPlaybackGlue.setHost(createGlueHost());
-                mLastVideoFragmentForGlueHost = findOrCreateVideoFragment();
-            } else {
-                mPlaybackGlue.setHost(playbackGlueHost);
-            }
-        }
-    }
-
-    /**
-     * Returns current PlaybackGlue or null if not set or cleared.
-     *
-     * @return Current PlaybackGlue or null
-     */
-    public final PlaybackGlue getPlaybackGlue() {
-        return mPlaybackGlue;
-    }
-
-    /**
-     * Precondition allows user navigate to video fragment using DPAD. Default implementation
-     * returns true if PlaybackGlue is not null. Subclass may override, e.g. only allow navigation
-     * when {@link PlaybackGlue#isPrepared()} is true. Note this method does not block
-     * app calls {@link #switchToVideo}.
-     *
-     * @return True allow to navigate to video fragment.
-     */
-    public boolean canNavigateToVideoFragment() {
-        return mPlaybackGlue != null;
-    }
-
-    void switchToVideoBeforeCreate() {
-        mVideoHelper.crossFadeBackgroundToVideo(true, true);
-        mInitialControlVisible = true;
-    }
-
-    /**
-     * Switch to video fragment, note that this method is not affected by result of
-     * {@link #canNavigateToVideoFragment()}. If the method is called in DetailsFragment.onCreate()
-     * it will make video fragment to be initially focused once it is created.
-     * <p>
-     * Calling switchToVideo() in DetailsFragment.onCreate() will clear the activity enter
-     * transition and shared element transition.
-     * </p>
-     * <p>
-     * If switchToVideo() is called after {@link DetailsFragment#prepareEntranceTransition()} and
-     * before {@link DetailsFragment#onEntranceTransitionEnd()}, it will be ignored.
-     * </p>
-     * <p>
-     * If {@link DetailsFragment#prepareEntranceTransition()} is called after switchToVideo(), an
-     * IllegalStateException will be thrown.
-     * </p>
-     */
-    public final void switchToVideo() {
-        mFragment.switchToVideo();
-    }
-
-    /**
-     * Switch to rows fragment.
-     */
-    public final void switchToRows() {
-        mFragment.switchToRows();
-    }
-
-    /**
-     * When fragment is started and no running transition. First set host if not yet set, second
-     * start playing if it was paused before.
-     */
-    void onStart() {
-        if (!mCanUseHost) {
-            mCanUseHost = true;
-            if (mPlaybackGlue != null) {
-                mPlaybackGlue.setHost(createGlueHost());
-                mLastVideoFragmentForGlueHost = findOrCreateVideoFragment();
-            }
-        }
-        if (mPlaybackGlue != null && mPlaybackGlue.isPrepared()) {
-            mPlaybackGlue.play();
-        }
-    }
-
-    void onStop() {
-        if (mPlaybackGlue != null) {
-            mPlaybackGlue.pause();
-        }
-    }
-
-    /**
-     * Disable parallax that would auto-start video playback
-     * @return true if video fragment is visible or false otherwise.
-     */
-    boolean disableVideoParallax() {
-        if (mVideoHelper != null) {
-            mVideoHelper.stopParallax();
-            return mVideoHelper.isVideoVisible();
-        }
-        return false;
-    }
-
-    /**
-     * Returns the cover drawable at top. Returns null if {@link #enableParallax()} is not called.
-     * By default it's a {@link FitWidthBitmapDrawable}.
-     *
-     * @return The cover drawable at top.
-     */
-    public final Drawable getCoverDrawable() {
-        if (mParallaxDrawable == null) {
-            return null;
-        }
-        return mParallaxDrawable.getCoverDrawable();
-    }
-
-    /**
-     * Returns the drawable at bottom. Returns null if {@link #enableParallax()} is not called.
-     * By default it's a {@link ColorDrawable}.
-     *
-     * @return The bottom drawable.
-     */
-    public final Drawable getBottomDrawable() {
-        if (mParallaxDrawable == null) {
-            return null;
-        }
-        return mParallaxDrawable.getBottomDrawable();
-    }
-
-    /**
-     * Creates a Fragment to host {@link PlaybackGlue}. Returns a new {@link VideoFragment} by
-     * default. App may override and return a different fragment and it also must override
-     * {@link #onCreateGlueHost()}.
-     *
-     * @return A new fragment used in {@link #onCreateGlueHost()}.
-     * @see #onCreateGlueHost()
-     * @see #setupVideoPlayback(PlaybackGlue)
-     */
-    public Fragment onCreateVideoFragment() {
-        return new VideoFragment();
-    }
-
-    /**
-     * Creates a PlaybackGlueHost to host PlaybackGlue. App may override this if it overrides
-     * {@link #onCreateVideoFragment()}. This method must be called after calling Fragment
-     * super.onCreate(). When override this method, app may call
-     * {@link #findOrCreateVideoFragment()} to get or create a fragment.
-     *
-     * @return A new PlaybackGlueHost to host PlaybackGlue.
-     * @see #onCreateVideoFragment()
-     * @see #findOrCreateVideoFragment()
-     * @see #setupVideoPlayback(PlaybackGlue)
-     */
-    public PlaybackGlueHost onCreateGlueHost() {
-        return new VideoFragmentGlueHost((VideoFragment) findOrCreateVideoFragment());
-    }
-
-    PlaybackGlueHost createGlueHost() {
-        PlaybackGlueHost host = onCreateGlueHost();
-        if (mInitialControlVisible) {
-            host.showControlsOverlay(false);
-        } else {
-            host.hideControlsOverlay(false);
-        }
-        return host;
-    }
-
-    /**
-     * Adds or gets fragment for rendering video in DetailsFragment. A subclass that
-     * overrides {@link #onCreateGlueHost()} should call this method to get a fragment for creating
-     * a {@link PlaybackGlueHost}.
-     *
-     * @return Fragment the added or restored fragment responsible for rendering video.
-     * @see #onCreateGlueHost()
-     */
-    public final Fragment findOrCreateVideoFragment() {
-        return mFragment.findOrCreateVideoFragment();
-    }
-
-    /**
-     * Convenient method to set Bitmap in cover drawable. If app is not using default
-     * {@link FitWidthBitmapDrawable}, app should not use this method  It's safe to call
-     * setCoverBitmap() before calling {@link #enableParallax()}.
-     *
-     * @param bitmap bitmap to set as cover.
-     */
-    public final void setCoverBitmap(Bitmap bitmap) {
-        mCoverBitmap = bitmap;
-        Drawable drawable = getCoverDrawable();
-        if (drawable instanceof FitWidthBitmapDrawable) {
-            ((FitWidthBitmapDrawable) drawable).setBitmap(mCoverBitmap);
-        }
-    }
-
-    /**
-     * Returns Bitmap set by {@link #setCoverBitmap(Bitmap)}.
-     *
-     * @return Bitmap for cover drawable.
-     */
-    public final Bitmap getCoverBitmap() {
-        return mCoverBitmap;
-    }
-
-    /**
-     * Returns color set by {@link #setSolidColor(int)}.
-     *
-     * @return Solid color used for bottom drawable.
-     */
-    public final @ColorInt int getSolidColor() {
-        return mSolidColor;
-    }
-
-    /**
-     * Convenient method to set color in bottom drawable. If app is not using default
-     * {@link ColorDrawable}, app should not use this method. It's safe to call setSolidColor()
-     * before calling {@link #enableParallax()}.
-     *
-     * @param color color for bottom drawable.
-     */
-    public final void setSolidColor(@ColorInt int color) {
-        mSolidColor = color;
-        Drawable bottomDrawable = getBottomDrawable();
-        if (bottomDrawable instanceof ColorDrawable) {
-            ((ColorDrawable) bottomDrawable).setColor(color);
-        }
-    }
-
-    /**
-     * Sets default parallax offset in pixels for bitmap moving vertically. This method must
-     * be called before {@link #enableParallax()}.
-     *
-     * @param offset Offset in pixels (e.g. 120).
-     * @see #enableParallax()
-     */
-    public final void setParallaxDrawableMaxOffset(int offset) {
-        if (mParallaxDrawable != null) {
-            throw new IllegalStateException("enableParallax already called");
-        }
-        mParallaxDrawableMaxOffset = offset;
-    }
-
-    /**
-     * Returns Default parallax offset in pixels for bitmap moving vertically.
-     * When 0, a default value would be used.
-     *
-     * @return Default parallax offset in pixels for bitmap moving vertically.
-     * @see #enableParallax()
-     */
-    public final int getParallaxDrawableMaxOffset() {
-        return mParallaxDrawableMaxOffset;
-    }
-
-}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/ErrorFragment.java b/v17/leanback/src/android/support/v17/leanback/app/ErrorFragment.java
deleted file mode 100644
index 2896d0f..0000000
--- a/v17/leanback/src/android/support/v17/leanback/app/ErrorFragment.java
+++ /dev/null
@@ -1,245 +0,0 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from ErrorSupportFragment.java.  DO NOT MODIFY. */
-
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-package android.support.v17.leanback.app;
-
-import android.graphics.Paint;
-import android.graphics.Paint.FontMetricsInt;
-import android.graphics.PixelFormat;
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.support.v17.leanback.R;
-import android.text.TextUtils;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.Button;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-/**
- * A fragment for displaying an error indication.
- */
-public class ErrorFragment extends BrandedFragment {
-
-    private ViewGroup mErrorFrame;
-    private ImageView mImageView;
-    private TextView mTextView;
-    private Button mButton;
-    private Drawable mDrawable;
-    private CharSequence mMessage;
-    private String mButtonText;
-    private View.OnClickListener mButtonClickListener;
-    private Drawable mBackgroundDrawable;
-    private boolean mIsBackgroundTranslucent = true;
-
-    /**
-     * Sets the default background.
-     *
-     * @param translucent True to set a translucent background.
-     */
-    public void setDefaultBackground(boolean translucent) {
-        mBackgroundDrawable = null;
-        mIsBackgroundTranslucent = translucent;
-        updateBackground();
-        updateMessage();
-    }
-
-    /**
-     * Returns true if the background is translucent.
-     */
-    public boolean isBackgroundTranslucent() {
-        return mIsBackgroundTranslucent;
-    }
-
-    /**
-     * Sets a drawable for the fragment background.
-     *
-     * @param drawable The drawable used for the background.
-     */
-    public void setBackgroundDrawable(Drawable drawable) {
-        mBackgroundDrawable = drawable;
-        if (drawable != null) {
-            final int opacity = drawable.getOpacity();
-            mIsBackgroundTranslucent = (opacity == PixelFormat.TRANSLUCENT
-                    || opacity == PixelFormat.TRANSPARENT);
-        }
-        updateBackground();
-        updateMessage();
-    }
-
-    /**
-     * Returns the background drawable.  May be null if a default is used.
-     */
-    public Drawable getBackgroundDrawable() {
-        return mBackgroundDrawable;
-    }
-
-    /**
-     * Sets the drawable to be used for the error image.
-     *
-     * @param drawable The drawable used for the error image.
-     */
-    public void setImageDrawable(Drawable drawable) {
-        mDrawable = drawable;
-        updateImageDrawable();
-    }
-
-    /**
-     * Returns the drawable used for the error image.
-     */
-    public Drawable getImageDrawable() {
-        return mDrawable;
-    }
-
-    /**
-     * Sets the error message.
-     *
-     * @param message The error message.
-     */
-    public void setMessage(CharSequence message) {
-        mMessage = message;
-        updateMessage();
-    }
-
-    /**
-     * Returns the error message.
-     */
-    public CharSequence getMessage() {
-        return mMessage;
-    }
-
-    /**
-     * Sets the button text.
-     *
-     * @param text The button text.
-     */
-    public void setButtonText(String text) {
-        mButtonText = text;
-        updateButton();
-    }
-
-    /**
-     * Returns the button text.
-     */
-    public String getButtonText() {
-        return mButtonText;
-    }
-
-    /**
-     * Set the button click listener.
-     *
-     * @param clickListener The click listener for the button.
-     */
-    public void setButtonClickListener(View.OnClickListener clickListener) {
-        mButtonClickListener = clickListener;
-        updateButton();
-    }
-
-    /**
-     * Returns the button click listener.
-     */
-    public View.OnClickListener getButtonClickListener() {
-        return mButtonClickListener;
-    }
-
-    @Override
-    public View onCreateView(LayoutInflater inflater, ViewGroup container,
-            Bundle savedInstanceState) {
-        View root = inflater.inflate(R.layout.lb_error_fragment, container, false);
-
-        mErrorFrame = (ViewGroup) root.findViewById(R.id.error_frame);
-        updateBackground();
-
-        installTitleView(inflater, mErrorFrame, savedInstanceState);
-
-        mImageView = (ImageView) root.findViewById(R.id.image);
-        updateImageDrawable();
-
-        mTextView = (TextView) root.findViewById(R.id.message);
-        updateMessage();
-
-        mButton = (Button) root.findViewById(R.id.button);
-        updateButton();
-
-        FontMetricsInt metrics = getFontMetricsInt(mTextView);
-        int underImageBaselineMargin = container.getResources().getDimensionPixelSize(
-                R.dimen.lb_error_under_image_baseline_margin);
-        setTopMargin(mTextView, underImageBaselineMargin + metrics.ascent);
-
-        int underMessageBaselineMargin = container.getResources().getDimensionPixelSize(
-                R.dimen.lb_error_under_message_baseline_margin);
-        setTopMargin(mButton, underMessageBaselineMargin - metrics.descent);
-
-        return root;
-    }
-
-    private void updateBackground() {
-        if (mErrorFrame != null) {
-            if (mBackgroundDrawable != null) {
-                mErrorFrame.setBackground(mBackgroundDrawable);
-            } else {
-                mErrorFrame.setBackgroundColor(mErrorFrame.getResources().getColor(
-                        mIsBackgroundTranslucent
-                                ? R.color.lb_error_background_color_translucent
-                                : R.color.lb_error_background_color_opaque));
-            }
-        }
-    }
-
-    private void updateMessage() {
-        if (mTextView != null) {
-            mTextView.setText(mMessage);
-            mTextView.setVisibility(TextUtils.isEmpty(mMessage) ? View.GONE : View.VISIBLE);
-        }
-    }
-
-    private void updateImageDrawable() {
-        if (mImageView != null) {
-            mImageView.setImageDrawable(mDrawable);
-            mImageView.setVisibility(mDrawable == null ? View.GONE : View.VISIBLE);
-        }
-    }
-
-    private void updateButton() {
-        if (mButton != null) {
-            mButton.setText(mButtonText);
-            mButton.setOnClickListener(mButtonClickListener);
-            mButton.setVisibility(TextUtils.isEmpty(mButtonText) ? View.GONE : View.VISIBLE);
-            mButton.requestFocus();
-        }
-    }
-
-    @Override
-    public void onStart() {
-        super.onStart();
-        mErrorFrame.requestFocus();
-    }
-
-    private static FontMetricsInt getFontMetricsInt(TextView textView) {
-        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
-        paint.setTextSize(textView.getTextSize());
-        paint.setTypeface(textView.getTypeface());
-        return paint.getFontMetricsInt();
-    }
-
-    private static void setTopMargin(TextView textView, int topMargin) {
-        ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) textView.getLayoutParams();
-        lp.topMargin = topMargin;
-        textView.setLayoutParams(lp);
-    }
-
-}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/GuidedStepFragment.java b/v17/leanback/src/android/support/v17/leanback/app/GuidedStepFragment.java
deleted file mode 100644
index 2b7f2d0..0000000
--- a/v17/leanback/src/android/support/v17/leanback/app/GuidedStepFragment.java
+++ /dev/null
@@ -1,1403 +0,0 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from GuidedStepSupportFragment.java.  DO NOT MODIFY. */
-
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-package android.support.v17.leanback.app;
-
-import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
-
-import android.animation.Animator;
-import android.animation.AnimatorSet;
-import android.content.Context;
-import android.os.Build;
-import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.support.annotation.RestrictTo;
-import android.support.v17.leanback.R;
-import android.support.v17.leanback.transition.TransitionHelper;
-import android.support.v17.leanback.widget.GuidanceStylist;
-import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
-import android.support.v17.leanback.widget.GuidedAction;
-import android.support.v17.leanback.widget.GuidedActionAdapter;
-import android.support.v17.leanback.widget.GuidedActionAdapterGroup;
-import android.support.v17.leanback.widget.GuidedActionsStylist;
-import android.support.v17.leanback.widget.NonOverlappingLinearLayout;
-import android.support.v4.app.ActivityCompat;
-import android.app.Fragment;
-import android.app.Activity;
-import android.app.FragmentManager;
-import android.app.FragmentManager.BackStackEntry;
-import android.app.FragmentTransaction;
-import android.support.v7.widget.RecyclerView;
-import android.util.Log;
-import android.util.TypedValue;
-import android.view.ContextThemeWrapper;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-import android.widget.LinearLayout;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * A GuidedStepFragment is used to guide the user through a decision or series of decisions.
- * It is composed of a guidance view on the left and a view on the right containing a list of
- * possible actions.
- * <p>
- * <h3>Basic Usage</h3>
- * <p>
- * Clients of GuidedStepFragment must create a custom subclass to attach to their Activities.
- * This custom subclass provides the information necessary to construct the user interface and
- * respond to user actions. At a minimum, subclasses should override:
- * <ul>
- * <li>{@link #onCreateGuidance}, to provide instructions to the user</li>
- * <li>{@link #onCreateActions}, to provide a set of {@link GuidedAction}s the user can take</li>
- * <li>{@link #onGuidedActionClicked}, to respond to those actions</li>
- * </ul>
- * <p>
- * Clients use following helper functions to add GuidedStepFragment to Activity or FragmentManager:
- * <ul>
- * <li>{@link #addAsRoot(Activity, GuidedStepFragment, int)}, to be called during Activity onCreate,
- * adds GuidedStepFragment as the first Fragment in activity.</li>
- * <li>{@link #add(FragmentManager, GuidedStepFragment)} or {@link #add(FragmentManager,
- * GuidedStepFragment, int)}, to add GuidedStepFragment on top of existing Fragments or
- * replacing existing GuidedStepFragment when moving forward to next step.</li>
- * <li>{@link #finishGuidedStepFragments()} can either finish the activity or pop all
- * GuidedStepFragment from stack.
- * <li>If app chooses not to use the helper function, it is the app's responsibility to call
- * {@link #setUiStyle(int)} to select fragment transition and remember the stack entry where it
- * need pops to.
- * </ul>
- * <h3>Theming and Stylists</h3>
- * <p>
- * GuidedStepFragment delegates its visual styling to classes called stylists. The {@link
- * GuidanceStylist} is responsible for the left guidance view, while the {@link
- * GuidedActionsStylist} is responsible for the right actions view. The stylists use theme
- * attributes to derive values associated with the presentation, such as colors, animations, etc.
- * Most simple visual aspects of GuidanceStylist and GuidedActionsStylist can be customized
- * via theming; see their documentation for more information.
- * <p>
- * GuidedStepFragments must have access to an appropriate theme in order for the stylists to
- * function properly.  Specifically, the fragment must receive {@link
- * android.support.v17.leanback.R.style#Theme_Leanback_GuidedStep}, or a theme whose parent is
- * is set to that theme. Themes can be provided in one of three ways:
- * <ul>
- * <li>The simplest way is to set the theme for the host Activity to the GuidedStep theme or a
- * theme that derives from it.</li>
- * <li>If the Activity already has a theme and setting its parent theme is inconvenient, the
- * existing Activity theme can have an entry added for the attribute {@link
- * android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedStepTheme}. If present,
- * this theme will be used by GuidedStepFragment as an overlay to the Activity's theme.</li>
- * <li>Finally, custom subclasses of GuidedStepFragment may provide a theme through the {@link
- * #onProvideTheme} method. This can be useful if a subclass is used across multiple
- * Activities.</li>
- * </ul>
- * <p>
- * If the theme is provided in multiple ways, the onProvideTheme override has priority, followed by
- * the Activity's theme.  (Themes whose parent theme is already set to the guided step theme do not
- * need to set the guidedStepTheme attribute; if set, it will be ignored.)
- * <p>
- * If themes do not provide enough customizability, the stylists themselves may be subclassed and
- * provided to the GuidedStepFragment through the {@link #onCreateGuidanceStylist} and {@link
- * #onCreateActionsStylist} methods.  The stylists have simple hooks so that subclasses
- * may override layout files; subclasses may also have more complex logic to determine styling.
- * <p>
- * <h3>Guided sequences</h3>
- * <p>
- * GuidedStepFragments can be grouped together to provide a guided sequence. GuidedStepFragments
- * grouped as a sequence use custom animations provided by {@link GuidanceStylist} and
- * {@link GuidedActionsStylist} (or subclasses) during transitions between steps. Clients
- * should use {@link #add} to place subsequent GuidedFragments onto the fragment stack so that
- * custom animations are properly configured. (Custom animations are triggered automatically when
- * the fragment stack is subsequently popped by any normal mechanism.)
- * <p>
- * <i>Note: Currently GuidedStepFragments grouped in this way must all be defined programmatically,
- * rather than in XML. This restriction may be removed in the future.</i>
- *
- * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedStepTheme
- * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedStepBackground
- * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionContentWidthWeight
- * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionContentWidthWeightTwoPanels
- * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionsBackground
- * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionsBackgroundDark
- * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionsElevation
- * @see GuidanceStylist
- * @see GuidanceStylist.Guidance
- * @see GuidedAction
- * @see GuidedActionsStylist
- */
-public class GuidedStepFragment extends Fragment implements GuidedActionAdapter.FocusListener {
-
-    private static final String TAG_LEAN_BACK_ACTIONS_FRAGMENT = "leanBackGuidedStepFragment";
-    private static final String EXTRA_ACTION_PREFIX = "action_";
-    private static final String EXTRA_BUTTON_ACTION_PREFIX = "buttonaction_";
-
-    private static final String ENTRY_NAME_REPLACE = "GuidedStepDefault";
-
-    private static final String ENTRY_NAME_ENTRANCE = "GuidedStepEntrance";
-
-    private static final boolean IS_FRAMEWORK_FRAGMENT = true;
-
-    /**
-     * Fragment argument name for UI style.  The argument value is persisted in fragment state and
-     * used to select fragment transition. The value is initially {@link #UI_STYLE_ENTRANCE} and
-     * might be changed in one of the three helper functions:
-     * <ul>
-     * <li>{@link #addAsRoot(Activity, GuidedStepFragment, int)} sets to
-     * {@link #UI_STYLE_ACTIVITY_ROOT}</li>
-     * <li>{@link #add(FragmentManager, GuidedStepFragment)} or {@link #add(FragmentManager,
-     * GuidedStepFragment, int)} sets it to {@link #UI_STYLE_REPLACE} if there is already a
-     * GuidedStepFragment on stack.</li>
-     * <li>{@link #finishGuidedStepFragments()} changes current GuidedStepFragment to
-     * {@link #UI_STYLE_ENTRANCE} for the non activity case.  This is a special case that changes
-     * the transition settings after fragment has been created,  in order to force current
-     * GuidedStepFragment run a return transition of {@link #UI_STYLE_ENTRANCE}</li>
-     * </ul>
-     * <p>
-     * Argument value can be either:
-     * <ul>
-     * <li>{@link #UI_STYLE_REPLACE}</li>
-     * <li>{@link #UI_STYLE_ENTRANCE}</li>
-     * <li>{@link #UI_STYLE_ACTIVITY_ROOT}</li>
-     * </ul>
-     */
-    public static final String EXTRA_UI_STYLE = "uiStyle";
-
-    /**
-     * This is the case that we use GuidedStepFragment to replace another existing
-     * GuidedStepFragment when moving forward to next step. Default behavior of this style is:
-     * <ul>
-     * <li>Enter transition slides in from END(right), exit transition same as
-     * {@link #UI_STYLE_ENTRANCE}.
-     * </li>
-     * </ul>
-     */
-    public static final int UI_STYLE_REPLACE = 0;
-
-    /**
-     * @deprecated Same value as {@link #UI_STYLE_REPLACE}.
-     */
-    @Deprecated
-    public static final int UI_STYLE_DEFAULT = 0;
-
-    /**
-     * Default value for argument {@link #EXTRA_UI_STYLE}. The default value is assigned in
-     * GuidedStepFragment constructor. This is the case that we show GuidedStepFragment on top of
-     * other content. The default behavior of this style:
-     * <ul>
-     * <li>Enter transition slides in from two sides, exit transition slide out to START(left).
-     * Background will be faded in. Note: Changing exit transition by UI style is not working
-     * because fragment transition asks for exit transition before UI style is restored in Fragment
-     * .onCreate().</li>
-     * </ul>
-     * When popping multiple GuidedStepFragment, {@link #finishGuidedStepFragments()} also changes
-     * the top GuidedStepFragment to UI_STYLE_ENTRANCE in order to run the return transition
-     * (reverse of enter transition) of UI_STYLE_ENTRANCE.
-     */
-    public static final int UI_STYLE_ENTRANCE = 1;
-
-    /**
-     * One possible value of argument {@link #EXTRA_UI_STYLE}. This is the case that we show first
-     * GuidedStepFragment in a separate activity. The default behavior of this style:
-     * <ul>
-     * <li>Enter transition is assigned null (will rely on activity transition), exit transition is
-     * same as {@link #UI_STYLE_ENTRANCE}. Note: Changing exit transition by UI style is not working
-     * because fragment transition asks for exit transition before UI style is restored in
-     * Fragment.onCreate().</li>
-     * </ul>
-     */
-    public static final int UI_STYLE_ACTIVITY_ROOT = 2;
-
-    /**
-     * Animation to slide the contents from the side (left/right).
-     * @hide
-     */
-    @RestrictTo(LIBRARY_GROUP)
-    public static final int SLIDE_FROM_SIDE = 0;
-
-    /**
-     * Animation to slide the contents from the bottom.
-     * @hide
-     */
-    @RestrictTo(LIBRARY_GROUP)
-    public static final int SLIDE_FROM_BOTTOM = 1;
-
-    private static final String TAG = "GuidedStepF";
-    private static final boolean DEBUG = false;
-
-    /**
-     * @hide
-     */
-    @RestrictTo(LIBRARY_GROUP)
-    public static class DummyFragment extends Fragment {
-        @Override
-        public View onCreateView(LayoutInflater inflater, ViewGroup container,
-                Bundle savedInstanceState) {
-            final View v = new View(inflater.getContext());
-            v.setVisibility(View.GONE);
-            return v;
-        }
-    }
-
-    private ContextThemeWrapper mThemeWrapper;
-    private GuidanceStylist mGuidanceStylist;
-    GuidedActionsStylist mActionsStylist;
-    private GuidedActionsStylist mButtonActionsStylist;
-    private GuidedActionAdapter mAdapter;
-    private GuidedActionAdapter mSubAdapter;
-    private GuidedActionAdapter mButtonAdapter;
-    private GuidedActionAdapterGroup mAdapterGroup;
-    private List<GuidedAction> mActions = new ArrayList<GuidedAction>();
-    private List<GuidedAction> mButtonActions = new ArrayList<GuidedAction>();
-    private int entranceTransitionType = SLIDE_FROM_SIDE;
-
-    public GuidedStepFragment() {
-        mGuidanceStylist = onCreateGuidanceStylist();
-        mActionsStylist = onCreateActionsStylist();
-        mButtonActionsStylist = onCreateButtonActionsStylist();
-        onProvideFragmentTransitions();
-    }
-
-    /**
-     * Creates the presenter used to style the guidance panel. The default implementation returns
-     * a basic GuidanceStylist.
-     * @return The GuidanceStylist used in this fragment.
-     */
-    public GuidanceStylist onCreateGuidanceStylist() {
-        return new GuidanceStylist();
-    }
-
-    /**
-     * Creates the presenter used to style the guided actions panel. The default implementation
-     * returns a basic GuidedActionsStylist.
-     * @return The GuidedActionsStylist used in this fragment.
-     */
-    public GuidedActionsStylist onCreateActionsStylist() {
-        return new GuidedActionsStylist();
-    }
-
-    /**
-     * Creates the presenter used to style a sided actions panel for button only.
-     * The default implementation returns a basic GuidedActionsStylist.
-     * @return The GuidedActionsStylist used in this fragment.
-     */
-    public GuidedActionsStylist onCreateButtonActionsStylist() {
-        GuidedActionsStylist stylist = new GuidedActionsStylist();
-        stylist.setAsButtonActions();
-        return stylist;
-    }
-
-    /**
-     * Returns the theme used for styling the fragment. The default returns -1, indicating that the
-     * host Activity's theme should be used.
-     * @return The theme resource ID of the theme to use in this fragment, or -1 to use the
-     * host Activity's theme.
-     */
-    public int onProvideTheme() {
-        return -1;
-    }
-
-    /**
-     * Returns the information required to provide guidance to the user. This hook is called during
-     * {@link #onCreateView}.  May be overridden to return a custom subclass of {@link
-     * GuidanceStylist.Guidance} for use in a subclass of {@link GuidanceStylist}. The default
-     * returns a Guidance object with empty fields; subclasses should override.
-     * @param savedInstanceState The saved instance state from onCreateView.
-     * @return The Guidance object representing the information used to guide the user.
-     */
-    public @NonNull Guidance onCreateGuidance(Bundle savedInstanceState) {
-        return new Guidance("", "", "", null);
-    }
-
-    /**
-     * Fills out the set of actions available to the user. This hook is called during {@link
-     * #onCreate}. The default leaves the list of actions empty; subclasses should override.
-     * @param actions A non-null, empty list ready to be populated.
-     * @param savedInstanceState The saved instance state from onCreate.
-     */
-    public void onCreateActions(@NonNull List<GuidedAction> actions, Bundle savedInstanceState) {
-    }
-
-    /**
-     * Fills out the set of actions shown at right available to the user. This hook is called during
-     * {@link #onCreate}. The default leaves the list of actions empty; subclasses may override.
-     * @param actions A non-null, empty list ready to be populated.
-     * @param savedInstanceState The saved instance state from onCreate.
-     */
-    public void onCreateButtonActions(@NonNull List<GuidedAction> actions,
-            Bundle savedInstanceState) {
-    }
-
-    /**
-     * Callback invoked when an action is taken by the user. Subclasses should override in
-     * order to act on the user's decisions.
-     * @param action The chosen action.
-     */
-    public void onGuidedActionClicked(GuidedAction action) {
-    }
-
-    /**
-     * Callback invoked when an action in sub actions is taken by the user. Subclasses should
-     * override in order to act on the user's decisions.  Default return value is true to close
-     * the sub actions list.
-     * @param action The chosen action.
-     * @return true to collapse the sub actions list, false to keep it expanded.
-     */
-    public boolean onSubGuidedActionClicked(GuidedAction action) {
-        return true;
-    }
-
-    /**
-     * @return True if is current expanded including subactions list or
-     * action with {@link GuidedAction#hasEditableActivatorView()} is true.
-     */
-    public boolean isExpanded() {
-        return mActionsStylist.isExpanded();
-    }
-
-    /**
-     * @return True if the sub actions list is expanded, false otherwise.
-     */
-    public boolean isSubActionsExpanded() {
-        return mActionsStylist.isSubActionsExpanded();
-    }
-
-    /**
-     * Expand a given action's sub actions list.
-     * @param action GuidedAction to expand.
-     * @see #expandAction(GuidedAction, boolean)
-     */
-    public void expandSubActions(GuidedAction action) {
-        if (!action.hasSubActions()) {
-            return;
-        }
-        expandAction(action, true);
-    }
-
-    /**
-     * Expand a given action with sub actions list or
-     * {@link GuidedAction#hasEditableActivatorView()} is true. The method must be called after
-     * {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)} creates fragment view.
-     *
-     * @param action GuidedAction to expand.
-     * @param withTransition True to run transition animation, false otherwise.
-     */
-    public void expandAction(GuidedAction action, boolean withTransition) {
-        mActionsStylist.expandAction(action, withTransition);
-    }
-
-    /**
-     * Collapse sub actions list.
-     * @see GuidedAction#getSubActions()
-     */
-    public void collapseSubActions() {
-        collapseAction(true);
-    }
-
-    /**
-     * Collapse action which either has a sub actions list or action with
-     * {@link GuidedAction#hasEditableActivatorView()} is true.
-     *
-     * @param withTransition True to run transition animation, false otherwise.
-     */
-    public void collapseAction(boolean withTransition) {
-        if (mActionsStylist != null && mActionsStylist.getActionsGridView() != null) {
-            mActionsStylist.collapseAction(withTransition);
-        }
-    }
-
-    /**
-     * Callback invoked when an action is focused (made to be the current selection) by the user.
-     */
-    @Override
-    public void onGuidedActionFocused(GuidedAction action) {
-    }
-
-    /**
-     * Callback invoked when an action's title or description has been edited, this happens either
-     * when user clicks confirm button in IME or user closes IME window by BACK key.
-     * @deprecated Override {@link #onGuidedActionEditedAndProceed(GuidedAction)} and/or
-     *             {@link #onGuidedActionEditCanceled(GuidedAction)}.
-     */
-    @Deprecated
-    public void onGuidedActionEdited(GuidedAction action) {
-    }
-
-    /**
-     * Callback invoked when an action has been canceled editing, for example when user closes
-     * IME window by BACK key.  Default implementation calls deprecated method
-     * {@link #onGuidedActionEdited(GuidedAction)}.
-     * @param action The action which has been canceled editing.
-     */
-    public void onGuidedActionEditCanceled(GuidedAction action) {
-        onGuidedActionEdited(action);
-    }
-
-    /**
-     * Callback invoked when an action has been edited, for example when user clicks confirm button
-     * in IME window.  Default implementation calls deprecated method
-     * {@link #onGuidedActionEdited(GuidedAction)} and returns {@link GuidedAction#ACTION_ID_NEXT}.
-     *
-     * @param action The action that has been edited.
-     * @return ID of the action will be focused or {@link GuidedAction#ACTION_ID_NEXT},
-     * {@link GuidedAction#ACTION_ID_CURRENT}.
-     */
-    public long onGuidedActionEditedAndProceed(GuidedAction action) {
-        onGuidedActionEdited(action);
-        return GuidedAction.ACTION_ID_NEXT;
-    }
-
-    /**
-     * Adds the specified GuidedStepFragment to the fragment stack, replacing any existing
-     * GuidedStepFragments in the stack, and configuring the fragment-to-fragment custom
-     * transitions.  A backstack entry is added, so the fragment will be dismissed when BACK key
-     * is pressed.
-     * <li>If current fragment on stack is GuidedStepFragment: assign {@link #UI_STYLE_REPLACE}
-     * <li>If current fragment on stack is not GuidedStepFragment: assign {@link #UI_STYLE_ENTRANCE}
-     * <p>
-     * Note: currently fragments added using this method must be created programmatically rather
-     * than via XML.
-     * @param fragmentManager The FragmentManager to be used in the transaction.
-     * @param fragment The GuidedStepFragment to be inserted into the fragment stack.
-     * @return The ID returned by the call FragmentTransaction.commit.
-     */
-    public static int add(FragmentManager fragmentManager, GuidedStepFragment fragment) {
-        return add(fragmentManager, fragment, android.R.id.content);
-    }
-
-    /**
-     * Adds the specified GuidedStepFragment to the fragment stack, replacing any existing
-     * GuidedStepFragments in the stack, and configuring the fragment-to-fragment custom
-     * transitions.  A backstack entry is added, so the fragment will be dismissed when BACK key
-     * is pressed.
-     * <li>If current fragment on stack is GuidedStepFragment: assign {@link #UI_STYLE_REPLACE} and
-     * {@link #onAddSharedElementTransition(FragmentTransaction, GuidedStepFragment)} will be called
-     * to perform shared element transition between GuidedStepFragments.
-     * <li>If current fragment on stack is not GuidedStepFragment: assign {@link #UI_STYLE_ENTRANCE}
-     * <p>
-     * Note: currently fragments added using this method must be created programmatically rather
-     * than via XML.
-     * @param fragmentManager The FragmentManager to be used in the transaction.
-     * @param fragment The GuidedStepFragment to be inserted into the fragment stack.
-     * @param id The id of container to add GuidedStepFragment, can be android.R.id.content.
-     * @return The ID returned by the call FragmentTransaction.commit.
-     */
-    public static int add(FragmentManager fragmentManager, GuidedStepFragment fragment, int id) {
-        GuidedStepFragment current = getCurrentGuidedStepFragment(fragmentManager);
-        boolean inGuidedStep = current != null;
-        if (IS_FRAMEWORK_FRAGMENT && Build.VERSION.SDK_INT >= 21 && Build.VERSION.SDK_INT < 23
-                && !inGuidedStep) {
-            // workaround b/22631964 for framework fragment
-            fragmentManager.beginTransaction()
-                .replace(id, new DummyFragment(), TAG_LEAN_BACK_ACTIONS_FRAGMENT)
-                .commit();
-        }
-        FragmentTransaction ft = fragmentManager.beginTransaction();
-
-        fragment.setUiStyle(inGuidedStep ? UI_STYLE_REPLACE : UI_STYLE_ENTRANCE);
-        ft.addToBackStack(fragment.generateStackEntryName());
-        if (current != null) {
-            fragment.onAddSharedElementTransition(ft, current);
-        }
-        return ft.replace(id, fragment, TAG_LEAN_BACK_ACTIONS_FRAGMENT).commit();
-    }
-
-    /**
-     * Called when this fragment is added to FragmentTransaction with {@link #UI_STYLE_REPLACE} (aka
-     * when the GuidedStepFragment replacing an existing GuidedStepFragment). Default implementation
-     * establishes connections between action background views to morph action background bounds
-     * change from disappearing GuidedStepFragment into this GuidedStepFragment. The default
-     * implementation heavily relies on {@link GuidedActionsStylist}'s layout, app may override this
-     * method when modifying the default layout of {@link GuidedActionsStylist}.
-     *
-     * @see GuidedActionsStylist
-     * @see #onProvideFragmentTransitions()
-     * @param ft The FragmentTransaction to add shared element.
-     * @param disappearing The disappearing fragment.
-     */
-    protected void onAddSharedElementTransition(FragmentTransaction ft, GuidedStepFragment
-            disappearing) {
-        View fragmentView = disappearing.getView();
-        addNonNullSharedElementTransition(ft, fragmentView.findViewById(
-                R.id.action_fragment_root), "action_fragment_root");
-        addNonNullSharedElementTransition(ft, fragmentView.findViewById(
-                R.id.action_fragment_background), "action_fragment_background");
-        addNonNullSharedElementTransition(ft, fragmentView.findViewById(
-                R.id.action_fragment), "action_fragment");
-        addNonNullSharedElementTransition(ft, fragmentView.findViewById(
-                R.id.guidedactions_root), "guidedactions_root");
-        addNonNullSharedElementTransition(ft, fragmentView.findViewById(
-                R.id.guidedactions_content), "guidedactions_content");
-        addNonNullSharedElementTransition(ft, fragmentView.findViewById(
-                R.id.guidedactions_list_background), "guidedactions_list_background");
-        addNonNullSharedElementTransition(ft, fragmentView.findViewById(
-                R.id.guidedactions_root2), "guidedactions_root2");
-        addNonNullSharedElementTransition(ft, fragmentView.findViewById(
-                R.id.guidedactions_content2), "guidedactions_content2");
-        addNonNullSharedElementTransition(ft, fragmentView.findViewById(
-                R.id.guidedactions_list_background2), "guidedactions_list_background2");
-    }
-
-    private static void addNonNullSharedElementTransition (FragmentTransaction ft, View subView,
-                                                           String transitionName)
-    {
-        if (subView != null)
-            TransitionHelper.addSharedElement(ft, subView, transitionName);
-    }
-
-    /**
-     * Returns BackStackEntry name for the GuidedStepFragment or empty String if no entry is
-     * associated.  Note {@link #UI_STYLE_ACTIVITY_ROOT} will return empty String.  The method
-     * returns undefined value if the fragment is not in FragmentManager.
-     * @return BackStackEntry name for the GuidedStepFragment or empty String if no entry is
-     * associated.
-     */
-    final String generateStackEntryName() {
-        return generateStackEntryName(getUiStyle(), getClass());
-    }
-
-    /**
-     * Generates BackStackEntry name for GuidedStepFragment class or empty String if no entry is
-     * associated.  Note {@link #UI_STYLE_ACTIVITY_ROOT} is not allowed and returns empty String.
-     * @param uiStyle {@link #UI_STYLE_REPLACE} or {@link #UI_STYLE_ENTRANCE}
-     * @return BackStackEntry name for the GuidedStepFragment or empty String if no entry is
-     * associated.
-     */
-    static String generateStackEntryName(int uiStyle, Class guidedStepFragmentClass) {
-        switch (uiStyle) {
-        case UI_STYLE_REPLACE:
-            return ENTRY_NAME_REPLACE + guidedStepFragmentClass.getName();
-        case UI_STYLE_ENTRANCE:
-            return ENTRY_NAME_ENTRANCE + guidedStepFragmentClass.getName();
-        case UI_STYLE_ACTIVITY_ROOT:
-        default:
-            return "";
-        }
-    }
-
-    /**
-     * Returns true if the backstack entry represents GuidedStepFragment with
-     * {@link #UI_STYLE_ENTRANCE}, i.e. this is the first GuidedStepFragment pushed to stack; false
-     * otherwise.
-     * @see #generateStackEntryName(int, Class)
-     * @param backStackEntryName Name of BackStackEntry.
-     * @return True if the backstack represents GuidedStepFragment with {@link #UI_STYLE_ENTRANCE};
-     * false otherwise.
-     */
-    static boolean isStackEntryUiStyleEntrance(String backStackEntryName) {
-        return backStackEntryName != null && backStackEntryName.startsWith(ENTRY_NAME_ENTRANCE);
-    }
-
-    /**
-     * Extract Class name from BackStackEntry name.
-     * @param backStackEntryName Name of BackStackEntry.
-     * @return Class name of GuidedStepFragment.
-     */
-    static String getGuidedStepFragmentClassName(String backStackEntryName) {
-        if (backStackEntryName.startsWith(ENTRY_NAME_REPLACE)) {
-            return backStackEntryName.substring(ENTRY_NAME_REPLACE.length());
-        } else if (backStackEntryName.startsWith(ENTRY_NAME_ENTRANCE)) {
-            return backStackEntryName.substring(ENTRY_NAME_ENTRANCE.length());
-        } else {
-            return "";
-        }
-    }
-
-    /**
-     * Adds the specified GuidedStepFragment as content of Activity; no backstack entry is added so
-     * the activity will be dismissed when BACK key is pressed.  The method is typically called in
-     * Activity.onCreate() when savedInstanceState is null.  When savedInstanceState is not null,
-     * the Activity is being restored,  do not call addAsRoot() to duplicate the Fragment restored
-     * by FragmentManager.
-     * {@link #UI_STYLE_ACTIVITY_ROOT} is assigned.
-     *
-     * Note: currently fragments added using this method must be created programmatically rather
-     * than via XML.
-     * @param activity The Activity to be used to insert GuidedstepFragment.
-     * @param fragment The GuidedStepFragment to be inserted into the fragment stack.
-     * @param id The id of container to add GuidedStepFragment, can be android.R.id.content.
-     * @return The ID returned by the call FragmentTransaction.commit, or -1 there is already
-     *         GuidedStepFragment.
-     */
-    public static int addAsRoot(Activity activity, GuidedStepFragment fragment, int id) {
-        // Workaround b/23764120: call getDecorView() to force requestFeature of ActivityTransition.
-        activity.getWindow().getDecorView();
-        FragmentManager fragmentManager = activity.getFragmentManager();
-        if (fragmentManager.findFragmentByTag(TAG_LEAN_BACK_ACTIONS_FRAGMENT) != null) {
-            Log.w(TAG, "Fragment is already exists, likely calling "
-                    + "addAsRoot() when savedInstanceState is not null in Activity.onCreate().");
-            return -1;
-        }
-        FragmentTransaction ft = fragmentManager.beginTransaction();
-        fragment.setUiStyle(UI_STYLE_ACTIVITY_ROOT);
-        return ft.replace(id, fragment, TAG_LEAN_BACK_ACTIONS_FRAGMENT).commit();
-    }
-
-    /**
-     * Returns the current GuidedStepFragment on the fragment transaction stack.
-     * @return The current GuidedStepFragment, if any, on the fragment transaction stack.
-     */
-    public static GuidedStepFragment getCurrentGuidedStepFragment(FragmentManager fm) {
-        Fragment f = fm.findFragmentByTag(TAG_LEAN_BACK_ACTIONS_FRAGMENT);
-        if (f instanceof GuidedStepFragment) {
-            return (GuidedStepFragment) f;
-        }
-        return null;
-    }
-
-    /**
-     * Returns the GuidanceStylist that displays guidance information for the user.
-     * @return The GuidanceStylist for this fragment.
-     */
-    public GuidanceStylist getGuidanceStylist() {
-        return mGuidanceStylist;
-    }
-
-    /**
-     * Returns the GuidedActionsStylist that displays the actions the user may take.
-     * @return The GuidedActionsStylist for this fragment.
-     */
-    public GuidedActionsStylist getGuidedActionsStylist() {
-        return mActionsStylist;
-    }
-
-    /**
-     * Returns the list of button GuidedActions that the user may take in this fragment.
-     * @return The list of button GuidedActions for this fragment.
-     */
-    public List<GuidedAction> getButtonActions() {
-        return mButtonActions;
-    }
-
-    /**
-     * Find button GuidedAction by Id.
-     * @param id  Id of the button action to search.
-     * @return  GuidedAction object or null if not found.
-     */
-    public GuidedAction findButtonActionById(long id) {
-        int index = findButtonActionPositionById(id);
-        return index >= 0 ? mButtonActions.get(index) : null;
-    }
-
-    /**
-     * Find button GuidedAction position in array by Id.
-     * @param id  Id of the button action to search.
-     * @return  position of GuidedAction object in array or -1 if not found.
-     */
-    public int findButtonActionPositionById(long id) {
-        if (mButtonActions != null) {
-            for (int i = 0; i < mButtonActions.size(); i++) {
-                GuidedAction action = mButtonActions.get(i);
-                if (mButtonActions.get(i).getId() == id) {
-                    return i;
-                }
-            }
-        }
-        return -1;
-    }
-
-    /**
-     * Returns the GuidedActionsStylist that displays the button actions the user may take.
-     * @return The GuidedActionsStylist for this fragment.
-     */
-    public GuidedActionsStylist getGuidedButtonActionsStylist() {
-        return mButtonActionsStylist;
-    }
-
-    /**
-     * Sets the list of button GuidedActions that the user may take in this fragment.
-     * @param actions The list of button GuidedActions for this fragment.
-     */
-    public void setButtonActions(List<GuidedAction> actions) {
-        mButtonActions = actions;
-        if (mButtonAdapter != null) {
-            mButtonAdapter.setActions(mButtonActions);
-        }
-    }
-
-    /**
-     * Notify an button action has changed and update its UI.
-     * @param position Position of the button GuidedAction in array.
-     */
-    public void notifyButtonActionChanged(int position) {
-        if (mButtonAdapter != null) {
-            mButtonAdapter.notifyItemChanged(position);
-        }
-    }
-
-    /**
-     * Returns the view corresponding to the button action at the indicated position in the list of
-     * actions for this fragment.
-     * @param position The integer position of the button action of interest.
-     * @return The View corresponding to the button action at the indicated position, or null if
-     * that action is not currently onscreen.
-     */
-    public View getButtonActionItemView(int position) {
-        final RecyclerView.ViewHolder holder = mButtonActionsStylist.getActionsGridView()
-                    .findViewHolderForPosition(position);
-        return holder == null ? null : holder.itemView;
-    }
-
-    /**
-     * Scrolls the action list to the position indicated, selecting that button action's view.
-     * @param position The integer position of the button action of interest.
-     */
-    public void setSelectedButtonActionPosition(int position) {
-        mButtonActionsStylist.getActionsGridView().setSelectedPosition(position);
-    }
-
-    /**
-     * Returns the position if the currently selected button GuidedAction.
-     * @return position The integer position of the currently selected button action.
-     */
-    public int getSelectedButtonActionPosition() {
-        return mButtonActionsStylist.getActionsGridView().getSelectedPosition();
-    }
-
-    /**
-     * Returns the list of GuidedActions that the user may take in this fragment.
-     * @return The list of GuidedActions for this fragment.
-     */
-    public List<GuidedAction> getActions() {
-        return mActions;
-    }
-
-    /**
-     * Find GuidedAction by Id.
-     * @param id  Id of the action to search.
-     * @return  GuidedAction object or null if not found.
-     */
-    public GuidedAction findActionById(long id) {
-        int index = findActionPositionById(id);
-        return index >= 0 ? mActions.get(index) : null;
-    }
-
-    /**
-     * Find GuidedAction position in array by Id.
-     * @param id  Id of the action to search.
-     * @return  position of GuidedAction object in array or -1 if not found.
-     */
-    public int findActionPositionById(long id) {
-        if (mActions != null) {
-            for (int i = 0; i < mActions.size(); i++) {
-                GuidedAction action = mActions.get(i);
-                if (mActions.get(i).getId() == id) {
-                    return i;
-                }
-            }
-        }
-        return -1;
-    }
-
-    /**
-     * Sets the list of GuidedActions that the user may take in this fragment.
-     * @param actions The list of GuidedActions for this fragment.
-     */
-    public void setActions(List<GuidedAction> actions) {
-        mActions = actions;
-        if (mAdapter != null) {
-            mAdapter.setActions(mActions);
-        }
-    }
-
-    /**
-     * Notify an action has changed and update its UI.
-     * @param position Position of the GuidedAction in array.
-     */
-    public void notifyActionChanged(int position) {
-        if (mAdapter != null) {
-            mAdapter.notifyItemChanged(position);
-        }
-    }
-
-    /**
-     * Returns the view corresponding to the action at the indicated position in the list of
-     * actions for this fragment.
-     * @param position The integer position of the action of interest.
-     * @return The View corresponding to the action at the indicated position, or null if that
-     * action is not currently onscreen.
-     */
-    public View getActionItemView(int position) {
-        final RecyclerView.ViewHolder holder = mActionsStylist.getActionsGridView()
-                    .findViewHolderForPosition(position);
-        return holder == null ? null : holder.itemView;
-    }
-
-    /**
-     * Scrolls the action list to the position indicated, selecting that action's view.
-     * @param position The integer position of the action of interest.
-     */
-    public void setSelectedActionPosition(int position) {
-        mActionsStylist.getActionsGridView().setSelectedPosition(position);
-    }
-
-    /**
-     * Returns the position if the currently selected GuidedAction.
-     * @return position The integer position of the currently selected action.
-     */
-    public int getSelectedActionPosition() {
-        return mActionsStylist.getActionsGridView().getSelectedPosition();
-    }
-
-    /**
-     * Called by Constructor to provide fragment transitions.  The default implementation assigns
-     * transitions based on {@link #getUiStyle()}:
-     * <ul>
-     * <li> {@link #UI_STYLE_REPLACE} Slide from/to end(right) for enter transition, slide from/to
-     * start(left) for exit transition, shared element enter transition is set to ChangeBounds.
-     * <li> {@link #UI_STYLE_ENTRANCE} Enter transition is set to slide from both sides, exit
-     * transition is same as {@link #UI_STYLE_REPLACE}, no shared element enter transition.
-     * <li> {@link #UI_STYLE_ACTIVITY_ROOT} Enter transition is set to null and app should rely on
-     * activity transition, exit transition is same as {@link #UI_STYLE_REPLACE}, no shared element
-     * enter transition.
-     * </ul>
-     * <p>
-     * The default implementation heavily relies on {@link GuidedActionsStylist} and
-     * {@link GuidanceStylist} layout, app may override this method when modifying the default
-     * layout of {@link GuidedActionsStylist} or {@link GuidanceStylist}.
-     * <p>
-     * TIP: because the fragment view is removed during fragment transition, in general app cannot
-     * use two Visibility transition together. Workaround is to create your own Visibility
-     * transition that controls multiple animators (e.g. slide and fade animation in one Transition
-     * class).
-     */
-    protected void onProvideFragmentTransitions() {
-        if (Build.VERSION.SDK_INT >= 21) {
-            final int uiStyle = getUiStyle();
-            if (uiStyle == UI_STYLE_REPLACE) {
-                Object enterTransition = TransitionHelper.createFadeAndShortSlide(Gravity.END);
-                TransitionHelper.exclude(enterTransition, R.id.guidedstep_background, true);
-                TransitionHelper.exclude(enterTransition, R.id.guidedactions_sub_list_background,
-                        true);
-                TransitionHelper.setEnterTransition(this, enterTransition);
-
-                Object fade = TransitionHelper.createFadeTransition(
-                        TransitionHelper.FADE_IN | TransitionHelper.FADE_OUT);
-                TransitionHelper.include(fade, R.id.guidedactions_sub_list_background);
-                Object changeBounds = TransitionHelper.createChangeBounds(false);
-                Object sharedElementTransition = TransitionHelper.createTransitionSet(false);
-                TransitionHelper.addTransition(sharedElementTransition, fade);
-                TransitionHelper.addTransition(sharedElementTransition, changeBounds);
-                TransitionHelper.setSharedElementEnterTransition(this, sharedElementTransition);
-            } else if (uiStyle == UI_STYLE_ENTRANCE) {
-                if (entranceTransitionType == SLIDE_FROM_SIDE) {
-                    Object fade = TransitionHelper.createFadeTransition(
-                            TransitionHelper.FADE_IN | TransitionHelper.FADE_OUT);
-                    TransitionHelper.include(fade, R.id.guidedstep_background);
-                    Object slideFromSide = TransitionHelper.createFadeAndShortSlide(
-                            Gravity.END | Gravity.START);
-                    TransitionHelper.include(slideFromSide, R.id.content_fragment);
-                    TransitionHelper.include(slideFromSide, R.id.action_fragment_root);
-                    Object enterTransition = TransitionHelper.createTransitionSet(false);
-                    TransitionHelper.addTransition(enterTransition, fade);
-                    TransitionHelper.addTransition(enterTransition, slideFromSide);
-                    TransitionHelper.setEnterTransition(this, enterTransition);
-                } else {
-                    Object slideFromBottom = TransitionHelper.createFadeAndShortSlide(
-                            Gravity.BOTTOM);
-                    TransitionHelper.include(slideFromBottom, R.id.guidedstep_background_view_root);
-                    Object enterTransition = TransitionHelper.createTransitionSet(false);
-                    TransitionHelper.addTransition(enterTransition, slideFromBottom);
-                    TransitionHelper.setEnterTransition(this, enterTransition);
-                }
-                // No shared element transition
-                TransitionHelper.setSharedElementEnterTransition(this, null);
-            } else if (uiStyle == UI_STYLE_ACTIVITY_ROOT) {
-                // for Activity root, we don't need enter transition, use activity transition
-                TransitionHelper.setEnterTransition(this, null);
-                // No shared element transition
-                TransitionHelper.setSharedElementEnterTransition(this, null);
-            }
-            // exitTransition is same for all style
-            Object exitTransition = TransitionHelper.createFadeAndShortSlide(Gravity.START);
-            TransitionHelper.exclude(exitTransition, R.id.guidedstep_background, true);
-            TransitionHelper.exclude(exitTransition, R.id.guidedactions_sub_list_background,
-                    true);
-            TransitionHelper.setExitTransition(this, exitTransition);
-        }
-    }
-
-    /**
-     * Called by onCreateView to inflate background view.  Default implementation loads view
-     * from {@link R.layout#lb_guidedstep_background} which holds a reference to
-     * guidedStepBackground.
-     * @param inflater LayoutInflater to load background view.
-     * @param container Parent view of background view.
-     * @param savedInstanceState
-     * @return Created background view or null if no background.
-     */
-    public View onCreateBackgroundView(LayoutInflater inflater, ViewGroup container,
-            Bundle savedInstanceState) {
-        return inflater.inflate(R.layout.lb_guidedstep_background, container, false);
-    }
-
-    /**
-     * Set UI style to fragment arguments. Default value is {@link #UI_STYLE_ENTRANCE} when fragment
-     * is first initialized. UI style is used to choose different fragment transition animations and
-     * determine if this is the first GuidedStepFragment on backstack. In most cases app does not
-     * directly call this method, app calls helper function
-     * {@link #add(FragmentManager, GuidedStepFragment, int)}. However if the app creates Fragment
-     * transaction and controls backstack by itself, it would need call setUiStyle() to select the
-     * fragment transition to use.
-     *
-     * @param style {@link #UI_STYLE_ACTIVITY_ROOT} {@link #UI_STYLE_REPLACE} or
-     *        {@link #UI_STYLE_ENTRANCE}.
-     */
-    public void setUiStyle(int style) {
-        int oldStyle = getUiStyle();
-        Bundle arguments = getArguments();
-        boolean isNew = false;
-        if (arguments == null) {
-            arguments = new Bundle();
-            isNew = true;
-        }
-        arguments.putInt(EXTRA_UI_STYLE, style);
-        // call setArgument() will validate if the fragment is already added.
-        if (isNew) {
-            setArguments(arguments);
-        }
-        if (style != oldStyle) {
-            onProvideFragmentTransitions();
-        }
-    }
-
-    /**
-     * Read UI style from fragment arguments.  Default value is {@link #UI_STYLE_ENTRANCE} when
-     * fragment is first initialized.  UI style is used to choose different fragment transition
-     * animations and determine if this is the first GuidedStepFragment on backstack.
-     *
-     * @return {@link #UI_STYLE_ACTIVITY_ROOT} {@link #UI_STYLE_REPLACE} or
-     * {@link #UI_STYLE_ENTRANCE}.
-     * @see #onProvideFragmentTransitions()
-     */
-    public int getUiStyle() {
-        Bundle b = getArguments();
-        if (b == null) return UI_STYLE_ENTRANCE;
-        return b.getInt(EXTRA_UI_STYLE, UI_STYLE_ENTRANCE);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        if (DEBUG) Log.v(TAG, "onCreate");
-        // Set correct transition from saved arguments.
-        onProvideFragmentTransitions();
-
-        ArrayList<GuidedAction> actions = new ArrayList<GuidedAction>();
-        onCreateActions(actions, savedInstanceState);
-        if (savedInstanceState != null) {
-            onRestoreActions(actions, savedInstanceState);
-        }
-        setActions(actions);
-        ArrayList<GuidedAction> buttonActions = new ArrayList<GuidedAction>();
-        onCreateButtonActions(buttonActions, savedInstanceState);
-        if (savedInstanceState != null) {
-            onRestoreButtonActions(buttonActions, savedInstanceState);
-        }
-        setButtonActions(buttonActions);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public void onDestroyView() {
-        mGuidanceStylist.onDestroyView();
-        mActionsStylist.onDestroyView();
-        mButtonActionsStylist.onDestroyView();
-        mAdapter = null;
-        mSubAdapter =  null;
-        mButtonAdapter = null;
-        mAdapterGroup = null;
-        super.onDestroyView();
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public View onCreateView(LayoutInflater inflater, ViewGroup container,
-            Bundle savedInstanceState) {
-        if (DEBUG) Log.v(TAG, "onCreateView");
-
-        resolveTheme();
-        inflater = getThemeInflater(inflater);
-
-        GuidedStepRootLayout root = (GuidedStepRootLayout) inflater.inflate(
-                R.layout.lb_guidedstep_fragment, container, false);
-
-        root.setFocusOutStart(isFocusOutStartAllowed());
-        root.setFocusOutEnd(isFocusOutEndAllowed());
-
-        ViewGroup guidanceContainer = (ViewGroup) root.findViewById(R.id.content_fragment);
-        ViewGroup actionContainer = (ViewGroup) root.findViewById(R.id.action_fragment);
-        ((NonOverlappingLinearLayout) actionContainer).setFocusableViewAvailableFixEnabled(true);
-
-        Guidance guidance = onCreateGuidance(savedInstanceState);
-        View guidanceView = mGuidanceStylist.onCreateView(inflater, guidanceContainer, guidance);
-        guidanceContainer.addView(guidanceView);
-
-        View actionsView = mActionsStylist.onCreateView(inflater, actionContainer);
-        actionContainer.addView(actionsView);
-
-        View buttonActionsView = mButtonActionsStylist.onCreateView(inflater, actionContainer);
-        actionContainer.addView(buttonActionsView);
-
-        GuidedActionAdapter.EditListener editListener = new GuidedActionAdapter.EditListener() {
-
-                @Override
-                public void onImeOpen() {
-                    runImeAnimations(true);
-                }
-
-                @Override
-                public void onImeClose() {
-                    runImeAnimations(false);
-                }
-
-                @Override
-                public long onGuidedActionEditedAndProceed(GuidedAction action) {
-                    return GuidedStepFragment.this.onGuidedActionEditedAndProceed(action);
-                }
-
-                @Override
-                public void onGuidedActionEditCanceled(GuidedAction action) {
-                    GuidedStepFragment.this.onGuidedActionEditCanceled(action);
-                }
-        };
-
-        mAdapter = new GuidedActionAdapter(mActions, new GuidedActionAdapter.ClickListener() {
-            @Override
-            public void onGuidedActionClicked(GuidedAction action) {
-                GuidedStepFragment.this.onGuidedActionClicked(action);
-                if (isExpanded()) {
-                    collapseAction(true);
-                } else if (action.hasSubActions() || action.hasEditableActivatorView()) {
-                    expandAction(action, true);
-                }
-            }
-        }, this, mActionsStylist, false);
-        mButtonAdapter =
-                new GuidedActionAdapter(mButtonActions, new GuidedActionAdapter.ClickListener() {
-                    @Override
-                    public void onGuidedActionClicked(GuidedAction action) {
-                        GuidedStepFragment.this.onGuidedActionClicked(action);
-                    }
-                }, this, mButtonActionsStylist, false);
-        mSubAdapter = new GuidedActionAdapter(null, new GuidedActionAdapter.ClickListener() {
-            @Override
-            public void onGuidedActionClicked(GuidedAction action) {
-                if (mActionsStylist.isInExpandTransition()) {
-                    return;
-                }
-                if (GuidedStepFragment.this.onSubGuidedActionClicked(action)) {
-                    collapseSubActions();
-                }
-            }
-        }, this, mActionsStylist, true);
-        mAdapterGroup = new GuidedActionAdapterGroup();
-        mAdapterGroup.addAdpter(mAdapter, mButtonAdapter);
-        mAdapterGroup.addAdpter(mSubAdapter, null);
-        mAdapterGroup.setEditListener(editListener);
-        mActionsStylist.setEditListener(editListener);
-
-        mActionsStylist.getActionsGridView().setAdapter(mAdapter);
-        if (mActionsStylist.getSubActionsGridView() != null) {
-            mActionsStylist.getSubActionsGridView().setAdapter(mSubAdapter);
-        }
-        mButtonActionsStylist.getActionsGridView().setAdapter(mButtonAdapter);
-        if (mButtonActions.size() == 0) {
-            // when there is no button actions, we don't need show the second panel, but keep
-            // the width zero to run ChangeBounds transition.
-            LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
-                    buttonActionsView.getLayoutParams();
-            lp.weight = 0;
-            buttonActionsView.setLayoutParams(lp);
-        } else {
-            // when there are two actions panel, we need adjust the weight of action to
-            // guidedActionContentWidthWeightTwoPanels.
-            Context ctx = mThemeWrapper != null ? mThemeWrapper : FragmentUtil.getContext(GuidedStepFragment.this);
-            TypedValue typedValue = new TypedValue();
-            if (ctx.getTheme().resolveAttribute(R.attr.guidedActionContentWidthWeightTwoPanels,
-                    typedValue, true)) {
-                View actionsRoot = root.findViewById(R.id.action_fragment_root);
-                float weight = typedValue.getFloat();
-                LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) actionsRoot
-                        .getLayoutParams();
-                lp.weight = weight;
-                actionsRoot.setLayoutParams(lp);
-            }
-        }
-
-        // Add the background view.
-        View backgroundView = onCreateBackgroundView(inflater, root, savedInstanceState);
-        if (backgroundView != null) {
-            FrameLayout backgroundViewRoot = (FrameLayout)root.findViewById(
-                R.id.guidedstep_background_view_root);
-            backgroundViewRoot.addView(backgroundView, 0);
-        }
-
-        return root;
-    }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-        getView().findViewById(R.id.action_fragment).requestFocus();
-    }
-
-    /**
-     * Get the key will be used to save GuidedAction with Fragment.
-     * @param action GuidedAction to get key.
-     * @return Key to save the GuidedAction.
-     */
-    final String getAutoRestoreKey(GuidedAction action) {
-        return EXTRA_ACTION_PREFIX + action.getId();
-    }
-
-    /**
-     * Get the key will be used to save GuidedAction with Fragment.
-     * @param action GuidedAction to get key.
-     * @return Key to save the GuidedAction.
-     */
-    final String getButtonAutoRestoreKey(GuidedAction action) {
-        return EXTRA_BUTTON_ACTION_PREFIX + action.getId();
-    }
-
-    final static boolean isSaveEnabled(GuidedAction action) {
-        return action.isAutoSaveRestoreEnabled() && action.getId() != GuidedAction.NO_ID;
-    }
-
-    final void onRestoreActions(List<GuidedAction> actions, Bundle savedInstanceState) {
-        for (int i = 0, size = actions.size(); i < size; i++) {
-            GuidedAction action = actions.get(i);
-            if (isSaveEnabled(action)) {
-                action.onRestoreInstanceState(savedInstanceState, getAutoRestoreKey(action));
-            }
-        }
-    }
-
-    final void onRestoreButtonActions(List<GuidedAction> actions, Bundle savedInstanceState) {
-        for (int i = 0, size = actions.size(); i < size; i++) {
-            GuidedAction action = actions.get(i);
-            if (isSaveEnabled(action)) {
-                action.onRestoreInstanceState(savedInstanceState, getButtonAutoRestoreKey(action));
-            }
-        }
-    }
-
-    final void onSaveActions(List<GuidedAction> actions, Bundle outState) {
-        for (int i = 0, size = actions.size(); i < size; i++) {
-            GuidedAction action = actions.get(i);
-            if (isSaveEnabled(action)) {
-                action.onSaveInstanceState(outState, getAutoRestoreKey(action));
-            }
-        }
-    }
-
-    final void onSaveButtonActions(List<GuidedAction> actions, Bundle outState) {
-        for (int i = 0, size = actions.size(); i < size; i++) {
-            GuidedAction action = actions.get(i);
-            if (isSaveEnabled(action)) {
-                action.onSaveInstanceState(outState, getButtonAutoRestoreKey(action));
-            }
-        }
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public void onSaveInstanceState(Bundle outState) {
-        super.onSaveInstanceState(outState);
-        onSaveActions(mActions, outState);
-        onSaveButtonActions(mButtonActions, outState);
-    }
-
-    private static boolean isGuidedStepTheme(Context context) {
-        int resId = R.attr.guidedStepThemeFlag;
-        TypedValue typedValue = new TypedValue();
-        boolean found = context.getTheme().resolveAttribute(resId, typedValue, true);
-        if (DEBUG) Log.v(TAG, "Found guided step theme flag? " + found);
-        return found && typedValue.type == TypedValue.TYPE_INT_BOOLEAN && typedValue.data != 0;
-    }
-
-    /**
-     * Convenient method to close GuidedStepFragments on top of other content or finish Activity if
-     * GuidedStepFragments were started in a separate activity.  Pops all stack entries including
-     * {@link #UI_STYLE_ENTRANCE}; if {@link #UI_STYLE_ENTRANCE} is not found, finish the activity.
-     * Note that this method must be paired with {@link #add(FragmentManager, GuidedStepFragment,
-     * int)} which sets up the stack entry name for finding which fragment we need to pop back to.
-     */
-    public void finishGuidedStepFragments() {
-        final FragmentManager fragmentManager = getFragmentManager();
-        final int entryCount = fragmentManager.getBackStackEntryCount();
-        if (entryCount > 0) {
-            for (int i = entryCount - 1; i >= 0; i--) {
-                BackStackEntry entry = fragmentManager.getBackStackEntryAt(i);
-                if (isStackEntryUiStyleEntrance(entry.getName())) {
-                    GuidedStepFragment top = getCurrentGuidedStepFragment(fragmentManager);
-                    if (top != null) {
-                        top.setUiStyle(UI_STYLE_ENTRANCE);
-                    }
-                    fragmentManager.popBackStackImmediate(entry.getId(),
-                            FragmentManager.POP_BACK_STACK_INCLUSIVE);
-                    return;
-                }
-            }
-        }
-        ActivityCompat.finishAfterTransition(getActivity());
-    }
-
-    /**
-     * Convenient method to pop to fragment with Given class.
-     * @param  guidedStepFragmentClass  Name of the Class of GuidedStepFragment to pop to.
-     * @param flags Either 0 or {@link FragmentManager#POP_BACK_STACK_INCLUSIVE}.
-     */
-    public void popBackStackToGuidedStepFragment(Class guidedStepFragmentClass, int flags) {
-        if (!GuidedStepFragment.class.isAssignableFrom(guidedStepFragmentClass)) {
-            return;
-        }
-        final FragmentManager fragmentManager = getFragmentManager();
-        final int entryCount = fragmentManager.getBackStackEntryCount();
-        String className = guidedStepFragmentClass.getName();
-        if (entryCount > 0) {
-            for (int i = entryCount - 1; i >= 0; i--) {
-                BackStackEntry entry = fragmentManager.getBackStackEntryAt(i);
-                String entryClassName = getGuidedStepFragmentClassName(entry.getName());
-                if (className.equals(entryClassName)) {
-                    fragmentManager.popBackStackImmediate(entry.getId(), flags);
-                    return;
-                }
-            }
-        }
-    }
-
-    /**
-     * Returns true if allows focus out of start edge of GuidedStepFragment, false otherwise.
-     * Default value is false, the reason is to disable FocusFinder to find focusable views
-     * beneath content of GuidedStepFragment.  Subclass may override.
-     * @return True if allows focus out of start edge of GuidedStepFragment.
-     */
-    public boolean isFocusOutStartAllowed() {
-        return false;
-    }
-
-    /**
-     * Returns true if allows focus out of end edge of GuidedStepFragment, false otherwise.
-     * Default value is false, the reason is to disable FocusFinder to find focusable views
-     * beneath content of GuidedStepFragment.  Subclass may override.
-     * @return True if allows focus out of end edge of GuidedStepFragment.
-     */
-    public boolean isFocusOutEndAllowed() {
-        return false;
-    }
-
-    /**
-     * Sets the transition type to be used for {@link #UI_STYLE_ENTRANCE} animation.
-     * Currently we provide 2 different variations for animation - slide in from
-     * side (default) or bottom.
-     *
-     * Ideally we can retrieve the screen mode settings from the theme attribute
-     * {@code Theme.Leanback.GuidedStep#guidedStepHeightWeight} and use that to
-     * determine the transition. But the fragment context to retrieve the theme
-     * isn't available on platform v23 or earlier.
-     *
-     * For now clients(subclasses) can call this method inside the constructor.
-     * @hide
-     */
-    @RestrictTo(LIBRARY_GROUP)
-    public void setEntranceTransitionType(int transitionType) {
-      this.entranceTransitionType = transitionType;
-    }
-
-    /**
-     * Opens the provided action in edit mode and raises ime. This can be
-     * used to programmatically skip the extra click required to go into edit mode. This method
-     * can be invoked in {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)}.
-     */
-    public void openInEditMode(GuidedAction action) {
-        mActionsStylist.openInEditMode(action);
-    }
-
-    private void resolveTheme() {
-        // Look up the guidedStepTheme in the currently specified theme.  If it exists,
-        // replace the theme with its value.
-        Context context = FragmentUtil.getContext(GuidedStepFragment.this);
-        int theme = onProvideTheme();
-        if (theme == -1 && !isGuidedStepTheme(context)) {
-            // Look up the guidedStepTheme in the activity's currently specified theme.  If it
-            // exists, replace the theme with its value.
-            int resId = R.attr.guidedStepTheme;
-            TypedValue typedValue = new TypedValue();
-            boolean found = context.getTheme().resolveAttribute(resId, typedValue, true);
-            if (DEBUG) Log.v(TAG, "Found guided step theme reference? " + found);
-            if (found) {
-                ContextThemeWrapper themeWrapper =
-                        new ContextThemeWrapper(context, typedValue.resourceId);
-                if (isGuidedStepTheme(themeWrapper)) {
-                    mThemeWrapper = themeWrapper;
-                } else {
-                    found = false;
-                    mThemeWrapper = null;
-                }
-            }
-            if (!found) {
-                Log.e(TAG, "GuidedStepFragment does not have an appropriate theme set.");
-            }
-        } else if (theme != -1) {
-            mThemeWrapper = new ContextThemeWrapper(context, theme);
-        }
-    }
-
-    private LayoutInflater getThemeInflater(LayoutInflater inflater) {
-        if (mThemeWrapper == null) {
-            return inflater;
-        } else {
-            return inflater.cloneInContext(mThemeWrapper);
-        }
-    }
-
-    private int getFirstCheckedAction() {
-        for (int i = 0, size = mActions.size(); i < size; i++) {
-            if (mActions.get(i).isChecked()) {
-                return i;
-            }
-        }
-        return 0;
-    }
-
-    void runImeAnimations(boolean entering) {
-        ArrayList<Animator> animators = new ArrayList<Animator>();
-        if (entering) {
-            mGuidanceStylist.onImeAppearing(animators);
-            mActionsStylist.onImeAppearing(animators);
-            mButtonActionsStylist.onImeAppearing(animators);
-        } else {
-            mGuidanceStylist.onImeDisappearing(animators);
-            mActionsStylist.onImeDisappearing(animators);
-            mButtonActionsStylist.onImeDisappearing(animators);
-        }
-        AnimatorSet set = new AnimatorSet();
-        set.playTogether(animators);
-        set.start();
-    }
-}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/GuidedStepSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/GuidedStepSupportFragment.java
deleted file mode 100644
index aeb2d33..0000000
--- a/v17/leanback/src/android/support/v17/leanback/app/GuidedStepSupportFragment.java
+++ /dev/null
@@ -1,1400 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-package android.support.v17.leanback.app;
-
-import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
-
-import android.animation.Animator;
-import android.animation.AnimatorSet;
-import android.content.Context;
-import android.os.Build;
-import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.support.annotation.RestrictTo;
-import android.support.v17.leanback.R;
-import android.support.v17.leanback.transition.TransitionHelper;
-import android.support.v17.leanback.widget.GuidanceStylist;
-import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
-import android.support.v17.leanback.widget.GuidedAction;
-import android.support.v17.leanback.widget.GuidedActionAdapter;
-import android.support.v17.leanback.widget.GuidedActionAdapterGroup;
-import android.support.v17.leanback.widget.GuidedActionsStylist;
-import android.support.v17.leanback.widget.NonOverlappingLinearLayout;
-import android.support.v4.app.ActivityCompat;
-import android.support.v4.app.Fragment;
-import android.support.v4.app.FragmentActivity;
-import android.support.v4.app.FragmentManager;
-import android.support.v4.app.FragmentManager.BackStackEntry;
-import android.support.v4.app.FragmentTransaction;
-import android.support.v7.widget.RecyclerView;
-import android.util.Log;
-import android.util.TypedValue;
-import android.view.ContextThemeWrapper;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-import android.widget.LinearLayout;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * A GuidedStepSupportFragment is used to guide the user through a decision or series of decisions.
- * It is composed of a guidance view on the left and a view on the right containing a list of
- * possible actions.
- * <p>
- * <h3>Basic Usage</h3>
- * <p>
- * Clients of GuidedStepSupportFragment must create a custom subclass to attach to their Activities.
- * This custom subclass provides the information necessary to construct the user interface and
- * respond to user actions. At a minimum, subclasses should override:
- * <ul>
- * <li>{@link #onCreateGuidance}, to provide instructions to the user</li>
- * <li>{@link #onCreateActions}, to provide a set of {@link GuidedAction}s the user can take</li>
- * <li>{@link #onGuidedActionClicked}, to respond to those actions</li>
- * </ul>
- * <p>
- * Clients use following helper functions to add GuidedStepSupportFragment to Activity or FragmentManager:
- * <ul>
- * <li>{@link #addAsRoot(FragmentActivity, GuidedStepSupportFragment, int)}, to be called during Activity onCreate,
- * adds GuidedStepSupportFragment as the first Fragment in activity.</li>
- * <li>{@link #add(FragmentManager, GuidedStepSupportFragment)} or {@link #add(FragmentManager,
- * GuidedStepSupportFragment, int)}, to add GuidedStepSupportFragment on top of existing Fragments or
- * replacing existing GuidedStepSupportFragment when moving forward to next step.</li>
- * <li>{@link #finishGuidedStepSupportFragments()} can either finish the activity or pop all
- * GuidedStepSupportFragment from stack.
- * <li>If app chooses not to use the helper function, it is the app's responsibility to call
- * {@link #setUiStyle(int)} to select fragment transition and remember the stack entry where it
- * need pops to.
- * </ul>
- * <h3>Theming and Stylists</h3>
- * <p>
- * GuidedStepSupportFragment delegates its visual styling to classes called stylists. The {@link
- * GuidanceStylist} is responsible for the left guidance view, while the {@link
- * GuidedActionsStylist} is responsible for the right actions view. The stylists use theme
- * attributes to derive values associated with the presentation, such as colors, animations, etc.
- * Most simple visual aspects of GuidanceStylist and GuidedActionsStylist can be customized
- * via theming; see their documentation for more information.
- * <p>
- * GuidedStepSupportFragments must have access to an appropriate theme in order for the stylists to
- * function properly.  Specifically, the fragment must receive {@link
- * android.support.v17.leanback.R.style#Theme_Leanback_GuidedStep}, or a theme whose parent is
- * is set to that theme. Themes can be provided in one of three ways:
- * <ul>
- * <li>The simplest way is to set the theme for the host Activity to the GuidedStep theme or a
- * theme that derives from it.</li>
- * <li>If the Activity already has a theme and setting its parent theme is inconvenient, the
- * existing Activity theme can have an entry added for the attribute {@link
- * android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedStepTheme}. If present,
- * this theme will be used by GuidedStepSupportFragment as an overlay to the Activity's theme.</li>
- * <li>Finally, custom subclasses of GuidedStepSupportFragment may provide a theme through the {@link
- * #onProvideTheme} method. This can be useful if a subclass is used across multiple
- * Activities.</li>
- * </ul>
- * <p>
- * If the theme is provided in multiple ways, the onProvideTheme override has priority, followed by
- * the Activity's theme.  (Themes whose parent theme is already set to the guided step theme do not
- * need to set the guidedStepTheme attribute; if set, it will be ignored.)
- * <p>
- * If themes do not provide enough customizability, the stylists themselves may be subclassed and
- * provided to the GuidedStepSupportFragment through the {@link #onCreateGuidanceStylist} and {@link
- * #onCreateActionsStylist} methods.  The stylists have simple hooks so that subclasses
- * may override layout files; subclasses may also have more complex logic to determine styling.
- * <p>
- * <h3>Guided sequences</h3>
- * <p>
- * GuidedStepSupportFragments can be grouped together to provide a guided sequence. GuidedStepSupportFragments
- * grouped as a sequence use custom animations provided by {@link GuidanceStylist} and
- * {@link GuidedActionsStylist} (or subclasses) during transitions between steps. Clients
- * should use {@link #add} to place subsequent GuidedFragments onto the fragment stack so that
- * custom animations are properly configured. (Custom animations are triggered automatically when
- * the fragment stack is subsequently popped by any normal mechanism.)
- * <p>
- * <i>Note: Currently GuidedStepSupportFragments grouped in this way must all be defined programmatically,
- * rather than in XML. This restriction may be removed in the future.</i>
- *
- * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedStepTheme
- * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedStepBackground
- * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionContentWidthWeight
- * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionContentWidthWeightTwoPanels
- * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionsBackground
- * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionsBackgroundDark
- * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionsElevation
- * @see GuidanceStylist
- * @see GuidanceStylist.Guidance
- * @see GuidedAction
- * @see GuidedActionsStylist
- */
-public class GuidedStepSupportFragment extends Fragment implements GuidedActionAdapter.FocusListener {
-
-    private static final String TAG_LEAN_BACK_ACTIONS_FRAGMENT = "leanBackGuidedStepSupportFragment";
-    private static final String EXTRA_ACTION_PREFIX = "action_";
-    private static final String EXTRA_BUTTON_ACTION_PREFIX = "buttonaction_";
-
-    private static final String ENTRY_NAME_REPLACE = "GuidedStepDefault";
-
-    private static final String ENTRY_NAME_ENTRANCE = "GuidedStepEntrance";
-
-    private static final boolean IS_FRAMEWORK_FRAGMENT = false;
-
-    /**
-     * Fragment argument name for UI style.  The argument value is persisted in fragment state and
-     * used to select fragment transition. The value is initially {@link #UI_STYLE_ENTRANCE} and
-     * might be changed in one of the three helper functions:
-     * <ul>
-     * <li>{@link #addAsRoot(FragmentActivity, GuidedStepSupportFragment, int)} sets to
-     * {@link #UI_STYLE_ACTIVITY_ROOT}</li>
-     * <li>{@link #add(FragmentManager, GuidedStepSupportFragment)} or {@link #add(FragmentManager,
-     * GuidedStepSupportFragment, int)} sets it to {@link #UI_STYLE_REPLACE} if there is already a
-     * GuidedStepSupportFragment on stack.</li>
-     * <li>{@link #finishGuidedStepSupportFragments()} changes current GuidedStepSupportFragment to
-     * {@link #UI_STYLE_ENTRANCE} for the non activity case.  This is a special case that changes
-     * the transition settings after fragment has been created,  in order to force current
-     * GuidedStepSupportFragment run a return transition of {@link #UI_STYLE_ENTRANCE}</li>
-     * </ul>
-     * <p>
-     * Argument value can be either:
-     * <ul>
-     * <li>{@link #UI_STYLE_REPLACE}</li>
-     * <li>{@link #UI_STYLE_ENTRANCE}</li>
-     * <li>{@link #UI_STYLE_ACTIVITY_ROOT}</li>
-     * </ul>
-     */
-    public static final String EXTRA_UI_STYLE = "uiStyle";
-
-    /**
-     * This is the case that we use GuidedStepSupportFragment to replace another existing
-     * GuidedStepSupportFragment when moving forward to next step. Default behavior of this style is:
-     * <ul>
-     * <li>Enter transition slides in from END(right), exit transition same as
-     * {@link #UI_STYLE_ENTRANCE}.
-     * </li>
-     * </ul>
-     */
-    public static final int UI_STYLE_REPLACE = 0;
-
-    /**
-     * @deprecated Same value as {@link #UI_STYLE_REPLACE}.
-     */
-    @Deprecated
-    public static final int UI_STYLE_DEFAULT = 0;
-
-    /**
-     * Default value for argument {@link #EXTRA_UI_STYLE}. The default value is assigned in
-     * GuidedStepSupportFragment constructor. This is the case that we show GuidedStepSupportFragment on top of
-     * other content. The default behavior of this style:
-     * <ul>
-     * <li>Enter transition slides in from two sides, exit transition slide out to START(left).
-     * Background will be faded in. Note: Changing exit transition by UI style is not working
-     * because fragment transition asks for exit transition before UI style is restored in Fragment
-     * .onCreate().</li>
-     * </ul>
-     * When popping multiple GuidedStepSupportFragment, {@link #finishGuidedStepSupportFragments()} also changes
-     * the top GuidedStepSupportFragment to UI_STYLE_ENTRANCE in order to run the return transition
-     * (reverse of enter transition) of UI_STYLE_ENTRANCE.
-     */
-    public static final int UI_STYLE_ENTRANCE = 1;
-
-    /**
-     * One possible value of argument {@link #EXTRA_UI_STYLE}. This is the case that we show first
-     * GuidedStepSupportFragment in a separate activity. The default behavior of this style:
-     * <ul>
-     * <li>Enter transition is assigned null (will rely on activity transition), exit transition is
-     * same as {@link #UI_STYLE_ENTRANCE}. Note: Changing exit transition by UI style is not working
-     * because fragment transition asks for exit transition before UI style is restored in
-     * Fragment.onCreate().</li>
-     * </ul>
-     */
-    public static final int UI_STYLE_ACTIVITY_ROOT = 2;
-
-    /**
-     * Animation to slide the contents from the side (left/right).
-     * @hide
-     */
-    @RestrictTo(LIBRARY_GROUP)
-    public static final int SLIDE_FROM_SIDE = 0;
-
-    /**
-     * Animation to slide the contents from the bottom.
-     * @hide
-     */
-    @RestrictTo(LIBRARY_GROUP)
-    public static final int SLIDE_FROM_BOTTOM = 1;
-
-    private static final String TAG = "GuidedStepF";
-    private static final boolean DEBUG = false;
-
-    /**
-     * @hide
-     */
-    @RestrictTo(LIBRARY_GROUP)
-    public static class DummyFragment extends Fragment {
-        @Override
-        public View onCreateView(LayoutInflater inflater, ViewGroup container,
-                Bundle savedInstanceState) {
-            final View v = new View(inflater.getContext());
-            v.setVisibility(View.GONE);
-            return v;
-        }
-    }
-
-    private ContextThemeWrapper mThemeWrapper;
-    private GuidanceStylist mGuidanceStylist;
-    GuidedActionsStylist mActionsStylist;
-    private GuidedActionsStylist mButtonActionsStylist;
-    private GuidedActionAdapter mAdapter;
-    private GuidedActionAdapter mSubAdapter;
-    private GuidedActionAdapter mButtonAdapter;
-    private GuidedActionAdapterGroup mAdapterGroup;
-    private List<GuidedAction> mActions = new ArrayList<GuidedAction>();
-    private List<GuidedAction> mButtonActions = new ArrayList<GuidedAction>();
-    private int entranceTransitionType = SLIDE_FROM_SIDE;
-
-    public GuidedStepSupportFragment() {
-        mGuidanceStylist = onCreateGuidanceStylist();
-        mActionsStylist = onCreateActionsStylist();
-        mButtonActionsStylist = onCreateButtonActionsStylist();
-        onProvideFragmentTransitions();
-    }
-
-    /**
-     * Creates the presenter used to style the guidance panel. The default implementation returns
-     * a basic GuidanceStylist.
-     * @return The GuidanceStylist used in this fragment.
-     */
-    public GuidanceStylist onCreateGuidanceStylist() {
-        return new GuidanceStylist();
-    }
-
-    /**
-     * Creates the presenter used to style the guided actions panel. The default implementation
-     * returns a basic GuidedActionsStylist.
-     * @return The GuidedActionsStylist used in this fragment.
-     */
-    public GuidedActionsStylist onCreateActionsStylist() {
-        return new GuidedActionsStylist();
-    }
-
-    /**
-     * Creates the presenter used to style a sided actions panel for button only.
-     * The default implementation returns a basic GuidedActionsStylist.
-     * @return The GuidedActionsStylist used in this fragment.
-     */
-    public GuidedActionsStylist onCreateButtonActionsStylist() {
-        GuidedActionsStylist stylist = new GuidedActionsStylist();
-        stylist.setAsButtonActions();
-        return stylist;
-    }
-
-    /**
-     * Returns the theme used for styling the fragment. The default returns -1, indicating that the
-     * host Activity's theme should be used.
-     * @return The theme resource ID of the theme to use in this fragment, or -1 to use the
-     * host Activity's theme.
-     */
-    public int onProvideTheme() {
-        return -1;
-    }
-
-    /**
-     * Returns the information required to provide guidance to the user. This hook is called during
-     * {@link #onCreateView}.  May be overridden to return a custom subclass of {@link
-     * GuidanceStylist.Guidance} for use in a subclass of {@link GuidanceStylist}. The default
-     * returns a Guidance object with empty fields; subclasses should override.
-     * @param savedInstanceState The saved instance state from onCreateView.
-     * @return The Guidance object representing the information used to guide the user.
-     */
-    public @NonNull Guidance onCreateGuidance(Bundle savedInstanceState) {
-        return new Guidance("", "", "", null);
-    }
-
-    /**
-     * Fills out the set of actions available to the user. This hook is called during {@link
-     * #onCreate}. The default leaves the list of actions empty; subclasses should override.
-     * @param actions A non-null, empty list ready to be populated.
-     * @param savedInstanceState The saved instance state from onCreate.
-     */
-    public void onCreateActions(@NonNull List<GuidedAction> actions, Bundle savedInstanceState) {
-    }
-
-    /**
-     * Fills out the set of actions shown at right available to the user. This hook is called during
-     * {@link #onCreate}. The default leaves the list of actions empty; subclasses may override.
-     * @param actions A non-null, empty list ready to be populated.
-     * @param savedInstanceState The saved instance state from onCreate.
-     */
-    public void onCreateButtonActions(@NonNull List<GuidedAction> actions,
-            Bundle savedInstanceState) {
-    }
-
-    /**
-     * Callback invoked when an action is taken by the user. Subclasses should override in
-     * order to act on the user's decisions.
-     * @param action The chosen action.
-     */
-    public void onGuidedActionClicked(GuidedAction action) {
-    }
-
-    /**
-     * Callback invoked when an action in sub actions is taken by the user. Subclasses should
-     * override in order to act on the user's decisions.  Default return value is true to close
-     * the sub actions list.
-     * @param action The chosen action.
-     * @return true to collapse the sub actions list, false to keep it expanded.
-     */
-    public boolean onSubGuidedActionClicked(GuidedAction action) {
-        return true;
-    }
-
-    /**
-     * @return True if is current expanded including subactions list or
-     * action with {@link GuidedAction#hasEditableActivatorView()} is true.
-     */
-    public boolean isExpanded() {
-        return mActionsStylist.isExpanded();
-    }
-
-    /**
-     * @return True if the sub actions list is expanded, false otherwise.
-     */
-    public boolean isSubActionsExpanded() {
-        return mActionsStylist.isSubActionsExpanded();
-    }
-
-    /**
-     * Expand a given action's sub actions list.
-     * @param action GuidedAction to expand.
-     * @see #expandAction(GuidedAction, boolean)
-     */
-    public void expandSubActions(GuidedAction action) {
-        if (!action.hasSubActions()) {
-            return;
-        }
-        expandAction(action, true);
-    }
-
-    /**
-     * Expand a given action with sub actions list or
-     * {@link GuidedAction#hasEditableActivatorView()} is true. The method must be called after
-     * {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)} creates fragment view.
-     *
-     * @param action GuidedAction to expand.
-     * @param withTransition True to run transition animation, false otherwise.
-     */
-    public void expandAction(GuidedAction action, boolean withTransition) {
-        mActionsStylist.expandAction(action, withTransition);
-    }
-
-    /**
-     * Collapse sub actions list.
-     * @see GuidedAction#getSubActions()
-     */
-    public void collapseSubActions() {
-        collapseAction(true);
-    }
-
-    /**
-     * Collapse action which either has a sub actions list or action with
-     * {@link GuidedAction#hasEditableActivatorView()} is true.
-     *
-     * @param withTransition True to run transition animation, false otherwise.
-     */
-    public void collapseAction(boolean withTransition) {
-        if (mActionsStylist != null && mActionsStylist.getActionsGridView() != null) {
-            mActionsStylist.collapseAction(withTransition);
-        }
-    }
-
-    /**
-     * Callback invoked when an action is focused (made to be the current selection) by the user.
-     */
-    @Override
-    public void onGuidedActionFocused(GuidedAction action) {
-    }
-
-    /**
-     * Callback invoked when an action's title or description has been edited, this happens either
-     * when user clicks confirm button in IME or user closes IME window by BACK key.
-     * @deprecated Override {@link #onGuidedActionEditedAndProceed(GuidedAction)} and/or
-     *             {@link #onGuidedActionEditCanceled(GuidedAction)}.
-     */
-    @Deprecated
-    public void onGuidedActionEdited(GuidedAction action) {
-    }
-
-    /**
-     * Callback invoked when an action has been canceled editing, for example when user closes
-     * IME window by BACK key.  Default implementation calls deprecated method
-     * {@link #onGuidedActionEdited(GuidedAction)}.
-     * @param action The action which has been canceled editing.
-     */
-    public void onGuidedActionEditCanceled(GuidedAction action) {
-        onGuidedActionEdited(action);
-    }
-
-    /**
-     * Callback invoked when an action has been edited, for example when user clicks confirm button
-     * in IME window.  Default implementation calls deprecated method
-     * {@link #onGuidedActionEdited(GuidedAction)} and returns {@link GuidedAction#ACTION_ID_NEXT}.
-     *
-     * @param action The action that has been edited.
-     * @return ID of the action will be focused or {@link GuidedAction#ACTION_ID_NEXT},
-     * {@link GuidedAction#ACTION_ID_CURRENT}.
-     */
-    public long onGuidedActionEditedAndProceed(GuidedAction action) {
-        onGuidedActionEdited(action);
-        return GuidedAction.ACTION_ID_NEXT;
-    }
-
-    /**
-     * Adds the specified GuidedStepSupportFragment to the fragment stack, replacing any existing
-     * GuidedStepSupportFragments in the stack, and configuring the fragment-to-fragment custom
-     * transitions.  A backstack entry is added, so the fragment will be dismissed when BACK key
-     * is pressed.
-     * <li>If current fragment on stack is GuidedStepSupportFragment: assign {@link #UI_STYLE_REPLACE}
-     * <li>If current fragment on stack is not GuidedStepSupportFragment: assign {@link #UI_STYLE_ENTRANCE}
-     * <p>
-     * Note: currently fragments added using this method must be created programmatically rather
-     * than via XML.
-     * @param fragmentManager The FragmentManager to be used in the transaction.
-     * @param fragment The GuidedStepSupportFragment to be inserted into the fragment stack.
-     * @return The ID returned by the call FragmentTransaction.commit.
-     */
-    public static int add(FragmentManager fragmentManager, GuidedStepSupportFragment fragment) {
-        return add(fragmentManager, fragment, android.R.id.content);
-    }
-
-    /**
-     * Adds the specified GuidedStepSupportFragment to the fragment stack, replacing any existing
-     * GuidedStepSupportFragments in the stack, and configuring the fragment-to-fragment custom
-     * transitions.  A backstack entry is added, so the fragment will be dismissed when BACK key
-     * is pressed.
-     * <li>If current fragment on stack is GuidedStepSupportFragment: assign {@link #UI_STYLE_REPLACE} and
-     * {@link #onAddSharedElementTransition(FragmentTransaction, GuidedStepSupportFragment)} will be called
-     * to perform shared element transition between GuidedStepSupportFragments.
-     * <li>If current fragment on stack is not GuidedStepSupportFragment: assign {@link #UI_STYLE_ENTRANCE}
-     * <p>
-     * Note: currently fragments added using this method must be created programmatically rather
-     * than via XML.
-     * @param fragmentManager The FragmentManager to be used in the transaction.
-     * @param fragment The GuidedStepSupportFragment to be inserted into the fragment stack.
-     * @param id The id of container to add GuidedStepSupportFragment, can be android.R.id.content.
-     * @return The ID returned by the call FragmentTransaction.commit.
-     */
-    public static int add(FragmentManager fragmentManager, GuidedStepSupportFragment fragment, int id) {
-        GuidedStepSupportFragment current = getCurrentGuidedStepSupportFragment(fragmentManager);
-        boolean inGuidedStep = current != null;
-        if (IS_FRAMEWORK_FRAGMENT && Build.VERSION.SDK_INT >= 21 && Build.VERSION.SDK_INT < 23
-                && !inGuidedStep) {
-            // workaround b/22631964 for framework fragment
-            fragmentManager.beginTransaction()
-                .replace(id, new DummyFragment(), TAG_LEAN_BACK_ACTIONS_FRAGMENT)
-                .commit();
-        }
-        FragmentTransaction ft = fragmentManager.beginTransaction();
-
-        fragment.setUiStyle(inGuidedStep ? UI_STYLE_REPLACE : UI_STYLE_ENTRANCE);
-        ft.addToBackStack(fragment.generateStackEntryName());
-        if (current != null) {
-            fragment.onAddSharedElementTransition(ft, current);
-        }
-        return ft.replace(id, fragment, TAG_LEAN_BACK_ACTIONS_FRAGMENT).commit();
-    }
-
-    /**
-     * Called when this fragment is added to FragmentTransaction with {@link #UI_STYLE_REPLACE} (aka
-     * when the GuidedStepSupportFragment replacing an existing GuidedStepSupportFragment). Default implementation
-     * establishes connections between action background views to morph action background bounds
-     * change from disappearing GuidedStepSupportFragment into this GuidedStepSupportFragment. The default
-     * implementation heavily relies on {@link GuidedActionsStylist}'s layout, app may override this
-     * method when modifying the default layout of {@link GuidedActionsStylist}.
-     *
-     * @see GuidedActionsStylist
-     * @see #onProvideFragmentTransitions()
-     * @param ft The FragmentTransaction to add shared element.
-     * @param disappearing The disappearing fragment.
-     */
-    protected void onAddSharedElementTransition(FragmentTransaction ft, GuidedStepSupportFragment
-            disappearing) {
-        View fragmentView = disappearing.getView();
-        addNonNullSharedElementTransition(ft, fragmentView.findViewById(
-                R.id.action_fragment_root), "action_fragment_root");
-        addNonNullSharedElementTransition(ft, fragmentView.findViewById(
-                R.id.action_fragment_background), "action_fragment_background");
-        addNonNullSharedElementTransition(ft, fragmentView.findViewById(
-                R.id.action_fragment), "action_fragment");
-        addNonNullSharedElementTransition(ft, fragmentView.findViewById(
-                R.id.guidedactions_root), "guidedactions_root");
-        addNonNullSharedElementTransition(ft, fragmentView.findViewById(
-                R.id.guidedactions_content), "guidedactions_content");
-        addNonNullSharedElementTransition(ft, fragmentView.findViewById(
-                R.id.guidedactions_list_background), "guidedactions_list_background");
-        addNonNullSharedElementTransition(ft, fragmentView.findViewById(
-                R.id.guidedactions_root2), "guidedactions_root2");
-        addNonNullSharedElementTransition(ft, fragmentView.findViewById(
-                R.id.guidedactions_content2), "guidedactions_content2");
-        addNonNullSharedElementTransition(ft, fragmentView.findViewById(
-                R.id.guidedactions_list_background2), "guidedactions_list_background2");
-    }
-
-    private static void addNonNullSharedElementTransition (FragmentTransaction ft, View subView,
-                                                           String transitionName)
-    {
-        if (subView != null)
-            TransitionHelper.addSharedElement(ft, subView, transitionName);
-    }
-
-    /**
-     * Returns BackStackEntry name for the GuidedStepSupportFragment or empty String if no entry is
-     * associated.  Note {@link #UI_STYLE_ACTIVITY_ROOT} will return empty String.  The method
-     * returns undefined value if the fragment is not in FragmentManager.
-     * @return BackStackEntry name for the GuidedStepSupportFragment or empty String if no entry is
-     * associated.
-     */
-    final String generateStackEntryName() {
-        return generateStackEntryName(getUiStyle(), getClass());
-    }
-
-    /**
-     * Generates BackStackEntry name for GuidedStepSupportFragment class or empty String if no entry is
-     * associated.  Note {@link #UI_STYLE_ACTIVITY_ROOT} is not allowed and returns empty String.
-     * @param uiStyle {@link #UI_STYLE_REPLACE} or {@link #UI_STYLE_ENTRANCE}
-     * @return BackStackEntry name for the GuidedStepSupportFragment or empty String if no entry is
-     * associated.
-     */
-    static String generateStackEntryName(int uiStyle, Class guidedStepFragmentClass) {
-        switch (uiStyle) {
-        case UI_STYLE_REPLACE:
-            return ENTRY_NAME_REPLACE + guidedStepFragmentClass.getName();
-        case UI_STYLE_ENTRANCE:
-            return ENTRY_NAME_ENTRANCE + guidedStepFragmentClass.getName();
-        case UI_STYLE_ACTIVITY_ROOT:
-        default:
-            return "";
-        }
-    }
-
-    /**
-     * Returns true if the backstack entry represents GuidedStepSupportFragment with
-     * {@link #UI_STYLE_ENTRANCE}, i.e. this is the first GuidedStepSupportFragment pushed to stack; false
-     * otherwise.
-     * @see #generateStackEntryName(int, Class)
-     * @param backStackEntryName Name of BackStackEntry.
-     * @return True if the backstack represents GuidedStepSupportFragment with {@link #UI_STYLE_ENTRANCE};
-     * false otherwise.
-     */
-    static boolean isStackEntryUiStyleEntrance(String backStackEntryName) {
-        return backStackEntryName != null && backStackEntryName.startsWith(ENTRY_NAME_ENTRANCE);
-    }
-
-    /**
-     * Extract Class name from BackStackEntry name.
-     * @param backStackEntryName Name of BackStackEntry.
-     * @return Class name of GuidedStepSupportFragment.
-     */
-    static String getGuidedStepSupportFragmentClassName(String backStackEntryName) {
-        if (backStackEntryName.startsWith(ENTRY_NAME_REPLACE)) {
-            return backStackEntryName.substring(ENTRY_NAME_REPLACE.length());
-        } else if (backStackEntryName.startsWith(ENTRY_NAME_ENTRANCE)) {
-            return backStackEntryName.substring(ENTRY_NAME_ENTRANCE.length());
-        } else {
-            return "";
-        }
-    }
-
-    /**
-     * Adds the specified GuidedStepSupportFragment as content of Activity; no backstack entry is added so
-     * the activity will be dismissed when BACK key is pressed.  The method is typically called in
-     * Activity.onCreate() when savedInstanceState is null.  When savedInstanceState is not null,
-     * the Activity is being restored,  do not call addAsRoot() to duplicate the Fragment restored
-     * by FragmentManager.
-     * {@link #UI_STYLE_ACTIVITY_ROOT} is assigned.
-     *
-     * Note: currently fragments added using this method must be created programmatically rather
-     * than via XML.
-     * @param activity The Activity to be used to insert GuidedstepFragment.
-     * @param fragment The GuidedStepSupportFragment to be inserted into the fragment stack.
-     * @param id The id of container to add GuidedStepSupportFragment, can be android.R.id.content.
-     * @return The ID returned by the call FragmentTransaction.commit, or -1 there is already
-     *         GuidedStepSupportFragment.
-     */
-    public static int addAsRoot(FragmentActivity activity, GuidedStepSupportFragment fragment, int id) {
-        // Workaround b/23764120: call getDecorView() to force requestFeature of ActivityTransition.
-        activity.getWindow().getDecorView();
-        FragmentManager fragmentManager = activity.getSupportFragmentManager();
-        if (fragmentManager.findFragmentByTag(TAG_LEAN_BACK_ACTIONS_FRAGMENT) != null) {
-            Log.w(TAG, "Fragment is already exists, likely calling "
-                    + "addAsRoot() when savedInstanceState is not null in Activity.onCreate().");
-            return -1;
-        }
-        FragmentTransaction ft = fragmentManager.beginTransaction();
-        fragment.setUiStyle(UI_STYLE_ACTIVITY_ROOT);
-        return ft.replace(id, fragment, TAG_LEAN_BACK_ACTIONS_FRAGMENT).commit();
-    }
-
-    /**
-     * Returns the current GuidedStepSupportFragment on the fragment transaction stack.
-     * @return The current GuidedStepSupportFragment, if any, on the fragment transaction stack.
-     */
-    public static GuidedStepSupportFragment getCurrentGuidedStepSupportFragment(FragmentManager fm) {
-        Fragment f = fm.findFragmentByTag(TAG_LEAN_BACK_ACTIONS_FRAGMENT);
-        if (f instanceof GuidedStepSupportFragment) {
-            return (GuidedStepSupportFragment) f;
-        }
-        return null;
-    }
-
-    /**
-     * Returns the GuidanceStylist that displays guidance information for the user.
-     * @return The GuidanceStylist for this fragment.
-     */
-    public GuidanceStylist getGuidanceStylist() {
-        return mGuidanceStylist;
-    }
-
-    /**
-     * Returns the GuidedActionsStylist that displays the actions the user may take.
-     * @return The GuidedActionsStylist for this fragment.
-     */
-    public GuidedActionsStylist getGuidedActionsStylist() {
-        return mActionsStylist;
-    }
-
-    /**
-     * Returns the list of button GuidedActions that the user may take in this fragment.
-     * @return The list of button GuidedActions for this fragment.
-     */
-    public List<GuidedAction> getButtonActions() {
-        return mButtonActions;
-    }
-
-    /**
-     * Find button GuidedAction by Id.
-     * @param id  Id of the button action to search.
-     * @return  GuidedAction object or null if not found.
-     */
-    public GuidedAction findButtonActionById(long id) {
-        int index = findButtonActionPositionById(id);
-        return index >= 0 ? mButtonActions.get(index) : null;
-    }
-
-    /**
-     * Find button GuidedAction position in array by Id.
-     * @param id  Id of the button action to search.
-     * @return  position of GuidedAction object in array or -1 if not found.
-     */
-    public int findButtonActionPositionById(long id) {
-        if (mButtonActions != null) {
-            for (int i = 0; i < mButtonActions.size(); i++) {
-                GuidedAction action = mButtonActions.get(i);
-                if (mButtonActions.get(i).getId() == id) {
-                    return i;
-                }
-            }
-        }
-        return -1;
-    }
-
-    /**
-     * Returns the GuidedActionsStylist that displays the button actions the user may take.
-     * @return The GuidedActionsStylist for this fragment.
-     */
-    public GuidedActionsStylist getGuidedButtonActionsStylist() {
-        return mButtonActionsStylist;
-    }
-
-    /**
-     * Sets the list of button GuidedActions that the user may take in this fragment.
-     * @param actions The list of button GuidedActions for this fragment.
-     */
-    public void setButtonActions(List<GuidedAction> actions) {
-        mButtonActions = actions;
-        if (mButtonAdapter != null) {
-            mButtonAdapter.setActions(mButtonActions);
-        }
-    }
-
-    /**
-     * Notify an button action has changed and update its UI.
-     * @param position Position of the button GuidedAction in array.
-     */
-    public void notifyButtonActionChanged(int position) {
-        if (mButtonAdapter != null) {
-            mButtonAdapter.notifyItemChanged(position);
-        }
-    }
-
-    /**
-     * Returns the view corresponding to the button action at the indicated position in the list of
-     * actions for this fragment.
-     * @param position The integer position of the button action of interest.
-     * @return The View corresponding to the button action at the indicated position, or null if
-     * that action is not currently onscreen.
-     */
-    public View getButtonActionItemView(int position) {
-        final RecyclerView.ViewHolder holder = mButtonActionsStylist.getActionsGridView()
-                    .findViewHolderForPosition(position);
-        return holder == null ? null : holder.itemView;
-    }
-
-    /**
-     * Scrolls the action list to the position indicated, selecting that button action's view.
-     * @param position The integer position of the button action of interest.
-     */
-    public void setSelectedButtonActionPosition(int position) {
-        mButtonActionsStylist.getActionsGridView().setSelectedPosition(position);
-    }
-
-    /**
-     * Returns the position if the currently selected button GuidedAction.
-     * @return position The integer position of the currently selected button action.
-     */
-    public int getSelectedButtonActionPosition() {
-        return mButtonActionsStylist.getActionsGridView().getSelectedPosition();
-    }
-
-    /**
-     * Returns the list of GuidedActions that the user may take in this fragment.
-     * @return The list of GuidedActions for this fragment.
-     */
-    public List<GuidedAction> getActions() {
-        return mActions;
-    }
-
-    /**
-     * Find GuidedAction by Id.
-     * @param id  Id of the action to search.
-     * @return  GuidedAction object or null if not found.
-     */
-    public GuidedAction findActionById(long id) {
-        int index = findActionPositionById(id);
-        return index >= 0 ? mActions.get(index) : null;
-    }
-
-    /**
-     * Find GuidedAction position in array by Id.
-     * @param id  Id of the action to search.
-     * @return  position of GuidedAction object in array or -1 if not found.
-     */
-    public int findActionPositionById(long id) {
-        if (mActions != null) {
-            for (int i = 0; i < mActions.size(); i++) {
-                GuidedAction action = mActions.get(i);
-                if (mActions.get(i).getId() == id) {
-                    return i;
-                }
-            }
-        }
-        return -1;
-    }
-
-    /**
-     * Sets the list of GuidedActions that the user may take in this fragment.
-     * @param actions The list of GuidedActions for this fragment.
-     */
-    public void setActions(List<GuidedAction> actions) {
-        mActions = actions;
-        if (mAdapter != null) {
-            mAdapter.setActions(mActions);
-        }
-    }
-
-    /**
-     * Notify an action has changed and update its UI.
-     * @param position Position of the GuidedAction in array.
-     */
-    public void notifyActionChanged(int position) {
-        if (mAdapter != null) {
-            mAdapter.notifyItemChanged(position);
-        }
-    }
-
-    /**
-     * Returns the view corresponding to the action at the indicated position in the list of
-     * actions for this fragment.
-     * @param position The integer position of the action of interest.
-     * @return The View corresponding to the action at the indicated position, or null if that
-     * action is not currently onscreen.
-     */
-    public View getActionItemView(int position) {
-        final RecyclerView.ViewHolder holder = mActionsStylist.getActionsGridView()
-                    .findViewHolderForPosition(position);
-        return holder == null ? null : holder.itemView;
-    }
-
-    /**
-     * Scrolls the action list to the position indicated, selecting that action's view.
-     * @param position The integer position of the action of interest.
-     */
-    public void setSelectedActionPosition(int position) {
-        mActionsStylist.getActionsGridView().setSelectedPosition(position);
-    }
-
-    /**
-     * Returns the position if the currently selected GuidedAction.
-     * @return position The integer position of the currently selected action.
-     */
-    public int getSelectedActionPosition() {
-        return mActionsStylist.getActionsGridView().getSelectedPosition();
-    }
-
-    /**
-     * Called by Constructor to provide fragment transitions.  The default implementation assigns
-     * transitions based on {@link #getUiStyle()}:
-     * <ul>
-     * <li> {@link #UI_STYLE_REPLACE} Slide from/to end(right) for enter transition, slide from/to
-     * start(left) for exit transition, shared element enter transition is set to ChangeBounds.
-     * <li> {@link #UI_STYLE_ENTRANCE} Enter transition is set to slide from both sides, exit
-     * transition is same as {@link #UI_STYLE_REPLACE}, no shared element enter transition.
-     * <li> {@link #UI_STYLE_ACTIVITY_ROOT} Enter transition is set to null and app should rely on
-     * activity transition, exit transition is same as {@link #UI_STYLE_REPLACE}, no shared element
-     * enter transition.
-     * </ul>
-     * <p>
-     * The default implementation heavily relies on {@link GuidedActionsStylist} and
-     * {@link GuidanceStylist} layout, app may override this method when modifying the default
-     * layout of {@link GuidedActionsStylist} or {@link GuidanceStylist}.
-     * <p>
-     * TIP: because the fragment view is removed during fragment transition, in general app cannot
-     * use two Visibility transition together. Workaround is to create your own Visibility
-     * transition that controls multiple animators (e.g. slide and fade animation in one Transition
-     * class).
-     */
-    protected void onProvideFragmentTransitions() {
-        if (Build.VERSION.SDK_INT >= 21) {
-            final int uiStyle = getUiStyle();
-            if (uiStyle == UI_STYLE_REPLACE) {
-                Object enterTransition = TransitionHelper.createFadeAndShortSlide(Gravity.END);
-                TransitionHelper.exclude(enterTransition, R.id.guidedstep_background, true);
-                TransitionHelper.exclude(enterTransition, R.id.guidedactions_sub_list_background,
-                        true);
-                TransitionHelper.setEnterTransition(this, enterTransition);
-
-                Object fade = TransitionHelper.createFadeTransition(
-                        TransitionHelper.FADE_IN | TransitionHelper.FADE_OUT);
-                TransitionHelper.include(fade, R.id.guidedactions_sub_list_background);
-                Object changeBounds = TransitionHelper.createChangeBounds(false);
-                Object sharedElementTransition = TransitionHelper.createTransitionSet(false);
-                TransitionHelper.addTransition(sharedElementTransition, fade);
-                TransitionHelper.addTransition(sharedElementTransition, changeBounds);
-                TransitionHelper.setSharedElementEnterTransition(this, sharedElementTransition);
-            } else if (uiStyle == UI_STYLE_ENTRANCE) {
-                if (entranceTransitionType == SLIDE_FROM_SIDE) {
-                    Object fade = TransitionHelper.createFadeTransition(
-                            TransitionHelper.FADE_IN | TransitionHelper.FADE_OUT);
-                    TransitionHelper.include(fade, R.id.guidedstep_background);
-                    Object slideFromSide = TransitionHelper.createFadeAndShortSlide(
-                            Gravity.END | Gravity.START);
-                    TransitionHelper.include(slideFromSide, R.id.content_fragment);
-                    TransitionHelper.include(slideFromSide, R.id.action_fragment_root);
-                    Object enterTransition = TransitionHelper.createTransitionSet(false);
-                    TransitionHelper.addTransition(enterTransition, fade);
-                    TransitionHelper.addTransition(enterTransition, slideFromSide);
-                    TransitionHelper.setEnterTransition(this, enterTransition);
-                } else {
-                    Object slideFromBottom = TransitionHelper.createFadeAndShortSlide(
-                            Gravity.BOTTOM);
-                    TransitionHelper.include(slideFromBottom, R.id.guidedstep_background_view_root);
-                    Object enterTransition = TransitionHelper.createTransitionSet(false);
-                    TransitionHelper.addTransition(enterTransition, slideFromBottom);
-                    TransitionHelper.setEnterTransition(this, enterTransition);
-                }
-                // No shared element transition
-                TransitionHelper.setSharedElementEnterTransition(this, null);
-            } else if (uiStyle == UI_STYLE_ACTIVITY_ROOT) {
-                // for Activity root, we don't need enter transition, use activity transition
-                TransitionHelper.setEnterTransition(this, null);
-                // No shared element transition
-                TransitionHelper.setSharedElementEnterTransition(this, null);
-            }
-            // exitTransition is same for all style
-            Object exitTransition = TransitionHelper.createFadeAndShortSlide(Gravity.START);
-            TransitionHelper.exclude(exitTransition, R.id.guidedstep_background, true);
-            TransitionHelper.exclude(exitTransition, R.id.guidedactions_sub_list_background,
-                    true);
-            TransitionHelper.setExitTransition(this, exitTransition);
-        }
-    }
-
-    /**
-     * Called by onCreateView to inflate background view.  Default implementation loads view
-     * from {@link R.layout#lb_guidedstep_background} which holds a reference to
-     * guidedStepBackground.
-     * @param inflater LayoutInflater to load background view.
-     * @param container Parent view of background view.
-     * @param savedInstanceState
-     * @return Created background view or null if no background.
-     */
-    public View onCreateBackgroundView(LayoutInflater inflater, ViewGroup container,
-            Bundle savedInstanceState) {
-        return inflater.inflate(R.layout.lb_guidedstep_background, container, false);
-    }
-
-    /**
-     * Set UI style to fragment arguments. Default value is {@link #UI_STYLE_ENTRANCE} when fragment
-     * is first initialized. UI style is used to choose different fragment transition animations and
-     * determine if this is the first GuidedStepSupportFragment on backstack. In most cases app does not
-     * directly call this method, app calls helper function
-     * {@link #add(FragmentManager, GuidedStepSupportFragment, int)}. However if the app creates Fragment
-     * transaction and controls backstack by itself, it would need call setUiStyle() to select the
-     * fragment transition to use.
-     *
-     * @param style {@link #UI_STYLE_ACTIVITY_ROOT} {@link #UI_STYLE_REPLACE} or
-     *        {@link #UI_STYLE_ENTRANCE}.
-     */
-    public void setUiStyle(int style) {
-        int oldStyle = getUiStyle();
-        Bundle arguments = getArguments();
-        boolean isNew = false;
-        if (arguments == null) {
-            arguments = new Bundle();
-            isNew = true;
-        }
-        arguments.putInt(EXTRA_UI_STYLE, style);
-        // call setArgument() will validate if the fragment is already added.
-        if (isNew) {
-            setArguments(arguments);
-        }
-        if (style != oldStyle) {
-            onProvideFragmentTransitions();
-        }
-    }
-
-    /**
-     * Read UI style from fragment arguments.  Default value is {@link #UI_STYLE_ENTRANCE} when
-     * fragment is first initialized.  UI style is used to choose different fragment transition
-     * animations and determine if this is the first GuidedStepSupportFragment on backstack.
-     *
-     * @return {@link #UI_STYLE_ACTIVITY_ROOT} {@link #UI_STYLE_REPLACE} or
-     * {@link #UI_STYLE_ENTRANCE}.
-     * @see #onProvideFragmentTransitions()
-     */
-    public int getUiStyle() {
-        Bundle b = getArguments();
-        if (b == null) return UI_STYLE_ENTRANCE;
-        return b.getInt(EXTRA_UI_STYLE, UI_STYLE_ENTRANCE);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        if (DEBUG) Log.v(TAG, "onCreate");
-        // Set correct transition from saved arguments.
-        onProvideFragmentTransitions();
-
-        ArrayList<GuidedAction> actions = new ArrayList<GuidedAction>();
-        onCreateActions(actions, savedInstanceState);
-        if (savedInstanceState != null) {
-            onRestoreActions(actions, savedInstanceState);
-        }
-        setActions(actions);
-        ArrayList<GuidedAction> buttonActions = new ArrayList<GuidedAction>();
-        onCreateButtonActions(buttonActions, savedInstanceState);
-        if (savedInstanceState != null) {
-            onRestoreButtonActions(buttonActions, savedInstanceState);
-        }
-        setButtonActions(buttonActions);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public void onDestroyView() {
-        mGuidanceStylist.onDestroyView();
-        mActionsStylist.onDestroyView();
-        mButtonActionsStylist.onDestroyView();
-        mAdapter = null;
-        mSubAdapter =  null;
-        mButtonAdapter = null;
-        mAdapterGroup = null;
-        super.onDestroyView();
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public View onCreateView(LayoutInflater inflater, ViewGroup container,
-            Bundle savedInstanceState) {
-        if (DEBUG) Log.v(TAG, "onCreateView");
-
-        resolveTheme();
-        inflater = getThemeInflater(inflater);
-
-        GuidedStepRootLayout root = (GuidedStepRootLayout) inflater.inflate(
-                R.layout.lb_guidedstep_fragment, container, false);
-
-        root.setFocusOutStart(isFocusOutStartAllowed());
-        root.setFocusOutEnd(isFocusOutEndAllowed());
-
-        ViewGroup guidanceContainer = (ViewGroup) root.findViewById(R.id.content_fragment);
-        ViewGroup actionContainer = (ViewGroup) root.findViewById(R.id.action_fragment);
-        ((NonOverlappingLinearLayout) actionContainer).setFocusableViewAvailableFixEnabled(true);
-
-        Guidance guidance = onCreateGuidance(savedInstanceState);
-        View guidanceView = mGuidanceStylist.onCreateView(inflater, guidanceContainer, guidance);
-        guidanceContainer.addView(guidanceView);
-
-        View actionsView = mActionsStylist.onCreateView(inflater, actionContainer);
-        actionContainer.addView(actionsView);
-
-        View buttonActionsView = mButtonActionsStylist.onCreateView(inflater, actionContainer);
-        actionContainer.addView(buttonActionsView);
-
-        GuidedActionAdapter.EditListener editListener = new GuidedActionAdapter.EditListener() {
-
-                @Override
-                public void onImeOpen() {
-                    runImeAnimations(true);
-                }
-
-                @Override
-                public void onImeClose() {
-                    runImeAnimations(false);
-                }
-
-                @Override
-                public long onGuidedActionEditedAndProceed(GuidedAction action) {
-                    return GuidedStepSupportFragment.this.onGuidedActionEditedAndProceed(action);
-                }
-
-                @Override
-                public void onGuidedActionEditCanceled(GuidedAction action) {
-                    GuidedStepSupportFragment.this.onGuidedActionEditCanceled(action);
-                }
-        };
-
-        mAdapter = new GuidedActionAdapter(mActions, new GuidedActionAdapter.ClickListener() {
-            @Override
-            public void onGuidedActionClicked(GuidedAction action) {
-                GuidedStepSupportFragment.this.onGuidedActionClicked(action);
-                if (isExpanded()) {
-                    collapseAction(true);
-                } else if (action.hasSubActions() || action.hasEditableActivatorView()) {
-                    expandAction(action, true);
-                }
-            }
-        }, this, mActionsStylist, false);
-        mButtonAdapter =
-                new GuidedActionAdapter(mButtonActions, new GuidedActionAdapter.ClickListener() {
-                    @Override
-                    public void onGuidedActionClicked(GuidedAction action) {
-                        GuidedStepSupportFragment.this.onGuidedActionClicked(action);
-                    }
-                }, this, mButtonActionsStylist, false);
-        mSubAdapter = new GuidedActionAdapter(null, new GuidedActionAdapter.ClickListener() {
-            @Override
-            public void onGuidedActionClicked(GuidedAction action) {
-                if (mActionsStylist.isInExpandTransition()) {
-                    return;
-                }
-                if (GuidedStepSupportFragment.this.onSubGuidedActionClicked(action)) {
-                    collapseSubActions();
-                }
-            }
-        }, this, mActionsStylist, true);
-        mAdapterGroup = new GuidedActionAdapterGroup();
-        mAdapterGroup.addAdpter(mAdapter, mButtonAdapter);
-        mAdapterGroup.addAdpter(mSubAdapter, null);
-        mAdapterGroup.setEditListener(editListener);
-        mActionsStylist.setEditListener(editListener);
-
-        mActionsStylist.getActionsGridView().setAdapter(mAdapter);
-        if (mActionsStylist.getSubActionsGridView() != null) {
-            mActionsStylist.getSubActionsGridView().setAdapter(mSubAdapter);
-        }
-        mButtonActionsStylist.getActionsGridView().setAdapter(mButtonAdapter);
-        if (mButtonActions.size() == 0) {
-            // when there is no button actions, we don't need show the second panel, but keep
-            // the width zero to run ChangeBounds transition.
-            LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
-                    buttonActionsView.getLayoutParams();
-            lp.weight = 0;
-            buttonActionsView.setLayoutParams(lp);
-        } else {
-            // when there are two actions panel, we need adjust the weight of action to
-            // guidedActionContentWidthWeightTwoPanels.
-            Context ctx = mThemeWrapper != null ? mThemeWrapper : getContext();
-            TypedValue typedValue = new TypedValue();
-            if (ctx.getTheme().resolveAttribute(R.attr.guidedActionContentWidthWeightTwoPanels,
-                    typedValue, true)) {
-                View actionsRoot = root.findViewById(R.id.action_fragment_root);
-                float weight = typedValue.getFloat();
-                LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) actionsRoot
-                        .getLayoutParams();
-                lp.weight = weight;
-                actionsRoot.setLayoutParams(lp);
-            }
-        }
-
-        // Add the background view.
-        View backgroundView = onCreateBackgroundView(inflater, root, savedInstanceState);
-        if (backgroundView != null) {
-            FrameLayout backgroundViewRoot = (FrameLayout)root.findViewById(
-                R.id.guidedstep_background_view_root);
-            backgroundViewRoot.addView(backgroundView, 0);
-        }
-
-        return root;
-    }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-        getView().findViewById(R.id.action_fragment).requestFocus();
-    }
-
-    /**
-     * Get the key will be used to save GuidedAction with Fragment.
-     * @param action GuidedAction to get key.
-     * @return Key to save the GuidedAction.
-     */
-    final String getAutoRestoreKey(GuidedAction action) {
-        return EXTRA_ACTION_PREFIX + action.getId();
-    }
-
-    /**
-     * Get the key will be used to save GuidedAction with Fragment.
-     * @param action GuidedAction to get key.
-     * @return Key to save the GuidedAction.
-     */
-    final String getButtonAutoRestoreKey(GuidedAction action) {
-        return EXTRA_BUTTON_ACTION_PREFIX + action.getId();
-    }
-
-    final static boolean isSaveEnabled(GuidedAction action) {
-        return action.isAutoSaveRestoreEnabled() && action.getId() != GuidedAction.NO_ID;
-    }
-
-    final void onRestoreActions(List<GuidedAction> actions, Bundle savedInstanceState) {
-        for (int i = 0, size = actions.size(); i < size; i++) {
-            GuidedAction action = actions.get(i);
-            if (isSaveEnabled(action)) {
-                action.onRestoreInstanceState(savedInstanceState, getAutoRestoreKey(action));
-            }
-        }
-    }
-
-    final void onRestoreButtonActions(List<GuidedAction> actions, Bundle savedInstanceState) {
-        for (int i = 0, size = actions.size(); i < size; i++) {
-            GuidedAction action = actions.get(i);
-            if (isSaveEnabled(action)) {
-                action.onRestoreInstanceState(savedInstanceState, getButtonAutoRestoreKey(action));
-            }
-        }
-    }
-
-    final void onSaveActions(List<GuidedAction> actions, Bundle outState) {
-        for (int i = 0, size = actions.size(); i < size; i++) {
-            GuidedAction action = actions.get(i);
-            if (isSaveEnabled(action)) {
-                action.onSaveInstanceState(outState, getAutoRestoreKey(action));
-            }
-        }
-    }
-
-    final void onSaveButtonActions(List<GuidedAction> actions, Bundle outState) {
-        for (int i = 0, size = actions.size(); i < size; i++) {
-            GuidedAction action = actions.get(i);
-            if (isSaveEnabled(action)) {
-                action.onSaveInstanceState(outState, getButtonAutoRestoreKey(action));
-            }
-        }
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public void onSaveInstanceState(Bundle outState) {
-        super.onSaveInstanceState(outState);
-        onSaveActions(mActions, outState);
-        onSaveButtonActions(mButtonActions, outState);
-    }
-
-    private static boolean isGuidedStepTheme(Context context) {
-        int resId = R.attr.guidedStepThemeFlag;
-        TypedValue typedValue = new TypedValue();
-        boolean found = context.getTheme().resolveAttribute(resId, typedValue, true);
-        if (DEBUG) Log.v(TAG, "Found guided step theme flag? " + found);
-        return found && typedValue.type == TypedValue.TYPE_INT_BOOLEAN && typedValue.data != 0;
-    }
-
-    /**
-     * Convenient method to close GuidedStepSupportFragments on top of other content or finish Activity if
-     * GuidedStepSupportFragments were started in a separate activity.  Pops all stack entries including
-     * {@link #UI_STYLE_ENTRANCE}; if {@link #UI_STYLE_ENTRANCE} is not found, finish the activity.
-     * Note that this method must be paired with {@link #add(FragmentManager, GuidedStepSupportFragment,
-     * int)} which sets up the stack entry name for finding which fragment we need to pop back to.
-     */
-    public void finishGuidedStepSupportFragments() {
-        final FragmentManager fragmentManager = getFragmentManager();
-        final int entryCount = fragmentManager.getBackStackEntryCount();
-        if (entryCount > 0) {
-            for (int i = entryCount - 1; i >= 0; i--) {
-                BackStackEntry entry = fragmentManager.getBackStackEntryAt(i);
-                if (isStackEntryUiStyleEntrance(entry.getName())) {
-                    GuidedStepSupportFragment top = getCurrentGuidedStepSupportFragment(fragmentManager);
-                    if (top != null) {
-                        top.setUiStyle(UI_STYLE_ENTRANCE);
-                    }
-                    fragmentManager.popBackStackImmediate(entry.getId(),
-                            FragmentManager.POP_BACK_STACK_INCLUSIVE);
-                    return;
-                }
-            }
-        }
-        ActivityCompat.finishAfterTransition(getActivity());
-    }
-
-    /**
-     * Convenient method to pop to fragment with Given class.
-     * @param  guidedStepFragmentClass  Name of the Class of GuidedStepSupportFragment to pop to.
-     * @param flags Either 0 or {@link FragmentManager#POP_BACK_STACK_INCLUSIVE}.
-     */
-    public void popBackStackToGuidedStepSupportFragment(Class guidedStepFragmentClass, int flags) {
-        if (!GuidedStepSupportFragment.class.isAssignableFrom(guidedStepFragmentClass)) {
-            return;
-        }
-        final FragmentManager fragmentManager = getFragmentManager();
-        final int entryCount = fragmentManager.getBackStackEntryCount();
-        String className = guidedStepFragmentClass.getName();
-        if (entryCount > 0) {
-            for (int i = entryCount - 1; i >= 0; i--) {
-                BackStackEntry entry = fragmentManager.getBackStackEntryAt(i);
-                String entryClassName = getGuidedStepSupportFragmentClassName(entry.getName());
-                if (className.equals(entryClassName)) {
-                    fragmentManager.popBackStackImmediate(entry.getId(), flags);
-                    return;
-                }
-            }
-        }
-    }
-
-    /**
-     * Returns true if allows focus out of start edge of GuidedStepSupportFragment, false otherwise.
-     * Default value is false, the reason is to disable FocusFinder to find focusable views
-     * beneath content of GuidedStepSupportFragment.  Subclass may override.
-     * @return True if allows focus out of start edge of GuidedStepSupportFragment.
-     */
-    public boolean isFocusOutStartAllowed() {
-        return false;
-    }
-
-    /**
-     * Returns true if allows focus out of end edge of GuidedStepSupportFragment, false otherwise.
-     * Default value is false, the reason is to disable FocusFinder to find focusable views
-     * beneath content of GuidedStepSupportFragment.  Subclass may override.
-     * @return True if allows focus out of end edge of GuidedStepSupportFragment.
-     */
-    public boolean isFocusOutEndAllowed() {
-        return false;
-    }
-
-    /**
-     * Sets the transition type to be used for {@link #UI_STYLE_ENTRANCE} animation.
-     * Currently we provide 2 different variations for animation - slide in from
-     * side (default) or bottom.
-     *
-     * Ideally we can retrieve the screen mode settings from the theme attribute
-     * {@code Theme.Leanback.GuidedStep#guidedStepHeightWeight} and use that to
-     * determine the transition. But the fragment context to retrieve the theme
-     * isn't available on platform v23 or earlier.
-     *
-     * For now clients(subclasses) can call this method inside the constructor.
-     * @hide
-     */
-    @RestrictTo(LIBRARY_GROUP)
-    public void setEntranceTransitionType(int transitionType) {
-      this.entranceTransitionType = transitionType;
-    }
-
-    /**
-     * Opens the provided action in edit mode and raises ime. This can be
-     * used to programmatically skip the extra click required to go into edit mode. This method
-     * can be invoked in {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)}.
-     */
-    public void openInEditMode(GuidedAction action) {
-        mActionsStylist.openInEditMode(action);
-    }
-
-    private void resolveTheme() {
-        // Look up the guidedStepTheme in the currently specified theme.  If it exists,
-        // replace the theme with its value.
-        Context context = getContext();
-        int theme = onProvideTheme();
-        if (theme == -1 && !isGuidedStepTheme(context)) {
-            // Look up the guidedStepTheme in the activity's currently specified theme.  If it
-            // exists, replace the theme with its value.
-            int resId = R.attr.guidedStepTheme;
-            TypedValue typedValue = new TypedValue();
-            boolean found = context.getTheme().resolveAttribute(resId, typedValue, true);
-            if (DEBUG) Log.v(TAG, "Found guided step theme reference? " + found);
-            if (found) {
-                ContextThemeWrapper themeWrapper =
-                        new ContextThemeWrapper(context, typedValue.resourceId);
-                if (isGuidedStepTheme(themeWrapper)) {
-                    mThemeWrapper = themeWrapper;
-                } else {
-                    found = false;
-                    mThemeWrapper = null;
-                }
-            }
-            if (!found) {
-                Log.e(TAG, "GuidedStepSupportFragment does not have an appropriate theme set.");
-            }
-        } else if (theme != -1) {
-            mThemeWrapper = new ContextThemeWrapper(context, theme);
-        }
-    }
-
-    private LayoutInflater getThemeInflater(LayoutInflater inflater) {
-        if (mThemeWrapper == null) {
-            return inflater;
-        } else {
-            return inflater.cloneInContext(mThemeWrapper);
-        }
-    }
-
-    private int getFirstCheckedAction() {
-        for (int i = 0, size = mActions.size(); i < size; i++) {
-            if (mActions.get(i).isChecked()) {
-                return i;
-            }
-        }
-        return 0;
-    }
-
-    void runImeAnimations(boolean entering) {
-        ArrayList<Animator> animators = new ArrayList<Animator>();
-        if (entering) {
-            mGuidanceStylist.onImeAppearing(animators);
-            mActionsStylist.onImeAppearing(animators);
-            mButtonActionsStylist.onImeAppearing(animators);
-        } else {
-            mGuidanceStylist.onImeDisappearing(animators);
-            mActionsStylist.onImeDisappearing(animators);
-            mButtonActionsStylist.onImeDisappearing(animators);
-        }
-        AnimatorSet set = new AnimatorSet();
-        set.playTogether(animators);
-        set.start();
-    }
-}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/HeadersFragment.java b/v17/leanback/src/android/support/v17/leanback/app/HeadersFragment.java
deleted file mode 100644
index dd037d2..0000000
--- a/v17/leanback/src/android/support/v17/leanback/app/HeadersFragment.java
+++ /dev/null
@@ -1,303 +0,0 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from HeadersSupportFragment.java.  DO NOT MODIFY. */
-
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package android.support.v17.leanback.app;
-
-import android.content.Context;
-import android.graphics.Color;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.GradientDrawable;
-import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.v17.leanback.R;
-import android.support.v17.leanback.widget.ClassPresenterSelector;
-import android.support.v17.leanback.widget.DividerPresenter;
-import android.support.v17.leanback.widget.DividerRow;
-import android.support.v17.leanback.widget.FocusHighlightHelper;
-import android.support.v17.leanback.widget.HorizontalGridView;
-import android.support.v17.leanback.widget.ItemBridgeAdapter;
-import android.support.v17.leanback.widget.PresenterSelector;
-import android.support.v17.leanback.widget.Row;
-import android.support.v17.leanback.widget.RowHeaderPresenter;
-import android.support.v17.leanback.widget.SectionRow;
-import android.support.v17.leanback.widget.VerticalGridView;
-import android.support.v7.widget.RecyclerView;
-import android.view.View;
-import android.view.View.OnLayoutChangeListener;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-
-/**
- * An fragment containing a list of row headers. Implementation must support three types of rows:
- * <ul>
- *     <li>{@link DividerRow} rendered by {@link DividerPresenter}.</li>
- *     <li>{@link Row} rendered by {@link RowHeaderPresenter}.</li>
- *     <li>{@link SectionRow} rendered by {@link RowHeaderPresenter}.</li>
- * </ul>
- * Use {@link #setPresenterSelector(PresenterSelector)} in subclass constructor to customize
- * Presenters. App may override {@link BrowseFragment#onCreateHeadersFragment()}.
- */
-public class HeadersFragment extends BaseRowFragment {
-
-    /**
-     * Interface definition for a callback to be invoked when a header item is clicked.
-     */
-    public interface OnHeaderClickedListener {
-        /**
-         * Called when a header item has been clicked.
-         *
-         * @param viewHolder Row ViewHolder object corresponding to the selected Header.
-         * @param row Row object corresponding to the selected Header.
-         */
-        void onHeaderClicked(RowHeaderPresenter.ViewHolder viewHolder, Row row);
-    }
-
-    /**
-     * Interface definition for a callback to be invoked when a header item is selected.
-     */
-    public interface OnHeaderViewSelectedListener {
-        /**
-         * Called when a header item has been selected.
-         *
-         * @param viewHolder Row ViewHolder object corresponding to the selected Header.
-         * @param row Row object corresponding to the selected Header.
-         */
-        void onHeaderSelected(RowHeaderPresenter.ViewHolder viewHolder, Row row);
-    }
-
-    private OnHeaderViewSelectedListener mOnHeaderViewSelectedListener;
-    OnHeaderClickedListener mOnHeaderClickedListener;
-    private boolean mHeadersEnabled = true;
-    private boolean mHeadersGone = false;
-    private int mBackgroundColor;
-    private boolean mBackgroundColorSet;
-
-    private static final PresenterSelector sHeaderPresenter = new ClassPresenterSelector()
-            .addClassPresenter(DividerRow.class, new DividerPresenter())
-            .addClassPresenter(SectionRow.class,
-                    new RowHeaderPresenter(R.layout.lb_section_header, false))
-            .addClassPresenter(Row.class, new RowHeaderPresenter(R.layout.lb_header));
-
-    public HeadersFragment() {
-        setPresenterSelector(sHeaderPresenter);
-        FocusHighlightHelper.setupHeaderItemFocusHighlight(getBridgeAdapter());
-    }
-
-    public void setOnHeaderClickedListener(OnHeaderClickedListener listener) {
-        mOnHeaderClickedListener = listener;
-    }
-
-    public void setOnHeaderViewSelectedListener(OnHeaderViewSelectedListener listener) {
-        mOnHeaderViewSelectedListener = listener;
-    }
-
-    @Override
-    VerticalGridView findGridViewFromRoot(View view) {
-        return (VerticalGridView) view.findViewById(R.id.browse_headers);
-    }
-
-    @Override
-    void onRowSelected(RecyclerView parent, RecyclerView.ViewHolder viewHolder,
-            int position, int subposition) {
-        if (mOnHeaderViewSelectedListener != null) {
-            if (viewHolder != null && position >= 0) {
-                ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder) viewHolder;
-                mOnHeaderViewSelectedListener.onHeaderSelected(
-                        (RowHeaderPresenter.ViewHolder) vh.getViewHolder(), (Row) vh.getItem());
-            } else {
-                mOnHeaderViewSelectedListener.onHeaderSelected(null, null);
-            }
-        }
-    }
-
-    private final ItemBridgeAdapter.AdapterListener mAdapterListener =
-            new ItemBridgeAdapter.AdapterListener() {
-        @Override
-        public void onCreate(final ItemBridgeAdapter.ViewHolder viewHolder) {
-            View headerView = viewHolder.getViewHolder().view;
-            headerView.setOnClickListener(new View.OnClickListener() {
-                @Override
-                public void onClick(View v) {
-                    if (mOnHeaderClickedListener != null) {
-                        mOnHeaderClickedListener.onHeaderClicked(
-                                (RowHeaderPresenter.ViewHolder) viewHolder.getViewHolder(),
-                                (Row) viewHolder.getItem());
-                    }
-                }
-            });
-            if (mWrapper != null) {
-                viewHolder.itemView.addOnLayoutChangeListener(sLayoutChangeListener);
-            } else {
-                headerView.addOnLayoutChangeListener(sLayoutChangeListener);
-            }
-        }
-
-    };
-
-    static OnLayoutChangeListener sLayoutChangeListener = new OnLayoutChangeListener() {
-        @Override
-        public void onLayoutChange(View v, int left, int top, int right, int bottom,
-            int oldLeft, int oldTop, int oldRight, int oldBottom) {
-            v.setPivotX(v.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL ? v.getWidth() : 0);
-            v.setPivotY(v.getMeasuredHeight() / 2);
-        }
-    };
-
-    @Override
-    int getLayoutResourceId() {
-        return R.layout.lb_headers_fragment;
-    }
-
-    @Override
-    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
-        super.onViewCreated(view, savedInstanceState);
-        final VerticalGridView listView = getVerticalGridView();
-        if (listView == null) {
-            return;
-        }
-        if (mBackgroundColorSet) {
-            listView.setBackgroundColor(mBackgroundColor);
-            updateFadingEdgeToBrandColor(mBackgroundColor);
-        } else {
-            Drawable d = listView.getBackground();
-            if (d instanceof ColorDrawable) {
-                updateFadingEdgeToBrandColor(((ColorDrawable) d).getColor());
-            }
-        }
-        updateListViewVisibility();
-    }
-
-    private void updateListViewVisibility() {
-        final VerticalGridView listView = getVerticalGridView();
-        if (listView != null) {
-            getView().setVisibility(mHeadersGone ? View.GONE : View.VISIBLE);
-            if (!mHeadersGone) {
-                if (mHeadersEnabled) {
-                    listView.setChildrenVisibility(View.VISIBLE);
-                } else {
-                    listView.setChildrenVisibility(View.INVISIBLE);
-                }
-            }
-        }
-    }
-
-    void setHeadersEnabled(boolean enabled) {
-        mHeadersEnabled = enabled;
-        updateListViewVisibility();
-    }
-
-    void setHeadersGone(boolean gone) {
-        mHeadersGone = gone;
-        updateListViewVisibility();
-    }
-
-    static class NoOverlappingFrameLayout extends FrameLayout {
-
-        public NoOverlappingFrameLayout(Context context) {
-            super(context);
-        }
-
-        /**
-         * Avoid creating hardware layer for header dock.
-         */
-        @Override
-        public boolean hasOverlappingRendering() {
-            return false;
-        }
-    }
-
-    // Wrapper needed because of conflict between RecyclerView's use of alpha
-    // for ADD animations, and RowHeaderPresenter's use of alpha for selected level.
-    final ItemBridgeAdapter.Wrapper mWrapper = new ItemBridgeAdapter.Wrapper() {
-        @Override
-        public void wrap(View wrapper, View wrapped) {
-            ((FrameLayout) wrapper).addView(wrapped);
-        }
-
-        @Override
-        public View createWrapper(View root) {
-            return new NoOverlappingFrameLayout(root.getContext());
-        }
-    };
-    @Override
-    void updateAdapter() {
-        super.updateAdapter();
-        ItemBridgeAdapter adapter = getBridgeAdapter();
-        adapter.setAdapterListener(mAdapterListener);
-        adapter.setWrapper(mWrapper);
-    }
-
-    void setBackgroundColor(int color) {
-        mBackgroundColor = color;
-        mBackgroundColorSet = true;
-
-        if (getVerticalGridView() != null) {
-            getVerticalGridView().setBackgroundColor(mBackgroundColor);
-            updateFadingEdgeToBrandColor(mBackgroundColor);
-        }
-    }
-
-    private void updateFadingEdgeToBrandColor(int backgroundColor) {
-        View fadingView = getView().findViewById(R.id.fade_out_edge);
-        Drawable background = fadingView.getBackground();
-        if (background instanceof GradientDrawable) {
-            background.mutate();
-            ((GradientDrawable) background).setColors(
-                    new int[] {Color.TRANSPARENT, backgroundColor});
-        }
-    }
-
-    @Override
-    public void onTransitionStart() {
-        super.onTransitionStart();
-        if (!mHeadersEnabled) {
-            // When enabling headers fragment,  the RowHeaderView gets a focus but
-            // isShown() is still false because its parent is INVISIBLE, accessibility
-            // event is not sent.
-            // Workaround is: prevent focus to a child view during transition and put
-            // focus on it after transition is done.
-            final VerticalGridView listView = getVerticalGridView();
-            if (listView != null) {
-                listView.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
-                if (listView.hasFocus()) {
-                    listView.requestFocus();
-                }
-            }
-        }
-    }
-
-    @Override
-    public void onTransitionEnd() {
-        if (mHeadersEnabled) {
-            final VerticalGridView listView = getVerticalGridView();
-            if (listView != null) {
-                listView.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
-                if (listView.hasFocus()) {
-                    listView.requestFocus();
-                }
-            }
-        }
-        super.onTransitionEnd();
-    }
-
-    public boolean isScrolling() {
-        return getVerticalGridView().getScrollState()
-                != HorizontalGridView.SCROLL_STATE_IDLE;
-    }
-}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/ListRowDataAdapter.java b/v17/leanback/src/android/support/v17/leanback/app/ListRowDataAdapter.java
deleted file mode 100644
index f9af12f..0000000
--- a/v17/leanback/src/android/support/v17/leanback/app/ListRowDataAdapter.java
+++ /dev/null
@@ -1,162 +0,0 @@
-package android.support.v17.leanback.app;
-
-import android.support.v17.leanback.widget.ObjectAdapter;
-import android.support.v17.leanback.widget.Row;
-
-/**
- * Wrapper class for {@link ObjectAdapter} used by {@link BrowseFragment} to initialize
- * {@link RowsFragment}. We use invisible rows to represent
- * {@link android.support.v17.leanback.widget.DividerRow},
- * {@link android.support.v17.leanback.widget.SectionRow} and
- * {@link android.support.v17.leanback.widget.PageRow} in RowsFragment. In case we have an
- * invisible row at the end of a RowsFragment, it creates a jumping effect as the layout manager
- * 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.
- */
-class ListRowDataAdapter extends ObjectAdapter {
-    public static final int ON_ITEM_RANGE_CHANGED = 2;
-    public static final int ON_ITEM_RANGE_INSERTED = 4;
-    public static final int ON_ITEM_RANGE_REMOVED = 8;
-    public static final int ON_CHANGED = 16;
-
-    private final ObjectAdapter mAdapter;
-    int mLastVisibleRowIndex;
-
-    public ListRowDataAdapter(ObjectAdapter adapter) {
-        super(adapter.getPresenterSelector());
-        this.mAdapter = adapter;
-        initialize();
-
-        // If an user implements its own ObjectAdapter, notification corresponding to data
-        // updates can be batched e.g. remove, add might be followed by notifyRemove, notifyAdd.
-        // But underlying data would have changed during the notifyRemove call by the previous add
-        // 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());
-        } else {
-            mAdapter.registerObserver(new QueueBasedDataObserver());
-        }
-    }
-
-    void initialize() {
-        mLastVisibleRowIndex = -1;
-        int i = mAdapter.size() - 1;
-        while (i >= 0) {
-            Row item = (Row) mAdapter.get(i);
-            if (item.isRenderedAsRowView()) {
-                mLastVisibleRowIndex = i;
-                break;
-            }
-            i--;
-        }
-    }
-
-    @Override
-    public int size() {
-        return mLastVisibleRowIndex + 1;
-    }
-
-    @Override
-    public Object get(int index) {
-        return mAdapter.get(index);
-    }
-
-    void doNotify(int eventType, int positionStart, int itemCount) {
-        switch (eventType) {
-            case ON_ITEM_RANGE_CHANGED:
-                notifyItemRangeChanged(positionStart, itemCount);
-                break;
-            case ON_ITEM_RANGE_INSERTED:
-                notifyItemRangeInserted(positionStart, itemCount);
-                break;
-            case ON_ITEM_RANGE_REMOVED:
-                notifyItemRangeRemoved(positionStart, itemCount);
-                break;
-            case ON_CHANGED:
-                notifyChanged();
-                break;
-            default:
-                throw new IllegalArgumentException("Invalid event type " + eventType);
-        }
-    }
-
-    private class SimpleDataObserver extends DataObserver {
-
-        SimpleDataObserver() {
-        }
-
-        @Override
-        public void onItemRangeChanged(int positionStart, int itemCount) {
-            if (positionStart <= mLastVisibleRowIndex) {
-                onEventFired(ON_ITEM_RANGE_CHANGED, positionStart,
-                        Math.min(itemCount, mLastVisibleRowIndex - positionStart + 1));
-            }
-        }
-
-        @Override
-        public void onItemRangeInserted(int positionStart, int itemCount) {
-            if (positionStart <= mLastVisibleRowIndex) {
-                mLastVisibleRowIndex += itemCount;
-                onEventFired(ON_ITEM_RANGE_INSERTED, positionStart, itemCount);
-                return;
-            }
-
-            int lastVisibleRowIndex = mLastVisibleRowIndex;
-            initialize();
-            if (mLastVisibleRowIndex > lastVisibleRowIndex) {
-                int totalItems = mLastVisibleRowIndex - lastVisibleRowIndex;
-                onEventFired(ON_ITEM_RANGE_INSERTED, lastVisibleRowIndex + 1, totalItems);
-            }
-        }
-
-        @Override
-        public void onItemRangeRemoved(int positionStart, int itemCount) {
-            if (positionStart + itemCount - 1 < mLastVisibleRowIndex) {
-                mLastVisibleRowIndex -= itemCount;
-                onEventFired(ON_ITEM_RANGE_REMOVED, positionStart, itemCount);
-                return;
-            }
-
-            int lastVisibleRowIndex = mLastVisibleRowIndex;
-            initialize();
-            int totalItems = lastVisibleRowIndex - mLastVisibleRowIndex;
-            if (totalItems > 0) {
-                onEventFired(ON_ITEM_RANGE_REMOVED,
-                        Math.min(mLastVisibleRowIndex + 1, positionStart),
-                        totalItems);
-            }
-        }
-
-        @Override
-        public void onChanged() {
-            initialize();
-            onEventFired(ON_CHANGED, -1, -1);
-        }
-
-        protected void onEventFired(int eventType, int positionStart, int itemCount) {
-            doNotify(eventType, positionStart, itemCount);
-        }
-    }
-
-
-    /**
-     * When using custom {@link ObjectAdapter}, it's possible that the user may make multiple
-     * changes to the underlying data at once. The notifications about those updates may be
-     * batched and the underlying data would have changed to reflect latest updates as opposed
-     * to intermediate changes. In order to force RecyclerView to refresh the view with access
-     * only to the final data, we call notifyChange().
-     */
-    private class QueueBasedDataObserver extends DataObserver {
-
-        QueueBasedDataObserver() {
-        }
-
-        @Override
-        public void onChanged() {
-            initialize();
-            notifyChanged();
-        }
-    }
-}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/OnboardingFragment.java b/v17/leanback/src/android/support/v17/leanback/app/OnboardingFragment.java
deleted file mode 100644
index b69d5a7..0000000
--- a/v17/leanback/src/android/support/v17/leanback/app/OnboardingFragment.java
+++ /dev/null
@@ -1,1025 +0,0 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from OnboardingSupportFragment.java.  DO NOT MODIFY. */
-
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.v17.leanback.app;
-
-import android.animation.Animator;
-import android.animation.AnimatorInflater;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.animation.TimeInterpolator;
-import android.content.Context;
-import android.graphics.Color;
-import android.os.Bundle;
-import android.support.annotation.ColorInt;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.v17.leanback.R;
-import android.support.v17.leanback.widget.PagingIndicator;
-import android.app.Fragment;
-import android.util.Log;
-import android.util.TypedValue;
-import android.view.ContextThemeWrapper;
-import android.view.Gravity;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.View.OnKeyListener;
-import android.view.ViewGroup;
-import android.view.ViewTreeObserver.OnPreDrawListener;
-import android.view.animation.AccelerateInterpolator;
-import android.view.animation.DecelerateInterpolator;
-import android.widget.Button;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * An OnboardingFragment provides a common and simple way to build onboarding screen for
- * applications.
- * <p>
- * <h3>Building the screen</h3>
- * The view structure of onboarding screen is composed of the common parts and custom parts. The
- * common parts are composed of icon, title, description and page navigator and the custom parts
- * are composed of background, contents and foreground.
- * <p>
- * To build the screen views, the inherited class should override:
- * <ul>
- * <li>{@link #onCreateBackgroundView} to provide the background view. Background view has the same
- * size as the screen and the lowest z-order.</li>
- * <li>{@link #onCreateContentView} to provide the contents view. The content view is located in
- * the content area at the center of the screen.</li>
- * <li>{@link #onCreateForegroundView} to provide the foreground view. Foreground view has the same
- * size as the screen and the highest z-order</li>
- * </ul>
- * <p>
- * Each of these methods can return {@code null} if the application doesn't want to provide it.
- * <p>
- * <h3>Page information</h3>
- * The onboarding screen may have several pages which explain the functionality of the application.
- * The inherited class should provide the page information by overriding the methods:
- * <p>
- * <ul>
- * <li>{@link #getPageCount} to provide the number of pages.</li>
- * <li>{@link #getPageTitle} to provide the title of the page.</li>
- * <li>{@link #getPageDescription} to provide the description of the page.</li>
- * </ul>
- * <p>
- * Note that the information is used in {@link #onCreateView}, so should be initialized before
- * calling {@code super.onCreateView}.
- * <p>
- * <h3>Animation</h3>
- * Onboarding screen has three kinds of animations:
- * <p>
- * <h4>Logo Splash Animation</a></h4>
- * When onboarding screen appears, the logo splash animation is played by default. The animation
- * fades in the logo image, pauses in a few seconds and fades it out.
- * <p>
- * In most cases, the logo animation needs to be customized because the logo images of applications
- * are different from each other, or some applications may want to show their own animations.
- * <p>
- * The logo animation can be customized in two ways:
- * <ul>
- * <li>The simplest way is to provide the logo image by calling {@link #setLogoResourceId} to show
- * the default logo animation. This method should be called in {@link Fragment#onCreateView}.</li>
- * <li>If the logo animation is complex, then override {@link #onCreateLogoAnimation} and return the
- * {@link Animator} object to run.</li>
- * </ul>
- * <p>
- * If the inherited class provides neither the logo image nor the animation, the logo animation will
- * be omitted.
- * <h4>Page enter animation</h4>
- * After logo animation finishes, page enter animation starts, which causes the header section -
- * title and description views to fade and slide in. Users can override the default
- * fade + slide animation by overriding {@link #onCreateTitleAnimator()} &
- * {@link #onCreateDescriptionAnimator()}. By default we don't animate the custom views but users
- * can provide animation by overriding {@link #onCreateEnterAnimation}.
- *
- * <h4>Page change animation</h4>
- * When the page changes, the default animations of the title and description are played. The
- * inherited class can override {@link #onPageChanged} to start the custom animations.
- * <p>
- * <h3>Finishing the screen</h3>
- * <p>
- * If the user finishes the onboarding screen after navigating all the pages,
- * {@link #onFinishFragment} is called. The inherited class can override this method to show another
- * fragment or activity, or just remove this fragment.
- * <p>
- * <h3>Theming</h3>
- * <p>
- * OnboardingFragment must have access to an appropriate theme. Specifically, the fragment must
- * receive  {@link R.style#Theme_Leanback_Onboarding}, or a theme whose parent is set to that theme.
- * Themes can be provided in one of three ways:
- * <ul>
- * <li>The simplest way is to set the theme for the host Activity to the Onboarding theme or a theme
- * that derives from it.</li>
- * <li>If the Activity already has a theme and setting its parent theme is inconvenient, the
- * existing Activity theme can have an entry added for the attribute
- * {@link R.styleable#LeanbackOnboardingTheme_onboardingTheme}. If present, this theme will be used
- * by OnboardingFragment as an overlay to the Activity's theme.</li>
- * <li>Finally, custom subclasses of OnboardingFragment may provide a theme through the
- * {@link #onProvideTheme} method. This can be useful if a subclass is used across multiple
- * Activities.</li>
- * </ul>
- * <p>
- * If the theme is provided in multiple ways, the onProvideTheme override has priority, followed by
- * the Activity's theme. (Themes whose parent theme is already set to the onboarding theme do not
- * need to set the onboardingTheme attribute; if set, it will be ignored.)
- *
- * @attr ref R.styleable#LeanbackOnboardingTheme_onboardingTheme
- * @attr ref R.styleable#LeanbackOnboardingTheme_onboardingHeaderStyle
- * @attr ref R.styleable#LeanbackOnboardingTheme_onboardingTitleStyle
- * @attr ref R.styleable#LeanbackOnboardingTheme_onboardingDescriptionStyle
- * @attr ref R.styleable#LeanbackOnboardingTheme_onboardingNavigatorContainerStyle
- * @attr ref R.styleable#LeanbackOnboardingTheme_onboardingPageIndicatorStyle
- * @attr ref R.styleable#LeanbackOnboardingTheme_onboardingStartButtonStyle
- * @attr ref R.styleable#LeanbackOnboardingTheme_onboardingLogoStyle
- */
-abstract public class OnboardingFragment extends Fragment {
-    private static final String TAG = "OnboardingF";
-    private static final boolean DEBUG = false;
-
-    private static final long LOGO_SPLASH_PAUSE_DURATION_MS = 1333;
-
-    private static final long HEADER_ANIMATION_DURATION_MS = 417;
-    private static final long DESCRIPTION_START_DELAY_MS = 33;
-    private static final long HEADER_APPEAR_DELAY_MS = 500;
-    private static final int SLIDE_DISTANCE = 60;
-
-    private static int sSlideDistance;
-
-    private static final TimeInterpolator HEADER_APPEAR_INTERPOLATOR = new DecelerateInterpolator();
-    private static final TimeInterpolator HEADER_DISAPPEAR_INTERPOLATOR =
-            new AccelerateInterpolator();
-
-    // Keys used to save and restore the states.
-    private static final String KEY_CURRENT_PAGE_INDEX = "leanback.onboarding.current_page_index";
-    private static final String KEY_LOGO_ANIMATION_FINISHED =
-            "leanback.onboarding.logo_animation_finished";
-    private static final String KEY_ENTER_ANIMATION_FINISHED =
-            "leanback.onboarding.enter_animation_finished";
-
-    private ContextThemeWrapper mThemeWrapper;
-
-    PagingIndicator mPageIndicator;
-    View mStartButton;
-    private ImageView mLogoView;
-    // Optional icon that can be displayed on top of the header section.
-    private ImageView mMainIconView;
-    private int mIconResourceId;
-
-    TextView mTitleView;
-    TextView mDescriptionView;
-
-    boolean mIsLtr;
-
-    // No need to save/restore the logo resource ID, because the logo animation will not appear when
-    // the fragment is restored.
-    private int mLogoResourceId;
-    boolean mLogoAnimationFinished;
-    boolean mEnterAnimationFinished;
-    int mCurrentPageIndex;
-
-    @ColorInt
-    private int mTitleViewTextColor = Color.TRANSPARENT;
-    private boolean mTitleViewTextColorSet;
-
-    @ColorInt
-    private int mDescriptionViewTextColor = Color.TRANSPARENT;
-    private boolean mDescriptionViewTextColorSet;
-
-    @ColorInt
-    private int mDotBackgroundColor = Color.TRANSPARENT;
-    private boolean mDotBackgroundColorSet;
-
-    @ColorInt
-    private int mArrowColor = Color.TRANSPARENT;
-    private boolean mArrowColorSet;
-
-    @ColorInt
-    private int mArrowBackgroundColor = Color.TRANSPARENT;
-    private boolean mArrowBackgroundColorSet;
-
-    private CharSequence mStartButtonText;
-    private boolean mStartButtonTextSet;
-
-
-    private AnimatorSet mAnimator;
-
-    private final OnClickListener mOnClickListener = new OnClickListener() {
-        @Override
-        public void onClick(View view) {
-            if (!mLogoAnimationFinished) {
-                // Do not change page until the enter transition finishes.
-                return;
-            }
-            if (mCurrentPageIndex == getPageCount() - 1) {
-                onFinishFragment();
-            } else {
-                moveToNextPage();
-            }
-        }
-    };
-
-    private final OnKeyListener mOnKeyListener = new OnKeyListener() {
-        @Override
-        public boolean onKey(View v, int keyCode, KeyEvent event) {
-            if (!mLogoAnimationFinished) {
-                // Ignore key event until the enter transition finishes.
-                return keyCode != KeyEvent.KEYCODE_BACK;
-            }
-            if (event.getAction() == KeyEvent.ACTION_DOWN) {
-                return false;
-            }
-            switch (keyCode) {
-                case KeyEvent.KEYCODE_BACK:
-                    if (mCurrentPageIndex == 0) {
-                        return false;
-                    }
-                    moveToPreviousPage();
-                    return true;
-                case KeyEvent.KEYCODE_DPAD_LEFT:
-                    if (mIsLtr) {
-                        moveToPreviousPage();
-                    } else {
-                        moveToNextPage();
-                    }
-                    return true;
-                case KeyEvent.KEYCODE_DPAD_RIGHT:
-                    if (mIsLtr) {
-                        moveToNextPage();
-                    } else {
-                        moveToPreviousPage();
-                    }
-                    return true;
-            }
-            return false;
-        }
-    };
-
-    /**
-     * Navigates to the previous page.
-     */
-    protected void moveToPreviousPage() {
-        if (!mLogoAnimationFinished) {
-            // Ignore if the logo enter transition is in progress.
-            return;
-        }
-        if (mCurrentPageIndex > 0) {
-            --mCurrentPageIndex;
-            onPageChangedInternal(mCurrentPageIndex + 1);
-        }
-    }
-
-    /**
-     * Navigates to the next page.
-     */
-    protected void moveToNextPage() {
-        if (!mLogoAnimationFinished) {
-            // Ignore if the logo enter transition is in progress.
-            return;
-        }
-        if (mCurrentPageIndex < getPageCount() - 1) {
-            ++mCurrentPageIndex;
-            onPageChangedInternal(mCurrentPageIndex - 1);
-        }
-    }
-
-    @Nullable
-    @Override
-    public View onCreateView(LayoutInflater inflater, final ViewGroup container,
-            Bundle savedInstanceState) {
-        resolveTheme();
-        LayoutInflater localInflater = getThemeInflater(inflater);
-        final ViewGroup view = (ViewGroup) localInflater.inflate(R.layout.lb_onboarding_fragment,
-                container, false);
-        mIsLtr = getResources().getConfiguration().getLayoutDirection()
-                == View.LAYOUT_DIRECTION_LTR;
-        mPageIndicator = (PagingIndicator) view.findViewById(R.id.page_indicator);
-        mPageIndicator.setOnClickListener(mOnClickListener);
-        mPageIndicator.setOnKeyListener(mOnKeyListener);
-        mStartButton = view.findViewById(R.id.button_start);
-        mStartButton.setOnClickListener(mOnClickListener);
-        mStartButton.setOnKeyListener(mOnKeyListener);
-        mMainIconView = (ImageView) view.findViewById(R.id.main_icon);
-        mLogoView = (ImageView) view.findViewById(R.id.logo);
-        mTitleView = (TextView) view.findViewById(R.id.title);
-        mDescriptionView = (TextView) view.findViewById(R.id.description);
-
-        if (mTitleViewTextColorSet) {
-            mTitleView.setTextColor(mTitleViewTextColor);
-        }
-        if (mDescriptionViewTextColorSet) {
-            mDescriptionView.setTextColor(mDescriptionViewTextColor);
-        }
-        if (mDotBackgroundColorSet) {
-            mPageIndicator.setDotBackgroundColor(mDotBackgroundColor);
-        }
-        if (mArrowColorSet) {
-            mPageIndicator.setArrowColor(mArrowColor);
-        }
-        if (mArrowBackgroundColorSet) {
-            mPageIndicator.setDotBackgroundColor(mArrowBackgroundColor);
-        }
-        if (mStartButtonTextSet) {
-            ((Button) mStartButton).setText(mStartButtonText);
-        }
-        final Context context = FragmentUtil.getContext(OnboardingFragment.this);
-        if (sSlideDistance == 0) {
-            sSlideDistance = (int) (SLIDE_DISTANCE * context.getResources()
-                    .getDisplayMetrics().scaledDensity);
-        }
-        view.requestFocus();
-        return view;
-    }
-
-    @Override
-    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
-        super.onViewCreated(view, savedInstanceState);
-        if (savedInstanceState == null) {
-            mCurrentPageIndex = 0;
-            mLogoAnimationFinished = false;
-            mEnterAnimationFinished = false;
-            mPageIndicator.onPageSelected(0, false);
-            view.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
-                @Override
-                public boolean onPreDraw() {
-                    getView().getViewTreeObserver().removeOnPreDrawListener(this);
-                    if (!startLogoAnimation()) {
-                        mLogoAnimationFinished = true;
-                        onLogoAnimationFinished();
-                    }
-                    return true;
-                }
-            });
-        } else {
-            mCurrentPageIndex = savedInstanceState.getInt(KEY_CURRENT_PAGE_INDEX);
-            mLogoAnimationFinished = savedInstanceState.getBoolean(KEY_LOGO_ANIMATION_FINISHED);
-            mEnterAnimationFinished = savedInstanceState.getBoolean(KEY_ENTER_ANIMATION_FINISHED);
-            if (!mLogoAnimationFinished) {
-                // logo animation wasn't started or was interrupted when the activity was destroyed;
-                // restart it againl
-                if (!startLogoAnimation()) {
-                    mLogoAnimationFinished = true;
-                    onLogoAnimationFinished();
-                }
-            } else {
-                onLogoAnimationFinished();
-            }
-        }
-    }
-
-    @Override
-    public void onSaveInstanceState(Bundle outState) {
-        super.onSaveInstanceState(outState);
-        outState.putInt(KEY_CURRENT_PAGE_INDEX, mCurrentPageIndex);
-        outState.putBoolean(KEY_LOGO_ANIMATION_FINISHED, mLogoAnimationFinished);
-        outState.putBoolean(KEY_ENTER_ANIMATION_FINISHED, mEnterAnimationFinished);
-    }
-
-    /**
-     * Sets the text color for TitleView. If not set, the default textColor set in style
-     * referenced by attr {@link R.attr#onboardingTitleStyle} will be used.
-     * @param color the color to use as the text color for TitleView
-     */
-    public void setTitleViewTextColor(@ColorInt int color) {
-        mTitleViewTextColor = color;
-        mTitleViewTextColorSet = true;
-        if (mTitleView != null) {
-            mTitleView.setTextColor(color);
-        }
-    }
-
-    /**
-     * Returns the text color of TitleView if it's set through
-     * {@link #setTitleViewTextColor(int)}. If no color was set, transparent is returned.
-     */
-    @ColorInt
-    public final int getTitleViewTextColor() {
-        return mTitleViewTextColor;
-    }
-
-    /**
-     * Sets the text color for DescriptionView. If not set, the default textColor set in style
-     * referenced by attr {@link R.attr#onboardingDescriptionStyle} will be used.
-     * @param color the color to use as the text color for DescriptionView
-     */
-    public void setDescriptionViewTextColor(@ColorInt int color) {
-        mDescriptionViewTextColor = color;
-        mDescriptionViewTextColorSet = true;
-        if (mDescriptionView != null) {
-            mDescriptionView.setTextColor(color);
-        }
-    }
-
-    /**
-     * Returns the text color of DescriptionView if it's set through
-     * {@link #setDescriptionViewTextColor(int)}. If no color was set, transparent is returned.
-     */
-    @ColorInt
-    public final int getDescriptionViewTextColor() {
-        return mDescriptionViewTextColor;
-    }
-    /**
-     * Sets the background color of the dots. If not set, the default color from attr
-     * {@link R.styleable#PagingIndicator_dotBgColor} in the theme will be used.
-     * @param color the color to use for dot backgrounds
-     */
-    public void setDotBackgroundColor(@ColorInt int color) {
-        mDotBackgroundColor = color;
-        mDotBackgroundColorSet = true;
-        if (mPageIndicator != null) {
-            mPageIndicator.setDotBackgroundColor(color);
-        }
-    }
-
-    /**
-     * Returns the background color of the dot if it's set through
-     * {@link #setDotBackgroundColor(int)}. If no color was set, transparent is returned.
-     */
-    @ColorInt
-    public final int getDotBackgroundColor() {
-        return mDotBackgroundColor;
-    }
-
-    /**
-     * Sets the color of the arrow. This color will supersede the color set in the theme attribute
-     * {@link R.styleable#PagingIndicator_arrowColor} if provided. If none of these two are set, the
-     * arrow will have its original bitmap color.
-     *
-     * @param color the color to use for arrow background
-     */
-    public void setArrowColor(@ColorInt int color) {
-        mArrowColor = color;
-        mArrowColorSet = true;
-        if (mPageIndicator != null) {
-            mPageIndicator.setArrowColor(color);
-        }
-    }
-
-    /**
-     * Returns the color of the arrow if it's set through
-     * {@link #setArrowColor(int)}. If no color was set, transparent is returned.
-     */
-    @ColorInt
-    public final int getArrowColor() {
-        return mArrowColor;
-    }
-
-    /**
-     * Sets the background color of the arrow. If not set, the default color from attr
-     * {@link R.styleable#PagingIndicator_arrowBgColor} in the theme will be used.
-     * @param color the color to use for arrow background
-     */
-    public void setArrowBackgroundColor(@ColorInt int color) {
-        mArrowBackgroundColor = color;
-        mArrowBackgroundColorSet = true;
-        if (mPageIndicator != null) {
-            mPageIndicator.setArrowBackgroundColor(color);
-        }
-    }
-
-    /**
-     * Returns the background color of the arrow if it's set through
-     * {@link #setArrowBackgroundColor(int)}. If no color was set, transparent is returned.
-     */
-    @ColorInt
-    public final int getArrowBackgroundColor() {
-        return mArrowBackgroundColor;
-    }
-
-    /**
-     * Returns the start button text if it's set through
-     * {@link #setStartButtonText(CharSequence)}}. If no string was set, null is returned.
-     */
-    public final CharSequence getStartButtonText() {
-        return mStartButtonText;
-    }
-
-    /**
-     * Sets the text on the start button text. If not set, the default text set in
-     * {@link R.styleable#LeanbackOnboardingTheme_onboardingStartButtonStyle} will be used.
-     *
-     * @param text the start button text
-     */
-    public void setStartButtonText(CharSequence text) {
-        mStartButtonText = text;
-        mStartButtonTextSet = true;
-        if (mStartButton != null) {
-            ((Button) mStartButton).setText(mStartButtonText);
-        }
-    }
-
-    /**
-     * Returns the theme used for styling the fragment. The default returns -1, indicating that the
-     * host Activity's theme should be used.
-     *
-     * @return The theme resource ID of the theme to use in this fragment, or -1 to use the host
-     *         Activity's theme.
-     */
-    public int onProvideTheme() {
-        return -1;
-    }
-
-    private void resolveTheme() {
-        final Context context = FragmentUtil.getContext(OnboardingFragment.this);
-        int theme = onProvideTheme();
-        if (theme == -1) {
-            // Look up the onboardingTheme in the activity's currently specified theme. If it
-            // exists, wrap the theme with its value.
-            int resId = R.attr.onboardingTheme;
-            TypedValue typedValue = new TypedValue();
-            boolean found = context.getTheme().resolveAttribute(resId, typedValue, true);
-            if (DEBUG) Log.v(TAG, "Found onboarding theme reference? " + found);
-            if (found) {
-                mThemeWrapper = new ContextThemeWrapper(context, typedValue.resourceId);
-            }
-        } else {
-            mThemeWrapper = new ContextThemeWrapper(context, theme);
-        }
-    }
-
-    private LayoutInflater getThemeInflater(LayoutInflater inflater) {
-        return mThemeWrapper == null ? inflater : inflater.cloneInContext(mThemeWrapper);
-    }
-
-    /**
-     * Sets the resource ID of the splash logo image. If the logo resource id set, the default logo
-     * splash animation will be played.
-     *
-     * @param id The resource ID of the logo image.
-     */
-    public final void setLogoResourceId(int id) {
-        mLogoResourceId = id;
-    }
-
-    /**
-     * Returns the resource ID of the splash logo image.
-     *
-     * @return The resource ID of the splash logo image.
-     */
-    public final int getLogoResourceId() {
-        return mLogoResourceId;
-    }
-
-    /**
-     * Called to have the inherited class create its own logo animation.
-     * <p>
-     * This is called only if the logo image resource ID is not set by {@link #setLogoResourceId}.
-     * If this returns {@code null}, the logo animation is skipped.
-     *
-     * @return The {@link Animator} object which runs the logo animation.
-     */
-    @Nullable
-    protected Animator onCreateLogoAnimation() {
-        return null;
-    }
-
-    boolean startLogoAnimation() {
-        final Context context = FragmentUtil.getContext(OnboardingFragment.this);
-        if (context == null) {
-            return false;
-        }
-        Animator animator = null;
-        if (mLogoResourceId != 0) {
-            mLogoView.setVisibility(View.VISIBLE);
-            mLogoView.setImageResource(mLogoResourceId);
-            Animator inAnimator = AnimatorInflater.loadAnimator(context,
-                    R.animator.lb_onboarding_logo_enter);
-            Animator outAnimator = AnimatorInflater.loadAnimator(context,
-                    R.animator.lb_onboarding_logo_exit);
-            outAnimator.setStartDelay(LOGO_SPLASH_PAUSE_DURATION_MS);
-            AnimatorSet logoAnimator = new AnimatorSet();
-            logoAnimator.playSequentially(inAnimator, outAnimator);
-            logoAnimator.setTarget(mLogoView);
-            animator = logoAnimator;
-        } else {
-            animator = onCreateLogoAnimation();
-        }
-        if (animator != null) {
-            animator.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    if (context != null) {
-                        mLogoAnimationFinished = true;
-                        onLogoAnimationFinished();
-                    }
-                }
-            });
-            animator.start();
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Called to have the inherited class create its enter animation. The start animation runs after
-     * logo animation ends.
-     *
-     * @return The {@link Animator} object which runs the page enter animation.
-     */
-    @Nullable
-    protected Animator onCreateEnterAnimation() {
-        return null;
-    }
-
-
-    /**
-     * Hides the logo view and makes other fragment views visible. Also initializes the texts for
-     * Title and Description views.
-     */
-    void hideLogoView() {
-        mLogoView.setVisibility(View.GONE);
-
-        if (mIconResourceId != 0) {
-            mMainIconView.setImageResource(mIconResourceId);
-            mMainIconView.setVisibility(View.VISIBLE);
-        }
-
-        View container = getView();
-        // Create custom views.
-        LayoutInflater inflater = getThemeInflater(LayoutInflater.from(
-                FragmentUtil.getContext(OnboardingFragment.this)));
-        ViewGroup backgroundContainer = (ViewGroup) container.findViewById(
-                R.id.background_container);
-        View background = onCreateBackgroundView(inflater, backgroundContainer);
-        if (background != null) {
-            backgroundContainer.setVisibility(View.VISIBLE);
-            backgroundContainer.addView(background);
-        }
-        ViewGroup contentContainer = (ViewGroup) container.findViewById(R.id.content_container);
-        View content = onCreateContentView(inflater, contentContainer);
-        if (content != null) {
-            contentContainer.setVisibility(View.VISIBLE);
-            contentContainer.addView(content);
-        }
-        ViewGroup foregroundContainer = (ViewGroup) container.findViewById(
-                R.id.foreground_container);
-        View foreground = onCreateForegroundView(inflater, foregroundContainer);
-        if (foreground != null) {
-            foregroundContainer.setVisibility(View.VISIBLE);
-            foregroundContainer.addView(foreground);
-        }
-        // Make views visible which were invisible while logo animation is running.
-        container.findViewById(R.id.page_container).setVisibility(View.VISIBLE);
-        container.findViewById(R.id.content_container).setVisibility(View.VISIBLE);
-        if (getPageCount() > 1) {
-            mPageIndicator.setPageCount(getPageCount());
-            mPageIndicator.onPageSelected(mCurrentPageIndex, false);
-        }
-        if (mCurrentPageIndex == getPageCount() - 1) {
-            mStartButton.setVisibility(View.VISIBLE);
-        } else {
-            mPageIndicator.setVisibility(View.VISIBLE);
-        }
-        // Header views.
-        mTitleView.setText(getPageTitle(mCurrentPageIndex));
-        mDescriptionView.setText(getPageDescription(mCurrentPageIndex));
-    }
-
-    /**
-     * Called immediately after the logo animation is complete or no logo animation is specified.
-     * This method can also be called when the activity is recreated, i.e. when no logo animation
-     * are performed.
-     * By default, this method will hide the logo view and start the entrance animation for this
-     * fragment.
-     * Overriding subclasses can provide their own data loading logic as to when the entrance
-     * animation should be executed.
-     */
-    protected void onLogoAnimationFinished() {
-        startEnterAnimation(false);
-    }
-
-    /**
-     * Called to start entrance transition. This can be called by subclasses when the logo animation
-     * and data loading is complete. If force flag is set to false, it will only start the animation
-     * if it's not already done yet. Otherwise, it will always start the enter animation. In both
-     * cases, the logo view will hide and the rest of fragment views become visible after this call.
-     *
-     * @param force {@code true} if enter animation has to be performed regardless of whether it's
-     *                          been done in the past, {@code false} otherwise
-     */
-    protected final void startEnterAnimation(boolean force) {
-        final Context context = FragmentUtil.getContext(OnboardingFragment.this);
-        if (context == null) {
-            return;
-        }
-        hideLogoView();
-        if (mEnterAnimationFinished && !force) {
-            return;
-        }
-        List<Animator> animators = new ArrayList<>();
-        Animator animator = AnimatorInflater.loadAnimator(context,
-                R.animator.lb_onboarding_page_indicator_enter);
-        animator.setTarget(getPageCount() <= 1 ? mStartButton : mPageIndicator);
-        animators.add(animator);
-
-        animator = onCreateTitleAnimator();
-        if (animator != null) {
-            // Header title.
-            animator.setTarget(mTitleView);
-            animators.add(animator);
-        }
-
-        animator = onCreateDescriptionAnimator();
-        if (animator != null) {
-            // Header description.
-            animator.setTarget(mDescriptionView);
-            animators.add(animator);
-        }
-
-        // Customized animation by the inherited class.
-        Animator customAnimator = onCreateEnterAnimation();
-        if (customAnimator != null) {
-            animators.add(customAnimator);
-        }
-
-        // Return if we don't have any animations.
-        if (animators.isEmpty()) {
-            return;
-        }
-        mAnimator = new AnimatorSet();
-        mAnimator.playTogether(animators);
-        mAnimator.start();
-        mAnimator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mEnterAnimationFinished = true;
-            }
-        });
-        // Search focus and give the focus to the appropriate child which has become visible.
-        getView().requestFocus();
-    }
-
-    /**
-     * Provides the entry animation for description view. This allows users to override the
-     * default fade and slide animation. Returning null will disable the animation.
-     */
-    protected Animator onCreateDescriptionAnimator() {
-        return AnimatorInflater.loadAnimator(FragmentUtil.getContext(OnboardingFragment.this),
-                R.animator.lb_onboarding_description_enter);
-    }
-
-    /**
-     * Provides the entry animation for title view. This allows users to override the
-     * default fade and slide animation. Returning null will disable the animation.
-     */
-    protected Animator onCreateTitleAnimator() {
-        return AnimatorInflater.loadAnimator(FragmentUtil.getContext(OnboardingFragment.this),
-                R.animator.lb_onboarding_title_enter);
-    }
-
-    /**
-     * Returns whether the logo enter animation is finished.
-     *
-     * @return {@code true} if the logo enter transition is finished, {@code false} otherwise
-     */
-    protected final boolean isLogoAnimationFinished() {
-        return mLogoAnimationFinished;
-    }
-
-    /**
-     * Returns the page count.
-     *
-     * @return The page count.
-     */
-    abstract protected int getPageCount();
-
-    /**
-     * Returns the title of the given page.
-     *
-     * @param pageIndex The page index.
-     *
-     * @return The title of the page.
-     */
-    abstract protected CharSequence getPageTitle(int pageIndex);
-
-    /**
-     * Returns the description of the given page.
-     *
-     * @param pageIndex The page index.
-     *
-     * @return The description of the page.
-     */
-    abstract protected CharSequence getPageDescription(int pageIndex);
-
-    /**
-     * Returns the index of the current page.
-     *
-     * @return The index of the current page.
-     */
-    protected final int getCurrentPageIndex() {
-        return mCurrentPageIndex;
-    }
-
-    /**
-     * Called to have the inherited class create background view. This is optional and the fragment
-     * which doesn't have the background view can return {@code null}. This is called inside
-     * {@link #onCreateView}.
-     *
-     * @param inflater The LayoutInflater object that can be used to inflate the views,
-     * @param container The parent view that the additional views are attached to.The fragment
-     *        should not add the view by itself.
-     *
-     * @return The background view for the onboarding screen, or {@code null}.
-     */
-    @Nullable
-    abstract protected View onCreateBackgroundView(LayoutInflater inflater, ViewGroup container);
-
-    /**
-     * Called to have the inherited class create content view. This is optional and the fragment
-     * which doesn't have the content view can return {@code null}. This is called inside
-     * {@link #onCreateView}.
-     *
-     * <p>The content view would be located at the center of the screen.
-     *
-     * @param inflater The LayoutInflater object that can be used to inflate the views,
-     * @param container The parent view that the additional views are attached to.The fragment
-     *        should not add the view by itself.
-     *
-     * @return The content view for the onboarding screen, or {@code null}.
-     */
-    @Nullable
-    abstract protected View onCreateContentView(LayoutInflater inflater, ViewGroup container);
-
-    /**
-     * Called to have the inherited class create foreground view. This is optional and the fragment
-     * which doesn't need the foreground view can return {@code null}. This is called inside
-     * {@link #onCreateView}.
-     *
-     * <p>This foreground view would have the highest z-order.
-     *
-     * @param inflater The LayoutInflater object that can be used to inflate the views,
-     * @param container The parent view that the additional views are attached to.The fragment
-     *        should not add the view by itself.
-     *
-     * @return The foreground view for the onboarding screen, or {@code null}.
-     */
-    @Nullable
-    abstract protected View onCreateForegroundView(LayoutInflater inflater, ViewGroup container);
-
-    /**
-     * Called when the onboarding flow finishes.
-     */
-    protected void onFinishFragment() { }
-
-    /**
-     * Called when the page changes.
-     */
-    private void onPageChangedInternal(int previousPage) {
-        if (mAnimator != null) {
-            mAnimator.end();
-        }
-        mPageIndicator.onPageSelected(mCurrentPageIndex, true);
-
-        List<Animator> animators = new ArrayList<>();
-        // Header animation
-        Animator fadeAnimator = null;
-        if (previousPage < getCurrentPageIndex()) {
-            // sliding to left
-            animators.add(createAnimator(mTitleView, false, Gravity.START, 0));
-            animators.add(fadeAnimator = createAnimator(mDescriptionView, false, Gravity.START,
-                    DESCRIPTION_START_DELAY_MS));
-            animators.add(createAnimator(mTitleView, true, Gravity.END,
-                    HEADER_APPEAR_DELAY_MS));
-            animators.add(createAnimator(mDescriptionView, true, Gravity.END,
-                    HEADER_APPEAR_DELAY_MS + DESCRIPTION_START_DELAY_MS));
-        } else {
-            // sliding to right
-            animators.add(createAnimator(mTitleView, false, Gravity.END, 0));
-            animators.add(fadeAnimator = createAnimator(mDescriptionView, false, Gravity.END,
-                    DESCRIPTION_START_DELAY_MS));
-            animators.add(createAnimator(mTitleView, true, Gravity.START,
-                    HEADER_APPEAR_DELAY_MS));
-            animators.add(createAnimator(mDescriptionView, true, Gravity.START,
-                    HEADER_APPEAR_DELAY_MS + DESCRIPTION_START_DELAY_MS));
-        }
-        final int currentPageIndex = getCurrentPageIndex();
-        fadeAnimator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mTitleView.setText(getPageTitle(currentPageIndex));
-                mDescriptionView.setText(getPageDescription(currentPageIndex));
-            }
-        });
-
-        final Context context = FragmentUtil.getContext(OnboardingFragment.this);
-        // Animator for switching between page indicator and button.
-        if (getCurrentPageIndex() == getPageCount() - 1) {
-            mStartButton.setVisibility(View.VISIBLE);
-            Animator navigatorFadeOutAnimator = AnimatorInflater.loadAnimator(context,
-                    R.animator.lb_onboarding_page_indicator_fade_out);
-            navigatorFadeOutAnimator.setTarget(mPageIndicator);
-            navigatorFadeOutAnimator.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    mPageIndicator.setVisibility(View.GONE);
-                }
-            });
-            animators.add(navigatorFadeOutAnimator);
-            Animator buttonFadeInAnimator = AnimatorInflater.loadAnimator(context,
-                    R.animator.lb_onboarding_start_button_fade_in);
-            buttonFadeInAnimator.setTarget(mStartButton);
-            animators.add(buttonFadeInAnimator);
-        } else if (previousPage == getPageCount() - 1) {
-            mPageIndicator.setVisibility(View.VISIBLE);
-            Animator navigatorFadeInAnimator = AnimatorInflater.loadAnimator(context,
-                    R.animator.lb_onboarding_page_indicator_fade_in);
-            navigatorFadeInAnimator.setTarget(mPageIndicator);
-            animators.add(navigatorFadeInAnimator);
-            Animator buttonFadeOutAnimator = AnimatorInflater.loadAnimator(context,
-                    R.animator.lb_onboarding_start_button_fade_out);
-            buttonFadeOutAnimator.setTarget(mStartButton);
-            buttonFadeOutAnimator.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    mStartButton.setVisibility(View.GONE);
-                }
-            });
-            animators.add(buttonFadeOutAnimator);
-        }
-        mAnimator = new AnimatorSet();
-        mAnimator.playTogether(animators);
-        mAnimator.start();
-        onPageChanged(mCurrentPageIndex, previousPage);
-    }
-
-    /**
-     * Called when the page has been changed.
-     *
-     * @param newPage The new page.
-     * @param previousPage The previous page.
-     */
-    protected void onPageChanged(int newPage, int previousPage) { }
-
-    private Animator createAnimator(View view, boolean fadeIn, int slideDirection,
-            long startDelay) {
-        boolean isLtr = getView().getLayoutDirection() == View.LAYOUT_DIRECTION_LTR;
-        boolean slideRight = (isLtr && slideDirection == Gravity.END)
-                || (!isLtr && slideDirection == Gravity.START)
-                || slideDirection == Gravity.RIGHT;
-        Animator fadeAnimator;
-        Animator slideAnimator;
-        if (fadeIn) {
-            fadeAnimator = ObjectAnimator.ofFloat(view, View.ALPHA, 0.0f, 1.0f);
-            slideAnimator = ObjectAnimator.ofFloat(view, View.TRANSLATION_X,
-                    slideRight ? sSlideDistance : -sSlideDistance, 0);
-            fadeAnimator.setInterpolator(HEADER_APPEAR_INTERPOLATOR);
-            slideAnimator.setInterpolator(HEADER_APPEAR_INTERPOLATOR);
-        } else {
-            fadeAnimator = ObjectAnimator.ofFloat(view, View.ALPHA, 1.0f, 0.0f);
-            slideAnimator = ObjectAnimator.ofFloat(view, View.TRANSLATION_X, 0,
-                    slideRight ? sSlideDistance : -sSlideDistance);
-            fadeAnimator.setInterpolator(HEADER_DISAPPEAR_INTERPOLATOR);
-            slideAnimator.setInterpolator(HEADER_DISAPPEAR_INTERPOLATOR);
-        }
-        fadeAnimator.setDuration(HEADER_ANIMATION_DURATION_MS);
-        fadeAnimator.setTarget(view);
-        slideAnimator.setDuration(HEADER_ANIMATION_DURATION_MS);
-        slideAnimator.setTarget(view);
-        AnimatorSet animator = new AnimatorSet();
-        animator.playTogether(fadeAnimator, slideAnimator);
-        if (startDelay > 0) {
-            animator.setStartDelay(startDelay);
-        }
-        return animator;
-    }
-
-    /**
-     * Sets the resource id for the main icon.
-     */
-    public final void setIconResouceId(int resourceId) {
-        this.mIconResourceId = resourceId;
-        if (mMainIconView != null) {
-            mMainIconView.setImageResource(resourceId);
-            mMainIconView.setVisibility(View.VISIBLE);
-        }
-    }
-
-    /**
-     * Returns the resource id of the main icon.
-     */
-    public final int getIconResourceId() {
-        return mIconResourceId;
-    }
-}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/PlaybackFragment.java b/v17/leanback/src/android/support/v17/leanback/app/PlaybackFragment.java
deleted file mode 100644
index 33e787c..0000000
--- a/v17/leanback/src/android/support/v17/leanback/app/PlaybackFragment.java
+++ /dev/null
@@ -1,1174 +0,0 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from PlaybackSupportFragment.java.  DO NOT MODIFY. */
-
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-package android.support.v17.leanback.app;
-
-import android.animation.Animator;
-import android.animation.AnimatorInflater;
-import android.animation.TimeInterpolator;
-import android.animation.ValueAnimator;
-import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.content.Context;
-import android.graphics.Color;
-import android.graphics.drawable.ColorDrawable;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.v17.leanback.R;
-import android.support.v17.leanback.animation.LogAccelerateInterpolator;
-import android.support.v17.leanback.animation.LogDecelerateInterpolator;
-import android.support.v17.leanback.media.PlaybackGlueHost;
-import android.support.v17.leanback.widget.ArrayObjectAdapter;
-import android.support.v17.leanback.widget.BaseOnItemViewClickedListener;
-import android.support.v17.leanback.widget.BaseOnItemViewSelectedListener;
-import android.support.v17.leanback.widget.ClassPresenterSelector;
-import android.support.v17.leanback.widget.ItemAlignmentFacet;
-import android.support.v17.leanback.widget.ItemBridgeAdapter;
-import android.support.v17.leanback.widget.ObjectAdapter;
-import android.support.v17.leanback.widget.PlaybackRowPresenter;
-import android.support.v17.leanback.widget.PlaybackSeekDataProvider;
-import android.support.v17.leanback.widget.PlaybackSeekUi;
-import android.support.v17.leanback.widget.Presenter;
-import android.support.v17.leanback.widget.PresenterSelector;
-import android.support.v17.leanback.widget.Row;
-import android.support.v17.leanback.widget.RowPresenter;
-import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
-import android.support.v17.leanback.widget.VerticalGridView;
-import android.app.Fragment;
-import android.support.v7.widget.RecyclerView;
-import android.util.Log;
-import android.view.InputEvent;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.animation.AccelerateInterpolator;
-
-/**
- * A fragment for displaying playback controls and related content.
- *
- * <p>
- * A PlaybackFragment renders the elements of its {@link ObjectAdapter} as a set
- * of rows in a vertical list.  The Adapter's {@link PresenterSelector} must maintain subclasses
- * of {@link RowPresenter}.
- * </p>
- * <p>
- * A playback row is a row rendered by {@link PlaybackRowPresenter}.
- * App can call {@link #setPlaybackRow(Row)} to set playback row for the first element of adapter.
- * App can call {@link #setPlaybackRowPresenter(PlaybackRowPresenter)} to set presenter for it.
- * {@link #setPlaybackRow(Row)} and {@link #setPlaybackRowPresenter(PlaybackRowPresenter)} are
- * optional, app can pass playback row and PlaybackRowPresenter in the adapter using
- * {@link #setAdapter(ObjectAdapter)}.
- * </p>
- * <p>
- * Auto hide controls upon playing: best practice is calling
- * {@link #setControlsOverlayAutoHideEnabled(boolean)} upon play/pause. The auto hiding timer will
- * be cancelled upon {@link #tickle()} triggered by input event.
- * </p>
- */
-public class PlaybackFragment extends Fragment {
-    static final String BUNDLE_CONTROL_VISIBLE_ON_CREATEVIEW = "controlvisible_oncreateview";
-
-    /**
-     * No background.
-     */
-    public static final int BG_NONE = 0;
-
-    /**
-     * A dark translucent background.
-     */
-    public static final int BG_DARK = 1;
-    PlaybackGlueHost.HostCallback mHostCallback;
-
-    PlaybackSeekUi.Client mSeekUiClient;
-    boolean mInSeek;
-    ProgressBarManager mProgressBarManager = new ProgressBarManager();
-
-    /**
-     * Resets the focus on the button in the middle of control row.
-     * @hide
-     */
-    public void resetFocus() {
-        ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder) getVerticalGridView()
-                .findViewHolderForAdapterPosition(0);
-        if (vh != null && vh.getPresenter() instanceof PlaybackRowPresenter) {
-            ((PlaybackRowPresenter) vh.getPresenter()).onReappear(
-                    (RowPresenter.ViewHolder) vh.getViewHolder());
-        }
-    }
-
-    private class SetSelectionRunnable implements Runnable {
-        int mPosition;
-        boolean mSmooth = true;
-
-        @Override
-        public void run() {
-            if (mRowsFragment == null) {
-                return;
-            }
-            mRowsFragment.setSelectedPosition(mPosition, mSmooth);
-        }
-    }
-
-    /**
-     * A light translucent background.
-     */
-    public static final int BG_LIGHT = 2;
-    RowsFragment mRowsFragment;
-    ObjectAdapter mAdapter;
-    PlaybackRowPresenter mPresenter;
-    Row mRow;
-    BaseOnItemViewSelectedListener mExternalItemSelectedListener;
-    BaseOnItemViewClickedListener mExternalItemClickedListener;
-    BaseOnItemViewClickedListener mPlaybackItemClickedListener;
-
-    private final BaseOnItemViewClickedListener mOnItemViewClickedListener =
-            new BaseOnItemViewClickedListener() {
-                @Override
-                public void onItemClicked(Presenter.ViewHolder itemViewHolder,
-                                          Object item,
-                                          RowPresenter.ViewHolder rowViewHolder,
-                                          Object row) {
-                    if (mPlaybackItemClickedListener != null
-                            && rowViewHolder instanceof PlaybackRowPresenter.ViewHolder) {
-                        mPlaybackItemClickedListener.onItemClicked(
-                                itemViewHolder, item, rowViewHolder, row);
-                    }
-                    if (mExternalItemClickedListener != null) {
-                        mExternalItemClickedListener.onItemClicked(
-                                itemViewHolder, item, rowViewHolder, row);
-                    }
-                }
-            };
-
-    private final BaseOnItemViewSelectedListener mOnItemViewSelectedListener =
-            new BaseOnItemViewSelectedListener() {
-                @Override
-                public void onItemSelected(Presenter.ViewHolder itemViewHolder,
-                                           Object item,
-                                           RowPresenter.ViewHolder rowViewHolder,
-                                           Object row) {
-                    if (mExternalItemSelectedListener != null) {
-                        mExternalItemSelectedListener.onItemSelected(
-                                itemViewHolder, item, rowViewHolder, row);
-                    }
-                }
-            };
-
-    private final SetSelectionRunnable mSetSelectionRunnable = new SetSelectionRunnable();
-
-    public ObjectAdapter getAdapter() {
-        return mAdapter;
-    }
-
-    /**
-     * Listener allowing the application to receive notification of fade in and/or fade out
-     * completion events.
-     * @hide
-     */
-    public static class OnFadeCompleteListener {
-        public void onFadeInComplete() {
-        }
-
-        public void onFadeOutComplete() {
-        }
-    }
-
-    private static final String TAG = "PlaybackFragment";
-    private static final boolean DEBUG = false;
-    private static final int ANIMATION_MULTIPLIER = 1;
-
-    private static int START_FADE_OUT = 1;
-
-    // Fading status
-    private static final int IDLE = 0;
-    private static final int ANIMATING = 1;
-
-    int mPaddingBottom;
-    int mOtherRowsCenterToBottom;
-    View mRootView;
-    View mBackgroundView;
-    int mBackgroundType = BG_DARK;
-    int mBgDarkColor;
-    int mBgLightColor;
-    int mShowTimeMs;
-    int mMajorFadeTranslateY, mMinorFadeTranslateY;
-    int mAnimationTranslateY;
-    OnFadeCompleteListener mFadeCompleteListener;
-    View.OnKeyListener mInputEventHandler;
-    boolean mFadingEnabled = true;
-    boolean mControlVisibleBeforeOnCreateView = true;
-    boolean mControlVisible = true;
-    int mBgAlpha;
-    ValueAnimator mBgFadeInAnimator, mBgFadeOutAnimator;
-    ValueAnimator mControlRowFadeInAnimator, mControlRowFadeOutAnimator;
-    ValueAnimator mOtherRowFadeInAnimator, mOtherRowFadeOutAnimator;
-
-    private final Animator.AnimatorListener mFadeListener =
-            new Animator.AnimatorListener() {
-                @Override
-                public void onAnimationStart(Animator animation) {
-                    enableVerticalGridAnimations(false);
-                }
-
-                @Override
-                public void onAnimationRepeat(Animator animation) {
-                }
-
-                @Override
-                public void onAnimationCancel(Animator animation) {
-                }
-
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    if (DEBUG) Log.v(TAG, "onAnimationEnd " + mBgAlpha);
-                    if (mBgAlpha > 0) {
-                        enableVerticalGridAnimations(true);
-                        if (mFadeCompleteListener != null) {
-                            mFadeCompleteListener.onFadeInComplete();
-                        }
-                    } else {
-                        VerticalGridView verticalView = getVerticalGridView();
-                        // reset focus to the primary actions only if the selected row was the controls row
-                        if (verticalView != null && verticalView.getSelectedPosition() == 0) {
-                            ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder)
-                                    verticalView.findViewHolderForAdapterPosition(0);
-                            if (vh != null && vh.getPresenter() instanceof PlaybackRowPresenter) {
-                                ((PlaybackRowPresenter)vh.getPresenter()).onReappear(
-                                        (RowPresenter.ViewHolder) vh.getViewHolder());
-                            }
-                        }
-                        if (mFadeCompleteListener != null) {
-                            mFadeCompleteListener.onFadeOutComplete();
-                        }
-                    }
-                }
-            };
-
-    public PlaybackFragment() {
-        mProgressBarManager.setInitialDelay(500);
-    }
-
-    VerticalGridView getVerticalGridView() {
-        if (mRowsFragment == null) {
-            return null;
-        }
-        return mRowsFragment.getVerticalGridView();
-    }
-
-    private final Handler mHandler = new Handler() {
-        @Override
-        public void handleMessage(Message message) {
-            if (message.what == START_FADE_OUT && mFadingEnabled) {
-                hideControlsOverlay(true);
-            }
-        }
-    };
-
-    private final VerticalGridView.OnTouchInterceptListener mOnTouchInterceptListener =
-            new VerticalGridView.OnTouchInterceptListener() {
-                @Override
-                public boolean onInterceptTouchEvent(MotionEvent event) {
-                    return onInterceptInputEvent(event);
-                }
-            };
-
-    private final VerticalGridView.OnKeyInterceptListener mOnKeyInterceptListener =
-            new VerticalGridView.OnKeyInterceptListener() {
-                @Override
-                public boolean onInterceptKeyEvent(KeyEvent event) {
-                    return onInterceptInputEvent(event);
-                }
-            };
-
-    private void setBgAlpha(int alpha) {
-        mBgAlpha = alpha;
-        if (mBackgroundView != null) {
-            mBackgroundView.getBackground().setAlpha(alpha);
-        }
-    }
-
-    private void enableVerticalGridAnimations(boolean enable) {
-        if (getVerticalGridView() != null) {
-            getVerticalGridView().setAnimateChildLayout(enable);
-        }
-    }
-
-    /**
-     * Enables or disables auto hiding controls overlay after a short delay fragment is resumed.
-     * If enabled and fragment is resumed, the view will fade out after a time period.
-     * {@link #tickle()} will kill the timer, next time fragment is resumed,
-     * the timer will be started again if {@link #isControlsOverlayAutoHideEnabled()} is true.
-     */
-    public void setControlsOverlayAutoHideEnabled(boolean enabled) {
-        if (DEBUG) Log.v(TAG, "setControlsOverlayAutoHideEnabled " + enabled);
-        if (enabled != mFadingEnabled) {
-            mFadingEnabled = enabled;
-            if (isResumed() && getView().hasFocus()) {
-                showControlsOverlay(true);
-                if (enabled) {
-                    // StateGraph 7->2 5->2
-                    startFadeTimer();
-                } else {
-                    // StateGraph 4->5 2->5
-                    stopFadeTimer();
-                }
-            } else {
-                // StateGraph 6->1 1->6
-            }
-        }
-    }
-
-    /**
-     * Returns true if controls will be auto hidden after a delay when fragment is resumed.
-     */
-    public boolean isControlsOverlayAutoHideEnabled() {
-        return mFadingEnabled;
-    }
-
-    /**
-     * @deprecated Uses {@link #setControlsOverlayAutoHideEnabled(boolean)}
-     */
-    @Deprecated
-    public void setFadingEnabled(boolean enabled) {
-        setControlsOverlayAutoHideEnabled(enabled);
-    }
-
-    /**
-     * @deprecated Uses {@link #isControlsOverlayAutoHideEnabled()}
-     */
-    @Deprecated
-    public boolean isFadingEnabled() {
-        return isControlsOverlayAutoHideEnabled();
-    }
-
-    /**
-     * Sets the listener to be called when fade in or out has completed.
-     * @hide
-     */
-    public void setFadeCompleteListener(OnFadeCompleteListener listener) {
-        mFadeCompleteListener = listener;
-    }
-
-    /**
-     * Returns the listener to be called when fade in or out has completed.
-     * @hide
-     */
-    public OnFadeCompleteListener getFadeCompleteListener() {
-        return mFadeCompleteListener;
-    }
-
-    /**
-     * Sets the input event handler.
-     */
-    public final void setOnKeyInterceptListener(View.OnKeyListener handler) {
-        mInputEventHandler = handler;
-    }
-
-    /**
-     * Tickles the playback controls. Fades in the view if it was faded out. {@link #tickle()} will
-     * also kill the timer created by {@link #setControlsOverlayAutoHideEnabled(boolean)}. When
-     * next time fragment is resumed, the timer will be started again if
-     * {@link #isControlsOverlayAutoHideEnabled()} is true. In most cases app does not need call
-     * this method, tickling on input events is handled by the fragment.
-     */
-    public void tickle() {
-        if (DEBUG) Log.v(TAG, "tickle enabled " + mFadingEnabled + " isResumed " + isResumed());
-        //StateGraph 2->4
-        stopFadeTimer();
-        showControlsOverlay(true);
-    }
-
-    private boolean onInterceptInputEvent(InputEvent event) {
-        final boolean controlsHidden = !mControlVisible;
-        if (DEBUG) Log.v(TAG, "onInterceptInputEvent hidden " + controlsHidden + " " + event);
-        boolean consumeEvent = false;
-        int keyCode = KeyEvent.KEYCODE_UNKNOWN;
-        int keyAction = 0;
-
-        if (event instanceof KeyEvent) {
-            keyCode = ((KeyEvent) event).getKeyCode();
-            keyAction = ((KeyEvent) event).getAction();
-            if (mInputEventHandler != null) {
-                consumeEvent = mInputEventHandler.onKey(getView(), keyCode, (KeyEvent) event);
-            }
-        }
-
-        switch (keyCode) {
-            case KeyEvent.KEYCODE_DPAD_CENTER:
-            case KeyEvent.KEYCODE_DPAD_DOWN:
-            case KeyEvent.KEYCODE_DPAD_UP:
-            case KeyEvent.KEYCODE_DPAD_LEFT:
-            case KeyEvent.KEYCODE_DPAD_RIGHT:
-                // Event may be consumed; regardless, if controls are hidden then these keys will
-                // bring up the controls.
-                if (controlsHidden) {
-                    consumeEvent = true;
-                }
-                if (keyAction == KeyEvent.ACTION_DOWN) {
-                    tickle();
-                }
-                break;
-            case KeyEvent.KEYCODE_BACK:
-            case KeyEvent.KEYCODE_ESCAPE:
-                if (mInSeek) {
-                    // when in seek, the SeekUi will handle the BACK.
-                    return false;
-                }
-                // If controls are not hidden, back will be consumed to fade
-                // them out (even if the key was consumed by the handler).
-                if (!controlsHidden) {
-                    consumeEvent = true;
-
-                    if (((KeyEvent) event).getAction() == KeyEvent.ACTION_UP) {
-                        hideControlsOverlay(true);
-                    }
-                }
-                break;
-            default:
-                if (consumeEvent) {
-                    if (keyAction == KeyEvent.ACTION_DOWN) {
-                        tickle();
-                    }
-                }
-        }
-        return consumeEvent;
-    }
-
-    @Override
-    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
-        super.onViewCreated(view, savedInstanceState);
-        // controls view are initially visible, make it invisible
-        // if app has called hideControlsOverlay() before view created.
-        mControlVisible = true;
-        if (!mControlVisibleBeforeOnCreateView) {
-            showControlsOverlay(false, false);
-            mControlVisibleBeforeOnCreateView = true;
-        }
-    }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-
-        if (mControlVisible) {
-            //StateGraph: 6->5 1->2
-            if (mFadingEnabled) {
-                // StateGraph 1->2
-                startFadeTimer();
-            }
-        } else {
-            //StateGraph: 6->7 1->3
-        }
-        getVerticalGridView().setOnTouchInterceptListener(mOnTouchInterceptListener);
-        getVerticalGridView().setOnKeyInterceptListener(mOnKeyInterceptListener);
-        if (mHostCallback != null) {
-            mHostCallback.onHostResume();
-        }
-    }
-
-    private void stopFadeTimer() {
-        if (mHandler != null) {
-            mHandler.removeMessages(START_FADE_OUT);
-        }
-    }
-
-    private void startFadeTimer() {
-        if (mHandler != null) {
-            mHandler.removeMessages(START_FADE_OUT);
-            mHandler.sendEmptyMessageDelayed(START_FADE_OUT, mShowTimeMs);
-        }
-    }
-
-    private static ValueAnimator loadAnimator(Context context, int resId) {
-        ValueAnimator animator = (ValueAnimator) AnimatorInflater.loadAnimator(context, resId);
-        animator.setDuration(animator.getDuration() * ANIMATION_MULTIPLIER);
-        return animator;
-    }
-
-    private void loadBgAnimator() {
-        AnimatorUpdateListener listener = new AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator arg0) {
-                setBgAlpha((Integer) arg0.getAnimatedValue());
-            }
-        };
-
-        Context context = FragmentUtil.getContext(PlaybackFragment.this);
-        mBgFadeInAnimator = loadAnimator(context, R.animator.lb_playback_bg_fade_in);
-        mBgFadeInAnimator.addUpdateListener(listener);
-        mBgFadeInAnimator.addListener(mFadeListener);
-
-        mBgFadeOutAnimator = loadAnimator(context, R.animator.lb_playback_bg_fade_out);
-        mBgFadeOutAnimator.addUpdateListener(listener);
-        mBgFadeOutAnimator.addListener(mFadeListener);
-    }
-
-    private TimeInterpolator mLogDecelerateInterpolator = new LogDecelerateInterpolator(100, 0);
-    private TimeInterpolator mLogAccelerateInterpolator = new LogAccelerateInterpolator(100, 0);
-
-    private void loadControlRowAnimator() {
-        final AnimatorUpdateListener updateListener = new AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator arg0) {
-                if (getVerticalGridView() == null) {
-                    return;
-                }
-                RecyclerView.ViewHolder vh = getVerticalGridView()
-                        .findViewHolderForAdapterPosition(0);
-                if (vh == null) {
-                    return;
-                }
-                View view = vh.itemView;
-                if (view != null) {
-                    final float fraction = (Float) arg0.getAnimatedValue();
-                    if (DEBUG) Log.v(TAG, "fraction " + fraction);
-                    view.setAlpha(fraction);
-                    view.setTranslationY((float) mAnimationTranslateY * (1f - fraction));
-                }
-            }
-        };
-
-        Context context = FragmentUtil.getContext(PlaybackFragment.this);
-        mControlRowFadeInAnimator = loadAnimator(context, R.animator.lb_playback_controls_fade_in);
-        mControlRowFadeInAnimator.addUpdateListener(updateListener);
-        mControlRowFadeInAnimator.setInterpolator(mLogDecelerateInterpolator);
-
-        mControlRowFadeOutAnimator = loadAnimator(context,
-                R.animator.lb_playback_controls_fade_out);
-        mControlRowFadeOutAnimator.addUpdateListener(updateListener);
-        mControlRowFadeOutAnimator.setInterpolator(mLogAccelerateInterpolator);
-    }
-
-    private void loadOtherRowAnimator() {
-        final AnimatorUpdateListener updateListener = new AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator arg0) {
-                if (getVerticalGridView() == null) {
-                    return;
-                }
-                final float fraction = (Float) arg0.getAnimatedValue();
-                final int count = getVerticalGridView().getChildCount();
-                for (int i = 0; i < count; i++) {
-                    View view = getVerticalGridView().getChildAt(i);
-                    if (getVerticalGridView().getChildAdapterPosition(view) > 0) {
-                        view.setAlpha(fraction);
-                        view.setTranslationY((float) mAnimationTranslateY * (1f - fraction));
-                    }
-                }
-            }
-        };
-
-        Context context = FragmentUtil.getContext(PlaybackFragment.this);
-        mOtherRowFadeInAnimator = loadAnimator(context, R.animator.lb_playback_controls_fade_in);
-        mOtherRowFadeInAnimator.addUpdateListener(updateListener);
-        mOtherRowFadeInAnimator.setInterpolator(mLogDecelerateInterpolator);
-
-        mOtherRowFadeOutAnimator = loadAnimator(context, R.animator.lb_playback_controls_fade_out);
-        mOtherRowFadeOutAnimator.addUpdateListener(updateListener);
-        mOtherRowFadeOutAnimator.setInterpolator(new AccelerateInterpolator());
-    }
-
-    /**
-     * Fades out the playback overlay immediately.
-     * @deprecated Call {@link #hideControlsOverlay(boolean)}
-     */
-    @Deprecated
-    public void fadeOut() {
-        showControlsOverlay(false, false);
-    }
-
-    /**
-     * Show controls overlay.
-     *
-     * @param runAnimation True to run animation, false otherwise.
-     */
-    public void showControlsOverlay(boolean runAnimation) {
-        showControlsOverlay(true, runAnimation);
-    }
-
-    /**
-     * Returns true if controls overlay is visible, false otherwise.
-     *
-     * @return True if controls overlay is visible, false otherwise.
-     * @see #showControlsOverlay(boolean)
-     * @see #hideControlsOverlay(boolean)
-     */
-    public boolean isControlsOverlayVisible() {
-        return mControlVisible;
-    }
-
-    /**
-     * Hide controls overlay.
-     *
-     * @param runAnimation True to run animation, false otherwise.
-     */
-    public void hideControlsOverlay(boolean runAnimation) {
-        showControlsOverlay(false, runAnimation);
-    }
-
-    /**
-     * if first animator is still running, reverse it; otherwise start second animator.
-     */
-    static void reverseFirstOrStartSecond(ValueAnimator first, ValueAnimator second,
-            boolean runAnimation) {
-        if (first.isStarted()) {
-            first.reverse();
-            if (!runAnimation) {
-                first.end();
-            }
-        } else {
-            second.start();
-            if (!runAnimation) {
-                second.end();
-            }
-        }
-    }
-
-    /**
-     * End first or second animator if they are still running.
-     */
-    static void endAll(ValueAnimator first, ValueAnimator second) {
-        if (first.isStarted()) {
-            first.end();
-        } else if (second.isStarted()) {
-            second.end();
-        }
-    }
-
-    /**
-     * Fade in or fade out rows and background.
-     *
-     * @param show True to fade in, false to fade out.
-     * @param animation True to run animation.
-     */
-    void showControlsOverlay(boolean show, boolean animation) {
-        if (DEBUG) Log.v(TAG, "showControlsOverlay " + show);
-        if (getView() == null) {
-            mControlVisibleBeforeOnCreateView = show;
-            return;
-        }
-        // force no animation when fragment is not resumed
-        if (!isResumed()) {
-            animation = false;
-        }
-        if (show == mControlVisible) {
-            if (!animation) {
-                // End animation if needed
-                endAll(mBgFadeInAnimator, mBgFadeOutAnimator);
-                endAll(mControlRowFadeInAnimator, mControlRowFadeOutAnimator);
-                endAll(mOtherRowFadeInAnimator, mOtherRowFadeOutAnimator);
-            }
-            return;
-        }
-        // StateGraph: 7<->5 4<->3 2->3
-        mControlVisible = show;
-        if (!mControlVisible) {
-            // StateGraph 2->3
-            stopFadeTimer();
-        }
-
-        mAnimationTranslateY = (getVerticalGridView() == null
-                || getVerticalGridView().getSelectedPosition() == 0)
-                ? mMajorFadeTranslateY : mMinorFadeTranslateY;
-
-        if (show) {
-            reverseFirstOrStartSecond(mBgFadeOutAnimator, mBgFadeInAnimator, animation);
-            reverseFirstOrStartSecond(mControlRowFadeOutAnimator, mControlRowFadeInAnimator,
-                    animation);
-            reverseFirstOrStartSecond(mOtherRowFadeOutAnimator, mOtherRowFadeInAnimator, animation);
-        } else {
-            reverseFirstOrStartSecond(mBgFadeInAnimator, mBgFadeOutAnimator, animation);
-            reverseFirstOrStartSecond(mControlRowFadeInAnimator, mControlRowFadeOutAnimator,
-                    animation);
-            reverseFirstOrStartSecond(mOtherRowFadeInAnimator, mOtherRowFadeOutAnimator, animation);
-        }
-        if (animation) {
-            getView().announceForAccessibility(getString(show
-                    ? R.string.lb_playback_controls_shown
-                    : R.string.lb_playback_controls_hidden));
-        }
-    }
-
-    /**
-     * Sets the selected row position with smooth animation.
-     */
-    public void setSelectedPosition(int position) {
-        setSelectedPosition(position, true);
-    }
-
-    /**
-     * Sets the selected row position.
-     */
-    public void setSelectedPosition(int position, boolean smooth) {
-        mSetSelectionRunnable.mPosition = position;
-        mSetSelectionRunnable.mSmooth = smooth;
-        if (getView() != null && getView().getHandler() != null) {
-            getView().getHandler().post(mSetSelectionRunnable);
-        }
-    }
-
-    private void setupChildFragmentLayout() {
-        setVerticalGridViewLayout(mRowsFragment.getVerticalGridView());
-    }
-
-    void setVerticalGridViewLayout(VerticalGridView listview) {
-        if (listview == null) {
-            return;
-        }
-
-        // we set the base line of alignment to -paddingBottom
-        listview.setWindowAlignmentOffset(-mPaddingBottom);
-        listview.setWindowAlignmentOffsetPercent(
-                VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
-
-        // align other rows that arent the last to center of screen, since our baseline is
-        // -mPaddingBottom, we need subtract that from mOtherRowsCenterToBottom.
-        listview.setItemAlignmentOffset(mOtherRowsCenterToBottom - mPaddingBottom);
-        listview.setItemAlignmentOffsetPercent(50);
-
-        // Push last row to the bottom padding
-        // Padding affects alignment when last row is focused
-        listview.setPadding(listview.getPaddingLeft(), listview.getPaddingTop(),
-                listview.getPaddingRight(), mPaddingBottom);
-        listview.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE);
-    }
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        mOtherRowsCenterToBottom = getResources()
-                .getDimensionPixelSize(R.dimen.lb_playback_other_rows_center_to_bottom);
-        mPaddingBottom =
-                getResources().getDimensionPixelSize(R.dimen.lb_playback_controls_padding_bottom);
-        mBgDarkColor =
-                getResources().getColor(R.color.lb_playback_controls_background_dark);
-        mBgLightColor =
-                getResources().getColor(R.color.lb_playback_controls_background_light);
-        mShowTimeMs =
-                getResources().getInteger(R.integer.lb_playback_controls_show_time_ms);
-        mMajorFadeTranslateY =
-                getResources().getDimensionPixelSize(R.dimen.lb_playback_major_fade_translate_y);
-        mMinorFadeTranslateY =
-                getResources().getDimensionPixelSize(R.dimen.lb_playback_minor_fade_translate_y);
-
-        loadBgAnimator();
-        loadControlRowAnimator();
-        loadOtherRowAnimator();
-    }
-
-    /**
-     * Sets the background type.
-     *
-     * @param type One of BG_LIGHT, BG_DARK, or BG_NONE.
-     */
-    public void setBackgroundType(int type) {
-        switch (type) {
-            case BG_LIGHT:
-            case BG_DARK:
-            case BG_NONE:
-                if (type != mBackgroundType) {
-                    mBackgroundType = type;
-                    updateBackground();
-                }
-                break;
-            default:
-                throw new IllegalArgumentException("Invalid background type");
-        }
-    }
-
-    /**
-     * Returns the background type.
-     */
-    public int getBackgroundType() {
-        return mBackgroundType;
-    }
-
-    private void updateBackground() {
-        if (mBackgroundView != null) {
-            int color = mBgDarkColor;
-            switch (mBackgroundType) {
-                case BG_DARK:
-                    break;
-                case BG_LIGHT:
-                    color = mBgLightColor;
-                    break;
-                case BG_NONE:
-                    color = Color.TRANSPARENT;
-                    break;
-            }
-            mBackgroundView.setBackground(new ColorDrawable(color));
-            setBgAlpha(mBgAlpha);
-        }
-    }
-
-    private final ItemBridgeAdapter.AdapterListener mAdapterListener =
-            new ItemBridgeAdapter.AdapterListener() {
-                @Override
-                public void onAttachedToWindow(ItemBridgeAdapter.ViewHolder vh) {
-                    if (DEBUG) Log.v(TAG, "onAttachedToWindow " + vh.getViewHolder().view);
-                    if (!mControlVisible) {
-                        if (DEBUG) Log.v(TAG, "setting alpha to 0");
-                        vh.getViewHolder().view.setAlpha(0);
-                    }
-                }
-
-                @Override
-                public void onCreate(ItemBridgeAdapter.ViewHolder vh) {
-                    Presenter.ViewHolder viewHolder = vh.getViewHolder();
-                    if (viewHolder instanceof PlaybackSeekUi) {
-                        ((PlaybackSeekUi) viewHolder).setPlaybackSeekUiClient(mChainedClient);
-                    }
-                }
-
-                @Override
-                public void onDetachedFromWindow(ItemBridgeAdapter.ViewHolder vh) {
-                    if (DEBUG) Log.v(TAG, "onDetachedFromWindow " + vh.getViewHolder().view);
-                    // Reset animation state
-                    vh.getViewHolder().view.setAlpha(1f);
-                    vh.getViewHolder().view.setTranslationY(0);
-                    vh.getViewHolder().view.setAlpha(1f);
-                }
-
-                @Override
-                public void onBind(ItemBridgeAdapter.ViewHolder vh) {
-                }
-            };
-
-    @Override
-    public View onCreateView(LayoutInflater inflater, ViewGroup container,
-                             Bundle savedInstanceState) {
-        mRootView = inflater.inflate(R.layout.lb_playback_fragment, container, false);
-        mBackgroundView = mRootView.findViewById(R.id.playback_fragment_background);
-        mRowsFragment = (RowsFragment) getChildFragmentManager().findFragmentById(
-                R.id.playback_controls_dock);
-        if (mRowsFragment == null) {
-            mRowsFragment = new RowsFragment();
-            getChildFragmentManager().beginTransaction()
-                    .replace(R.id.playback_controls_dock, mRowsFragment)
-                    .commit();
-        }
-        if (mAdapter == null) {
-            setAdapter(new ArrayObjectAdapter(new ClassPresenterSelector()));
-        } else {
-            mRowsFragment.setAdapter(mAdapter);
-        }
-        mRowsFragment.setOnItemViewSelectedListener(mOnItemViewSelectedListener);
-        mRowsFragment.setOnItemViewClickedListener(mOnItemViewClickedListener);
-
-        mBgAlpha = 255;
-        updateBackground();
-        mRowsFragment.setExternalAdapterListener(mAdapterListener);
-        ProgressBarManager progressBarManager = getProgressBarManager();
-        if (progressBarManager != null) {
-            progressBarManager.setRootView((ViewGroup) mRootView);
-        }
-        return mRootView;
-    }
-
-    /**
-     * Sets the {@link PlaybackGlueHost.HostCallback}. Implementor of this interface will
-     * take appropriate actions to take action when the hosting fragment starts/stops processing.
-     */
-    public void setHostCallback(PlaybackGlueHost.HostCallback hostCallback) {
-        this.mHostCallback = hostCallback;
-    }
-
-    @Override
-    public void onStart() {
-        super.onStart();
-        setupChildFragmentLayout();
-        mRowsFragment.setAdapter(mAdapter);
-        if (mHostCallback != null) {
-            mHostCallback.onHostStart();
-        }
-    }
-
-    @Override
-    public void onStop() {
-        if (mHostCallback != null) {
-            mHostCallback.onHostStop();
-        }
-        super.onStop();
-    }
-
-    @Override
-    public void onPause() {
-        if (mHostCallback != null) {
-            mHostCallback.onHostPause();
-        }
-        if (mHandler.hasMessages(START_FADE_OUT)) {
-            // StateGraph: 2->1
-            mHandler.removeMessages(START_FADE_OUT);
-        } else {
-            // StateGraph: 5->6, 7->6, 4->1, 3->1
-        }
-        super.onPause();
-    }
-
-    /**
-     * This listener is called every time there is a selection in {@link RowsFragment}. This can
-     * be used by users to take additional actions such as animations.
-     */
-    public void setOnItemViewSelectedListener(final BaseOnItemViewSelectedListener listener) {
-        mExternalItemSelectedListener = listener;
-    }
-
-    /**
-     * This listener is called every time there is a click in {@link RowsFragment}. This can
-     * be used by users to take additional actions such as animations.
-     */
-    public void setOnItemViewClickedListener(final BaseOnItemViewClickedListener listener) {
-        mExternalItemClickedListener = listener;
-    }
-
-    /**
-     * Sets the {@link BaseOnItemViewClickedListener} that would be invoked for clicks
-     * only on {@link android.support.v17.leanback.widget.PlaybackRowPresenter.ViewHolder}.
-     */
-    public void setOnPlaybackItemViewClickedListener(final BaseOnItemViewClickedListener listener) {
-        mPlaybackItemClickedListener = listener;
-    }
-
-    @Override
-    public void onDestroyView() {
-        mRootView = null;
-        mBackgroundView = null;
-        super.onDestroyView();
-    }
-
-    @Override
-    public void onDestroy() {
-        if (mHostCallback != null) {
-            mHostCallback.onHostDestroy();
-        }
-        super.onDestroy();
-    }
-
-    /**
-     * Sets the playback row for the playback controls. The row will be set as first element
-     * of adapter if the adapter is {@link ArrayObjectAdapter} or {@link SparseArrayObjectAdapter}.
-     * @param row The row that represents the playback.
-     */
-    public void setPlaybackRow(Row row) {
-        this.mRow = row;
-        setupRow();
-        setupPresenter();
-    }
-
-    /**
-     * Sets the presenter for rendering the playback row set by {@link #setPlaybackRow(Row)}. If
-     * adapter does not set a {@link PresenterSelector}, {@link #setAdapter(ObjectAdapter)} will
-     * create a {@link ClassPresenterSelector} by default and map from the row object class to this
-     * {@link PlaybackRowPresenter}.
-     *
-     * @param  presenter Presenter used to render {@link #setPlaybackRow(Row)}.
-     */
-    public void setPlaybackRowPresenter(PlaybackRowPresenter presenter) {
-        this.mPresenter = presenter;
-        setupPresenter();
-        setPlaybackRowPresenterAlignment();
-    }
-
-    void setPlaybackRowPresenterAlignment() {
-        if (mAdapter != null && mAdapter.getPresenterSelector() != null) {
-            Presenter[] presenters = mAdapter.getPresenterSelector().getPresenters();
-            if (presenters != null) {
-                for (int i = 0; i < presenters.length; i++) {
-                    if (presenters[i] instanceof PlaybackRowPresenter
-                            && presenters[i].getFacet(ItemAlignmentFacet.class) == null) {
-                        ItemAlignmentFacet itemAlignment = new ItemAlignmentFacet();
-                        ItemAlignmentFacet.ItemAlignmentDef def =
-                                new ItemAlignmentFacet.ItemAlignmentDef();
-                        def.setItemAlignmentOffset(0);
-                        def.setItemAlignmentOffsetPercent(100);
-                        itemAlignment.setAlignmentDefs(new ItemAlignmentFacet.ItemAlignmentDef[]
-                                {def});
-                        presenters[i].setFacet(ItemAlignmentFacet.class, itemAlignment);
-                    }
-                }
-            }
-        }
-    }
-
-    /**
-     * Updates the ui when the row data changes.
-     */
-    public void notifyPlaybackRowChanged() {
-        if (mAdapter == null) {
-            return;
-        }
-        mAdapter.notifyItemRangeChanged(0, 1);
-    }
-
-    /**
-     * Sets the list of rows for the fragment. A default {@link ClassPresenterSelector} will be
-     * created if {@link ObjectAdapter#getPresenterSelector()} is null. if user provides
-     * {@link #setPlaybackRow(Row)} and {@link #setPlaybackRowPresenter(PlaybackRowPresenter)},
-     * the row and presenter will be set onto the adapter.
-     *
-     * @param adapter The adapter that contains related rows and optional playback row.
-     */
-    public void setAdapter(ObjectAdapter adapter) {
-        mAdapter = adapter;
-        setupRow();
-        setupPresenter();
-        setPlaybackRowPresenterAlignment();
-
-        if (mRowsFragment != null) {
-            mRowsFragment.setAdapter(adapter);
-        }
-    }
-
-    private void setupRow() {
-        if (mAdapter instanceof ArrayObjectAdapter && mRow != null) {
-            ArrayObjectAdapter adapter = ((ArrayObjectAdapter) mAdapter);
-            if (adapter.size() == 0) {
-                adapter.add(mRow);
-            } else {
-                adapter.replace(0, mRow);
-            }
-        } else if (mAdapter instanceof SparseArrayObjectAdapter && mRow != null) {
-            SparseArrayObjectAdapter adapter = ((SparseArrayObjectAdapter) mAdapter);
-            adapter.set(0, mRow);
-        }
-    }
-
-    private void setupPresenter() {
-        if (mAdapter != null && mRow != null && mPresenter != null) {
-            PresenterSelector selector = mAdapter.getPresenterSelector();
-            if (selector == null) {
-                selector = new ClassPresenterSelector();
-                ((ClassPresenterSelector) selector).addClassPresenter(mRow.getClass(), mPresenter);
-                mAdapter.setPresenterSelector(selector);
-            } else if (selector instanceof ClassPresenterSelector) {
-                ((ClassPresenterSelector) selector).addClassPresenter(mRow.getClass(), mPresenter);
-            }
-        }
-    }
-
-    final PlaybackSeekUi.Client mChainedClient = new PlaybackSeekUi.Client() {
-        @Override
-        public boolean isSeekEnabled() {
-            return mSeekUiClient == null ? false : mSeekUiClient.isSeekEnabled();
-        }
-
-        @Override
-        public void onSeekStarted() {
-            if (mSeekUiClient != null) {
-                mSeekUiClient.onSeekStarted();
-            }
-            setSeekMode(true);
-        }
-
-        @Override
-        public PlaybackSeekDataProvider getPlaybackSeekDataProvider() {
-            return mSeekUiClient == null ? null : mSeekUiClient.getPlaybackSeekDataProvider();
-        }
-
-        @Override
-        public void onSeekPositionChanged(long pos) {
-            if (mSeekUiClient != null) {
-                mSeekUiClient.onSeekPositionChanged(pos);
-            }
-        }
-
-        @Override
-        public void onSeekFinished(boolean cancelled) {
-            if (mSeekUiClient != null) {
-                mSeekUiClient.onSeekFinished(cancelled);
-            }
-            setSeekMode(false);
-        }
-    };
-
-    /**
-     * Interface to be implemented by UI widget to support PlaybackSeekUi.
-     */
-    public void setPlaybackSeekUiClient(PlaybackSeekUi.Client client) {
-        mSeekUiClient = client;
-    }
-
-    /**
-     * Show or hide other rows other than PlaybackRow.
-     * @param inSeek True to make other rows visible, false to make other rows invisible.
-     */
-    void setSeekMode(boolean inSeek) {
-        if (mInSeek == inSeek) {
-            return;
-        }
-        mInSeek = inSeek;
-        getVerticalGridView().setSelectedPosition(0);
-        if (mInSeek) {
-            stopFadeTimer();
-        }
-        // immediately fade in control row.
-        showControlsOverlay(true);
-        final int count = getVerticalGridView().getChildCount();
-        for (int i = 0; i < count; i++) {
-            View view = getVerticalGridView().getChildAt(i);
-            if (getVerticalGridView().getChildAdapterPosition(view) > 0) {
-                view.setVisibility(mInSeek ? View.INVISIBLE : View.VISIBLE);
-            }
-        }
-    }
-
-    /**
-     * Called when size of the video changes. App may override.
-     * @param videoWidth Intrinsic width of video
-     * @param videoHeight Intrinsic height of video
-     */
-    protected void onVideoSizeChanged(int videoWidth, int videoHeight) {
-    }
-
-    /**
-     * Called when media has start or stop buffering. App may override. The default initial state
-     * is not buffering.
-     * @param start True for buffering start, false otherwise.
-     */
-    protected void onBufferingStateChanged(boolean start) {
-        ProgressBarManager progressBarManager = getProgressBarManager();
-        if (progressBarManager != null) {
-            if (start) {
-                progressBarManager.show();
-            } else {
-                progressBarManager.hide();
-            }
-        }
-    }
-
-    /**
-     * Called when media has error. App may override.
-     * @param errorCode Optional error code for specific implementation.
-     * @param errorMessage Optional error message for specific implementation.
-     */
-    protected void onError(int errorCode, CharSequence errorMessage) {
-    }
-
-    /**
-     * Returns the ProgressBarManager that will show or hide progress bar in
-     * {@link #onBufferingStateChanged(boolean)}.
-     * @return The ProgressBarManager that will show or hide progress bar in
-     * {@link #onBufferingStateChanged(boolean)}.
-     */
-    public ProgressBarManager getProgressBarManager() {
-        return mProgressBarManager;
-    }
-}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/PlaybackFragmentGlueHost.java b/v17/leanback/src/android/support/v17/leanback/app/PlaybackFragmentGlueHost.java
deleted file mode 100644
index 4a9d10f..0000000
--- a/v17/leanback/src/android/support/v17/leanback/app/PlaybackFragmentGlueHost.java
+++ /dev/null
@@ -1,140 +0,0 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from {}PlaybackSupportFragmentGlueHost.java.  DO NOT MODIFY. */
-
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-package android.support.v17.leanback.app;
-
-import android.support.v17.leanback.media.PlaybackGlueHost;
-import android.support.v17.leanback.widget.Action;
-import android.support.v17.leanback.widget.OnActionClickedListener;
-import android.support.v17.leanback.widget.OnItemViewClickedListener;
-import android.support.v17.leanback.widget.PlaybackRowPresenter;
-import android.support.v17.leanback.widget.PlaybackSeekUi;
-import android.support.v17.leanback.widget.Presenter;
-import android.support.v17.leanback.widget.Row;
-import android.support.v17.leanback.widget.RowPresenter;
-import android.view.View;
-
-/**
- * {@link PlaybackGlueHost} implementation
- * the interaction between this class and {@link PlaybackFragment}.
- */
-public class PlaybackFragmentGlueHost extends PlaybackGlueHost implements PlaybackSeekUi {
-    private final PlaybackFragment mFragment;
-
-    public PlaybackFragmentGlueHost(PlaybackFragment fragment) {
-        this.mFragment = fragment;
-    }
-
-    @Override
-    public void setControlsOverlayAutoHideEnabled(boolean enabled) {
-        mFragment.setControlsOverlayAutoHideEnabled(enabled);
-    }
-
-    @Override
-    public boolean isControlsOverlayAutoHideEnabled() {
-        return mFragment.isControlsOverlayAutoHideEnabled();
-    }
-
-    @Override
-    public void setOnKeyInterceptListener(View.OnKeyListener onKeyListener) {
-        mFragment.setOnKeyInterceptListener(onKeyListener);
-    }
-
-    @Override
-    public void setOnActionClickedListener(final OnActionClickedListener listener) {
-        if (listener == null) {
-            mFragment.setOnPlaybackItemViewClickedListener(null);
-        } else {
-            mFragment.setOnPlaybackItemViewClickedListener(new OnItemViewClickedListener() {
-                @Override
-                public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
-                                          RowPresenter.ViewHolder rowViewHolder, Row row) {
-                    if (item instanceof Action) {
-                        listener.onActionClicked((Action) item);
-                    }
-                }
-            });
-        }
-    }
-
-    @Override
-    public void setHostCallback(HostCallback callback) {
-        mFragment.setHostCallback(callback);
-    }
-
-    @Override
-    public void notifyPlaybackRowChanged() {
-        mFragment.notifyPlaybackRowChanged();
-    }
-
-    @Override
-    public void setPlaybackRowPresenter(PlaybackRowPresenter presenter) {
-        mFragment.setPlaybackRowPresenter(presenter);
-    }
-
-    @Override
-    public void setPlaybackRow(Row row) {
-        mFragment.setPlaybackRow(row);
-    }
-
-    @Override
-    public void fadeOut() {
-        mFragment.fadeOut();
-    }
-
-    @Override
-    public boolean isControlsOverlayVisible() {
-        return mFragment.isControlsOverlayVisible();
-    }
-
-    @Override
-    public void hideControlsOverlay(boolean runAnimation) {
-        mFragment.hideControlsOverlay(runAnimation);
-    }
-
-    @Override
-    public void showControlsOverlay(boolean runAnimation) {
-        mFragment.showControlsOverlay(runAnimation);
-    }
-
-    @Override
-    public void setPlaybackSeekUiClient(Client client) {
-        mFragment.setPlaybackSeekUiClient(client);
-    }
-
-    final PlayerCallback mPlayerCallback =
-            new PlayerCallback() {
-                @Override
-                public void onBufferingStateChanged(boolean start) {
-                    mFragment.onBufferingStateChanged(start);
-                }
-
-                @Override
-                public void onError(int errorCode, CharSequence errorMessage) {
-                    mFragment.onError(errorCode, errorMessage);
-                }
-
-                @Override
-                public void onVideoSizeChanged(int videoWidth, int videoHeight) {
-                    mFragment.onVideoSizeChanged(videoWidth, videoHeight);
-                }
-            };
-
-    @Override
-    public PlayerCallback getPlayerCallback() {
-        return mPlayerCallback;
-    }
-}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/RowsFragment.java b/v17/leanback/src/android/support/v17/leanback/app/RowsFragment.java
deleted file mode 100644
index a008ad6..0000000
--- a/v17/leanback/src/android/support/v17/leanback/app/RowsFragment.java
+++ /dev/null
@@ -1,685 +0,0 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from RowsSupportFragment.java.  DO NOT MODIFY. */
-
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-package android.support.v17.leanback.app;
-
-import android.animation.TimeAnimator;
-import android.animation.TimeAnimator.TimeListener;
-import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.v17.leanback.R;
-import android.support.v17.leanback.widget.BaseOnItemViewClickedListener;
-import android.support.v17.leanback.widget.BaseOnItemViewSelectedListener;
-import android.support.v17.leanback.widget.HorizontalGridView;
-import android.support.v17.leanback.widget.ItemBridgeAdapter;
-import android.support.v17.leanback.widget.ListRowPresenter;
-import android.support.v17.leanback.widget.ObjectAdapter;
-import android.support.v17.leanback.widget.OnItemViewClickedListener;
-import android.support.v17.leanback.widget.OnItemViewSelectedListener;
-import android.support.v17.leanback.widget.Presenter;
-import android.support.v17.leanback.widget.PresenterSelector;
-import android.support.v17.leanback.widget.RowPresenter;
-import android.support.v17.leanback.widget.VerticalGridView;
-import android.support.v17.leanback.widget.ViewHolderTask;
-import android.support.v7.widget.RecyclerView;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.animation.DecelerateInterpolator;
-import android.view.animation.Interpolator;
-
-import java.util.ArrayList;
-
-/**
- * An ordered set of rows of leanback widgets.
- * <p>
- * A RowsFragment renders the elements of its
- * {@link android.support.v17.leanback.widget.ObjectAdapter} as a set
- * of rows in a vertical list. The Adapter's {@link PresenterSelector} must maintain subclasses
- * of {@link RowPresenter}.
- * </p>
- */
-public class RowsFragment extends BaseRowFragment implements
-        BrowseFragment.MainFragmentRowsAdapterProvider,
-        BrowseFragment.MainFragmentAdapterProvider {
-
-    private MainFragmentAdapter mMainFragmentAdapter;
-    private MainFragmentRowsAdapter mMainFragmentRowsAdapter;
-
-    @Override
-    public BrowseFragment.MainFragmentAdapter getMainFragmentAdapter() {
-        if (mMainFragmentAdapter == null) {
-            mMainFragmentAdapter = new MainFragmentAdapter(this);
-        }
-        return mMainFragmentAdapter;
-    }
-
-    @Override
-    public BrowseFragment.MainFragmentRowsAdapter getMainFragmentRowsAdapter() {
-        if (mMainFragmentRowsAdapter == null) {
-            mMainFragmentRowsAdapter = new MainFragmentRowsAdapter(this);
-        }
-        return mMainFragmentRowsAdapter;
-    }
-
-    /**
-     * Internal helper class that manages row select animation and apply a default
-     * dim to each row.
-     */
-    final class RowViewHolderExtra implements TimeListener {
-        final RowPresenter mRowPresenter;
-        final Presenter.ViewHolder mRowViewHolder;
-
-        final TimeAnimator mSelectAnimator = new TimeAnimator();
-
-        int mSelectAnimatorDurationInUse;
-        Interpolator mSelectAnimatorInterpolatorInUse;
-        float mSelectLevelAnimStart;
-        float mSelectLevelAnimDelta;
-
-        RowViewHolderExtra(ItemBridgeAdapter.ViewHolder ibvh) {
-            mRowPresenter = (RowPresenter) ibvh.getPresenter();
-            mRowViewHolder = ibvh.getViewHolder();
-            mSelectAnimator.setTimeListener(this);
-        }
-
-        @Override
-        public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) {
-            if (mSelectAnimator.isRunning()) {
-                updateSelect(totalTime, deltaTime);
-            }
-        }
-
-        void updateSelect(long totalTime, long deltaTime) {
-            float fraction;
-            if (totalTime >= mSelectAnimatorDurationInUse) {
-                fraction = 1;
-                mSelectAnimator.end();
-            } else {
-                fraction = (float) (totalTime / (double) mSelectAnimatorDurationInUse);
-            }
-            if (mSelectAnimatorInterpolatorInUse != null) {
-                fraction = mSelectAnimatorInterpolatorInUse.getInterpolation(fraction);
-            }
-            float level = mSelectLevelAnimStart + fraction * mSelectLevelAnimDelta;
-            mRowPresenter.setSelectLevel(mRowViewHolder, level);
-        }
-
-        void animateSelect(boolean select, boolean immediate) {
-            mSelectAnimator.end();
-            final float end = select ? 1 : 0;
-            if (immediate) {
-                mRowPresenter.setSelectLevel(mRowViewHolder, end);
-            } else if (mRowPresenter.getSelectLevel(mRowViewHolder) != end) {
-                mSelectAnimatorDurationInUse = mSelectAnimatorDuration;
-                mSelectAnimatorInterpolatorInUse = mSelectAnimatorInterpolator;
-                mSelectLevelAnimStart = mRowPresenter.getSelectLevel(mRowViewHolder);
-                mSelectLevelAnimDelta = end - mSelectLevelAnimStart;
-                mSelectAnimator.start();
-            }
-        }
-
-    }
-
-    static final String TAG = "RowsFragment";
-    static final boolean DEBUG = false;
-    static final int ALIGN_TOP_NOT_SET = Integer.MIN_VALUE;
-
-    ItemBridgeAdapter.ViewHolder mSelectedViewHolder;
-    private int mSubPosition;
-    boolean mExpand = true;
-    boolean mViewsCreated;
-    private int mAlignedTop = ALIGN_TOP_NOT_SET;
-    boolean mAfterEntranceTransition = true;
-    boolean mFreezeRows;
-
-    BaseOnItemViewSelectedListener mOnItemViewSelectedListener;
-    BaseOnItemViewClickedListener mOnItemViewClickedListener;
-
-    // Select animation and interpolator are not intended to be
-    // exposed at this moment. They might be synced with vertical scroll
-    // animation later.
-    int mSelectAnimatorDuration;
-    Interpolator mSelectAnimatorInterpolator = new DecelerateInterpolator(2);
-
-    private RecyclerView.RecycledViewPool mRecycledViewPool;
-    private ArrayList<Presenter> mPresenterMapper;
-
-    ItemBridgeAdapter.AdapterListener mExternalAdapterListener;
-
-    @Override
-    protected VerticalGridView findGridViewFromRoot(View view) {
-        return (VerticalGridView) view.findViewById(R.id.container_list);
-    }
-
-    /**
-     * Sets an item clicked listener on the fragment.
-     * OnItemViewClickedListener will override {@link View.OnClickListener} that
-     * item presenter sets during {@link Presenter#onCreateViewHolder(ViewGroup)}.
-     * So in general, developer should choose one of the listeners but not both.
-     */
-    public void setOnItemViewClickedListener(BaseOnItemViewClickedListener listener) {
-        mOnItemViewClickedListener = listener;
-        if (mViewsCreated) {
-            throw new IllegalStateException(
-                    "Item clicked listener must be set before views are created");
-        }
-    }
-
-    /**
-     * Returns the item clicked listener.
-     */
-    public BaseOnItemViewClickedListener getOnItemViewClickedListener() {
-        return mOnItemViewClickedListener;
-    }
-
-    /**
-     * @deprecated use {@link BrowseFragment#enableRowScaling(boolean)} instead.
-     *
-     * @param enable true to enable row scaling
-     */
-    @Deprecated
-    public void enableRowScaling(boolean enable) {
-    }
-
-    /**
-     * Set the visibility of titles/hovercard of browse rows.
-     */
-    public void setExpand(boolean expand) {
-        mExpand = expand;
-        VerticalGridView listView = getVerticalGridView();
-        if (listView != null) {
-            final int count = listView.getChildCount();
-            if (DEBUG) Log.v(TAG, "setExpand " + expand + " count " + count);
-            for (int i = 0; i < count; i++) {
-                View view = listView.getChildAt(i);
-                ItemBridgeAdapter.ViewHolder vh =
-                        (ItemBridgeAdapter.ViewHolder) listView.getChildViewHolder(view);
-                setRowViewExpanded(vh, mExpand);
-            }
-        }
-    }
-
-    /**
-     * Sets an item selection listener.
-     */
-    public void setOnItemViewSelectedListener(BaseOnItemViewSelectedListener listener) {
-        mOnItemViewSelectedListener = listener;
-        VerticalGridView listView = getVerticalGridView();
-        if (listView != null) {
-            final int count = listView.getChildCount();
-            for (int i = 0; i < count; i++) {
-                View view = listView.getChildAt(i);
-                ItemBridgeAdapter.ViewHolder ibvh = (ItemBridgeAdapter.ViewHolder)
-                        listView.getChildViewHolder(view);
-                getRowViewHolder(ibvh).setOnItemViewSelectedListener(mOnItemViewSelectedListener);
-            }
-        }
-    }
-
-    /**
-     * Returns an item selection listener.
-     */
-    public BaseOnItemViewSelectedListener getOnItemViewSelectedListener() {
-        return mOnItemViewSelectedListener;
-    }
-
-    @Override
-    void onRowSelected(RecyclerView parent, RecyclerView.ViewHolder viewHolder,
-            int position, int subposition) {
-        if (mSelectedViewHolder != viewHolder || mSubPosition != subposition) {
-            if (DEBUG) Log.v(TAG, "new row selected position " + position + " subposition "
-                    + subposition + " view " + viewHolder.itemView);
-            mSubPosition = subposition;
-            if (mSelectedViewHolder != null) {
-                setRowViewSelected(mSelectedViewHolder, false, false);
-            }
-            mSelectedViewHolder = (ItemBridgeAdapter.ViewHolder) viewHolder;
-            if (mSelectedViewHolder != null) {
-                setRowViewSelected(mSelectedViewHolder, true, false);
-            }
-        }
-        // When RowsFragment is embedded inside a page fragment, we want to show
-        // the title view only when we're on the first row or there is no data.
-        if (mMainFragmentAdapter != null) {
-            mMainFragmentAdapter.getFragmentHost().showTitleView(position <= 0);
-        }
-    }
-
-    /**
-     * Get row ViewHolder at adapter position.  Returns null if the row object is not in adapter or
-     * the row object has not been bound to a row view.
-     *
-     * @param position Position of row in adapter.
-     * @return Row ViewHolder at a given adapter position.
-     */
-    public RowPresenter.ViewHolder getRowViewHolder(int position) {
-        VerticalGridView verticalView = getVerticalGridView();
-        if (verticalView == null) {
-            return null;
-        }
-        return getRowViewHolder((ItemBridgeAdapter.ViewHolder)
-                verticalView.findViewHolderForAdapterPosition(position));
-    }
-
-    @Override
-    int getLayoutResourceId() {
-        return R.layout.lb_rows_fragment;
-    }
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        mSelectAnimatorDuration = getResources().getInteger(
-                R.integer.lb_browse_rows_anim_duration);
-    }
-
-    @Override
-    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
-        if (DEBUG) Log.v(TAG, "onViewCreated");
-        super.onViewCreated(view, savedInstanceState);
-        // Align the top edge of child with id row_content.
-        // Need set this for directly using RowsFragment.
-        getVerticalGridView().setItemAlignmentViewId(R.id.row_content);
-        getVerticalGridView().setSaveChildrenPolicy(VerticalGridView.SAVE_LIMITED_CHILD);
-
-        setAlignment(mAlignedTop);
-
-        mRecycledViewPool = null;
-        mPresenterMapper = null;
-        if (mMainFragmentAdapter != null) {
-            mMainFragmentAdapter.getFragmentHost().notifyViewCreated(mMainFragmentAdapter);
-        }
-
-    }
-
-    @Override
-    public void onDestroyView() {
-        mViewsCreated = false;
-        super.onDestroyView();
-    }
-
-    void setExternalAdapterListener(ItemBridgeAdapter.AdapterListener listener) {
-        mExternalAdapterListener = listener;
-    }
-
-    static void setRowViewExpanded(ItemBridgeAdapter.ViewHolder vh, boolean expanded) {
-        ((RowPresenter) vh.getPresenter()).setRowViewExpanded(vh.getViewHolder(), expanded);
-    }
-
-    static void setRowViewSelected(ItemBridgeAdapter.ViewHolder vh, boolean selected,
-            boolean immediate) {
-        RowViewHolderExtra extra = (RowViewHolderExtra) vh.getExtraObject();
-        extra.animateSelect(selected, immediate);
-        ((RowPresenter) vh.getPresenter()).setRowViewSelected(vh.getViewHolder(), selected);
-    }
-
-    private final ItemBridgeAdapter.AdapterListener mBridgeAdapterListener =
-            new ItemBridgeAdapter.AdapterListener() {
-        @Override
-        public void onAddPresenter(Presenter presenter, int type) {
-            if (mExternalAdapterListener != null) {
-                mExternalAdapterListener.onAddPresenter(presenter, type);
-            }
-        }
-
-        @Override
-        public void onCreate(ItemBridgeAdapter.ViewHolder vh) {
-            VerticalGridView listView = getVerticalGridView();
-            if (listView != null) {
-                // set clip children false for slide animation
-                listView.setClipChildren(false);
-            }
-            setupSharedViewPool(vh);
-            mViewsCreated = true;
-            vh.setExtraObject(new RowViewHolderExtra(vh));
-            // selected state is initialized to false, then driven by grid view onChildSelected
-            // events.  When there is rebind, grid view fires onChildSelected event properly.
-            // So we don't need do anything special later in onBind or onAttachedToWindow.
-            setRowViewSelected(vh, false, true);
-            if (mExternalAdapterListener != null) {
-                mExternalAdapterListener.onCreate(vh);
-            }
-            RowPresenter rowPresenter = (RowPresenter) vh.getPresenter();
-            RowPresenter.ViewHolder rowVh = rowPresenter.getRowViewHolder(vh.getViewHolder());
-            rowVh.setOnItemViewSelectedListener(mOnItemViewSelectedListener);
-            rowVh.setOnItemViewClickedListener(mOnItemViewClickedListener);
-        }
-
-        @Override
-        public void onAttachedToWindow(ItemBridgeAdapter.ViewHolder vh) {
-            if (DEBUG) Log.v(TAG, "onAttachToWindow");
-            // All views share the same mExpand value.  When we attach a view to grid view,
-            // we should make sure it pick up the latest mExpand value we set early on other
-            // attached views.  For no-structure-change update,  the view is rebound to new data,
-            // but again it should use the unchanged mExpand value,  so we don't need do any
-            // thing in onBind.
-            setRowViewExpanded(vh, mExpand);
-            RowPresenter rowPresenter = (RowPresenter) vh.getPresenter();
-            RowPresenter.ViewHolder rowVh = rowPresenter.getRowViewHolder(vh.getViewHolder());
-            rowPresenter.setEntranceTransitionState(rowVh, mAfterEntranceTransition);
-
-            // freeze the rows attached after RowsFragment#freezeRows() is called
-            rowPresenter.freeze(rowVh, mFreezeRows);
-
-            if (mExternalAdapterListener != null) {
-                mExternalAdapterListener.onAttachedToWindow(vh);
-            }
-        }
-
-        @Override
-        public void onDetachedFromWindow(ItemBridgeAdapter.ViewHolder vh) {
-            if (mSelectedViewHolder == vh) {
-                setRowViewSelected(mSelectedViewHolder, false, true);
-                mSelectedViewHolder = null;
-            }
-            if (mExternalAdapterListener != null) {
-                mExternalAdapterListener.onDetachedFromWindow(vh);
-            }
-        }
-
-        @Override
-        public void onBind(ItemBridgeAdapter.ViewHolder vh) {
-            if (mExternalAdapterListener != null) {
-                mExternalAdapterListener.onBind(vh);
-            }
-        }
-
-        @Override
-        public void onUnbind(ItemBridgeAdapter.ViewHolder vh) {
-            setRowViewSelected(vh, false, true);
-            if (mExternalAdapterListener != null) {
-                mExternalAdapterListener.onUnbind(vh);
-            }
-        }
-    };
-
-    void setupSharedViewPool(ItemBridgeAdapter.ViewHolder bridgeVh) {
-        RowPresenter rowPresenter = (RowPresenter) bridgeVh.getPresenter();
-        RowPresenter.ViewHolder rowVh = rowPresenter.getRowViewHolder(bridgeVh.getViewHolder());
-
-        if (rowVh instanceof ListRowPresenter.ViewHolder) {
-            HorizontalGridView view = ((ListRowPresenter.ViewHolder) rowVh).getGridView();
-            // Recycled view pool is shared between all list rows
-            if (mRecycledViewPool == null) {
-                mRecycledViewPool = view.getRecycledViewPool();
-            } else {
-                view.setRecycledViewPool(mRecycledViewPool);
-            }
-
-            ItemBridgeAdapter bridgeAdapter =
-                    ((ListRowPresenter.ViewHolder) rowVh).getBridgeAdapter();
-            if (mPresenterMapper == null) {
-                mPresenterMapper = bridgeAdapter.getPresenterMapper();
-            } else {
-                bridgeAdapter.setPresenterMapper(mPresenterMapper);
-            }
-        }
-    }
-
-    @Override
-    void updateAdapter() {
-        super.updateAdapter();
-        mSelectedViewHolder = null;
-        mViewsCreated = false;
-
-        ItemBridgeAdapter adapter = getBridgeAdapter();
-        if (adapter != null) {
-            adapter.setAdapterListener(mBridgeAdapterListener);
-        }
-    }
-
-    @Override
-    public boolean onTransitionPrepare() {
-        boolean prepared = super.onTransitionPrepare();
-        if (prepared) {
-            freezeRows(true);
-        }
-        return prepared;
-    }
-
-    @Override
-    public void onTransitionEnd() {
-        super.onTransitionEnd();
-        freezeRows(false);
-    }
-
-    private void freezeRows(boolean freeze) {
-        mFreezeRows = freeze;
-        VerticalGridView verticalView = getVerticalGridView();
-        if (verticalView != null) {
-            final int count = verticalView.getChildCount();
-            for (int i = 0; i < count; i++) {
-                ItemBridgeAdapter.ViewHolder ibvh = (ItemBridgeAdapter.ViewHolder)
-                        verticalView.getChildViewHolder(verticalView.getChildAt(i));
-                RowPresenter rowPresenter = (RowPresenter) ibvh.getPresenter();
-                RowPresenter.ViewHolder vh = rowPresenter.getRowViewHolder(ibvh.getViewHolder());
-                rowPresenter.freeze(vh, freeze);
-            }
-        }
-    }
-
-    /**
-     * For rows that willing to participate entrance transition,  this function
-     * hide views if afterTransition is true,  show views if afterTransition is false.
-     */
-    public void setEntranceTransitionState(boolean afterTransition) {
-        mAfterEntranceTransition = afterTransition;
-        VerticalGridView verticalView = getVerticalGridView();
-        if (verticalView != null) {
-            final int count = verticalView.getChildCount();
-            for (int i = 0; i < count; i++) {
-                ItemBridgeAdapter.ViewHolder ibvh = (ItemBridgeAdapter.ViewHolder)
-                        verticalView.getChildViewHolder(verticalView.getChildAt(i));
-                RowPresenter rowPresenter = (RowPresenter) ibvh.getPresenter();
-                RowPresenter.ViewHolder vh = rowPresenter.getRowViewHolder(ibvh.getViewHolder());
-                rowPresenter.setEntranceTransitionState(vh, mAfterEntranceTransition);
-            }
-        }
-    }
-
-    /**
-     * Selects a Row and perform an optional task on the Row. For example
-     * <code>setSelectedPosition(10, true, new ListRowPresenterSelectItemViewHolderTask(5))</code>
-     * Scroll to 11th row and selects 6th item on that row.  The method will be ignored if
-     * RowsFragment has not been created (i.e. before {@link #onCreateView(LayoutInflater,
-     * ViewGroup, Bundle)}).
-     *
-     * @param rowPosition Which row to select.
-     * @param smooth True to scroll to the row, false for no animation.
-     * @param rowHolderTask Task to perform on the Row.
-     */
-    public void setSelectedPosition(int rowPosition, boolean smooth,
-            final Presenter.ViewHolderTask rowHolderTask) {
-        VerticalGridView verticalView = getVerticalGridView();
-        if (verticalView == null) {
-            return;
-        }
-        ViewHolderTask task = null;
-        if (rowHolderTask != null) {
-            // This task will execute once the scroll completes. Once the scrolling finishes,
-            // we will get a success callback to update selected row position. Since the
-            // update to selected row position happens in a post, we want to ensure that this
-            // gets called after that.
-            task = new ViewHolderTask() {
-                @Override
-                public void run(final RecyclerView.ViewHolder rvh) {
-                    rvh.itemView.post(new Runnable() {
-                        @Override
-                        public void run() {
-                            rowHolderTask.run(
-                                    getRowViewHolder((ItemBridgeAdapter.ViewHolder) rvh));
-                        }
-                    });
-                }
-            };
-        }
-
-        if (smooth) {
-            verticalView.setSelectedPositionSmooth(rowPosition, task);
-        } else {
-            verticalView.setSelectedPosition(rowPosition, task);
-        }
-    }
-
-    static RowPresenter.ViewHolder getRowViewHolder(ItemBridgeAdapter.ViewHolder ibvh) {
-        if (ibvh == null) {
-            return null;
-        }
-        RowPresenter rowPresenter = (RowPresenter) ibvh.getPresenter();
-        return rowPresenter.getRowViewHolder(ibvh.getViewHolder());
-    }
-
-    public boolean isScrolling() {
-        if (getVerticalGridView() == null) {
-            return false;
-        }
-        return getVerticalGridView().getScrollState() != HorizontalGridView.SCROLL_STATE_IDLE;
-    }
-
-    @Override
-    public void setAlignment(int windowAlignOffsetFromTop) {
-        if (windowAlignOffsetFromTop == ALIGN_TOP_NOT_SET) {
-            return;
-        }
-        mAlignedTop = windowAlignOffsetFromTop;
-        final VerticalGridView gridView = getVerticalGridView();
-
-        if (gridView != null) {
-            gridView.setItemAlignmentOffset(0);
-            gridView.setItemAlignmentOffsetPercent(
-                    VerticalGridView.ITEM_ALIGN_OFFSET_PERCENT_DISABLED);
-            gridView.setItemAlignmentOffsetWithPadding(true);
-            gridView.setWindowAlignmentOffset(mAlignedTop);
-            // align to a fixed position from top
-            gridView.setWindowAlignmentOffsetPercent(
-                    VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
-            gridView.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE);
-        }
-    }
-
-    /**
-     * Find row ViewHolder by position in adapter.
-     * @param position Position of row.
-     * @return ViewHolder of Row.
-     */
-    public RowPresenter.ViewHolder findRowViewHolderByPosition(int position) {
-        if (mVerticalGridView == null) {
-            return null;
-        }
-        return getRowViewHolder((ItemBridgeAdapter.ViewHolder) mVerticalGridView
-                .findViewHolderForAdapterPosition(position));
-    }
-
-    public static class MainFragmentAdapter extends BrowseFragment.MainFragmentAdapter<RowsFragment> {
-
-        public MainFragmentAdapter(RowsFragment fragment) {
-            super(fragment);
-            setScalingEnabled(true);
-        }
-
-        @Override
-        public boolean isScrolling() {
-            return getFragment().isScrolling();
-        }
-
-        @Override
-        public void setExpand(boolean expand) {
-            getFragment().setExpand(expand);
-        }
-
-        @Override
-        public void setEntranceTransitionState(boolean state) {
-            getFragment().setEntranceTransitionState(state);
-        }
-
-        @Override
-        public void setAlignment(int windowAlignOffsetFromTop) {
-            getFragment().setAlignment(windowAlignOffsetFromTop);
-        }
-
-        @Override
-        public boolean onTransitionPrepare() {
-            return getFragment().onTransitionPrepare();
-        }
-
-        @Override
-        public void onTransitionStart() {
-            getFragment().onTransitionStart();
-        }
-
-        @Override
-        public void onTransitionEnd() {
-            getFragment().onTransitionEnd();
-        }
-
-    }
-
-    /**
-     * The adapter that RowsFragment implements
-     * BrowseFragment.MainFragmentRowsAdapter.
-     * @see #getMainFragmentRowsAdapter().
-     */
-    public static class MainFragmentRowsAdapter
-            extends BrowseFragment.MainFragmentRowsAdapter<RowsFragment> {
-
-        public MainFragmentRowsAdapter(RowsFragment fragment) {
-            super(fragment);
-        }
-
-        @Override
-        public void setAdapter(ObjectAdapter adapter) {
-            getFragment().setAdapter(adapter);
-        }
-
-        /**
-         * Sets an item clicked listener on the fragment.
-         */
-        @Override
-        public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
-            getFragment().setOnItemViewClickedListener(listener);
-        }
-
-        @Override
-        public void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
-            getFragment().setOnItemViewSelectedListener(listener);
-        }
-
-        @Override
-        public void setSelectedPosition(int rowPosition,
-                                        boolean smooth,
-                                        final Presenter.ViewHolderTask rowHolderTask) {
-            getFragment().setSelectedPosition(rowPosition, smooth, rowHolderTask);
-        }
-
-        @Override
-        public void setSelectedPosition(int rowPosition, boolean smooth) {
-            getFragment().setSelectedPosition(rowPosition, smooth);
-        }
-
-        @Override
-        public int getSelectedPosition() {
-            return getFragment().getSelectedPosition();
-        }
-
-        @Override
-        public RowPresenter.ViewHolder findRowViewHolderByPosition(int position) {
-            return getFragment().findRowViewHolderByPosition(position);
-        }
-    }
-}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/SearchFragment.java b/v17/leanback/src/android/support/v17/leanback/app/SearchFragment.java
deleted file mode 100644
index 2154ff2..0000000
--- a/v17/leanback/src/android/support/v17/leanback/app/SearchFragment.java
+++ /dev/null
@@ -1,772 +0,0 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from SearchSupportFragment.java.  DO NOT MODIFY. */
-
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-package android.support.v17.leanback.app;
-
-import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-
-import android.Manifest;
-import android.content.Intent;
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.os.Handler;
-import android.speech.RecognizerIntent;
-import android.speech.SpeechRecognizer;
-import android.support.v17.leanback.R;
-import android.support.v17.leanback.widget.ObjectAdapter;
-import android.support.v17.leanback.widget.ObjectAdapter.DataObserver;
-import android.support.v17.leanback.widget.OnItemViewClickedListener;
-import android.support.v17.leanback.widget.OnItemViewSelectedListener;
-import android.support.v17.leanback.widget.Presenter.ViewHolder;
-import android.support.v17.leanback.widget.Row;
-import android.support.v17.leanback.widget.RowPresenter;
-import android.support.v17.leanback.widget.SearchBar;
-import android.support.v17.leanback.widget.SearchOrbView;
-import android.support.v17.leanback.widget.SpeechRecognitionCallback;
-import android.support.v17.leanback.widget.VerticalGridView;
-import android.app.Fragment;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.inputmethod.CompletionInfo;
-import android.widget.FrameLayout;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * A fragment to handle searches. An application will supply an implementation
- * of the {@link SearchResultProvider} interface to handle the search and return
- * an {@link ObjectAdapter} containing the results. The results are rendered
- * into a {@link RowsFragment}, in the same way that they are in a {@link
- * BrowseFragment}.
- *
- * <p>A SpeechRecognizer object will be created for which your application will need to declare
- * android.permission.RECORD_AUDIO in AndroidManifest file. If app's target version is >= 23 and
- * the device version is >= 23, a permission dialog will show first time using speech recognition.
- * 0 will be used as requestCode in requestPermissions() call.
- * {@link #setSpeechRecognitionCallback(SpeechRecognitionCallback)} is deprecated.
- * </p>
- * <p>
- * Speech recognition is automatically started when fragment is created, but
- * not when fragment is restored from an instance state.  Activity may manually
- * call {@link #startRecognition()}, typically in onNewIntent().
- * </p>
- */
-public class SearchFragment extends Fragment {
-    static final String TAG = SearchFragment.class.getSimpleName();
-    static final boolean DEBUG = false;
-
-    private static final String EXTRA_LEANBACK_BADGE_PRESENT = "LEANBACK_BADGE_PRESENT";
-    private static final String ARG_PREFIX = SearchFragment.class.getCanonicalName();
-    private static final String ARG_QUERY =  ARG_PREFIX + ".query";
-    private static final String ARG_TITLE = ARG_PREFIX  + ".title";
-
-    static final long SPEECH_RECOGNITION_DELAY_MS = 300;
-
-    static final int RESULTS_CHANGED = 0x1;
-    static final int QUERY_COMPLETE = 0x2;
-
-    static final int AUDIO_PERMISSION_REQUEST_CODE = 0;
-
-    /**
-     * Search API to be provided by the application.
-     */
-    public static interface SearchResultProvider {
-        /**
-         * <p>Method invoked some time prior to the first call to onQueryTextChange to retrieve
-         * an ObjectAdapter that will contain the results to future updates of the search query.</p>
-         *
-         * <p>As results are retrieved, the application should use the data set notification methods
-         * on the ObjectAdapter to instruct the SearchFragment to update the results.</p>
-         *
-         * @return ObjectAdapter The result object adapter.
-         */
-        public ObjectAdapter getResultsAdapter();
-
-        /**
-         * <p>Method invoked when the search query is updated.</p>
-         *
-         * <p>This is called as soon as the query changes; it is up to the application to add a
-         * delay before actually executing the queries if needed.
-         *
-         * <p>This method might not always be called before onQueryTextSubmit gets called, in
-         * particular for voice input.
-         *
-         * @param newQuery The current search query.
-         * @return whether the results changed as a result of the new query.
-         */
-        public boolean onQueryTextChange(String newQuery);
-
-        /**
-         * Method invoked when the search query is submitted, either by dismissing the keyboard,
-         * pressing search or next on the keyboard or when voice has detected the end of the query.
-         *
-         * @param query The query entered.
-         * @return whether the results changed as a result of the query.
-         */
-        public boolean onQueryTextSubmit(String query);
-    }
-
-    final DataObserver mAdapterObserver = new DataObserver() {
-        @Override
-        public void onChanged() {
-            // onChanged() may be called multiple times e.g. the provider add
-            // rows to ArrayObjectAdapter one by one.
-            mHandler.removeCallbacks(mResultsChangedCallback);
-            mHandler.post(mResultsChangedCallback);
-        }
-    };
-
-    final Handler mHandler = new Handler();
-
-    final Runnable mResultsChangedCallback = new Runnable() {
-        @Override
-        public void run() {
-            if (DEBUG) Log.v(TAG, "results changed, new size " + mResultAdapter.size());
-            if (mRowsFragment != null
-                    && mRowsFragment.getAdapter() != mResultAdapter) {
-                if (!(mRowsFragment.getAdapter() == null && mResultAdapter.size() == 0)) {
-                    mRowsFragment.setAdapter(mResultAdapter);
-                    mRowsFragment.setSelectedPosition(0);
-                }
-            }
-            updateSearchBarVisibility();
-            mStatus |= RESULTS_CHANGED;
-            if ((mStatus & QUERY_COMPLETE) != 0) {
-                updateFocus();
-            }
-            updateSearchBarNextFocusId();
-        }
-    };
-
-    /**
-     * Runs when a new provider is set AND when the fragment view is created.
-     */
-    private final Runnable mSetSearchResultProvider = new Runnable() {
-        @Override
-        public void run() {
-            if (mRowsFragment == null) {
-                // We'll retry once we have a rows fragment
-                return;
-            }
-            // Retrieve the result adapter
-            ObjectAdapter adapter = mProvider.getResultsAdapter();
-            if (DEBUG) Log.v(TAG, "Got results adapter " + adapter);
-            if (adapter != mResultAdapter) {
-                boolean firstTime = mResultAdapter == null;
-                releaseAdapter();
-                mResultAdapter = adapter;
-                if (mResultAdapter != null) {
-                    mResultAdapter.registerObserver(mAdapterObserver);
-                }
-                if (DEBUG) {
-                    Log.v(TAG, "mResultAdapter " + mResultAdapter + " size "
-                            + (mResultAdapter == null ? 0 : mResultAdapter.size()));
-                }
-                // delay the first time to avoid setting a empty result adapter
-                // until we got first onChange() from the provider
-                if (!(firstTime && (mResultAdapter == null || mResultAdapter.size() == 0))) {
-                    mRowsFragment.setAdapter(mResultAdapter);
-                }
-                executePendingQuery();
-            }
-            updateSearchBarNextFocusId();
-
-            if (DEBUG) {
-                Log.v(TAG, "mAutoStartRecognition " + mAutoStartRecognition
-                        + " mResultAdapter " + mResultAdapter
-                        + " adapter " + mRowsFragment.getAdapter());
-            }
-            if (mAutoStartRecognition) {
-                mHandler.removeCallbacks(mStartRecognitionRunnable);
-                mHandler.postDelayed(mStartRecognitionRunnable, SPEECH_RECOGNITION_DELAY_MS);
-            } else {
-                updateFocus();
-            }
-        }
-    };
-
-    final Runnable mStartRecognitionRunnable = new Runnable() {
-        @Override
-        public void run() {
-            mAutoStartRecognition = false;
-            mSearchBar.startRecognition();
-        }
-    };
-
-    RowsFragment mRowsFragment;
-    SearchBar mSearchBar;
-    SearchResultProvider mProvider;
-    String mPendingQuery = null;
-
-    OnItemViewSelectedListener mOnItemViewSelectedListener;
-    private OnItemViewClickedListener mOnItemViewClickedListener;
-    ObjectAdapter mResultAdapter;
-    private SpeechRecognitionCallback mSpeechRecognitionCallback;
-
-    private String mTitle;
-    private Drawable mBadgeDrawable;
-    private ExternalQuery mExternalQuery;
-
-    private SpeechRecognizer mSpeechRecognizer;
-
-    int mStatus;
-    boolean mAutoStartRecognition = true;
-
-    private boolean mIsPaused;
-    private boolean mPendingStartRecognitionWhenPaused;
-    private SearchBar.SearchBarPermissionListener mPermissionListener =
-            new SearchBar.SearchBarPermissionListener() {
-        @Override
-        public void requestAudioPermission() {
-            PermissionHelper.requestPermissions(SearchFragment.this,
-                    new String[]{Manifest.permission.RECORD_AUDIO}, AUDIO_PERMISSION_REQUEST_CODE);
-        }
-    };
-
-    @Override
-    public void onRequestPermissionsResult(int requestCode, String[] permissions,
-                                           int[] grantResults) {
-        if (requestCode == AUDIO_PERMISSION_REQUEST_CODE && permissions.length > 0) {
-            if (permissions[0].equals(Manifest.permission.RECORD_AUDIO)
-                    && grantResults[0] == PERMISSION_GRANTED) {
-                startRecognition();
-            }
-        }
-    }
-
-    /**
-     * @param args Bundle to use for the arguments, if null a new Bundle will be created.
-     */
-    public static Bundle createArgs(Bundle args, String query) {
-        return createArgs(args, query, null);
-    }
-
-    public static Bundle createArgs(Bundle args, String query, String title)  {
-        if (args == null) {
-            args = new Bundle();
-        }
-        args.putString(ARG_QUERY, query);
-        args.putString(ARG_TITLE, title);
-        return args;
-    }
-
-    /**
-     * Creates a search fragment with a given search query.
-     *
-     * <p>You should only use this if you need to start the search fragment with a
-     * pre-filled query.
-     *
-     * @param query The search query to begin with.
-     * @return A new SearchFragment.
-     */
-    public static SearchFragment newInstance(String query) {
-        SearchFragment fragment = new SearchFragment();
-        Bundle args = createArgs(null, query);
-        fragment.setArguments(args);
-        return fragment;
-    }
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        if (mAutoStartRecognition) {
-            mAutoStartRecognition = savedInstanceState == null;
-        }
-        super.onCreate(savedInstanceState);
-    }
-
-    @Override
-    public View onCreateView(LayoutInflater inflater, ViewGroup container,
-                             Bundle savedInstanceState) {
-        View root = inflater.inflate(R.layout.lb_search_fragment, container, false);
-
-        FrameLayout searchFrame = (FrameLayout) root.findViewById(R.id.lb_search_frame);
-        mSearchBar = (SearchBar) searchFrame.findViewById(R.id.lb_search_bar);
-        mSearchBar.setSearchBarListener(new SearchBar.SearchBarListener() {
-            @Override
-            public void onSearchQueryChange(String query) {
-                if (DEBUG) Log.v(TAG, String.format("onSearchQueryChange %s %s", query,
-                        null == mProvider ? "(null)" : mProvider));
-                if (null != mProvider) {
-                    retrieveResults(query);
-                } else {
-                    mPendingQuery = query;
-                }
-            }
-
-            @Override
-            public void onSearchQuerySubmit(String query) {
-                if (DEBUG) Log.v(TAG, String.format("onSearchQuerySubmit %s", query));
-                submitQuery(query);
-            }
-
-            @Override
-            public void onKeyboardDismiss(String query) {
-                if (DEBUG) Log.v(TAG, String.format("onKeyboardDismiss %s", query));
-                queryComplete();
-            }
-        });
-        mSearchBar.setSpeechRecognitionCallback(mSpeechRecognitionCallback);
-        mSearchBar.setPermissionListener(mPermissionListener);
-        applyExternalQuery();
-
-        readArguments(getArguments());
-        if (null != mBadgeDrawable) {
-            setBadgeDrawable(mBadgeDrawable);
-        }
-        if (null != mTitle) {
-            setTitle(mTitle);
-        }
-
-        // Inject the RowsFragment in the results container
-        if (getChildFragmentManager().findFragmentById(R.id.lb_results_frame) == null) {
-            mRowsFragment = new RowsFragment();
-            getChildFragmentManager().beginTransaction()
-                    .replace(R.id.lb_results_frame, mRowsFragment).commit();
-        } else {
-            mRowsFragment = (RowsFragment) getChildFragmentManager()
-                    .findFragmentById(R.id.lb_results_frame);
-        }
-        mRowsFragment.setOnItemViewSelectedListener(new OnItemViewSelectedListener() {
-            @Override
-            public void onItemSelected(ViewHolder itemViewHolder, Object item,
-                                       RowPresenter.ViewHolder rowViewHolder, Row row) {
-                if (DEBUG) {
-                    int position = mRowsFragment.getSelectedPosition();
-                    Log.v(TAG, String.format("onItemSelected %d", position));
-                }
-                updateSearchBarVisibility();
-                if (null != mOnItemViewSelectedListener) {
-                    mOnItemViewSelectedListener.onItemSelected(itemViewHolder, item,
-                            rowViewHolder, row);
-                }
-            }
-        });
-        mRowsFragment.setOnItemViewClickedListener(mOnItemViewClickedListener);
-        mRowsFragment.setExpand(true);
-        if (null != mProvider) {
-            onSetSearchResultProvider();
-        }
-        return root;
-    }
-
-    private void resultsAvailable() {
-        if ((mStatus & QUERY_COMPLETE) != 0) {
-            focusOnResults();
-        }
-        updateSearchBarNextFocusId();
-    }
-
-    @Override
-    public void onStart() {
-        super.onStart();
-
-        VerticalGridView list = mRowsFragment.getVerticalGridView();
-        int mContainerListAlignTop =
-                getResources().getDimensionPixelSize(R.dimen.lb_search_browse_rows_align_top);
-        list.setItemAlignmentOffset(0);
-        list.setItemAlignmentOffsetPercent(VerticalGridView.ITEM_ALIGN_OFFSET_PERCENT_DISABLED);
-        list.setWindowAlignmentOffset(mContainerListAlignTop);
-        list.setWindowAlignmentOffsetPercent(VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
-        list.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE);
-        // VerticalGridView should not be focusable (see b/26894680 for details).
-        list.setFocusable(false);
-        list.setFocusableInTouchMode(false);
-    }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-        mIsPaused = false;
-        if (mSpeechRecognitionCallback == null && null == mSpeechRecognizer) {
-            mSpeechRecognizer = SpeechRecognizer.createSpeechRecognizer(
-                    FragmentUtil.getContext(SearchFragment.this));
-            mSearchBar.setSpeechRecognizer(mSpeechRecognizer);
-        }
-        if (mPendingStartRecognitionWhenPaused) {
-            mPendingStartRecognitionWhenPaused = false;
-            mSearchBar.startRecognition();
-        } else {
-            // Ensure search bar state consistency when using external recognizer
-            mSearchBar.stopRecognition();
-        }
-    }
-
-    @Override
-    public void onPause() {
-        releaseRecognizer();
-        mIsPaused = true;
-        super.onPause();
-    }
-
-    @Override
-    public void onDestroy() {
-        releaseAdapter();
-        super.onDestroy();
-    }
-
-    /**
-     * Returns RowsFragment that shows result rows. RowsFragment is initialized after
-     * SearchFragment.onCreateView().
-     *
-     * @return RowsFragment that shows result rows.
-     */
-    public RowsFragment getRowsFragment() {
-        return mRowsFragment;
-    }
-
-    private void releaseRecognizer() {
-        if (null != mSpeechRecognizer) {
-            mSearchBar.setSpeechRecognizer(null);
-            mSpeechRecognizer.destroy();
-            mSpeechRecognizer = null;
-        }
-    }
-
-    /**
-     * Starts speech recognition.  Typical use case is that
-     * activity receives onNewIntent() call when user clicks a MIC button.
-     * Note that SearchFragment automatically starts speech recognition
-     * at first time created, there is no need to call startRecognition()
-     * when fragment is created.
-     */
-    public void startRecognition() {
-        if (mIsPaused) {
-            mPendingStartRecognitionWhenPaused = true;
-        } else {
-            mSearchBar.startRecognition();
-        }
-    }
-
-    /**
-     * Sets the search provider that is responsible for returning results for the
-     * search query.
-     */
-    public void setSearchResultProvider(SearchResultProvider searchResultProvider) {
-        if (mProvider != searchResultProvider) {
-            mProvider = searchResultProvider;
-            onSetSearchResultProvider();
-        }
-    }
-
-    /**
-     * Sets an item selection listener for the results.
-     *
-     * @param listener The item selection listener to be invoked when an item in
-     *        the search results is selected.
-     */
-    public void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
-        mOnItemViewSelectedListener = listener;
-    }
-
-    /**
-     * Sets an item clicked listener for the results.
-     *
-     * @param listener The item clicked listener to be invoked when an item in
-     *        the search results is clicked.
-     */
-    public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
-        if (listener != mOnItemViewClickedListener) {
-            mOnItemViewClickedListener = listener;
-            if (mRowsFragment != null) {
-                mRowsFragment.setOnItemViewClickedListener(mOnItemViewClickedListener);
-            }
-        }
-    }
-
-    /**
-     * Sets the title string to be be shown in an empty search bar. The title
-     * may be placed in a call-to-action, such as "Search <i>title</i>" or
-     * "Speak to search <i>title</i>".
-     */
-    public void setTitle(String title) {
-        mTitle = title;
-        if (null != mSearchBar) {
-            mSearchBar.setTitle(title);
-        }
-    }
-
-    /**
-     * Returns the title set in the search bar.
-     */
-    public String getTitle() {
-        if (null != mSearchBar) {
-            return mSearchBar.getTitle();
-        }
-        return null;
-    }
-
-    /**
-     * Sets the badge drawable that will be shown inside the search bar next to
-     * the title.
-     */
-    public void setBadgeDrawable(Drawable drawable) {
-        mBadgeDrawable = drawable;
-        if (null != mSearchBar) {
-            mSearchBar.setBadgeDrawable(drawable);
-        }
-    }
-
-    /**
-     * Returns the badge drawable in the search bar.
-     */
-    public Drawable getBadgeDrawable() {
-        if (null != mSearchBar) {
-            return mSearchBar.getBadgeDrawable();
-        }
-        return null;
-    }
-
-    /**
-     * Sets background color of not-listening state search orb.
-     *
-     * @param colors SearchOrbView.Colors.
-     */
-    public void setSearchAffordanceColors(SearchOrbView.Colors colors) {
-        if (mSearchBar != null) {
-            mSearchBar.setSearchAffordanceColors(colors);
-        }
-    }
-
-    /**
-     * Sets background color of listening state search orb.
-     *
-     * @param colors SearchOrbView.Colors.
-     */
-    public void setSearchAffordanceColorsInListening(SearchOrbView.Colors colors) {
-        if (mSearchBar != null) {
-            mSearchBar.setSearchAffordanceColorsInListening(colors);
-        }
-    }
-
-    /**
-     * Displays the completions shown by the IME. An application may provide
-     * a list of query completions that the system will show in the IME.
-     *
-     * @param completions A list of completions to show in the IME. Setting to
-     *        null or empty will clear the list.
-     */
-    public void displayCompletions(List<String> completions) {
-        mSearchBar.displayCompletions(completions);
-    }
-
-    /**
-     * Displays the completions shown by the IME. An application may provide
-     * a list of query completions that the system will show in the IME.
-     *
-     * @param completions A list of completions to show in the IME. Setting to
-     *        null or empty will clear the list.
-     */
-    public void displayCompletions(CompletionInfo[] completions) {
-        mSearchBar.displayCompletions(completions);
-    }
-
-    /**
-     * Sets this callback to have the fragment pass speech recognition requests
-     * to the activity rather than using a SpeechRecognizer object.
-     * @deprecated Launching voice recognition activity is no longer supported. App should declare
-     *             android.permission.RECORD_AUDIO in AndroidManifest file.
-     */
-    @Deprecated
-    public void setSpeechRecognitionCallback(SpeechRecognitionCallback callback) {
-        mSpeechRecognitionCallback = callback;
-        if (mSearchBar != null) {
-            mSearchBar.setSpeechRecognitionCallback(mSpeechRecognitionCallback);
-        }
-        if (callback != null) {
-            releaseRecognizer();
-        }
-    }
-
-    /**
-     * Sets the text of the search query and optionally submits the query. Either
-     * {@link SearchResultProvider#onQueryTextChange onQueryTextChange} or
-     * {@link SearchResultProvider#onQueryTextSubmit onQueryTextSubmit} will be
-     * called on the provider if it is set.
-     *
-     * @param query The search query to set.
-     * @param submit Whether to submit the query.
-     */
-    public void setSearchQuery(String query, boolean submit) {
-        if (DEBUG) Log.v(TAG, "setSearchQuery " + query + " submit " + submit);
-        if (query == null) {
-            return;
-        }
-        mExternalQuery = new ExternalQuery(query, submit);
-        applyExternalQuery();
-        if (mAutoStartRecognition) {
-            mAutoStartRecognition = false;
-            mHandler.removeCallbacks(mStartRecognitionRunnable);
-        }
-    }
-
-    /**
-     * Sets the text of the search query based on the {@link RecognizerIntent#EXTRA_RESULTS} in
-     * the given intent, and optionally submit the query.  If more than one result is present
-     * in the results list, the first will be used.
-     *
-     * @param intent Intent received from a speech recognition service.
-     * @param submit Whether to submit the query.
-     */
-    public void setSearchQuery(Intent intent, boolean submit) {
-        ArrayList<String> matches = intent.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
-        if (matches != null && matches.size() > 0) {
-            setSearchQuery(matches.get(0), submit);
-        }
-    }
-
-    /**
-     * Returns an intent that can be used to request speech recognition.
-     * Built from the base {@link RecognizerIntent#ACTION_RECOGNIZE_SPEECH} plus
-     * extras:
-     *
-     * <ul>
-     * <li>{@link RecognizerIntent#EXTRA_LANGUAGE_MODEL} set to
-     * {@link RecognizerIntent#LANGUAGE_MODEL_FREE_FORM}</li>
-     * <li>{@link RecognizerIntent#EXTRA_PARTIAL_RESULTS} set to true</li>
-     * <li>{@link RecognizerIntent#EXTRA_PROMPT} set to the search bar hint text</li>
-     * </ul>
-     *
-     * For handling the intent returned from the service, see
-     * {@link #setSearchQuery(Intent, boolean)}.
-     */
-    public Intent getRecognizerIntent() {
-        Intent recognizerIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
-        recognizerIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
-                RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
-        recognizerIntent.putExtra(RecognizerIntent.EXTRA_PARTIAL_RESULTS, true);
-        if (mSearchBar != null && mSearchBar.getHint() != null) {
-            recognizerIntent.putExtra(RecognizerIntent.EXTRA_PROMPT, mSearchBar.getHint());
-        }
-        recognizerIntent.putExtra(EXTRA_LEANBACK_BADGE_PRESENT, mBadgeDrawable != null);
-        return recognizerIntent;
-    }
-
-    void retrieveResults(String searchQuery) {
-        if (DEBUG) Log.v(TAG, "retrieveResults " + searchQuery);
-        if (mProvider.onQueryTextChange(searchQuery)) {
-            mStatus &= ~QUERY_COMPLETE;
-        }
-    }
-
-    void submitQuery(String query) {
-        queryComplete();
-        if (null != mProvider) {
-            mProvider.onQueryTextSubmit(query);
-        }
-    }
-
-    void queryComplete() {
-        if (DEBUG) Log.v(TAG, "queryComplete");
-        mStatus |= QUERY_COMPLETE;
-        focusOnResults();
-    }
-
-    void updateSearchBarVisibility() {
-        int position = mRowsFragment != null ? mRowsFragment.getSelectedPosition() : -1;
-        mSearchBar.setVisibility(position <=0 || mResultAdapter == null
-                || mResultAdapter.size() == 0 ? View.VISIBLE : View.GONE);
-    }
-
-    void updateSearchBarNextFocusId() {
-        if (mSearchBar == null || mResultAdapter == null) {
-            return;
-        }
-        final int viewId = (mResultAdapter.size() == 0 || mRowsFragment == null
-                || mRowsFragment.getVerticalGridView() == null)
-                        ? 0 : mRowsFragment.getVerticalGridView().getId();
-        mSearchBar.setNextFocusDownId(viewId);
-    }
-
-    void updateFocus() {
-        if (mResultAdapter != null && mResultAdapter.size() > 0
-                && mRowsFragment != null && mRowsFragment.getAdapter() == mResultAdapter) {
-            focusOnResults();
-        } else {
-            mSearchBar.requestFocus();
-        }
-    }
-
-    private void focusOnResults() {
-        if (mRowsFragment == null || mRowsFragment.getVerticalGridView() == null
-                || mResultAdapter.size() == 0) {
-            return;
-        }
-        if (mRowsFragment.getVerticalGridView().requestFocus()) {
-            mStatus &= ~RESULTS_CHANGED;
-        }
-    }
-
-    private void onSetSearchResultProvider() {
-        mHandler.removeCallbacks(mSetSearchResultProvider);
-        mHandler.post(mSetSearchResultProvider);
-    }
-
-    void releaseAdapter() {
-        if (mResultAdapter != null) {
-            mResultAdapter.unregisterObserver(mAdapterObserver);
-            mResultAdapter = null;
-        }
-    }
-
-    void executePendingQuery() {
-        if (null != mPendingQuery && null != mResultAdapter) {
-            String query = mPendingQuery;
-            mPendingQuery = null;
-            retrieveResults(query);
-        }
-    }
-
-    private void applyExternalQuery() {
-        if (mExternalQuery == null || mSearchBar == null) {
-            return;
-        }
-        mSearchBar.setSearchQuery(mExternalQuery.mQuery);
-        if (mExternalQuery.mSubmit) {
-            submitQuery(mExternalQuery.mQuery);
-        }
-        mExternalQuery = null;
-    }
-
-    private void readArguments(Bundle args) {
-        if (null == args) {
-            return;
-        }
-        if (args.containsKey(ARG_QUERY)) {
-            setSearchQuery(args.getString(ARG_QUERY));
-        }
-
-        if (args.containsKey(ARG_TITLE)) {
-            setTitle(args.getString(ARG_TITLE));
-        }
-    }
-
-    private void setSearchQuery(String query) {
-        mSearchBar.setSearchQuery(query);
-    }
-
-    static class ExternalQuery {
-        String mQuery;
-        boolean mSubmit;
-
-        ExternalQuery(String query, boolean submit) {
-            mQuery = query;
-            mSubmit = submit;
-        }
-    }
-}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/VerticalGridFragment.java b/v17/leanback/src/android/support/v17/leanback/app/VerticalGridFragment.java
deleted file mode 100644
index 5bc52ff..0000000
--- a/v17/leanback/src/android/support/v17/leanback/app/VerticalGridFragment.java
+++ /dev/null
@@ -1,258 +0,0 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from VerticalGridSupportFragment.java.  DO NOT MODIFY. */
-
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-package android.support.v17.leanback.app;
-
-import android.os.Bundle;
-import android.support.v17.leanback.R;
-import android.support.v17.leanback.transition.TransitionHelper;
-import android.support.v17.leanback.util.StateMachine.State;
-import android.support.v17.leanback.widget.BrowseFrameLayout;
-import android.support.v17.leanback.widget.ObjectAdapter;
-import android.support.v17.leanback.widget.OnChildLaidOutListener;
-import android.support.v17.leanback.widget.OnItemViewClickedListener;
-import android.support.v17.leanback.widget.OnItemViewSelectedListener;
-import android.support.v17.leanback.widget.Presenter;
-import android.support.v17.leanback.widget.Row;
-import android.support.v17.leanback.widget.RowPresenter;
-import android.support.v17.leanback.widget.VerticalGridPresenter;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-/**
- * A fragment for creating leanback vertical grids.
- *
- * <p>Renders a vertical grid of objects given a {@link VerticalGridPresenter} and
- * an {@link ObjectAdapter}.
- */
-public class VerticalGridFragment extends BaseFragment {
-    static final String TAG = "VerticalGF";
-    static boolean DEBUG = false;
-
-    private ObjectAdapter mAdapter;
-    private VerticalGridPresenter mGridPresenter;
-    VerticalGridPresenter.ViewHolder mGridViewHolder;
-    OnItemViewSelectedListener mOnItemViewSelectedListener;
-    private OnItemViewClickedListener mOnItemViewClickedListener;
-    private Object mSceneAfterEntranceTransition;
-    private int mSelectedPosition = -1;
-
-    /**
-     * State to setEntranceTransitionState(false)
-     */
-    final State STATE_SET_ENTRANCE_START_STATE = new State("SET_ENTRANCE_START_STATE") {
-        @Override
-        public void run() {
-            setEntranceTransitionState(false);
-        }
-    };
-
-    @Override
-    void createStateMachineStates() {
-        super.createStateMachineStates();
-        mStateMachine.addState(STATE_SET_ENTRANCE_START_STATE);
-    }
-
-    @Override
-    void createStateMachineTransitions() {
-        super.createStateMachineTransitions();
-        mStateMachine.addTransition(STATE_ENTRANCE_ON_PREPARED,
-                STATE_SET_ENTRANCE_START_STATE, EVT_ON_CREATEVIEW);
-    }
-
-    /**
-     * Sets the grid presenter.
-     */
-    public void setGridPresenter(VerticalGridPresenter gridPresenter) {
-        if (gridPresenter == null) {
-            throw new IllegalArgumentException("Grid presenter may not be null");
-        }
-        mGridPresenter = gridPresenter;
-        mGridPresenter.setOnItemViewSelectedListener(mViewSelectedListener);
-        if (mOnItemViewClickedListener != null) {
-            mGridPresenter.setOnItemViewClickedListener(mOnItemViewClickedListener);
-        }
-    }
-
-    /**
-     * Returns the grid presenter.
-     */
-    public VerticalGridPresenter getGridPresenter() {
-        return mGridPresenter;
-    }
-
-    /**
-     * Sets the object adapter for the fragment.
-     */
-    public void setAdapter(ObjectAdapter adapter) {
-        mAdapter = adapter;
-        updateAdapter();
-    }
-
-    /**
-     * Returns the object adapter.
-     */
-    public ObjectAdapter getAdapter() {
-        return mAdapter;
-    }
-
-    final private OnItemViewSelectedListener mViewSelectedListener =
-            new OnItemViewSelectedListener() {
-        @Override
-        public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
-                RowPresenter.ViewHolder rowViewHolder, Row row) {
-            int position = mGridViewHolder.getGridView().getSelectedPosition();
-            if (DEBUG) Log.v(TAG, "grid selected position " + position);
-            gridOnItemSelected(position);
-            if (mOnItemViewSelectedListener != null) {
-                mOnItemViewSelectedListener.onItemSelected(itemViewHolder, item,
-                        rowViewHolder, row);
-            }
-        }
-    };
-
-    final private OnChildLaidOutListener mChildLaidOutListener =
-            new OnChildLaidOutListener() {
-        @Override
-        public void onChildLaidOut(ViewGroup parent, View view, int position, long id) {
-            if (position == 0) {
-                showOrHideTitle();
-            }
-        }
-    };
-
-    /**
-     * Sets an item selection listener.
-     */
-    public void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
-        mOnItemViewSelectedListener = listener;
-    }
-
-    void gridOnItemSelected(int position) {
-        if (position != mSelectedPosition) {
-            mSelectedPosition = position;
-            showOrHideTitle();
-        }
-    }
-
-    void showOrHideTitle() {
-        if (mGridViewHolder.getGridView().findViewHolderForAdapterPosition(mSelectedPosition)
-                == null) {
-            return;
-        }
-        if (!mGridViewHolder.getGridView().hasPreviousViewInSameRow(mSelectedPosition)) {
-            showTitle(true);
-        } else {
-            showTitle(false);
-        }
-    }
-
-    /**
-     * Sets an item clicked listener.
-     */
-    public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
-        mOnItemViewClickedListener = listener;
-        if (mGridPresenter != null) {
-            mGridPresenter.setOnItemViewClickedListener(mOnItemViewClickedListener);
-        }
-    }
-
-    /**
-     * Returns the item clicked listener.
-     */
-    public OnItemViewClickedListener getOnItemViewClickedListener() {
-        return mOnItemViewClickedListener;
-    }
-
-    @Override
-    public View onCreateView(LayoutInflater inflater, ViewGroup container,
-            Bundle savedInstanceState) {
-        ViewGroup root = (ViewGroup) inflater.inflate(R.layout.lb_vertical_grid_fragment,
-                container, false);
-        ViewGroup gridFrame = (ViewGroup) root.findViewById(R.id.grid_frame);
-        installTitleView(inflater, gridFrame, savedInstanceState);
-        getProgressBarManager().setRootView(root);
-
-        ViewGroup gridDock = (ViewGroup) root.findViewById(R.id.browse_grid_dock);
-        mGridViewHolder = mGridPresenter.onCreateViewHolder(gridDock);
-        gridDock.addView(mGridViewHolder.view);
-        mGridViewHolder.getGridView().setOnChildLaidOutListener(mChildLaidOutListener);
-
-        mSceneAfterEntranceTransition = TransitionHelper.createScene(gridDock, new Runnable() {
-            @Override
-            public void run() {
-                setEntranceTransitionState(true);
-            }
-        });
-
-        updateAdapter();
-        return root;
-    }
-
-    private void setupFocusSearchListener() {
-        BrowseFrameLayout browseFrameLayout = (BrowseFrameLayout) getView().findViewById(
-                R.id.grid_frame);
-        browseFrameLayout.setOnFocusSearchListener(getTitleHelper().getOnFocusSearchListener());
-    }
-
-    @Override
-    public void onStart() {
-        super.onStart();
-        setupFocusSearchListener();
-    }
-
-    @Override
-    public void onDestroyView() {
-        super.onDestroyView();
-        mGridViewHolder = null;
-    }
-
-    /**
-     * Sets the selected item position.
-     */
-    public void setSelectedPosition(int position) {
-        mSelectedPosition = position;
-        if(mGridViewHolder != null && mGridViewHolder.getGridView().getAdapter() != null) {
-            mGridViewHolder.getGridView().setSelectedPositionSmooth(position);
-        }
-    }
-
-    private void updateAdapter() {
-        if (mGridViewHolder != null) {
-            mGridPresenter.onBindViewHolder(mGridViewHolder, mAdapter);
-            if (mSelectedPosition != -1) {
-                mGridViewHolder.getGridView().setSelectedPosition(mSelectedPosition);
-            }
-        }
-    }
-
-    @Override
-    protected Object createEntranceTransition() {
-        return TransitionHelper.loadTransition(FragmentUtil.getContext(VerticalGridFragment.this),
-                R.transition.lb_vertical_grid_entrance_transition);
-    }
-
-    @Override
-    protected void runEntranceTransition(Object entranceTransition) {
-        TransitionHelper.runTransition(mSceneAfterEntranceTransition, entranceTransition);
-    }
-
-    void setEntranceTransitionState(boolean afterTransition) {
-        mGridPresenter.setEntranceTransitionState(mGridViewHolder, afterTransition);
-    }
-}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/VideoFragment.java b/v17/leanback/src/android/support/v17/leanback/app/VideoFragment.java
deleted file mode 100644
index 1b2b8d0..0000000
--- a/v17/leanback/src/android/support/v17/leanback/app/VideoFragment.java
+++ /dev/null
@@ -1,120 +0,0 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from VideoSupportFragment.java.  DO NOT MODIFY. */
-
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-package android.support.v17.leanback.app;
-
-import android.os.Bundle;
-import android.support.v17.leanback.R;
-import android.view.LayoutInflater;
-import android.view.SurfaceHolder;
-import android.view.SurfaceView;
-import android.view.View;
-import android.view.ViewGroup;
-
-/**
- * Subclass of {@link PlaybackFragment} that is responsible for providing a {@link SurfaceView}
- * and rendering video.
- */
-public class VideoFragment extends PlaybackFragment {
-    static final int SURFACE_NOT_CREATED = 0;
-    static final int SURFACE_CREATED = 1;
-
-    SurfaceView mVideoSurface;
-    SurfaceHolder.Callback mMediaPlaybackCallback;
-
-    int mState = SURFACE_NOT_CREATED;
-
-    @Override
-    public View onCreateView(
-            LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
-        ViewGroup root = (ViewGroup) super.onCreateView(inflater, container, savedInstanceState);
-        mVideoSurface = (SurfaceView) LayoutInflater.from(FragmentUtil.getContext(VideoFragment.this)).inflate(
-                R.layout.lb_video_surface, root, false);
-        root.addView(mVideoSurface, 0);
-        mVideoSurface.getHolder().addCallback(new SurfaceHolder.Callback() {
-
-            @Override
-            public void surfaceCreated(SurfaceHolder holder) {
-                if (mMediaPlaybackCallback != null) {
-                    mMediaPlaybackCallback.surfaceCreated(holder);
-                }
-                mState = SURFACE_CREATED;
-            }
-
-            @Override
-            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
-                if (mMediaPlaybackCallback != null) {
-                    mMediaPlaybackCallback.surfaceChanged(holder, format, width, height);
-                }
-            }
-
-            @Override
-            public void surfaceDestroyed(SurfaceHolder holder) {
-                if (mMediaPlaybackCallback != null) {
-                    mMediaPlaybackCallback.surfaceDestroyed(holder);
-                }
-                mState = SURFACE_NOT_CREATED;
-            }
-        });
-        setBackgroundType(PlaybackFragment.BG_LIGHT);
-        return root;
-    }
-
-    /**
-     * Adds {@link SurfaceHolder.Callback} to {@link android.view.SurfaceView}.
-     */
-    public void setSurfaceHolderCallback(SurfaceHolder.Callback callback) {
-        mMediaPlaybackCallback = callback;
-
-        if (callback != null) {
-            if (mState == SURFACE_CREATED) {
-                mMediaPlaybackCallback.surfaceCreated(mVideoSurface.getHolder());
-            }
-        }
-    }
-
-    @Override
-    protected void onVideoSizeChanged(int width, int height) {
-        int screenWidth = getView().getWidth();
-        int screenHeight = getView().getHeight();
-
-        ViewGroup.LayoutParams p = mVideoSurface.getLayoutParams();
-        if (screenWidth * height > width * screenHeight) {
-            // fit in screen height
-            p.height = screenHeight;
-            p.width = screenHeight * width / height;
-        } else {
-            // fit in screen width
-            p.width = screenWidth;
-            p.height = screenWidth * height / width;
-        }
-        mVideoSurface.setLayoutParams(p);
-    }
-
-    /**
-     * Returns the surface view.
-     */
-    public SurfaceView getSurfaceView() {
-        return mVideoSurface;
-    }
-
-    @Override
-    public void onDestroyView() {
-        mVideoSurface = null;
-        mState = SURFACE_NOT_CREATED;
-        super.onDestroyView();
-    }
-}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/VideoFragmentGlueHost.java b/v17/leanback/src/android/support/v17/leanback/app/VideoFragmentGlueHost.java
deleted file mode 100644
index d123676..0000000
--- a/v17/leanback/src/android/support/v17/leanback/app/VideoFragmentGlueHost.java
+++ /dev/null
@@ -1,47 +0,0 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from VideoSupportFragmentGlueHost.java.  DO NOT MODIFY. */
-
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-package android.support.v17.leanback.app;
-
-import android.support.v17.leanback.media.PlaybackGlue;
-import android.support.v17.leanback.media.PlaybackGlueHost;
-import android.support.v17.leanback.media.SurfaceHolderGlueHost;
-import android.view.SurfaceHolder;
-
-/**
- * {@link PlaybackGlueHost} implementation
- * the interaction between {@link PlaybackGlue} and {@link VideoFragment}.
- */
-public class VideoFragmentGlueHost extends PlaybackFragmentGlueHost
-        implements SurfaceHolderGlueHost {
-    private final VideoFragment mFragment;
-
-    public VideoFragmentGlueHost(VideoFragment fragment) {
-        super(fragment);
-        this.mFragment = fragment;
-    }
-
-    /**
-     * Sets the {@link android.view.SurfaceHolder.Callback} on the host.
-     * {@link PlaybackGlueHost} is assumed to either host the {@link SurfaceHolder} or
-     * have a reference to the component hosting it for rendering the video.
-     */
-    @Override
-    public void setSurfaceHolderCallback(SurfaceHolder.Callback callback) {
-        mFragment.setSurfaceHolderCallback(callback);
-    }
-
-}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ArrayObjectAdapter.java b/v17/leanback/src/android/support/v17/leanback/widget/ArrayObjectAdapter.java
deleted file mode 100644
index 00bc073..0000000
--- a/v17/leanback/src/android/support/v17/leanback/widget/ArrayObjectAdapter.java
+++ /dev/null
@@ -1,318 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-package android.support.v17.leanback.widget;
-
-import android.support.annotation.Nullable;
-import android.support.v7.util.DiffUtil;
-import android.support.v7.util.ListUpdateCallback;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * An {@link ObjectAdapter} implemented with an {@link ArrayList}.
- */
-public class ArrayObjectAdapter extends ObjectAdapter {
-
-    private static final Boolean DEBUG = false;
-    private static final String TAG = "ArrayObjectAdapter";
-
-    private final List mItems = new ArrayList<Object>();
-
-    // To compute the payload correctly, we should use a temporary list to hold all the old items.
-    private final List mOldItems = new ArrayList<Object>();
-
-    // Un modifiable version of mItems;
-    private List mUnmodifiableItems;
-
-    /**
-     * Constructs an adapter with the given {@link PresenterSelector}.
-     */
-    public ArrayObjectAdapter(PresenterSelector presenterSelector) {
-        super(presenterSelector);
-    }
-
-    /**
-     * Constructs an adapter that uses the given {@link Presenter} for all items.
-     */
-    public ArrayObjectAdapter(Presenter presenter) {
-        super(presenter);
-    }
-
-    /**
-     * Constructs an adapter.
-     */
-    public ArrayObjectAdapter() {
-        super();
-    }
-
-    @Override
-    public int size() {
-        return mItems.size();
-    }
-
-    @Override
-    public Object get(int index) {
-        return mItems.get(index);
-    }
-
-    /**
-     * Returns the index for the first occurrence of item in the adapter, or -1 if
-     * not found.
-     *
-     * @param item The item to find in the list.
-     * @return Index of the first occurrence of the item in the adapter, or -1
-     * if not found.
-     */
-    public int indexOf(Object item) {
-        return mItems.indexOf(item);
-    }
-
-    /**
-     * Notify that the content of a range of items changed. Note that this is
-     * not same as items being added or removed.
-     *
-     * @param positionStart The position of first item that has changed.
-     * @param itemCount     The count of how many items have changed.
-     */
-    public void notifyArrayItemRangeChanged(int positionStart, int itemCount) {
-        notifyItemRangeChanged(positionStart, itemCount);
-    }
-
-    /**
-     * Adds an item to the end of the adapter.
-     *
-     * @param item The item to add to the end of the adapter.
-     */
-    public void add(Object item) {
-        add(mItems.size(), item);
-    }
-
-    /**
-     * Inserts an item into this adapter at the specified index.
-     * If the index is > {@link #size} an exception will be thrown.
-     *
-     * @param index The index at which the item should be inserted.
-     * @param item  The item to insert into the adapter.
-     */
-    public void add(int index, Object item) {
-        mItems.add(index, item);
-        notifyItemRangeInserted(index, 1);
-    }
-
-    /**
-     * Adds the objects in the given collection to the adapter, starting at the
-     * given index.  If the index is >= {@link #size} an exception will be thrown.
-     *
-     * @param index The index at which the items should be inserted.
-     * @param items A {@link Collection} of items to insert.
-     */
-    public void addAll(int index, Collection items) {
-        int itemsCount = items.size();
-        if (itemsCount == 0) {
-            return;
-        }
-        mItems.addAll(index, items);
-        notifyItemRangeInserted(index, itemsCount);
-    }
-
-    /**
-     * Removes the first occurrence of the given item from the adapter.
-     *
-     * @param item The item to remove from the adapter.
-     * @return True if the item was found and thus removed from the adapter.
-     */
-    public boolean remove(Object item) {
-        int index = mItems.indexOf(item);
-        if (index >= 0) {
-            mItems.remove(index);
-            notifyItemRangeRemoved(index, 1);
-        }
-        return index >= 0;
-    }
-
-    /**
-     * Moved the item at fromPosition to toPosition.
-     *
-     * @param fromPosition Previous position of the item.
-     * @param toPosition   New position of the item.
-     */
-    public void move(int fromPosition, int toPosition) {
-        if (fromPosition == toPosition) {
-            // no-op
-            return;
-        }
-        Object item = mItems.remove(fromPosition);
-        mItems.add(toPosition, item);
-        notifyItemMoved(fromPosition, toPosition);
-    }
-
-    /**
-     * Replaces item at position with a new item and calls notifyItemRangeChanged()
-     * at the given position.  Note that this method does not compare new item to
-     * existing item.
-     *
-     * @param position The index of item to replace.
-     * @param item     The new item to be placed at given position.
-     */
-    public void replace(int position, Object item) {
-        mItems.set(position, item);
-        notifyItemRangeChanged(position, 1);
-    }
-
-    /**
-     * Removes a range of items from the adapter. The range is specified by giving
-     * the starting position and the number of elements to remove.
-     *
-     * @param position The index of the first item to remove.
-     * @param count    The number of items to remove.
-     * @return The number of items removed.
-     */
-    public int removeItems(int position, int count) {
-        int itemsToRemove = Math.min(count, mItems.size() - position);
-        if (itemsToRemove <= 0) {
-            return 0;
-        }
-
-        for (int i = 0; i < itemsToRemove; i++) {
-            mItems.remove(position);
-        }
-        notifyItemRangeRemoved(position, itemsToRemove);
-        return itemsToRemove;
-    }
-
-    /**
-     * Removes all items from this adapter, leaving it empty.
-     */
-    public void clear() {
-        int itemCount = mItems.size();
-        if (itemCount == 0) {
-            return;
-        }
-        mItems.clear();
-        notifyItemRangeRemoved(0, itemCount);
-    }
-
-    /**
-     * Gets a read-only view of the list of object of this ArrayObjectAdapter.
-     */
-    public <E> List<E> unmodifiableList() {
-
-        // The mUnmodifiableItems will only be created once as long as the content of mItems has not
-        // been changed.
-        if (mUnmodifiableItems == null) {
-            mUnmodifiableItems = Collections.unmodifiableList(mItems);
-        }
-        return mUnmodifiableItems;
-    }
-
-    @Override
-    public boolean isImmediateNotifySupported() {
-        return true;
-    }
-
-    /**
-     * Set a new item list to adapter. The DiffUtil will compute the difference and dispatch it to
-     * specified position.
-     *
-     * @param itemList List of new Items
-     * @param callback Optional DiffCallback Object to compute the difference between the old data
-     *                 set and new data set. When null, {@link #notifyChanged()} will be fired.
-     */
-    public void setItems(final List itemList, final DiffCallback callback) {
-        if (callback == null) {
-            // shortcut when DiffCallback is not provided
-            mItems.clear();
-            mItems.addAll(itemList);
-            notifyChanged();
-            return;
-        }
-        mOldItems.clear();
-        mOldItems.addAll(mItems);
-
-        DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffUtil.Callback() {
-            @Override
-            public int getOldListSize() {
-                return mOldItems.size();
-            }
-
-            @Override
-            public int getNewListSize() {
-                return itemList.size();
-            }
-
-            @Override
-            public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
-                return callback.areItemsTheSame(mOldItems.get(oldItemPosition),
-                        itemList.get(newItemPosition));
-            }
-
-            @Override
-            public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
-                return callback.areContentsTheSame(mOldItems.get(oldItemPosition),
-                        itemList.get(newItemPosition));
-            }
-
-            @Nullable
-            @Override
-            public Object getChangePayload(int oldItemPosition, int newItemPosition) {
-                return callback.getChangePayload(mOldItems.get(oldItemPosition),
-                        itemList.get(newItemPosition));
-            }
-        });
-
-        // update items.
-        mItems.clear();
-        mItems.addAll(itemList);
-
-        // dispatch diff result
-        diffResult.dispatchUpdatesTo(new ListUpdateCallback() {
-
-            @Override
-            public void onInserted(int position, int count) {
-                if (DEBUG) {
-                    Log.d(TAG, "onInserted");
-                }
-                notifyItemRangeInserted(position, count);
-            }
-
-            @Override
-            public void onRemoved(int position, int count) {
-                if (DEBUG) {
-                    Log.d(TAG, "onRemoved");
-                }
-                notifyItemRangeRemoved(position, count);
-            }
-
-            @Override
-            public void onMoved(int fromPosition, int toPosition) {
-                if (DEBUG) {
-                    Log.d(TAG, "onMoved");
-                }
-                notifyItemMoved(fromPosition, toPosition);
-            }
-
-            @Override
-            public void onChanged(int position, int count, Object payload) {
-                if (DEBUG) {
-                    Log.d(TAG, "onChanged");
-                }
-                notifyItemRangeChanged(position, count, payload);
-            }
-        });
-    }
-}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/BaseGridView.java b/v17/leanback/src/android/support/v17/leanback/widget/BaseGridView.java
deleted file mode 100644
index f4e01c0..0000000
--- a/v17/leanback/src/android/support/v17/leanback/widget/BaseGridView.java
+++ /dev/null
@@ -1,1202 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-package android.support.v17.leanback.widget;
-
-import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
-
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Rect;
-import android.support.annotation.RestrictTo;
-import android.support.v17.leanback.R;
-import android.support.v7.widget.RecyclerView;
-import android.support.v7.widget.SimpleItemAnimator;
-import android.util.AttributeSet;
-import android.view.Gravity;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-import android.view.View;
-
-/**
- * An abstract base class for vertically and horizontally scrolling lists. The items come
- * from the {@link RecyclerView.Adapter} associated with this view.
- * Do not directly use this class, use {@link VerticalGridView} and {@link HorizontalGridView}.
- * The class is not intended to be subclassed other than {@link VerticalGridView} and
- * {@link HorizontalGridView}.
- */
-public abstract class BaseGridView extends RecyclerView {
-
-    /**
-     * Always keep focused item at a aligned position.  Developer can use
-     * WINDOW_ALIGN_XXX and ITEM_ALIGN_XXX to define how focused item is aligned.
-     * In this mode, the last focused position will be remembered and restored when focus
-     * is back to the view.
-     * @hide
-     */
-    @RestrictTo(LIBRARY_GROUP)
-    public final static int FOCUS_SCROLL_ALIGNED = 0;
-
-    /**
-     * Scroll to make the focused item inside client area.
-     * @hide
-     */
-    @RestrictTo(LIBRARY_GROUP)
-    public final static int FOCUS_SCROLL_ITEM = 1;
-
-    /**
-     * Scroll a page of items when focusing to item outside the client area.
-     * The page size matches the client area size of RecyclerView.
-     * @hide
-     */
-    @RestrictTo(LIBRARY_GROUP)
-    public final static int FOCUS_SCROLL_PAGE = 2;
-
-    /**
-     * The first item is aligned with the low edge of the viewport. When
-     * navigating away from the first item, the focus item is aligned to a key line location.
-     * <p>
-     * For HorizontalGridView, low edge refers to getPaddingLeft() when RTL is false or
-     * getWidth() - getPaddingRight() when RTL is true.
-     * For VerticalGridView, low edge refers to getPaddingTop().
-     * <p>
-     * The key line location is calculated by "windowAlignOffset" and
-     * "windowAlignOffsetPercent"; if neither of these two is defined, the
-     * default value is 1/2 of the size.
-     * <p>
-     * Note if there are very few items between low edge and key line, use
-     * {@link #setWindowAlignmentPreferKeyLineOverLowEdge(boolean)} to control whether you prefer
-     * to align the items to key line or low edge. Default is preferring low edge.
-     */
-    public final static int WINDOW_ALIGN_LOW_EDGE = 1;
-
-    /**
-     * The last item is aligned with the high edge of the viewport when
-     * navigating to the end of list. When navigating away from the end, the
-     * focus item is aligned to a key line location.
-     * <p>
-     * For HorizontalGridView, high edge refers to getWidth() - getPaddingRight() when RTL is false
-     * or getPaddingLeft() when RTL is true.
-     * For VerticalGridView, high edge refers to getHeight() - getPaddingBottom().
-     * <p>
-     * The key line location is calculated by "windowAlignOffset" and
-     * "windowAlignOffsetPercent"; if neither of these two is defined, the
-     * default value is 1/2 of the size.
-     * <p>
-     * Note if there are very few items between high edge and key line, use
-     * {@link #setWindowAlignmentPreferKeyLineOverHighEdge(boolean)} to control whether you prefer
-     * to align the items to key line or high edge. Default is preferring key line.
-     */
-    public final static int WINDOW_ALIGN_HIGH_EDGE = 1 << 1;
-
-    /**
-     * The first item and last item are aligned with the two edges of the
-     * viewport. When navigating in the middle of list, the focus maintains a
-     * key line location.
-     * <p>
-     * The key line location is calculated by "windowAlignOffset" and
-     * "windowAlignOffsetPercent"; if neither of these two is defined, the
-     * default value is 1/2 of the size.
-     */
-    public final static int WINDOW_ALIGN_BOTH_EDGE =
-            WINDOW_ALIGN_LOW_EDGE | WINDOW_ALIGN_HIGH_EDGE;
-
-    /**
-     * The focused item always stays in a key line location.
-     * <p>
-     * The key line location is calculated by "windowAlignOffset" and
-     * "windowAlignOffsetPercent"; if neither of these two is defined, the
-     * default value is 1/2 of the size.
-     */
-    public final static int WINDOW_ALIGN_NO_EDGE = 0;
-
-    /**
-     * Value indicates that percent is not used.
-     */
-    public final static float WINDOW_ALIGN_OFFSET_PERCENT_DISABLED = -1;
-
-    /**
-     * Value indicates that percent is not used.
-     */
-    public final static float ITEM_ALIGN_OFFSET_PERCENT_DISABLED =
-            ItemAlignmentFacet.ITEM_ALIGN_OFFSET_PERCENT_DISABLED;
-
-    /**
-     * Dont save states of any child views.
-     */
-    public static final int SAVE_NO_CHILD = 0;
-
-    /**
-     * Only save on screen child views, the states are lost when they become off screen.
-     */
-    public static final int SAVE_ON_SCREEN_CHILD = 1;
-
-    /**
-     * Save on screen views plus save off screen child views states up to
-     * {@link #getSaveChildrenLimitNumber()}.
-     */
-    public static final int SAVE_LIMITED_CHILD = 2;
-
-    /**
-     * Save on screen views plus save off screen child views without any limitation.
-     * This might cause out of memory, only use it when you are dealing with limited data.
-     */
-    public static final int SAVE_ALL_CHILD = 3;
-
-    /**
-     * Listener for intercepting touch dispatch events.
-     */
-    public interface OnTouchInterceptListener {
-        /**
-         * Returns true if the touch dispatch event should be consumed.
-         */
-        public boolean onInterceptTouchEvent(MotionEvent event);
-    }
-
-    /**
-     * Listener for intercepting generic motion dispatch events.
-     */
-    public interface OnMotionInterceptListener {
-        /**
-         * Returns true if the touch dispatch event should be consumed.
-         */
-        public boolean onInterceptMotionEvent(MotionEvent event);
-    }
-
-    /**
-     * Listener for intercepting key dispatch events.
-     */
-    public interface OnKeyInterceptListener {
-        /**
-         * Returns true if the key dispatch event should be consumed.
-         */
-        public boolean onInterceptKeyEvent(KeyEvent event);
-    }
-
-    public interface OnUnhandledKeyListener {
-        /**
-         * Returns true if the key event should be consumed.
-         */
-        public boolean onUnhandledKey(KeyEvent event);
-    }
-
-    final GridLayoutManager mLayoutManager;
-
-    /**
-     * Animate layout changes from a child resizing or adding/removing a child.
-     */
-    private boolean mAnimateChildLayout = true;
-
-    private boolean mHasOverlappingRendering = true;
-
-    private RecyclerView.ItemAnimator mSavedItemAnimator;
-
-    private OnTouchInterceptListener mOnTouchInterceptListener;
-    private OnMotionInterceptListener mOnMotionInterceptListener;
-    private OnKeyInterceptListener mOnKeyInterceptListener;
-    RecyclerView.RecyclerListener mChainedRecyclerListener;
-    private OnUnhandledKeyListener mOnUnhandledKeyListener;
-
-    /**
-     * Number of items to prefetch when first coming on screen with new data.
-     */
-    int mInitialPrefetchItemCount = 4;
-
-    BaseGridView(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
-        mLayoutManager = new GridLayoutManager(this);
-        setLayoutManager(mLayoutManager);
-        // leanback LayoutManager already restores focus inside onLayoutChildren().
-        setPreserveFocusAfterLayout(false);
-        setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
-        setHasFixedSize(true);
-        setChildrenDrawingOrderEnabled(true);
-        setWillNotDraw(true);
-        setOverScrollMode(View.OVER_SCROLL_NEVER);
-        // Disable change animation by default on leanback.
-        // Change animation will create a new view and cause undesired
-        // focus animation between the old view and new view.
-        ((SimpleItemAnimator)getItemAnimator()).setSupportsChangeAnimations(false);
-        super.setRecyclerListener(new RecyclerView.RecyclerListener() {
-            @Override
-            public void onViewRecycled(RecyclerView.ViewHolder holder) {
-                mLayoutManager.onChildRecycled(holder);
-                if (mChainedRecyclerListener != null) {
-                    mChainedRecyclerListener.onViewRecycled(holder);
-                }
-            }
-        });
-    }
-
-    void initBaseGridViewAttributes(Context context, AttributeSet attrs) {
-        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.lbBaseGridView);
-        boolean throughFront = a.getBoolean(R.styleable.lbBaseGridView_focusOutFront, false);
-        boolean throughEnd = a.getBoolean(R.styleable.lbBaseGridView_focusOutEnd, false);
-        mLayoutManager.setFocusOutAllowed(throughFront, throughEnd);
-        boolean throughSideStart = a.getBoolean(R.styleable.lbBaseGridView_focusOutSideStart, true);
-        boolean throughSideEnd = a.getBoolean(R.styleable.lbBaseGridView_focusOutSideEnd, true);
-        mLayoutManager.setFocusOutSideAllowed(throughSideStart, throughSideEnd);
-        mLayoutManager.setVerticalSpacing(
-                a.getDimensionPixelSize(R.styleable.lbBaseGridView_android_verticalSpacing,
-                        a.getDimensionPixelSize(R.styleable.lbBaseGridView_verticalMargin, 0)));
-        mLayoutManager.setHorizontalSpacing(
-                a.getDimensionPixelSize(R.styleable.lbBaseGridView_android_horizontalSpacing,
-                        a.getDimensionPixelSize(R.styleable.lbBaseGridView_horizontalMargin, 0)));
-        if (a.hasValue(R.styleable.lbBaseGridView_android_gravity)) {
-            setGravity(a.getInt(R.styleable.lbBaseGridView_android_gravity, Gravity.NO_GRAVITY));
-        }
-        a.recycle();
-    }
-
-    /**
-     * Sets the strategy used to scroll in response to item focus changing:
-     * <ul>
-     * <li>{@link #FOCUS_SCROLL_ALIGNED} (default) </li>
-     * <li>{@link #FOCUS_SCROLL_ITEM}</li>
-     * <li>{@link #FOCUS_SCROLL_PAGE}</li>
-     * </ul>
-     * @hide
-     */
-    @RestrictTo(LIBRARY_GROUP)
-    public void setFocusScrollStrategy(int scrollStrategy) {
-        if (scrollStrategy != FOCUS_SCROLL_ALIGNED && scrollStrategy != FOCUS_SCROLL_ITEM
-            && scrollStrategy != FOCUS_SCROLL_PAGE) {
-            throw new IllegalArgumentException("Invalid scrollStrategy");
-        }
-        mLayoutManager.setFocusScrollStrategy(scrollStrategy);
-        requestLayout();
-    }
-
-    /**
-     * Returns the strategy used to scroll in response to item focus changing.
-     * <ul>
-     * <li>{@link #FOCUS_SCROLL_ALIGNED} (default) </li>
-     * <li>{@link #FOCUS_SCROLL_ITEM}</li>
-     * <li>{@link #FOCUS_SCROLL_PAGE}</li>
-     * </ul>
-     * @hide
-     */
-    @RestrictTo(LIBRARY_GROUP)
-    public int getFocusScrollStrategy() {
-        return mLayoutManager.getFocusScrollStrategy();
-    }
-
-    /**
-     * Sets the method for focused item alignment in the view.
-     *
-     * @param windowAlignment {@link #WINDOW_ALIGN_BOTH_EDGE},
-     *        {@link #WINDOW_ALIGN_LOW_EDGE}, {@link #WINDOW_ALIGN_HIGH_EDGE} or
-     *        {@link #WINDOW_ALIGN_NO_EDGE}.
-     */
-    public void setWindowAlignment(int windowAlignment) {
-        mLayoutManager.setWindowAlignment(windowAlignment);
-        requestLayout();
-    }
-
-    /**
-     * Returns the method for focused item alignment in the view.
-     *
-     * @return {@link #WINDOW_ALIGN_BOTH_EDGE}, {@link #WINDOW_ALIGN_LOW_EDGE},
-     *         {@link #WINDOW_ALIGN_HIGH_EDGE} or {@link #WINDOW_ALIGN_NO_EDGE}.
-     */
-    public int getWindowAlignment() {
-        return mLayoutManager.getWindowAlignment();
-    }
-
-    /**
-     * Sets whether prefer key line over low edge when {@link #WINDOW_ALIGN_LOW_EDGE} is used.
-     * When true, if there are very few items between low edge and key line, align items to key
-     * line instead of align items to low edge.
-     * Default value is false (aka prefer align to low edge).
-     *
-     * @param preferKeyLineOverLowEdge True to prefer key line over low edge, false otherwise.
-     */
-    public void setWindowAlignmentPreferKeyLineOverLowEdge(boolean preferKeyLineOverLowEdge) {
-        mLayoutManager.mWindowAlignment.mainAxis()
-                .setPreferKeylineOverLowEdge(preferKeyLineOverLowEdge);
-        requestLayout();
-    }
-
-
-    /**
-     * Returns whether prefer key line over high edge when {@link #WINDOW_ALIGN_HIGH_EDGE} is used.
-     * When true, if there are very few items between high edge and key line, align items to key
-     * line instead of align items to high edge.
-     * Default value is true (aka prefer align to key line).
-     *
-     * @param preferKeyLineOverHighEdge True to prefer key line over high edge, false otherwise.
-     */
-    public void setWindowAlignmentPreferKeyLineOverHighEdge(boolean preferKeyLineOverHighEdge) {
-        mLayoutManager.mWindowAlignment.mainAxis()
-                .setPreferKeylineOverHighEdge(preferKeyLineOverHighEdge);
-        requestLayout();
-    }
-
-    /**
-     * Returns whether prefer key line over low edge when {@link #WINDOW_ALIGN_LOW_EDGE} is used.
-     * When true, if there are very few items between low edge and key line, align items to key
-     * line instead of align items to low edge.
-     * Default value is false (aka prefer align to low edge).
-     *
-     * @return True to prefer key line over low edge, false otherwise.
-     */
-    public boolean isWindowAlignmentPreferKeyLineOverLowEdge() {
-        return mLayoutManager.mWindowAlignment.mainAxis().isPreferKeylineOverLowEdge();
-    }
-
-
-    /**
-     * Returns whether prefer key line over high edge when {@link #WINDOW_ALIGN_HIGH_EDGE} is used.
-     * When true, if there are very few items between high edge and key line, align items to key
-     * line instead of align items to high edge.
-     * Default value is true (aka prefer align to key line).
-     *
-     * @return True to prefer key line over high edge, false otherwise.
-     */
-    public boolean isWindowAlignmentPreferKeyLineOverHighEdge() {
-        return mLayoutManager.mWindowAlignment.mainAxis().isPreferKeylineOverHighEdge();
-    }
-
-
-    /**
-     * Sets the offset in pixels for window alignment key line.
-     *
-     * @param offset The number of pixels to offset.  If the offset is positive,
-     *        it is distance from low edge (see {@link #WINDOW_ALIGN_LOW_EDGE});
-     *        if the offset is negative, the absolute value is distance from high
-     *        edge (see {@link #WINDOW_ALIGN_HIGH_EDGE}).
-     *        Default value is 0.
-     */
-    public void setWindowAlignmentOffset(int offset) {
-        mLayoutManager.setWindowAlignmentOffset(offset);
-        requestLayout();
-    }
-
-    /**
-     * Returns the offset in pixels for window alignment key line.
-     *
-     * @return The number of pixels to offset.  If the offset is positive,
-     *        it is distance from low edge (see {@link #WINDOW_ALIGN_LOW_EDGE});
-     *        if the offset is negative, the absolute value is distance from high
-     *        edge (see {@link #WINDOW_ALIGN_HIGH_EDGE}).
-     *        Default value is 0.
-     */
-    public int getWindowAlignmentOffset() {
-        return mLayoutManager.getWindowAlignmentOffset();
-    }
-
-    /**
-     * Sets the offset percent for window alignment key line in addition to {@link
-     * #getWindowAlignmentOffset()}.
-     *
-     * @param offsetPercent Percentage to offset. E.g., 40 means 40% of the
-     *        width from low edge. Use
-     *        {@link #WINDOW_ALIGN_OFFSET_PERCENT_DISABLED} to disable.
-     *         Default value is 50.
-     */
-    public void setWindowAlignmentOffsetPercent(float offsetPercent) {
-        mLayoutManager.setWindowAlignmentOffsetPercent(offsetPercent);
-        requestLayout();
-    }
-
-    /**
-     * Returns the offset percent for window alignment key line in addition to
-     * {@link #getWindowAlignmentOffset()}.
-     *
-     * @return Percentage to offset. E.g., 40 means 40% of the width from the
-     *         low edge, or {@link #WINDOW_ALIGN_OFFSET_PERCENT_DISABLED} if
-     *         disabled. Default value is 50.
-     */
-    public float getWindowAlignmentOffsetPercent() {
-        return mLayoutManager.getWindowAlignmentOffsetPercent();
-    }
-
-    /**
-     * Sets number of pixels to the end of low edge. Supports right to left layout direction.
-     * Item alignment settings are ignored for the child if {@link ItemAlignmentFacet}
-     * is provided by {@link RecyclerView.ViewHolder} or {@link FacetProviderAdapter}.
-     *
-     * @param offset In left to right or vertical case, it's the offset added to left/top edge.
-     *               In right to left case, it's the offset subtracted from right edge.
-     */
-    public void setItemAlignmentOffset(int offset) {
-        mLayoutManager.setItemAlignmentOffset(offset);
-        requestLayout();
-    }
-
-    /**
-     * Returns number of pixels to the end of low edge. Supports right to left layout direction. In
-     * left to right or vertical case, it's the offset added to left/top edge. In right to left
-     * case, it's the offset subtracted from right edge.
-     * Item alignment settings are ignored for the child if {@link ItemAlignmentFacet}
-     * is provided by {@link RecyclerView.ViewHolder} or {@link FacetProviderAdapter}.
-     *
-     * @return The number of pixels to the end of low edge.
-     */
-    public int getItemAlignmentOffset() {
-        return mLayoutManager.getItemAlignmentOffset();
-    }
-
-    /**
-     * Sets whether applies padding to item alignment when {@link #getItemAlignmentOffsetPercent()}
-     * is 0 or 100.
-     * <p>When true:
-     * Applies start/top padding if {@link #getItemAlignmentOffsetPercent()} is 0.
-     * Applies end/bottom padding if {@link #getItemAlignmentOffsetPercent()} is 100.
-     * Does not apply padding if {@link #getItemAlignmentOffsetPercent()} is neither 0 nor 100.
-     * </p>
-     * <p>When false: does not apply padding</p>
-     */
-    public void setItemAlignmentOffsetWithPadding(boolean withPadding) {
-        mLayoutManager.setItemAlignmentOffsetWithPadding(withPadding);
-        requestLayout();
-    }
-
-    /**
-     * Returns true if applies padding to item alignment when
-     * {@link #getItemAlignmentOffsetPercent()} is 0 or 100; returns false otherwise.
-     * <p>When true:
-     * Applies start/top padding when {@link #getItemAlignmentOffsetPercent()} is 0.
-     * Applies end/bottom padding when {@link #getItemAlignmentOffsetPercent()} is 100.
-     * Does not apply padding if {@link #getItemAlignmentOffsetPercent()} is neither 0 nor 100.
-     * </p>
-     * <p>When false: does not apply padding</p>
-     */
-    public boolean isItemAlignmentOffsetWithPadding() {
-        return mLayoutManager.isItemAlignmentOffsetWithPadding();
-    }
-
-    /**
-     * Sets the offset percent for item alignment in addition to {@link
-     * #getItemAlignmentOffset()}.
-     * Item alignment settings are ignored for the child if {@link ItemAlignmentFacet}
-     * is provided by {@link RecyclerView.ViewHolder} or {@link FacetProviderAdapter}.
-     *
-     * @param offsetPercent Percentage to offset. E.g., 40 means 40% of the
-     *        width from the low edge. Use
-     *        {@link #ITEM_ALIGN_OFFSET_PERCENT_DISABLED} to disable.
-     */
-    public void setItemAlignmentOffsetPercent(float offsetPercent) {
-        mLayoutManager.setItemAlignmentOffsetPercent(offsetPercent);
-        requestLayout();
-    }
-
-    /**
-     * Returns the offset percent for item alignment in addition to {@link
-     * #getItemAlignmentOffset()}.
-     *
-     * @return Percentage to offset. E.g., 40 means 40% of the width from the
-     *         low edge, or {@link #ITEM_ALIGN_OFFSET_PERCENT_DISABLED} if
-     *         disabled. Default value is 50.
-     */
-    public float getItemAlignmentOffsetPercent() {
-        return mLayoutManager.getItemAlignmentOffsetPercent();
-    }
-
-    /**
-     * Sets the id of the view to align with. Use {@link android.view.View#NO_ID} (default)
-     * for the root {@link RecyclerView.ViewHolder#itemView}.
-     * Item alignment settings on BaseGridView are if {@link ItemAlignmentFacet}
-     * is provided by {@link RecyclerView.ViewHolder} or {@link FacetProviderAdapter}.
-     */
-    public void setItemAlignmentViewId(int viewId) {
-        mLayoutManager.setItemAlignmentViewId(viewId);
-    }
-
-    /**
-     * Returns the id of the view to align with, or {@link android.view.View#NO_ID} for the root
-     * {@link RecyclerView.ViewHolder#itemView}.
-     * @return The id of the view to align with, or {@link android.view.View#NO_ID} for the root
-     * {@link RecyclerView.ViewHolder#itemView}.
-     */
-    public int getItemAlignmentViewId() {
-        return mLayoutManager.getItemAlignmentViewId();
-    }
-
-    /**
-     * Sets the spacing in pixels between two child items.
-     * @deprecated use {@link #setItemSpacing(int)}
-     */
-    @Deprecated
-    public void setItemMargin(int margin) {
-        setItemSpacing(margin);
-    }
-
-    /**
-     * Sets the vertical and horizontal spacing in pixels between two child items.
-     * @param spacing Vertical and horizontal spacing in pixels between two child items.
-     */
-    public void setItemSpacing(int spacing) {
-        mLayoutManager.setItemSpacing(spacing);
-        requestLayout();
-    }
-
-    /**
-     * Sets the spacing in pixels between two child items vertically.
-     * @deprecated Use {@link #setVerticalSpacing(int)}
-     */
-    @Deprecated
-    public void setVerticalMargin(int margin) {
-        setVerticalSpacing(margin);
-    }
-
-    /**
-     * Returns the spacing in pixels between two child items vertically.
-     * @deprecated Use {@link #getVerticalSpacing()}
-     */
-    @Deprecated
-    public int getVerticalMargin() {
-        return mLayoutManager.getVerticalSpacing();
-    }
-
-    /**
-     * Sets the spacing in pixels between two child items horizontally.
-     * @deprecated Use {@link #setHorizontalSpacing(int)}
-     */
-    @Deprecated
-    public void setHorizontalMargin(int margin) {
-        setHorizontalSpacing(margin);
-    }
-
-    /**
-     * Returns the spacing in pixels between two child items horizontally.
-     * @deprecated Use {@link #getHorizontalSpacing()}
-     */
-    @Deprecated
-    public int getHorizontalMargin() {
-        return mLayoutManager.getHorizontalSpacing();
-    }
-
-    /**
-     * Sets the vertical spacing in pixels between two child items.
-     * @param spacing Vertical spacing between two child items.
-     */
-    public void setVerticalSpacing(int spacing) {
-        mLayoutManager.setVerticalSpacing(spacing);
-        requestLayout();
-    }
-
-    /**
-     * Returns the vertical spacing in pixels between two child items.
-     * @return The vertical spacing in pixels between two child items.
-     */
-    public int getVerticalSpacing() {
-        return mLayoutManager.getVerticalSpacing();
-    }
-
-    /**
-     * Sets the horizontal spacing in pixels between two child items.
-     * @param spacing Horizontal spacing in pixels between two child items.
-     */
-    public void setHorizontalSpacing(int spacing) {
-        mLayoutManager.setHorizontalSpacing(spacing);
-        requestLayout();
-    }
-
-    /**
-     * Returns the horizontal spacing in pixels between two child items.
-     * @return The Horizontal spacing in pixels between two child items.
-     */
-    public int getHorizontalSpacing() {
-        return mLayoutManager.getHorizontalSpacing();
-    }
-
-    /**
-     * Registers a callback to be invoked when an item in BaseGridView has
-     * been laid out.
-     *
-     * @param listener The listener to be invoked.
-     */
-    public void setOnChildLaidOutListener(OnChildLaidOutListener listener) {
-        mLayoutManager.setOnChildLaidOutListener(listener);
-    }
-
-    /**
-     * Registers a callback to be invoked when an item in BaseGridView has
-     * been selected.  Note that the listener may be invoked when there is a
-     * layout pending on the view, affording the listener an opportunity to
-     * adjust the upcoming layout based on the selection state.
-     *
-     * @param listener The listener to be invoked.
-     */
-    public void setOnChildSelectedListener(OnChildSelectedListener listener) {
-        mLayoutManager.setOnChildSelectedListener(listener);
-    }
-
-    /**
-     * Registers a callback to be invoked when an item in BaseGridView has
-     * been selected.  Note that the listener may be invoked when there is a
-     * layout pending on the view, affording the listener an opportunity to
-     * adjust the upcoming layout based on the selection state.
-     * This method will clear all existing listeners added by
-     * {@link #addOnChildViewHolderSelectedListener}.
-     *
-     * @param listener The listener to be invoked.
-     */
-    public void setOnChildViewHolderSelectedListener(OnChildViewHolderSelectedListener listener) {
-        mLayoutManager.setOnChildViewHolderSelectedListener(listener);
-    }
-
-    /**
-     * Registers a callback to be invoked when an item in BaseGridView has
-     * been selected.  Note that the listener may be invoked when there is a
-     * layout pending on the view, affording the listener an opportunity to
-     * adjust the upcoming layout based on the selection state.
-     *
-     * @param listener The listener to be invoked.
-     */
-    public void addOnChildViewHolderSelectedListener(OnChildViewHolderSelectedListener listener) {
-        mLayoutManager.addOnChildViewHolderSelectedListener(listener);
-    }
-
-    /**
-     * Remove the callback invoked when an item in BaseGridView has been selected.
-     *
-     * @param listener The listener to be removed.
-     */
-    public void removeOnChildViewHolderSelectedListener(OnChildViewHolderSelectedListener listener)
-            {
-        mLayoutManager.removeOnChildViewHolderSelectedListener(listener);
-    }
-
-    /**
-     * Changes the selected item immediately without animation.
-     */
-    public void setSelectedPosition(int position) {
-        mLayoutManager.setSelection(position, 0);
-    }
-
-    /**
-     * Changes the selected item and/or subposition immediately without animation.
-     * @hide
-     */
-    @RestrictTo(LIBRARY_GROUP)
-    public void setSelectedPositionWithSub(int position, int subposition) {
-        mLayoutManager.setSelectionWithSub(position, subposition, 0);
-    }
-
-    /**
-     * Changes the selected item immediately without animation, scrollExtra is
-     * applied in primary scroll direction.  The scrollExtra will be kept until
-     * another {@link #setSelectedPosition} or {@link #setSelectedPositionSmooth} call.
-     */
-    public void setSelectedPosition(int position, int scrollExtra) {
-        mLayoutManager.setSelection(position, scrollExtra);
-    }
-
-    /**
-     * Changes the selected item and/or subposition immediately without animation, scrollExtra is
-     * applied in primary scroll direction.  The scrollExtra will be kept until
-     * another {@link #setSelectedPosition} or {@link #setSelectedPositionSmooth} call.
-     * @hide
-     */
-    @RestrictTo(LIBRARY_GROUP)
-    public void setSelectedPositionWithSub(int position, int subposition, int scrollExtra) {
-        mLayoutManager.setSelectionWithSub(position, subposition, scrollExtra);
-    }
-
-    /**
-     * Changes the selected item and run an animation to scroll to the target
-     * position.
-     * @param position Adapter position of the item to select.
-     */
-    public void setSelectedPositionSmooth(int position) {
-        mLayoutManager.setSelectionSmooth(position);
-    }
-
-    /**
-     * Changes the selected item and/or subposition, runs an animation to scroll to the target
-     * position.
-     * @hide
-     */
-    @RestrictTo(LIBRARY_GROUP)
-    public void setSelectedPositionSmoothWithSub(int position, int subposition) {
-        mLayoutManager.setSelectionSmoothWithSub(position, subposition);
-    }
-
-    /**
-     * Perform a task on ViewHolder at given position after smooth scrolling to it.
-     * @param position Position of item in adapter.
-     * @param task Task to executed on the ViewHolder at a given position.
-     */
-    public void setSelectedPositionSmooth(final int position, final ViewHolderTask task) {
-        if (task != null) {
-            RecyclerView.ViewHolder vh = findViewHolderForPosition(position);
-            if (vh == null || hasPendingAdapterUpdates()) {
-                addOnChildViewHolderSelectedListener(new OnChildViewHolderSelectedListener() {
-                    @Override
-                    public void onChildViewHolderSelected(RecyclerView parent,
-                            RecyclerView.ViewHolder child, int selectedPosition, int subposition) {
-                        if (selectedPosition == position) {
-                            removeOnChildViewHolderSelectedListener(this);
-                            task.run(child);
-                        }
-                    }
-                });
-            } else {
-                task.run(vh);
-            }
-        }
-        setSelectedPositionSmooth(position);
-    }
-
-    /**
-     * Perform a task on ViewHolder at given position after scroll to it.
-     * @param position Position of item in adapter.
-     * @param task Task to executed on the ViewHolder at a given position.
-     */
-    public void setSelectedPosition(final int position, final ViewHolderTask task) {
-        if (task != null) {
-            RecyclerView.ViewHolder vh = findViewHolderForPosition(position);
-            if (vh == null || hasPendingAdapterUpdates()) {
-                addOnChildViewHolderSelectedListener(new OnChildViewHolderSelectedListener() {
-                    @Override
-                    public void onChildViewHolderSelectedAndPositioned(RecyclerView parent,
-                            RecyclerView.ViewHolder child, int selectedPosition, int subposition) {
-                        if (selectedPosition == position) {
-                            removeOnChildViewHolderSelectedListener(this);
-                            task.run(child);
-                        }
-                    }
-                });
-            } else {
-                task.run(vh);
-            }
-        }
-        setSelectedPosition(position);
-    }
-
-    /**
-     * Returns the adapter position of selected item.
-     * @return The adapter position of selected item.
-     */
-    public int getSelectedPosition() {
-        return mLayoutManager.getSelection();
-    }
-
-    /**
-     * Returns the sub selected item position started from zero.  An item can have
-     * multiple {@link ItemAlignmentFacet}s provided by {@link RecyclerView.ViewHolder}
-     * or {@link FacetProviderAdapter}.  Zero is returned when no {@link ItemAlignmentFacet}
-     * is defined.
-     * @hide
-     */
-    @RestrictTo(LIBRARY_GROUP)
-    public int getSelectedSubPosition() {
-        return mLayoutManager.getSubSelection();
-    }
-
-    /**
-     * Sets whether ItemAnimator should run when a child changes size or when adding
-     * or removing a child.
-     * @param animateChildLayout True to enable ItemAnimator, false to disable.
-     */
-    public void setAnimateChildLayout(boolean animateChildLayout) {
-        if (mAnimateChildLayout != animateChildLayout) {
-            mAnimateChildLayout = animateChildLayout;
-            if (!mAnimateChildLayout) {
-                mSavedItemAnimator = getItemAnimator();
-                super.setItemAnimator(null);
-            } else {
-                super.setItemAnimator(mSavedItemAnimator);
-            }
-        }
-    }
-
-    /**
-     * Returns true if an animation will run when a child changes size or when
-     * adding or removing a child.
-     * @return True if ItemAnimator is enabled, false otherwise.
-     */
-    public boolean isChildLayoutAnimated() {
-        return mAnimateChildLayout;
-    }
-
-    /**
-     * Sets the gravity used for child view positioning. Defaults to
-     * GRAVITY_TOP|GRAVITY_START.
-     *
-     * @param gravity See {@link android.view.Gravity}
-     */
-    public void setGravity(int gravity) {
-        mLayoutManager.setGravity(gravity);
-        requestLayout();
-    }
-
-    @Override
-    public boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
-        return mLayoutManager.gridOnRequestFocusInDescendants(this, direction,
-                previouslyFocusedRect);
-    }
-
-    /**
-     * Returns the x/y offsets to final position from current position if the view
-     * is selected.
-     *
-     * @param view The view to get offsets.
-     * @param offsets offsets[0] holds offset of X, offsets[1] holds offset of Y.
-     */
-    public void getViewSelectedOffsets(View view, int[] offsets) {
-        mLayoutManager.getViewSelectedOffsets(view, offsets);
-    }
-
-    @Override
-    public int getChildDrawingOrder(int childCount, int i) {
-        return mLayoutManager.getChildDrawingOrder(this, childCount, i);
-    }
-
-    final boolean isChildrenDrawingOrderEnabledInternal() {
-        return isChildrenDrawingOrderEnabled();
-    }
-
-    @Override
-    public View focusSearch(int direction) {
-        if (isFocused()) {
-            // focusSearch(int) is called when GridView itself is focused.
-            // Calling focusSearch(view, int) to get next sibling of current selected child.
-            View view = mLayoutManager.findViewByPosition(mLayoutManager.getSelection());
-            if (view != null) {
-                return focusSearch(view, direction);
-            }
-        }
-        // otherwise, go to mParent to perform focusSearch
-        return super.focusSearch(direction);
-    }
-
-    @Override
-    protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
-        super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
-        mLayoutManager.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
-    }
-
-    /**
-     * Disables or enables focus search.
-     * @param disabled True to disable focus search, false to enable.
-     */
-    public final void setFocusSearchDisabled(boolean disabled) {
-        // LayoutManager may detachView and attachView in fastRelayout, it causes RowsFragment
-        // re-gain focus after a BACK key pressed, so block children focus during transition.
-        setDescendantFocusability(disabled ? FOCUS_BLOCK_DESCENDANTS: FOCUS_AFTER_DESCENDANTS);
-        mLayoutManager.setFocusSearchDisabled(disabled);
-    }
-
-    /**
-     * Returns true if focus search is disabled.
-     * @return True if focus search is disabled.
-     */
-    public final boolean isFocusSearchDisabled() {
-        return mLayoutManager.isFocusSearchDisabled();
-    }
-
-    /**
-     * Enables or disables layout.  All children will be removed when layout is
-     * disabled.
-     * @param layoutEnabled True to enable layout, false otherwise.
-     */
-    public void setLayoutEnabled(boolean layoutEnabled) {
-        mLayoutManager.setLayoutEnabled(layoutEnabled);
-    }
-
-    /**
-     * Changes and overrides children's visibility.
-     * @param visibility See {@link View#getVisibility()}.
-     */
-    public void setChildrenVisibility(int visibility) {
-        mLayoutManager.setChildrenVisibility(visibility);
-    }
-
-    /**
-     * Enables or disables pruning of children.  Disable is useful during transition.
-     * @param pruneChild True to prune children out side visible area, false to enable.
-     */
-    public void setPruneChild(boolean pruneChild) {
-        mLayoutManager.setPruneChild(pruneChild);
-    }
-
-    /**
-     * Enables or disables scrolling.  Disable is useful during transition.
-     * @param scrollEnabled True to enable scroll, false to disable.
-     */
-    public void setScrollEnabled(boolean scrollEnabled) {
-        mLayoutManager.setScrollEnabled(scrollEnabled);
-    }
-
-    /**
-     * Returns true if scrolling is enabled, false otherwise.
-     * @return True if scrolling is enabled, false otherwise.
-     */
-    public boolean isScrollEnabled() {
-        return mLayoutManager.isScrollEnabled();
-    }
-
-    /**
-     * Returns true if the view at the given position has a same row sibling
-     * in front of it.  This will return true if first item view is not created.
-     *
-     * @param position Position in adapter.
-     * @return True if the view at the given position has a same row sibling in front of it.
-     */
-    public boolean hasPreviousViewInSameRow(int position) {
-        return mLayoutManager.hasPreviousViewInSameRow(position);
-    }
-
-    /**
-     * Enables or disables the default "focus draw at last" order rule. Default is enabled.
-     * @param enabled True to draw the selected child at last, false otherwise.
-     */
-    public void setFocusDrawingOrderEnabled(boolean enabled) {
-        super.setChildrenDrawingOrderEnabled(enabled);
-    }
-
-    /**
-     * Returns true if draws selected child at last, false otherwise. Default is enabled.
-     * @return True if draws selected child at last, false otherwise.
-     */
-    public boolean isFocusDrawingOrderEnabled() {
-        return super.isChildrenDrawingOrderEnabled();
-    }
-
-    /**
-     * Sets the touch intercept listener.
-     * @param listener The touch intercept listener.
-     */
-    public void setOnTouchInterceptListener(OnTouchInterceptListener listener) {
-        mOnTouchInterceptListener = listener;
-    }
-
-    /**
-     * Sets the generic motion intercept listener.
-     * @param listener The motion intercept listener.
-     */
-    public void setOnMotionInterceptListener(OnMotionInterceptListener listener) {
-        mOnMotionInterceptListener = listener;
-    }
-
-    /**
-     * Sets the key intercept listener.
-     * @param listener The key intercept listener.
-     */
-    public void setOnKeyInterceptListener(OnKeyInterceptListener listener) {
-        mOnKeyInterceptListener = listener;
-    }
-
-    /**
-     * Sets the unhandled key listener.
-     * @param listener The unhandled key intercept listener.
-     */
-    public void setOnUnhandledKeyListener(OnUnhandledKeyListener listener) {
-        mOnUnhandledKeyListener = listener;
-    }
-
-    /**
-     * Returns the unhandled key listener.
-     * @return The unhandled key listener.
-     */
-    public OnUnhandledKeyListener getOnUnhandledKeyListener() {
-        return mOnUnhandledKeyListener;
-    }
-
-    @Override
-    public boolean dispatchKeyEvent(KeyEvent event) {
-        if (mOnKeyInterceptListener != null && mOnKeyInterceptListener.onInterceptKeyEvent(event)) {
-            return true;
-        }
-        if (super.dispatchKeyEvent(event)) {
-            return true;
-        }
-        return mOnUnhandledKeyListener != null && mOnUnhandledKeyListener.onUnhandledKey(event);
-    }
-
-    @Override
-    public boolean dispatchTouchEvent(MotionEvent event) {
-        if (mOnTouchInterceptListener != null) {
-            if (mOnTouchInterceptListener.onInterceptTouchEvent(event)) {
-                return true;
-            }
-        }
-        return super.dispatchTouchEvent(event);
-    }
-
-    @Override
-    protected boolean dispatchGenericFocusedEvent(MotionEvent event) {
-        if (mOnMotionInterceptListener != null) {
-            if (mOnMotionInterceptListener.onInterceptMotionEvent(event)) {
-                return true;
-            }
-        }
-        return super.dispatchGenericFocusedEvent(event);
-    }
-
-    /**
-     * Returns the policy for saving children.
-     *
-     * @return policy, one of {@link #SAVE_NO_CHILD}
-     * {@link #SAVE_ON_SCREEN_CHILD} {@link #SAVE_LIMITED_CHILD} {@link #SAVE_ALL_CHILD}.
-     */
-    public final int getSaveChildrenPolicy() {
-        return mLayoutManager.mChildrenStates.getSavePolicy();
-    }
-
-    /**
-     * Returns the limit used when when {@link #getSaveChildrenPolicy()} is
-     *         {@link #SAVE_LIMITED_CHILD}
-     */
-    public final int getSaveChildrenLimitNumber() {
-        return mLayoutManager.mChildrenStates.getLimitNumber();
-    }
-
-    /**
-     * Sets the policy for saving children.
-     * @param savePolicy One of {@link #SAVE_NO_CHILD} {@link #SAVE_ON_SCREEN_CHILD}
-     * {@link #SAVE_LIMITED_CHILD} {@link #SAVE_ALL_CHILD}.
-     */
-    public final void setSaveChildrenPolicy(int savePolicy) {
-        mLayoutManager.mChildrenStates.setSavePolicy(savePolicy);
-    }
-
-    /**
-     * Sets the limit number when {@link #getSaveChildrenPolicy()} is {@link #SAVE_LIMITED_CHILD}.
-     */
-    public final void setSaveChildrenLimitNumber(int limitNumber) {
-        mLayoutManager.mChildrenStates.setLimitNumber(limitNumber);
-    }
-
-    @Override
-    public boolean hasOverlappingRendering() {
-        return mHasOverlappingRendering;
-    }
-
-    public void setHasOverlappingRendering(boolean hasOverlapping) {
-        mHasOverlappingRendering = hasOverlapping;
-    }
-
-    /**
-     * Notify layout manager that layout directionality has been updated
-     */
-    @Override
-    public void onRtlPropertiesChanged(int layoutDirection) {
-        mLayoutManager.onRtlPropertiesChanged(layoutDirection);
-    }
-
-    @Override
-    public void setRecyclerListener(RecyclerView.RecyclerListener listener) {
-        mChainedRecyclerListener = listener;
-    }
-
-    /**
-     * Sets pixels of extra space for layout child in invisible area.
-     *
-     * @param extraLayoutSpace  Pixels of extra space for layout invisible child.
-     *                          Must be bigger or equals to 0.
-     * @hide
-     */
-    @RestrictTo(LIBRARY_GROUP)
-    public void setExtraLayoutSpace(int extraLayoutSpace) {
-        mLayoutManager.setExtraLayoutSpace(extraLayoutSpace);
-    }
-
-    /**
-     * Returns pixels of extra space for layout child in invisible area.
-     *
-     * @hide
-     */
-    @RestrictTo(LIBRARY_GROUP)
-    public int getExtraLayoutSpace() {
-        return mLayoutManager.getExtraLayoutSpace();
-    }
-
-    /**
-     * Temporarily slide out child views to bottom (for VerticalGridView) or end
-     * (for HorizontalGridView). Layout and scrolling will be suppressed until
-     * {@link #animateIn()} is called.
-     */
-    public void animateOut() {
-        mLayoutManager.slideOut();
-    }
-
-    /**
-     * Undo animateOut() and slide in child views.
-     */
-    public void animateIn() {
-        mLayoutManager.slideIn();
-    }
-
-    @Override
-    public void scrollToPosition(int position) {
-        // dont abort the animateOut() animation, just record the position
-        if (mLayoutManager.mIsSlidingChildViews) {
-            mLayoutManager.setSelectionWithSub(position, 0, 0);
-            return;
-        }
-        super.scrollToPosition(position);
-    }
-
-    @Override
-    public void smoothScrollToPosition(int position) {
-        // dont abort the animateOut() animation, just record the position
-        if (mLayoutManager.mIsSlidingChildViews) {
-            mLayoutManager.setSelectionWithSub(position, 0, 0);
-            return;
-        }
-        super.smoothScrollToPosition(position);
-    }
-
-    /**
-     * Sets the number of items to prefetch in
-     * {@link RecyclerView.LayoutManager#collectInitialPrefetchPositions(int, RecyclerView.LayoutManager.LayoutPrefetchRegistry)},
-     * which defines how many inner items should be prefetched when this GridView is nested inside
-     * another RecyclerView.
-     *
-     * <p>Set this value to the number of items this inner GridView will display when it is
-     * first scrolled into the viewport. RecyclerView will attempt to prefetch that number of items
-     * so they are ready, avoiding jank as the inner GridView is scrolled into the viewport.</p>
-     *
-     * <p>For example, take a VerticalGridView of scrolling HorizontalGridViews. The rows always
-     * have 6 items visible in them (or 7 if not aligned). Passing <code>6</code> to this method
-     * for each inner GridView will enable RecyclerView's prefetching feature to do create/bind work
-     * for 6 views within a row early, before it is scrolled on screen, instead of just the default
-     * 4.</p>
-     *
-     * <p>Calling this method does nothing unless the LayoutManager is in a RecyclerView
-     * nested in another RecyclerView.</p>
-     *
-     * <p class="note"><strong>Note:</strong> Setting this value to be larger than the number of
-     * views that will be visible in this view can incur unnecessary bind work, and an increase to
-     * the number of Views created and in active use.</p>
-     *
-     * @param itemCount Number of items to prefetch
-     *
-     * @see #getInitialPrefetchItemCount()
-     * @see RecyclerView.LayoutManager#isItemPrefetchEnabled()
-     * @see RecyclerView.LayoutManager#collectInitialPrefetchPositions(int, RecyclerView.LayoutManager.LayoutPrefetchRegistry)
-     */
-    public void setInitialPrefetchItemCount(int itemCount) {
-        mInitialPrefetchItemCount = itemCount;
-    }
-
-    /**
-     * Gets the number of items to prefetch in
-     * {@link RecyclerView.LayoutManager#collectInitialPrefetchPositions(int, RecyclerView.LayoutManager.LayoutPrefetchRegistry)},
-     * which defines how many inner items should be prefetched when this GridView is nested inside
-     * another RecyclerView.
-     *
-     * @see RecyclerView.LayoutManager#isItemPrefetchEnabled()
-     * @see #setInitialPrefetchItemCount(int)
-     * @see RecyclerView.LayoutManager#collectInitialPrefetchPositions(int, RecyclerView.LayoutManager.LayoutPrefetchRegistry)
-     *
-     * @return number of items to prefetch.
-     */
-    public int getInitialPrefetchItemCount() {
-        return mInitialPrefetchItemCount;
-    }
-}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java b/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java
deleted file mode 100644
index af37f77..0000000
--- a/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java
+++ /dev/null
@@ -1,3712 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-package android.support.v17.leanback.widget;
-
-import static android.support.v7.widget.RecyclerView.HORIZONTAL;
-import static android.support.v7.widget.RecyclerView.NO_ID;
-import static android.support.v7.widget.RecyclerView.NO_POSITION;
-import static android.support.v7.widget.RecyclerView.SCROLL_STATE_IDLE;
-import static android.support.v7.widget.RecyclerView.VERTICAL;
-
-import android.content.Context;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.os.Bundle;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.support.annotation.VisibleForTesting;
-import android.support.v4.os.TraceCompat;
-import android.support.v4.util.CircularIntArray;
-import android.support.v4.view.ViewCompat;
-import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
-import android.support.v7.widget.LinearSmoothScroller;
-import android.support.v7.widget.OrientationHelper;
-import android.support.v7.widget.RecyclerView;
-import android.support.v7.widget.RecyclerView.Recycler;
-import android.support.v7.widget.RecyclerView.State;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.util.SparseIntArray;
-import android.view.FocusFinder;
-import android.view.Gravity;
-import android.view.View;
-import android.view.View.MeasureSpec;
-import android.view.ViewGroup;
-import android.view.ViewGroup.MarginLayoutParams;
-import android.view.animation.AccelerateDecelerateInterpolator;
-
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-final class GridLayoutManager extends RecyclerView.LayoutManager {
-
-    /*
-     * LayoutParams for {@link HorizontalGridView} and {@link VerticalGridView}.
-     * The class currently does two internal jobs:
-     * - Saves optical bounds insets.
-     * - Caches focus align view center.
-     */
-    final static class LayoutParams extends RecyclerView.LayoutParams {
-
-        // For placement
-        int mLeftInset;
-        int mTopInset;
-        int mRightInset;
-        int mBottomInset;
-
-        // For alignment
-        private int mAlignX;
-        private int mAlignY;
-        private int[] mAlignMultiple;
-        private ItemAlignmentFacet mAlignmentFacet;
-
-        public LayoutParams(Context c, AttributeSet attrs) {
-            super(c, attrs);
-        }
-
-        public LayoutParams(int width, int height) {
-            super(width, height);
-        }
-
-        public LayoutParams(MarginLayoutParams source) {
-            super(source);
-        }
-
-        public LayoutParams(ViewGroup.LayoutParams source) {
-            super(source);
-        }
-
-        public LayoutParams(RecyclerView.LayoutParams source) {
-            super(source);
-        }
-
-        public LayoutParams(LayoutParams source) {
-            super(source);
-        }
-
-        int getAlignX() {
-            return mAlignX;
-        }
-
-        int getAlignY() {
-            return mAlignY;
-        }
-
-        int getOpticalLeft(View view) {
-            return view.getLeft() + mLeftInset;
-        }
-
-        int getOpticalTop(View view) {
-            return view.getTop() + mTopInset;
-        }
-
-        int getOpticalRight(View view) {
-            return view.getRight() - mRightInset;
-        }
-
-        int getOpticalBottom(View view) {
-            return view.getBottom() - mBottomInset;
-        }
-
-        int getOpticalWidth(View view) {
-            return view.getWidth() - mLeftInset - mRightInset;
-        }
-
-        int getOpticalHeight(View view) {
-            return view.getHeight() - mTopInset - mBottomInset;
-        }
-
-        int getOpticalLeftInset() {
-            return mLeftInset;
-        }
-
-        int getOpticalRightInset() {
-            return mRightInset;
-        }
-
-        int getOpticalTopInset() {
-            return mTopInset;
-        }
-
-        int getOpticalBottomInset() {
-            return mBottomInset;
-        }
-
-        void setAlignX(int alignX) {
-            mAlignX = alignX;
-        }
-
-        void setAlignY(int alignY) {
-            mAlignY = alignY;
-        }
-
-        void setItemAlignmentFacet(ItemAlignmentFacet facet) {
-            mAlignmentFacet = facet;
-        }
-
-        ItemAlignmentFacet getItemAlignmentFacet() {
-            return mAlignmentFacet;
-        }
-
-        void calculateItemAlignments(int orientation, View view) {
-            ItemAlignmentFacet.ItemAlignmentDef[] defs = mAlignmentFacet.getAlignmentDefs();
-            if (mAlignMultiple == null || mAlignMultiple.length != defs.length) {
-                mAlignMultiple = new int[defs.length];
-            }
-            for (int i = 0; i < defs.length; i++) {
-                mAlignMultiple[i] = ItemAlignmentFacetHelper
-                        .getAlignmentPosition(view, defs[i], orientation);
-            }
-            if (orientation == HORIZONTAL) {
-                mAlignX = mAlignMultiple[0];
-            } else {
-                mAlignY = mAlignMultiple[0];
-            }
-        }
-
-        int[] getAlignMultiple() {
-            return mAlignMultiple;
-        }
-
-        void setOpticalInsets(int leftInset, int topInset, int rightInset, int bottomInset) {
-            mLeftInset = leftInset;
-            mTopInset = topInset;
-            mRightInset = rightInset;
-            mBottomInset = bottomInset;
-        }
-
-    }
-
-    /**
-     * Base class which scrolls to selected view in onStop().
-     */
-    abstract class GridLinearSmoothScroller extends LinearSmoothScroller {
-        GridLinearSmoothScroller() {
-            super(mBaseGridView.getContext());
-        }
-
-        @Override
-        protected void onStop() {
-            // onTargetFound() may not be called if we hit the "wall" first or get cancelled.
-            View targetView = findViewByPosition(getTargetPosition());
-            if (targetView == null) {
-                if (getTargetPosition() >= 0) {
-                    // if smooth scroller is stopped without target, immediately jumps
-                    // to the target position.
-                    scrollToSelection(getTargetPosition(), 0, false, 0);
-                }
-                super.onStop();
-                return;
-            }
-            if (mFocusPosition != getTargetPosition()) {
-                // This should not happen since we cropped value in startPositionSmoothScroller()
-                mFocusPosition = getTargetPosition();
-            }
-            if (hasFocus()) {
-                mInSelection = true;
-                targetView.requestFocus();
-                mInSelection = false;
-            }
-            dispatchChildSelected();
-            dispatchChildSelectedAndPositioned();
-            super.onStop();
-        }
-
-        @Override
-        protected int calculateTimeForScrolling(int dx) {
-            int ms = super.calculateTimeForScrolling(dx);
-            if (mWindowAlignment.mainAxis().getSize() > 0) {
-                float minMs = (float) MIN_MS_SMOOTH_SCROLL_MAIN_SCREEN
-                        / mWindowAlignment.mainAxis().getSize() * dx;
-                if (ms < minMs) {
-                    ms = (int) minMs;
-                }
-            }
-            return ms;
-        }
-
-        @Override
-        protected void onTargetFound(View targetView,
-                RecyclerView.State state, Action action) {
-            if (getScrollPosition(targetView, null, sTwoInts)) {
-                int dx, dy;
-                if (mOrientation == HORIZONTAL) {
-                    dx = sTwoInts[0];
-                    dy = sTwoInts[1];
-                } else {
-                    dx = sTwoInts[1];
-                    dy = sTwoInts[0];
-                }
-                final int distance = (int) Math.sqrt(dx * dx + dy * dy);
-                final int time = calculateTimeForDeceleration(distance);
-                action.update(dx, dy, time, mDecelerateInterpolator);
-            }
-        }
-    }
-
-    /**
-     * The SmoothScroller that remembers pending DPAD keys and consume pending keys
-     * during scroll.
-     */
-    final class PendingMoveSmoothScroller extends GridLinearSmoothScroller {
-        // -2 is a target position that LinearSmoothScroller can never find until
-        // consumePendingMovesXXX() sets real targetPosition.
-        final static int TARGET_UNDEFINED = -2;
-        // whether the grid is staggered.
-        private final boolean mStaggeredGrid;
-        // Number of pending movements on primary direction, negative if PREV_ITEM.
-        private int mPendingMoves;
-
-        PendingMoveSmoothScroller(int initialPendingMoves, boolean staggeredGrid) {
-            mPendingMoves = initialPendingMoves;
-            mStaggeredGrid = staggeredGrid;
-            setTargetPosition(TARGET_UNDEFINED);
-        }
-
-        void increasePendingMoves() {
-            if (mPendingMoves < mMaxPendingMoves) {
-                mPendingMoves++;
-            }
-        }
-
-        void decreasePendingMoves() {
-            if (mPendingMoves > -mMaxPendingMoves) {
-                mPendingMoves--;
-            }
-        }
-
-        /**
-         * Called before laid out an item when non-staggered grid can handle pending movements
-         * by skipping "mNumRows" per movement;  staggered grid will have to wait the item
-         * has been laid out in consumePendingMovesAfterLayout().
-         */
-        void consumePendingMovesBeforeLayout() {
-            if (mStaggeredGrid || mPendingMoves == 0) {
-                return;
-            }
-            View newSelected = null;
-            int startPos = mPendingMoves > 0 ? mFocusPosition + mNumRows :
-                    mFocusPosition - mNumRows;
-            for (int pos = startPos; mPendingMoves != 0;
-                    pos = mPendingMoves > 0 ? pos + mNumRows: pos - mNumRows) {
-                View v = findViewByPosition(pos);
-                if (v == null) {
-                    break;
-                }
-                if (!canScrollTo(v)) {
-                    continue;
-                }
-                newSelected = v;
-                mFocusPosition = pos;
-                mSubFocusPosition = 0;
-                if (mPendingMoves > 0) {
-                    mPendingMoves--;
-                } else {
-                    mPendingMoves++;
-                }
-            }
-            if (newSelected != null && hasFocus()) {
-                mInSelection = true;
-                newSelected.requestFocus();
-                mInSelection = false;
-            }
-        }
-
-        /**
-         * Called after laid out an item.  Staggered grid should find view on same
-         * Row and consume pending movements.
-         */
-        void consumePendingMovesAfterLayout() {
-            if (mStaggeredGrid && mPendingMoves != 0) {
-                // consume pending moves, focus to item on the same row.
-                mPendingMoves = processSelectionMoves(true, mPendingMoves);
-            }
-            if (mPendingMoves == 0 || (mPendingMoves > 0 && hasCreatedLastItem())
-                    || (mPendingMoves < 0 && hasCreatedFirstItem())) {
-                setTargetPosition(mFocusPosition);
-                stop();
-            }
-        }
-
-        @Override
-        protected void updateActionForInterimTarget(Action action) {
-            if (mPendingMoves == 0) {
-                return;
-            }
-            super.updateActionForInterimTarget(action);
-        }
-
-        @Override
-        public PointF computeScrollVectorForPosition(int targetPosition) {
-            if (mPendingMoves == 0) {
-                return null;
-            }
-            int direction = (mReverseFlowPrimary ? mPendingMoves > 0 : mPendingMoves < 0)
-                    ? -1 : 1;
-            if (mOrientation == HORIZONTAL) {
-                return new PointF(direction, 0);
-            } else {
-                return new PointF(0, direction);
-            }
-        }
-
-        @Override
-        protected void onStop() {
-            super.onStop();
-            // if we hit wall,  need clear the remaining pending moves.
-            mPendingMoves = 0;
-            mPendingMoveSmoothScroller = null;
-            View v = findViewByPosition(getTargetPosition());
-            if (v != null) scrollToView(v, true);
-        }
-    };
-
-    private static final String TAG = "GridLayoutManager";
-    static final boolean DEBUG = false;
-    static final boolean TRACE = false;
-
-    // maximum pending movement in one direction.
-    static final int DEFAULT_MAX_PENDING_MOVES = 10;
-    int mMaxPendingMoves = DEFAULT_MAX_PENDING_MOVES;
-    // minimal milliseconds to scroll window size in major direction,  we put a cap to prevent the
-    // effect smooth scrolling too over to bind an item view then drag the item view back.
-    final static int MIN_MS_SMOOTH_SCROLL_MAIN_SCREEN = 30;
-
-    // Represents whether child views are temporarily sliding out
-    boolean mIsSlidingChildViews;
-    boolean mLayoutEatenInSliding;
-
-    String getTag() {
-        return TAG + ":" + mBaseGridView.getId();
-    }
-
-    final BaseGridView mBaseGridView;
-
-    /**
-     * Note on conventions in the presence of RTL layout directions:
-     * Many properties and method names reference entities related to the
-     * beginnings and ends of things.  In the presence of RTL flows,
-     * it may not be clear whether this is intended to reference a
-     * quantity that changes direction in RTL cases, or a quantity that
-     * does not.  Here are the conventions in use:
-     *
-     * start/end: coordinate quantities - do reverse
-     * (optical) left/right: coordinate quantities - do not reverse
-     * low/high: coordinate quantities - do not reverse
-     * min/max: coordinate quantities - do not reverse
-     * scroll offset - coordinate quantities - do not reverse
-     * first/last: positional indices - do not reverse
-     * front/end: positional indices - do not reverse
-     * prepend/append: related to positional indices - do not reverse
-     *
-     * Note that although quantities do not reverse in RTL flows, their
-     * relationship does.  In LTR flows, the first positional index is
-     * leftmost; in RTL flows, it is rightmost.  Thus, anywhere that
-     * positional quantities are mapped onto coordinate quantities,
-     * the flow must be checked and the logic reversed.
-     */
-
-    /**
-     * The orientation of a "row".
-     */
-    @RecyclerView.Orientation
-    int mOrientation = HORIZONTAL;
-    private OrientationHelper mOrientationHelper = OrientationHelper.createHorizontalHelper(this);
-
-    RecyclerView.State mState;
-    // Suppose currently showing 4, 5, 6, 7; removing 2,3,4 will make the layoutPosition to be
-    // 2(deleted), 3, 4, 5 in prelayout pass. So when we add item in prelayout, we must subtract 2
-    // from index of Grid.createItem.
-    int mPositionDeltaInPreLayout;
-    // Extra layout space needs to fill in prelayout pass. Note we apply the extra space to both
-    // appends and prepends due to the fact leanback is doing mario scrolling: removing items to
-    // the left of focused item might need extra layout on the right.
-    int mExtraLayoutSpaceInPreLayout;
-    // mPositionToRowInPostLayout and mDisappearingPositions are temp variables in post layout.
-    final SparseIntArray mPositionToRowInPostLayout = new SparseIntArray();
-    int[] mDisappearingPositions;
-
-    RecyclerView.Recycler mRecycler;
-
-    private static final Rect sTempRect = new Rect();
-
-    boolean mInLayout;
-    private boolean mInScroll;
-    boolean mInFastRelayout;
-    /**
-     * During full layout pass, when GridView had focus: onLayoutChildren will
-     * skip non-focusable child and adjust mFocusPosition.
-     */
-    boolean mInLayoutSearchFocus;
-    boolean mInSelection = false;
-
-    private OnChildSelectedListener mChildSelectedListener = null;
-
-    private ArrayList<OnChildViewHolderSelectedListener> mChildViewHolderSelectedListeners = null;
-
-    OnChildLaidOutListener mChildLaidOutListener = null;
-
-    /**
-     * The focused position, it's not the currently visually aligned position
-     * but it is the final position that we intend to focus on. If there are
-     * multiple setSelection() called, mFocusPosition saves last value.
-     */
-    int mFocusPosition = NO_POSITION;
-
-    /**
-     * A view can have multiple alignment position,  this is the index of which
-     * alignment is used,  by default is 0.
-     */
-    int mSubFocusPosition = 0;
-
-    /**
-     * LinearSmoothScroller that consume pending DPAD movements.
-     */
-    PendingMoveSmoothScroller mPendingMoveSmoothScroller;
-
-    /**
-     * The offset to be applied to mFocusPosition, due to adapter change, on the next
-     * layout.  Set to Integer.MIN_VALUE means we should stop adding delta to mFocusPosition
-     * until next layout cycler.
-     * TODO:  This is somewhat duplication of RecyclerView getOldPosition() which is
-     * unfortunately cleared after prelayout.
-     */
-    private int mFocusPositionOffset = 0;
-
-    /**
-     * Extra pixels applied on primary direction.
-     */
-    private int mPrimaryScrollExtra;
-
-    /**
-     * Force a full layout under certain situations.  E.g. Rows change, jump to invisible child.
-     */
-    private boolean mForceFullLayout;
-
-    /**
-     * True if layout is enabled.
-     */
-    private boolean mLayoutEnabled = true;
-
-    /**
-     * override child visibility
-     */
-    @Visibility
-    int mChildVisibility;
-
-    /**
-     * Pixels that scrolled in secondary forward direction. Negative value means backward.
-     * Note that we treat secondary differently than main. For the main axis, update scroll min/max
-     * based on first/last item's view location. For second axis, we don't use item's view location.
-     * We are using the {@link #getRowSizeSecondary(int)} plus mScrollOffsetSecondary. see
-     * details in {@link #updateSecondaryScrollLimits()}.
-     */
-    int mScrollOffsetSecondary;
-
-    /**
-     * User-specified row height/column width.  Can be WRAP_CONTENT.
-     */
-    private int mRowSizeSecondaryRequested;
-
-    /**
-     * The fixed size of each grid item in the secondary direction. This corresponds to
-     * the row height, equal for all rows. Grid items may have variable length
-     * in the primary direction.
-     */
-    private int mFixedRowSizeSecondary;
-
-    /**
-     * Tracks the secondary size of each row.
-     */
-    private int[] mRowSizeSecondary;
-
-    /**
-     * Flag controlling whether the current/next layout should
-     * be updating the secondary size of rows.
-     */
-    private boolean mRowSecondarySizeRefresh;
-
-    /**
-     * The maximum measured size of the view.
-     */
-    private int mMaxSizeSecondary;
-
-    /**
-     * Margin between items.
-     */
-    private int mHorizontalSpacing;
-    /**
-     * Margin between items vertically.
-     */
-    private int mVerticalSpacing;
-    /**
-     * Margin in main direction.
-     */
-    private int mSpacingPrimary;
-    /**
-     * Margin in second direction.
-     */
-    private int mSpacingSecondary;
-    /**
-     * How to position child in secondary direction.
-     */
-    private int mGravity = Gravity.START | Gravity.TOP;
-    /**
-     * The number of rows in the grid.
-     */
-    int mNumRows;
-    /**
-     * Number of rows requested, can be 0 to be determined by parent size and
-     * rowHeight.
-     */
-    private int mNumRowsRequested = 1;
-
-    /**
-     * Saves grid information of each view.
-     */
-    Grid mGrid;
-
-    /**
-     * Focus Scroll strategy.
-     */
-    private int mFocusScrollStrategy = BaseGridView.FOCUS_SCROLL_ALIGNED;
-    /**
-     * Defines how item view is aligned in the window.
-     */
-    final WindowAlignment mWindowAlignment = new WindowAlignment();
-
-    /**
-     * Defines how item view is aligned.
-     */
-    private final ItemAlignment mItemAlignment = new ItemAlignment();
-
-    /**
-     * Dimensions of the view, width or height depending on orientation.
-     */
-    private int mSizePrimary;
-
-    /**
-     * Pixels of extra space for layout item (outside the widget)
-     */
-    private int mExtraLayoutSpace;
-
-    /**
-     *  Allow DPAD key to navigate out at the front of the View (where position = 0),
-     *  default is false.
-     */
-    private boolean mFocusOutFront;
-
-    /**
-     * Allow DPAD key to navigate out at the end of the view, default is false.
-     */
-    private boolean mFocusOutEnd;
-
-    /**
-     *  Allow DPAD key to navigate out of second axis.
-     *  default is true.
-     */
-    private boolean mFocusOutSideStart = true;
-
-    /**
-     * Allow DPAD key to navigate out of second axis.
-     */
-    private boolean mFocusOutSideEnd = true;
-
-    /**
-     * True if focus search is disabled.
-     */
-    private boolean mFocusSearchDisabled;
-
-    /**
-     * True if prune child,  might be disabled during transition.
-     */
-    private boolean mPruneChild = true;
-
-    /**
-     * True if scroll content,  might be disabled during transition.
-     */
-    private boolean mScrollEnabled = true;
-
-    /**
-     * Temporary variable: an int array of length=2.
-     */
-    static int[] sTwoInts = new int[2];
-
-    /**
-     * Set to true for RTL layout in horizontal orientation
-     */
-    boolean mReverseFlowPrimary = false;
-
-    /**
-     * Set to true for RTL layout in vertical orientation
-     */
-    private boolean mReverseFlowSecondary = false;
-
-    /**
-     * Temporaries used for measuring.
-     */
-    private int[] mMeasuredDimension = new int[2];
-
-    final ViewsStateBundle mChildrenStates = new ViewsStateBundle();
-
-    /**
-     * Optional interface implemented by Adapter.
-     */
-    private FacetProviderAdapter mFacetProviderAdapter;
-
-    public GridLayoutManager(BaseGridView baseGridView) {
-        mBaseGridView = baseGridView;
-        mChildVisibility = -1;
-        // disable prefetch by default, prefetch causes regression on low power chipset
-        setItemPrefetchEnabled(false);
-    }
-
-    public void setOrientation(@RecyclerView.Orientation int orientation) {
-        if (orientation != HORIZONTAL && orientation != VERTICAL) {
-            if (DEBUG) Log.v(getTag(), "invalid orientation: " + orientation);
-            return;
-        }
-
-        mOrientation = orientation;
-        mOrientationHelper = OrientationHelper.createOrientationHelper(this, mOrientation);
-        mWindowAlignment.setOrientation(orientation);
-        mItemAlignment.setOrientation(orientation);
-        mForceFullLayout = true;
-    }
-
-    public void onRtlPropertiesChanged(int layoutDirection) {
-        boolean reversePrimary, reverseSecondary;
-        if (mOrientation == HORIZONTAL) {
-            reversePrimary = layoutDirection == View.LAYOUT_DIRECTION_RTL;
-            reverseSecondary = false;
-        } else {
-            reverseSecondary = layoutDirection == View.LAYOUT_DIRECTION_RTL;
-            reversePrimary = false;
-        }
-        if (mReverseFlowPrimary == reversePrimary && mReverseFlowSecondary == reverseSecondary) {
-            return;
-        }
-        mReverseFlowPrimary = reversePrimary;
-        mReverseFlowSecondary = reverseSecondary;
-        mForceFullLayout = true;
-        mWindowAlignment.horizontal.setReversedFlow(layoutDirection == View.LAYOUT_DIRECTION_RTL);
-    }
-
-    public int getFocusScrollStrategy() {
-        return mFocusScrollStrategy;
-    }
-
-    public void setFocusScrollStrategy(int focusScrollStrategy) {
-        mFocusScrollStrategy = focusScrollStrategy;
-    }
-
-    public void setWindowAlignment(int windowAlignment) {
-        mWindowAlignment.mainAxis().setWindowAlignment(windowAlignment);
-    }
-
-    public int getWindowAlignment() {
-        return mWindowAlignment.mainAxis().getWindowAlignment();
-    }
-
-    public void setWindowAlignmentOffset(int alignmentOffset) {
-        mWindowAlignment.mainAxis().setWindowAlignmentOffset(alignmentOffset);
-    }
-
-    public int getWindowAlignmentOffset() {
-        return mWindowAlignment.mainAxis().getWindowAlignmentOffset();
-    }
-
-    public void setWindowAlignmentOffsetPercent(float offsetPercent) {
-        mWindowAlignment.mainAxis().setWindowAlignmentOffsetPercent(offsetPercent);
-    }
-
-    public float getWindowAlignmentOffsetPercent() {
-        return mWindowAlignment.mainAxis().getWindowAlignmentOffsetPercent();
-    }
-
-    public void setItemAlignmentOffset(int alignmentOffset) {
-        mItemAlignment.mainAxis().setItemAlignmentOffset(alignmentOffset);
-        updateChildAlignments();
-    }
-
-    public int getItemAlignmentOffset() {
-        return mItemAlignment.mainAxis().getItemAlignmentOffset();
-    }
-
-    public void setItemAlignmentOffsetWithPadding(boolean withPadding) {
-        mItemAlignment.mainAxis().setItemAlignmentOffsetWithPadding(withPadding);
-        updateChildAlignments();
-    }
-
-    public boolean isItemAlignmentOffsetWithPadding() {
-        return mItemAlignment.mainAxis().isItemAlignmentOffsetWithPadding();
-    }
-
-    public void setItemAlignmentOffsetPercent(float offsetPercent) {
-        mItemAlignment.mainAxis().setItemAlignmentOffsetPercent(offsetPercent);
-        updateChildAlignments();
-    }
-
-    public float getItemAlignmentOffsetPercent() {
-        return mItemAlignment.mainAxis().getItemAlignmentOffsetPercent();
-    }
-
-    public void setItemAlignmentViewId(int viewId) {
-        mItemAlignment.mainAxis().setItemAlignmentViewId(viewId);
-        updateChildAlignments();
-    }
-
-    public int getItemAlignmentViewId() {
-        return mItemAlignment.mainAxis().getItemAlignmentViewId();
-    }
-
-    public void setFocusOutAllowed(boolean throughFront, boolean throughEnd) {
-        mFocusOutFront = throughFront;
-        mFocusOutEnd = throughEnd;
-    }
-
-    public void setFocusOutSideAllowed(boolean throughStart, boolean throughEnd) {
-        mFocusOutSideStart = throughStart;
-        mFocusOutSideEnd = throughEnd;
-    }
-
-    public void setNumRows(int numRows) {
-        if (numRows < 0) throw new IllegalArgumentException();
-        mNumRowsRequested = numRows;
-    }
-
-    /**
-     * Set the row height. May be WRAP_CONTENT, or a size in pixels.
-     */
-    public void setRowHeight(int height) {
-        if (height >= 0 || height == ViewGroup.LayoutParams.WRAP_CONTENT) {
-            mRowSizeSecondaryRequested = height;
-        } else {
-            throw new IllegalArgumentException("Invalid row height: " + height);
-        }
-    }
-
-    public void setItemSpacing(int space) {
-        mVerticalSpacing = mHorizontalSpacing = space;
-        mSpacingPrimary = mSpacingSecondary = space;
-    }
-
-    public void setVerticalSpacing(int space) {
-        if (mOrientation == VERTICAL) {
-            mSpacingPrimary = mVerticalSpacing = space;
-        } else {
-            mSpacingSecondary = mVerticalSpacing = space;
-        }
-    }
-
-    public void setHorizontalSpacing(int space) {
-        if (mOrientation == HORIZONTAL) {
-            mSpacingPrimary = mHorizontalSpacing = space;
-        } else {
-            mSpacingSecondary = mHorizontalSpacing = space;
-        }
-    }
-
-    public int getVerticalSpacing() {
-        return mVerticalSpacing;
-    }
-
-    public int getHorizontalSpacing() {
-        return mHorizontalSpacing;
-    }
-
-    public void setGravity(int gravity) {
-        mGravity = gravity;
-    }
-
-    protected boolean hasDoneFirstLayout() {
-        return mGrid != null;
-    }
-
-    public void setOnChildSelectedListener(OnChildSelectedListener listener) {
-        mChildSelectedListener = listener;
-    }
-
-    public void setOnChildViewHolderSelectedListener(OnChildViewHolderSelectedListener listener) {
-        if (listener == null) {
-            mChildViewHolderSelectedListeners = null;
-            return;
-        }
-        if (mChildViewHolderSelectedListeners == null) {
-            mChildViewHolderSelectedListeners = new ArrayList<OnChildViewHolderSelectedListener>();
-        } else {
-            mChildViewHolderSelectedListeners.clear();
-        }
-        mChildViewHolderSelectedListeners.add(listener);
-    }
-
-    public void addOnChildViewHolderSelectedListener(OnChildViewHolderSelectedListener listener) {
-        if (mChildViewHolderSelectedListeners == null) {
-            mChildViewHolderSelectedListeners = new ArrayList<OnChildViewHolderSelectedListener>();
-        }
-        mChildViewHolderSelectedListeners.add(listener);
-    }
-
-    public void removeOnChildViewHolderSelectedListener(OnChildViewHolderSelectedListener
-            listener) {
-        if (mChildViewHolderSelectedListeners != null) {
-            mChildViewHolderSelectedListeners.remove(listener);
-        }
-    }
-
-    boolean hasOnChildViewHolderSelectedListener() {
-        return mChildViewHolderSelectedListeners != null
-                && mChildViewHolderSelectedListeners.size() > 0;
-    }
-
-    void fireOnChildViewHolderSelected(RecyclerView parent, RecyclerView.ViewHolder child,
-            int position, int subposition) {
-        if (mChildViewHolderSelectedListeners == null) {
-            return;
-        }
-        for (int i = mChildViewHolderSelectedListeners.size() - 1; i >= 0 ; i--) {
-            mChildViewHolderSelectedListeners.get(i).onChildViewHolderSelected(parent, child,
-                    position, subposition);
-        }
-    }
-
-    void fireOnChildViewHolderSelectedAndPositioned(RecyclerView parent, RecyclerView.ViewHolder
-            child, int position, int subposition) {
-        if (mChildViewHolderSelectedListeners == null) {
-            return;
-        }
-        for (int i = mChildViewHolderSelectedListeners.size() - 1; i >= 0 ; i--) {
-            mChildViewHolderSelectedListeners.get(i).onChildViewHolderSelectedAndPositioned(parent,
-                    child, position, subposition);
-        }
-    }
-
-    void setOnChildLaidOutListener(OnChildLaidOutListener listener) {
-        mChildLaidOutListener = listener;
-    }
-
-    private int getAdapterPositionByView(View view) {
-        if (view == null) {
-            return NO_POSITION;
-        }
-        LayoutParams params = (LayoutParams) view.getLayoutParams();
-        if (params == null || params.isItemRemoved()) {
-            // when item is removed, the position value can be any value.
-            return NO_POSITION;
-        }
-        return params.getViewAdapterPosition();
-    }
-
-    int getSubPositionByView(View view, View childView) {
-        if (view == null || childView == null) {
-            return 0;
-        }
-        final LayoutParams lp = (LayoutParams) view.getLayoutParams();
-        final ItemAlignmentFacet facet = lp.getItemAlignmentFacet();
-        if (facet != null) {
-            final ItemAlignmentFacet.ItemAlignmentDef[] defs = facet.getAlignmentDefs();
-            if (defs.length > 1) {
-                while (childView != view) {
-                    int id = childView.getId();
-                    if (id != View.NO_ID) {
-                        for (int i = 1; i < defs.length; i++) {
-                            if (defs[i].getItemAlignmentFocusViewId() == id) {
-                                return i;
-                            }
-                        }
-                    }
-                    childView = (View) childView.getParent();
-                }
-            }
-        }
-        return 0;
-    }
-
-    private int getAdapterPositionByIndex(int index) {
-        return getAdapterPositionByView(getChildAt(index));
-    }
-
-    void dispatchChildSelected() {
-        if (mChildSelectedListener == null && !hasOnChildViewHolderSelectedListener()) {
-            return;
-        }
-
-        if (TRACE) TraceCompat.beginSection("onChildSelected");
-        View view = mFocusPosition == NO_POSITION ? null : findViewByPosition(mFocusPosition);
-        if (view != null) {
-            RecyclerView.ViewHolder vh = mBaseGridView.getChildViewHolder(view);
-            if (mChildSelectedListener != null) {
-                mChildSelectedListener.onChildSelected(mBaseGridView, view, mFocusPosition,
-                        vh == null? NO_ID: vh.getItemId());
-            }
-            fireOnChildViewHolderSelected(mBaseGridView, vh, mFocusPosition, mSubFocusPosition);
-        } else {
-            if (mChildSelectedListener != null) {
-                mChildSelectedListener.onChildSelected(mBaseGridView, null, NO_POSITION, NO_ID);
-            }
-            fireOnChildViewHolderSelected(mBaseGridView, null, NO_POSITION, 0);
-        }
-        if (TRACE) TraceCompat.endSection();
-
-        // Children may request layout when a child selection event occurs (such as a change of
-        // padding on the current and previously selected rows).
-        // If in layout, a child requesting layout may have been laid out before the selection
-        // callback.
-        // If it was not, the child will be laid out after the selection callback.
-        // If so, the layout request will be honoured though the view system will emit a double-
-        // layout warning.
-        // If not in layout, we may be scrolling in which case the child layout request will be
-        // eaten by recyclerview.  Post a requestLayout.
-        if (!mInLayout && !mBaseGridView.isLayoutRequested()) {
-            int childCount = getChildCount();
-            for (int i = 0; i < childCount; i++) {
-                if (getChildAt(i).isLayoutRequested()) {
-                    forceRequestLayout();
-                    break;
-                }
-            }
-        }
-    }
-
-    private void dispatchChildSelectedAndPositioned() {
-        if (!hasOnChildViewHolderSelectedListener()) {
-            return;
-        }
-
-        if (TRACE) TraceCompat.beginSection("onChildSelectedAndPositioned");
-        View view = mFocusPosition == NO_POSITION ? null : findViewByPosition(mFocusPosition);
-        if (view != null) {
-            RecyclerView.ViewHolder vh = mBaseGridView.getChildViewHolder(view);
-            fireOnChildViewHolderSelectedAndPositioned(mBaseGridView, vh, mFocusPosition,
-                    mSubFocusPosition);
-        } else {
-            if (mChildSelectedListener != null) {
-                mChildSelectedListener.onChildSelected(mBaseGridView, null, NO_POSITION, NO_ID);
-            }
-            fireOnChildViewHolderSelectedAndPositioned(mBaseGridView, null, NO_POSITION, 0);
-        }
-        if (TRACE) TraceCompat.endSection();
-
-    }
-
-    @Override
-    public boolean canScrollHorizontally() {
-        // We can scroll horizontally if we have horizontal orientation, or if
-        // we are vertical and have more than one column.
-        return mOrientation == HORIZONTAL || mNumRows > 1;
-    }
-
-    @Override
-    public boolean canScrollVertically() {
-        // We can scroll vertically if we have vertical orientation, or if we
-        // are horizontal and have more than one row.
-        return mOrientation == VERTICAL || mNumRows > 1;
-    }
-
-    @Override
-    public RecyclerView.LayoutParams generateDefaultLayoutParams() {
-        return new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
-                ViewGroup.LayoutParams.WRAP_CONTENT);
-    }
-
-    @Override
-    public RecyclerView.LayoutParams generateLayoutParams(Context context, AttributeSet attrs) {
-        return new LayoutParams(context, attrs);
-    }
-
-    @Override
-    public RecyclerView.LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
-        if (lp instanceof LayoutParams) {
-            return new LayoutParams((LayoutParams) lp);
-        } else if (lp instanceof RecyclerView.LayoutParams) {
-            return new LayoutParams((RecyclerView.LayoutParams) lp);
-        } else if (lp instanceof MarginLayoutParams) {
-            return new LayoutParams((MarginLayoutParams) lp);
-        } else {
-            return new LayoutParams(lp);
-        }
-    }
-
-    protected View getViewForPosition(int position) {
-        return mRecycler.getViewForPosition(position);
-    }
-
-    final int getOpticalLeft(View v) {
-        return ((LayoutParams) v.getLayoutParams()).getOpticalLeft(v);
-    }
-
-    final int getOpticalRight(View v) {
-        return ((LayoutParams) v.getLayoutParams()).getOpticalRight(v);
-    }
-
-    final int getOpticalTop(View v) {
-        return ((LayoutParams) v.getLayoutParams()).getOpticalTop(v);
-    }
-
-    final int getOpticalBottom(View v) {
-        return ((LayoutParams) v.getLayoutParams()).getOpticalBottom(v);
-    }
-
-    @Override
-    public int getDecoratedLeft(View child) {
-        return super.getDecoratedLeft(child) + ((LayoutParams) child.getLayoutParams()).mLeftInset;
-    }
-
-    @Override
-    public int getDecoratedTop(View child) {
-        return super.getDecoratedTop(child) + ((LayoutParams) child.getLayoutParams()).mTopInset;
-    }
-
-    @Override
-    public int getDecoratedRight(View child) {
-        return super.getDecoratedRight(child)
-                - ((LayoutParams) child.getLayoutParams()).mRightInset;
-    }
-
-    @Override
-    public int getDecoratedBottom(View child) {
-        return super.getDecoratedBottom(child)
-                - ((LayoutParams) child.getLayoutParams()).mBottomInset;
-    }
-
-    @Override
-    public void getDecoratedBoundsWithMargins(View view, Rect outBounds) {
-        super.getDecoratedBoundsWithMargins(view, outBounds);
-        LayoutParams params = ((LayoutParams) view.getLayoutParams());
-        outBounds.left += params.mLeftInset;
-        outBounds.top += params.mTopInset;
-        outBounds.right -= params.mRightInset;
-        outBounds.bottom -= params.mBottomInset;
-    }
-
-    int getViewMin(View v) {
-        return mOrientationHelper.getDecoratedStart(v);
-    }
-
-    int getViewMax(View v) {
-        return mOrientationHelper.getDecoratedEnd(v);
-    }
-
-    int getViewPrimarySize(View view) {
-        getDecoratedBoundsWithMargins(view, sTempRect);
-        return mOrientation == HORIZONTAL ? sTempRect.width() : sTempRect.height();
-    }
-
-    private int getViewCenter(View view) {
-        return (mOrientation == HORIZONTAL) ? getViewCenterX(view) : getViewCenterY(view);
-    }
-
-    private int getAdjustedViewCenter(View view) {
-        if (view.hasFocus()) {
-            View child = view.findFocus();
-            if (child != null && child != view) {
-                return getAdjustedPrimaryAlignedScrollDistance(getViewCenter(view), view, child);
-            }
-        }
-        return getViewCenter(view);
-    }
-
-    private int getViewCenterSecondary(View view) {
-        return (mOrientation == HORIZONTAL) ? getViewCenterY(view) : getViewCenterX(view);
-    }
-
-    private int getViewCenterX(View v) {
-        LayoutParams p = (LayoutParams) v.getLayoutParams();
-        return p.getOpticalLeft(v) + p.getAlignX();
-    }
-
-    private int getViewCenterY(View v) {
-        LayoutParams p = (LayoutParams) v.getLayoutParams();
-        return p.getOpticalTop(v) + p.getAlignY();
-    }
-
-    /**
-     * Save Recycler and State for convenience.  Must be paired with leaveContext().
-     */
-    private void saveContext(Recycler recycler, State state) {
-        if (mRecycler != null || mState != null) {
-            Log.e(TAG, "Recycler information was not released, bug!");
-        }
-        mRecycler = recycler;
-        mState = state;
-        mPositionDeltaInPreLayout = 0;
-        mExtraLayoutSpaceInPreLayout = 0;
-    }
-
-    /**
-     * Discard saved Recycler and State.
-     */
-    private void leaveContext() {
-        mRecycler = null;
-        mState = null;
-        mPositionDeltaInPreLayout = 0;
-        mExtraLayoutSpaceInPreLayout = 0;
-    }
-
-    /**
-     * Re-initialize data structures for a data change or handling invisible
-     * selection. The method tries its best to preserve position information so
-     * that staggered grid looks same before and after re-initialize.
-     * @return true if can fastRelayout()
-     */
-    private boolean layoutInit() {
-        final int newItemCount = mState.getItemCount();
-        if (newItemCount == 0) {
-            mFocusPosition = NO_POSITION;
-            mSubFocusPosition = 0;
-        } else if (mFocusPosition >= newItemCount) {
-            mFocusPosition = newItemCount - 1;
-            mSubFocusPosition = 0;
-        } else if (mFocusPosition == NO_POSITION && newItemCount > 0) {
-            // if focus position is never set before,  initialize it to 0
-            mFocusPosition = 0;
-            mSubFocusPosition = 0;
-        }
-        if (!mState.didStructureChange() && mGrid != null && mGrid.getFirstVisibleIndex() >= 0
-                && !mForceFullLayout && mGrid.getNumRows() == mNumRows) {
-            updateScrollController();
-            updateSecondaryScrollLimits();
-            mGrid.setSpacing(mSpacingPrimary);
-            return true;
-        } else {
-            mForceFullLayout = false;
-
-            if (mGrid == null || mNumRows != mGrid.getNumRows()
-                    || mReverseFlowPrimary != mGrid.isReversedFlow()) {
-                mGrid = Grid.createGrid(mNumRows);
-                mGrid.setProvider(mGridProvider);
-                mGrid.setReversedFlow(mReverseFlowPrimary);
-            }
-            initScrollController();
-            updateSecondaryScrollLimits();
-            mGrid.setSpacing(mSpacingPrimary);
-            detachAndScrapAttachedViews(mRecycler);
-            mGrid.resetVisibleIndex();
-            mWindowAlignment.mainAxis().invalidateScrollMin();
-            mWindowAlignment.mainAxis().invalidateScrollMax();
-            return false;
-        }
-    }
-
-    private int getRowSizeSecondary(int rowIndex) {
-        if (mFixedRowSizeSecondary != 0) {
-            return mFixedRowSizeSecondary;
-        }
-        if (mRowSizeSecondary == null) {
-            return 0;
-        }
-        return mRowSizeSecondary[rowIndex];
-    }
-
-    int getRowStartSecondary(int rowIndex) {
-        int start = 0;
-        // Iterate from left to right, which is a different index traversal
-        // in RTL flow
-        if (mReverseFlowSecondary) {
-            for (int i = mNumRows-1; i > rowIndex; i--) {
-                start += getRowSizeSecondary(i) + mSpacingSecondary;
-            }
-        } else {
-            for (int i = 0; i < rowIndex; i++) {
-                start += getRowSizeSecondary(i) + mSpacingSecondary;
-            }
-        }
-        return start;
-    }
-
-    private int getSizeSecondary() {
-        int rightmostIndex = mReverseFlowSecondary ? 0 : mNumRows - 1;
-        return getRowStartSecondary(rightmostIndex) + getRowSizeSecondary(rightmostIndex);
-    }
-
-    int getDecoratedMeasuredWidthWithMargin(View v) {
-        final LayoutParams lp = (LayoutParams) v.getLayoutParams();
-        return getDecoratedMeasuredWidth(v) + lp.leftMargin + lp.rightMargin;
-    }
-
-    int getDecoratedMeasuredHeightWithMargin(View v) {
-        final LayoutParams lp = (LayoutParams) v.getLayoutParams();
-        return getDecoratedMeasuredHeight(v) + lp.topMargin + lp.bottomMargin;
-    }
-
-    private void measureScrapChild(int position, int widthSpec, int heightSpec,
-            int[] measuredDimension) {
-        View view = mRecycler.getViewForPosition(position);
-        if (view != null) {
-            final LayoutParams p = (LayoutParams) view.getLayoutParams();
-            calculateItemDecorationsForChild(view, sTempRect);
-            int widthUsed = p.leftMargin + p.rightMargin + sTempRect.left + sTempRect.right;
-            int heightUsed = p.topMargin + p.bottomMargin + sTempRect.top + sTempRect.bottom;
-
-            int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,
-                    getPaddingLeft() + getPaddingRight() + widthUsed, p.width);
-            int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
-                    getPaddingTop() + getPaddingBottom() + heightUsed, p.height);
-            view.measure(childWidthSpec, childHeightSpec);
-
-            measuredDimension[0] = getDecoratedMeasuredWidthWithMargin(view);
-            measuredDimension[1] = getDecoratedMeasuredHeightWithMargin(view);
-            mRecycler.recycleView(view);
-        }
-    }
-
-    private boolean processRowSizeSecondary(boolean measure) {
-        if (mFixedRowSizeSecondary != 0 || mRowSizeSecondary == null) {
-            return false;
-        }
-
-        if (TRACE) TraceCompat.beginSection("processRowSizeSecondary");
-        CircularIntArray[] rows = mGrid == null ? null : mGrid.getItemPositionsInRows();
-        boolean changed = false;
-        int scrapeChildSize = -1;
-
-        for (int rowIndex = 0; rowIndex < mNumRows; rowIndex++) {
-            CircularIntArray row = rows == null ? null : rows[rowIndex];
-            final int rowItemsPairCount = row == null ? 0 : row.size();
-            int rowSize = -1;
-            for (int rowItemPairIndex = 0; rowItemPairIndex < rowItemsPairCount;
-                    rowItemPairIndex += 2) {
-                final int rowIndexStart = row.get(rowItemPairIndex);
-                final int rowIndexEnd = row.get(rowItemPairIndex + 1);
-                for (int i = rowIndexStart; i <= rowIndexEnd; i++) {
-                    final View view = findViewByPosition(i - mPositionDeltaInPreLayout);
-                    if (view == null) {
-                        continue;
-                    }
-                    if (measure) {
-                        measureChild(view);
-                    }
-                    final int secondarySize = mOrientation == HORIZONTAL
-                            ? getDecoratedMeasuredHeightWithMargin(view)
-                            : getDecoratedMeasuredWidthWithMargin(view);
-                    if (secondarySize > rowSize) {
-                        rowSize = secondarySize;
-                    }
-                }
-            }
-
-            final int itemCount = mState.getItemCount();
-            if (!mBaseGridView.hasFixedSize() && measure && rowSize < 0 && itemCount > 0) {
-                if (scrapeChildSize < 0) {
-                    // measure a child that is close to mFocusPosition but not currently visible
-                    int position = mFocusPosition;
-                    if (position < 0) {
-                        position = 0;
-                    } else if (position >= itemCount) {
-                        position = itemCount - 1;
-                    }
-                    if (getChildCount() > 0) {
-                        int firstPos = mBaseGridView.getChildViewHolder(
-                                getChildAt(0)).getLayoutPosition();
-                        int lastPos = mBaseGridView.getChildViewHolder(
-                                getChildAt(getChildCount() - 1)).getLayoutPosition();
-                        // if mFocusPosition is between first and last, choose either
-                        // first - 1 or last + 1
-                        if (position >= firstPos && position <= lastPos) {
-                            position = (position - firstPos <= lastPos - position)
-                                    ? (firstPos - 1) : (lastPos + 1);
-                            // try the other value if the position is invalid. if both values are
-                            // invalid, skip measureScrapChild below.
-                            if (position < 0 && lastPos < itemCount - 1) {
-                                position = lastPos + 1;
-                            } else if (position >= itemCount && firstPos > 0) {
-                                position = firstPos - 1;
-                            }
-                        }
-                    }
-                    if (position >= 0 && position < itemCount) {
-                        measureScrapChild(position,
-                                MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
-                                MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
-                                mMeasuredDimension);
-                        scrapeChildSize = mOrientation == HORIZONTAL ? mMeasuredDimension[1] :
-                                mMeasuredDimension[0];
-                        if (DEBUG) {
-                            Log.v(TAG, "measured scrap child: " + mMeasuredDimension[0] + " "
-                                    + mMeasuredDimension[1]);
-                        }
-                    }
-                }
-                if (scrapeChildSize >= 0) {
-                    rowSize = scrapeChildSize;
-                }
-            }
-            if (rowSize < 0) {
-                rowSize = 0;
-            }
-            if (mRowSizeSecondary[rowIndex] != rowSize) {
-                if (DEBUG) {
-                    Log.v(getTag(), "row size secondary changed: " + mRowSizeSecondary[rowIndex]
-                            + ", " + rowSize);
-                }
-                mRowSizeSecondary[rowIndex] = rowSize;
-                changed = true;
-            }
-        }
-
-        if (TRACE) TraceCompat.endSection();
-        return changed;
-    }
-
-    /**
-     * Checks if we need to update row secondary sizes.
-     */
-    private void updateRowSecondarySizeRefresh() {
-        mRowSecondarySizeRefresh = processRowSizeSecondary(false);
-        if (mRowSecondarySizeRefresh) {
-            if (DEBUG) Log.v(getTag(), "mRowSecondarySizeRefresh now set");
-            forceRequestLayout();
-        }
-    }
-
-    private void forceRequestLayout() {
-        if (DEBUG) Log.v(getTag(), "forceRequestLayout");
-        // RecyclerView prevents us from requesting layout in many cases
-        // (during layout, during scroll, etc.)
-        // For secondary row size wrap_content support we currently need a
-        // second layout pass to update the measured size after having measured
-        // and added child views in layoutChildren.
-        // Force the second layout by posting a delayed runnable.
-        // TODO: investigate allowing a second layout pass,
-        // or move child add/measure logic to the measure phase.
-        ViewCompat.postOnAnimation(mBaseGridView, mRequestLayoutRunnable);
-    }
-
-    private final Runnable mRequestLayoutRunnable = new Runnable() {
-        @Override
-        public void run() {
-            if (DEBUG) Log.v(getTag(), "request Layout from runnable");
-            requestLayout();
-        }
-    };
-
-    @Override
-    public void onMeasure(Recycler recycler, State state, int widthSpec, int heightSpec) {
-        saveContext(recycler, state);
-
-        int sizePrimary, sizeSecondary, modeSecondary, paddingSecondary;
-        int measuredSizeSecondary;
-        if (mOrientation == HORIZONTAL) {
-            sizePrimary = MeasureSpec.getSize(widthSpec);
-            sizeSecondary = MeasureSpec.getSize(heightSpec);
-            modeSecondary = MeasureSpec.getMode(heightSpec);
-            paddingSecondary = getPaddingTop() + getPaddingBottom();
-        } else {
-            sizeSecondary = MeasureSpec.getSize(widthSpec);
-            sizePrimary = MeasureSpec.getSize(heightSpec);
-            modeSecondary = MeasureSpec.getMode(widthSpec);
-            paddingSecondary = getPaddingLeft() + getPaddingRight();
-        }
-        if (DEBUG) {
-            Log.v(getTag(), "onMeasure widthSpec " + Integer.toHexString(widthSpec)
-                    + " heightSpec " + Integer.toHexString(heightSpec)
-                    + " modeSecondary " + Integer.toHexString(modeSecondary)
-                    + " sizeSecondary " + sizeSecondary + " " + this);
-        }
-
-        mMaxSizeSecondary = sizeSecondary;
-
-        if (mRowSizeSecondaryRequested == ViewGroup.LayoutParams.WRAP_CONTENT) {
-            mNumRows = mNumRowsRequested == 0 ? 1 : mNumRowsRequested;
-            mFixedRowSizeSecondary = 0;
-
-            if (mRowSizeSecondary == null || mRowSizeSecondary.length != mNumRows) {
-                mRowSizeSecondary = new int[mNumRows];
-            }
-
-            if (mState.isPreLayout()) {
-                updatePositionDeltaInPreLayout();
-            }
-            // Measure all current children and update cached row height or column width
-            processRowSizeSecondary(true);
-
-            switch (modeSecondary) {
-                case MeasureSpec.UNSPECIFIED:
-                    measuredSizeSecondary = getSizeSecondary() + paddingSecondary;
-                    break;
-                case MeasureSpec.AT_MOST:
-                    measuredSizeSecondary = Math.min(getSizeSecondary() + paddingSecondary,
-                            mMaxSizeSecondary);
-                    break;
-                case MeasureSpec.EXACTLY:
-                    measuredSizeSecondary = mMaxSizeSecondary;
-                    break;
-                default:
-                    throw new IllegalStateException("wrong spec");
-            }
-
-        } else {
-            switch (modeSecondary) {
-                case MeasureSpec.UNSPECIFIED:
-                    mFixedRowSizeSecondary = mRowSizeSecondaryRequested == 0
-                            ? sizeSecondary - paddingSecondary : mRowSizeSecondaryRequested;
-                    mNumRows = mNumRowsRequested == 0 ? 1 : mNumRowsRequested;
-                    measuredSizeSecondary = mFixedRowSizeSecondary * mNumRows + mSpacingSecondary
-                            * (mNumRows - 1) + paddingSecondary;
-                    break;
-                case MeasureSpec.AT_MOST:
-                case MeasureSpec.EXACTLY:
-                    if (mNumRowsRequested == 0 && mRowSizeSecondaryRequested == 0) {
-                        mNumRows = 1;
-                        mFixedRowSizeSecondary = sizeSecondary - paddingSecondary;
-                    } else if (mNumRowsRequested == 0) {
-                        mFixedRowSizeSecondary = mRowSizeSecondaryRequested;
-                        mNumRows = (sizeSecondary + mSpacingSecondary)
-                                / (mRowSizeSecondaryRequested + mSpacingSecondary);
-                    } else if (mRowSizeSecondaryRequested == 0) {
-                        mNumRows = mNumRowsRequested;
-                        mFixedRowSizeSecondary = (sizeSecondary - paddingSecondary
-                                - mSpacingSecondary * (mNumRows - 1)) / mNumRows;
-                    } else {
-                        mNumRows = mNumRowsRequested;
-                        mFixedRowSizeSecondary = mRowSizeSecondaryRequested;
-                    }
-                    measuredSizeSecondary = sizeSecondary;
-                    if (modeSecondary == MeasureSpec.AT_MOST) {
-                        int childrenSize = mFixedRowSizeSecondary * mNumRows + mSpacingSecondary
-                                * (mNumRows - 1) + paddingSecondary;
-                        if (childrenSize < measuredSizeSecondary) {
-                            measuredSizeSecondary = childrenSize;
-                        }
-                    }
-                    break;
-                default:
-                    throw new IllegalStateException("wrong spec");
-            }
-        }
-        if (mOrientation == HORIZONTAL) {
-            setMeasuredDimension(sizePrimary, measuredSizeSecondary);
-        } else {
-            setMeasuredDimension(measuredSizeSecondary, sizePrimary);
-        }
-        if (DEBUG) {
-            Log.v(getTag(), "onMeasure sizePrimary " + sizePrimary
-                    + " measuredSizeSecondary " + measuredSizeSecondary
-                    + " mFixedRowSizeSecondary " + mFixedRowSizeSecondary
-                    + " mNumRows " + mNumRows);
-        }
-        leaveContext();
-    }
-
-    void measureChild(View child) {
-        if (TRACE) TraceCompat.beginSection("measureChild");
-        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-        calculateItemDecorationsForChild(child, sTempRect);
-        int widthUsed = lp.leftMargin + lp.rightMargin + sTempRect.left + sTempRect.right;
-        int heightUsed = lp.topMargin + lp.bottomMargin + sTempRect.top + sTempRect.bottom;
-
-        final int secondarySpec =
-                (mRowSizeSecondaryRequested == ViewGroup.LayoutParams.WRAP_CONTENT)
-                        ? MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)
-                        : MeasureSpec.makeMeasureSpec(mFixedRowSizeSecondary, MeasureSpec.EXACTLY);
-        int widthSpec, heightSpec;
-
-        if (mOrientation == HORIZONTAL) {
-            widthSpec = ViewGroup.getChildMeasureSpec(
-                    MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), widthUsed, lp.width);
-            heightSpec = ViewGroup.getChildMeasureSpec(secondarySpec, heightUsed, lp.height);
-        } else {
-            heightSpec = ViewGroup.getChildMeasureSpec(
-                    MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), heightUsed, lp.height);
-            widthSpec = ViewGroup.getChildMeasureSpec(secondarySpec, widthUsed, lp.width);
-        }
-        child.measure(widthSpec, heightSpec);
-        if (DEBUG) {
-            Log.v(getTag(), "measureChild secondarySpec " + Integer.toHexString(secondarySpec)
-                    + " widthSpec " + Integer.toHexString(widthSpec)
-                    + " heightSpec " + Integer.toHexString(heightSpec)
-                    + " measuredWidth " + child.getMeasuredWidth()
-                    + " measuredHeight " + child.getMeasuredHeight());
-        }
-        if (DEBUG) Log.v(getTag(), "child lp width " + lp.width + " height " + lp.height);
-        if (TRACE) TraceCompat.endSection();
-    }
-
-    /**
-     * Get facet from the ViewHolder or the viewType.
-     */
-    <E> E getFacet(RecyclerView.ViewHolder vh, Class<? extends E> facetClass) {
-        E facet = null;
-        if (vh instanceof FacetProvider) {
-            facet = (E) ((FacetProvider) vh).getFacet(facetClass);
-        }
-        if (facet == null && mFacetProviderAdapter != null) {
-            FacetProvider p = mFacetProviderAdapter.getFacetProvider(vh.getItemViewType());
-            if (p != null) {
-                facet = (E) p.getFacet(facetClass);
-            }
-        }
-        return facet;
-    }
-
-    private Grid.Provider mGridProvider = new Grid.Provider() {
-
-        @Override
-        public int getMinIndex() {
-            return mPositionDeltaInPreLayout;
-        }
-
-        @Override
-        public int getCount() {
-            return mState.getItemCount() + mPositionDeltaInPreLayout;
-        }
-
-        @Override
-        public int createItem(int index, boolean append, Object[] item, boolean disappearingItem) {
-            if (TRACE) TraceCompat.beginSection("createItem");
-            if (TRACE) TraceCompat.beginSection("getview");
-            View v = getViewForPosition(index - mPositionDeltaInPreLayout);
-            if (TRACE) TraceCompat.endSection();
-            LayoutParams lp = (LayoutParams) v.getLayoutParams();
-            RecyclerView.ViewHolder vh = mBaseGridView.getChildViewHolder(v);
-            lp.setItemAlignmentFacet((ItemAlignmentFacet)getFacet(vh, ItemAlignmentFacet.class));
-            // See recyclerView docs:  we don't need re-add scraped view if it was removed.
-            if (!lp.isItemRemoved()) {
-                if (TRACE) TraceCompat.beginSection("addView");
-                if (disappearingItem) {
-                    if (append) {
-                        addDisappearingView(v);
-                    } else {
-                        addDisappearingView(v, 0);
-                    }
-                } else {
-                    if (append) {
-                        addView(v);
-                    } else {
-                        addView(v, 0);
-                    }
-                }
-                if (TRACE) TraceCompat.endSection();
-                if (mChildVisibility != -1) {
-                    v.setVisibility(mChildVisibility);
-                }
-
-                if (mPendingMoveSmoothScroller != null) {
-                    mPendingMoveSmoothScroller.consumePendingMovesBeforeLayout();
-                }
-                int subindex = getSubPositionByView(v, v.findFocus());
-                if (!mInLayout) {
-                    // when we are appending item during scroll pass and the item's position
-                    // matches the mFocusPosition,  we should signal a childSelected event.
-                    // However if we are still running PendingMoveSmoothScroller,  we defer and
-                    // signal the event in PendingMoveSmoothScroller.onStop().  This can
-                    // avoid lots of childSelected events during a long smooth scrolling and
-                    // increase performance.
-                    if (index == mFocusPosition && subindex == mSubFocusPosition
-                            && mPendingMoveSmoothScroller == null) {
-                        dispatchChildSelected();
-                    }
-                } else if (!mInFastRelayout) {
-                    // fastRelayout will dispatch event at end of onLayoutChildren().
-                    // For full layout, two situations here:
-                    // 1. mInLayoutSearchFocus is false, dispatchChildSelected() at mFocusPosition.
-                    // 2. mInLayoutSearchFocus is true:  dispatchChildSelected() on first child
-                    //    equal to or after mFocusPosition that can take focus.
-                    if (!mInLayoutSearchFocus && index == mFocusPosition
-                            && subindex == mSubFocusPosition) {
-                        dispatchChildSelected();
-                    } else if (mInLayoutSearchFocus && index >= mFocusPosition
-                            && v.hasFocusable()) {
-                        mFocusPosition = index;
-                        mSubFocusPosition = subindex;
-                        mInLayoutSearchFocus = false;
-                        dispatchChildSelected();
-                    }
-                }
-                measureChild(v);
-            }
-            item[0] = v;
-            return mOrientation == HORIZONTAL ? getDecoratedMeasuredWidthWithMargin(v)
-                    : getDecoratedMeasuredHeightWithMargin(v);
-        }
-
-        @Override
-        public void addItem(Object item, int index, int length, int rowIndex, int edge) {
-            View v = (View) item;
-            int start, end;
-            if (edge == Integer.MIN_VALUE || edge == Integer.MAX_VALUE) {
-                edge = !mGrid.isReversedFlow() ? mWindowAlignment.mainAxis().getPaddingMin()
-                        : mWindowAlignment.mainAxis().getSize()
-                                - mWindowAlignment.mainAxis().getPaddingMax();
-            }
-            boolean edgeIsMin = !mGrid.isReversedFlow();
-            if (edgeIsMin) {
-                start = edge;
-                end = edge + length;
-            } else {
-                start = edge - length;
-                end = edge;
-            }
-            int startSecondary = getRowStartSecondary(rowIndex)
-                    + mWindowAlignment.secondAxis().getPaddingMin() - mScrollOffsetSecondary;
-            mChildrenStates.loadView(v, index);
-            layoutChild(rowIndex, v, start, end, startSecondary);
-            if (DEBUG) {
-                Log.d(getTag(), "addView " + index + " " + v);
-            }
-            if (TRACE) TraceCompat.endSection();
-
-            if (!mState.isPreLayout()) {
-                updateScrollLimits();
-            }
-            if (!mInLayout && mPendingMoveSmoothScroller != null) {
-                mPendingMoveSmoothScroller.consumePendingMovesAfterLayout();
-            }
-            if (mChildLaidOutListener != null) {
-                RecyclerView.ViewHolder vh = mBaseGridView.getChildViewHolder(v);
-                mChildLaidOutListener.onChildLaidOut(mBaseGridView, v, index,
-                        vh == null ? NO_ID : vh.getItemId());
-            }
-        }
-
-        @Override
-        public void removeItem(int index) {
-            if (TRACE) TraceCompat.beginSection("removeItem");
-            View v = findViewByPosition(index - mPositionDeltaInPreLayout);
-            if (mInLayout) {
-                detachAndScrapView(v, mRecycler);
-            } else {
-                removeAndRecycleView(v, mRecycler);
-            }
-            if (TRACE) TraceCompat.endSection();
-        }
-
-        @Override
-        public int getEdge(int index) {
-            View v = findViewByPosition(index - mPositionDeltaInPreLayout);
-            return mReverseFlowPrimary ? getViewMax(v) : getViewMin(v);
-        }
-
-        @Override
-        public int getSize(int index) {
-            return getViewPrimarySize(findViewByPosition(index - mPositionDeltaInPreLayout));
-        }
-    };
-
-    void layoutChild(int rowIndex, View v, int start, int end, int startSecondary) {
-        if (TRACE) TraceCompat.beginSection("layoutChild");
-        int sizeSecondary = mOrientation == HORIZONTAL ? getDecoratedMeasuredHeightWithMargin(v)
-                : getDecoratedMeasuredWidthWithMargin(v);
-        if (mFixedRowSizeSecondary > 0) {
-            sizeSecondary = Math.min(sizeSecondary, mFixedRowSizeSecondary);
-        }
-        final int verticalGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
-        final int horizontalGravity = (mReverseFlowPrimary || mReverseFlowSecondary)
-                ? Gravity.getAbsoluteGravity(mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK,
-                View.LAYOUT_DIRECTION_RTL)
-                : mGravity & Gravity.HORIZONTAL_GRAVITY_MASK;
-        if ((mOrientation == HORIZONTAL && verticalGravity == Gravity.TOP)
-                || (mOrientation == VERTICAL && horizontalGravity == Gravity.LEFT)) {
-            // do nothing
-        } else if ((mOrientation == HORIZONTAL && verticalGravity == Gravity.BOTTOM)
-                || (mOrientation == VERTICAL && horizontalGravity == Gravity.RIGHT)) {
-            startSecondary += getRowSizeSecondary(rowIndex) - sizeSecondary;
-        } else if ((mOrientation == HORIZONTAL && verticalGravity == Gravity.CENTER_VERTICAL)
-                || (mOrientation == VERTICAL && horizontalGravity == Gravity.CENTER_HORIZONTAL)) {
-            startSecondary += (getRowSizeSecondary(rowIndex) - sizeSecondary) / 2;
-        }
-        int left, top, right, bottom;
-        if (mOrientation == HORIZONTAL) {
-            left = start;
-            top = startSecondary;
-            right = end;
-            bottom = startSecondary + sizeSecondary;
-        } else {
-            top = start;
-            left = startSecondary;
-            bottom = end;
-            right = startSecondary + sizeSecondary;
-        }
-        LayoutParams params = (LayoutParams) v.getLayoutParams();
-        layoutDecoratedWithMargins(v, left, top, right, bottom);
-        // Now super.getDecoratedBoundsWithMargins() includes the extra space for optical bounds,
-        // subtracting it from value passed in layoutDecoratedWithMargins(), we can get the optical
-        // bounds insets.
-        super.getDecoratedBoundsWithMargins(v, sTempRect);
-        params.setOpticalInsets(left - sTempRect.left, top - sTempRect.top,
-                sTempRect.right - right, sTempRect.bottom - bottom);
-        updateChildAlignments(v);
-        if (TRACE) TraceCompat.endSection();
-    }
-
-    private void updateChildAlignments(View v) {
-        final LayoutParams p = (LayoutParams) v.getLayoutParams();
-        if (p.getItemAlignmentFacet() == null) {
-            // Fallback to global settings on grid view
-            p.setAlignX(mItemAlignment.horizontal.getAlignmentPosition(v));
-            p.setAlignY(mItemAlignment.vertical.getAlignmentPosition(v));
-        } else {
-            // Use ItemAlignmentFacet defined on specific ViewHolder
-            p.calculateItemAlignments(mOrientation, v);
-            if (mOrientation == HORIZONTAL) {
-                p.setAlignY(mItemAlignment.vertical.getAlignmentPosition(v));
-            } else {
-                p.setAlignX(mItemAlignment.horizontal.getAlignmentPosition(v));
-            }
-        }
-    }
-
-    private void updateChildAlignments() {
-        for (int i = 0, c = getChildCount(); i < c; i++) {
-            updateChildAlignments(getChildAt(i));
-        }
-    }
-
-    void setExtraLayoutSpace(int extraLayoutSpace) {
-        if (mExtraLayoutSpace == extraLayoutSpace) {
-            return;
-        } else if (mExtraLayoutSpace < 0) {
-            throw new IllegalArgumentException("ExtraLayoutSpace must >= 0");
-        }
-        mExtraLayoutSpace = extraLayoutSpace;
-        requestLayout();
-    }
-
-    int getExtraLayoutSpace() {
-        return mExtraLayoutSpace;
-    }
-
-    private void removeInvisibleViewsAtEnd() {
-        if (mPruneChild && !mIsSlidingChildViews) {
-            mGrid.removeInvisibleItemsAtEnd(mFocusPosition,
-                    mReverseFlowPrimary ? -mExtraLayoutSpace : mSizePrimary + mExtraLayoutSpace);
-        }
-    }
-
-    private void removeInvisibleViewsAtFront() {
-        if (mPruneChild && !mIsSlidingChildViews) {
-            mGrid.removeInvisibleItemsAtFront(mFocusPosition,
-                    mReverseFlowPrimary ? mSizePrimary + mExtraLayoutSpace: -mExtraLayoutSpace);
-        }
-    }
-
-    private boolean appendOneColumnVisibleItems() {
-        return mGrid.appendOneColumnVisibleItems();
-    }
-
-    void slideIn() {
-        if (mIsSlidingChildViews) {
-            mIsSlidingChildViews = false;
-            if (mFocusPosition >= 0) {
-                scrollToSelection(mFocusPosition, mSubFocusPosition, true, mPrimaryScrollExtra);
-            } else {
-                mLayoutEatenInSliding = false;
-                requestLayout();
-            }
-            if (mLayoutEatenInSliding) {
-                mLayoutEatenInSliding = false;
-                if (mBaseGridView.getScrollState() != SCROLL_STATE_IDLE || isSmoothScrolling()) {
-                    mBaseGridView.addOnScrollListener(new RecyclerView.OnScrollListener() {
-                        @Override
-                        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
-                            if (newState == SCROLL_STATE_IDLE) {
-                                mBaseGridView.removeOnScrollListener(this);
-                                requestLayout();
-                            }
-                        }
-                    });
-                } else {
-                    requestLayout();
-                }
-            }
-        }
-    }
-
-    int getSlideOutDistance() {
-        int distance;
-        if (mOrientation == VERTICAL) {
-            distance = -getHeight();
-            if (getChildCount() > 0) {
-                int top = getChildAt(0).getTop();
-                if (top < 0) {
-                    // scroll more if first child is above top edge
-                    distance = distance + top;
-                }
-            }
-        } else {
-            if (mReverseFlowPrimary) {
-                distance = getWidth();
-                if (getChildCount() > 0) {
-                    int start = getChildAt(0).getRight();
-                    if (start > distance) {
-                        // scroll more if first child is outside right edge
-                        distance = start;
-                    }
-                }
-            } else {
-                distance = -getWidth();
-                if (getChildCount() > 0) {
-                    int start = getChildAt(0).getLeft();
-                    if (start < 0) {
-                        // scroll more if first child is out side left edge
-                        distance = distance + start;
-                    }
-                }
-            }
-        }
-        return distance;
-    }
-
-    /**
-     * Temporarily slide out child and block layout and scroll requests.
-     */
-    void slideOut() {
-        if (mIsSlidingChildViews) {
-            return;
-        }
-        mIsSlidingChildViews = true;
-        if (getChildCount() == 0) {
-            return;
-        }
-        if (mOrientation == VERTICAL) {
-            mBaseGridView.smoothScrollBy(0, getSlideOutDistance(),
-                    new AccelerateDecelerateInterpolator());
-        } else {
-            mBaseGridView.smoothScrollBy(getSlideOutDistance(), 0,
-                    new AccelerateDecelerateInterpolator());
-        }
-    }
-
-    private boolean prependOneColumnVisibleItems() {
-        return mGrid.prependOneColumnVisibleItems();
-    }
-
-    private void appendVisibleItems() {
-        mGrid.appendVisibleItems(mReverseFlowPrimary
-                ? -mExtraLayoutSpace - mExtraLayoutSpaceInPreLayout
-                : mSizePrimary + mExtraLayoutSpace + mExtraLayoutSpaceInPreLayout);
-    }
-
-    private void prependVisibleItems() {
-        mGrid.prependVisibleItems(mReverseFlowPrimary
-                ? mSizePrimary + mExtraLayoutSpace + mExtraLayoutSpaceInPreLayout
-                : -mExtraLayoutSpace - mExtraLayoutSpaceInPreLayout);
-    }
-
-    /**
-     * Fast layout when there is no structure change, adapter change, etc.
-     * It will layout all views was layout requested or updated, until hit a view
-     * with different size,  then it break and detachAndScrap all views after that.
-     */
-    private void fastRelayout() {
-        boolean invalidateAfter = false;
-        final int childCount = getChildCount();
-        int position = mGrid.getFirstVisibleIndex();
-        int index = 0;
-        for (; index < childCount; index++, position++) {
-            View view = getChildAt(index);
-            // We don't hit fastRelayout() if State.didStructure() is true, but prelayout may add
-            // extra views and invalidate existing Grid position. Also the prelayout calling
-            // getViewForPosotion() may retrieve item from cache with FLAG_INVALID. The adapter
-            // postion will be -1 for this case. Either case, we should invalidate after this item
-            // and call getViewForPosition() again to rebind.
-            if (position != getAdapterPositionByView(view)) {
-                invalidateAfter = true;
-                break;
-            }
-            Grid.Location location = mGrid.getLocation(position);
-            if (location == null) {
-                invalidateAfter = true;
-                break;
-            }
-
-            int startSecondary = getRowStartSecondary(location.row)
-                    + mWindowAlignment.secondAxis().getPaddingMin() - mScrollOffsetSecondary;
-            int primarySize, end;
-            int start = getViewMin(view);
-            int oldPrimarySize = getViewPrimarySize(view);
-
-            LayoutParams lp = (LayoutParams) view.getLayoutParams();
-            if (lp.viewNeedsUpdate()) {
-                detachAndScrapView(view, mRecycler);
-                view = getViewForPosition(position);
-                addView(view, index);
-            }
-
-            measureChild(view);
-            if (mOrientation == HORIZONTAL) {
-                primarySize = getDecoratedMeasuredWidthWithMargin(view);
-                end = start + primarySize;
-            } else {
-                primarySize = getDecoratedMeasuredHeightWithMargin(view);
-                end = start + primarySize;
-            }
-            layoutChild(location.row, view, start, end, startSecondary);
-            if (oldPrimarySize != primarySize) {
-                // size changed invalidate remaining Locations
-                if (DEBUG) Log.d(getTag(), "fastRelayout: view size changed at " + position);
-                invalidateAfter = true;
-                break;
-            }
-        }
-        if (invalidateAfter) {
-            final int savedLastPos = mGrid.getLastVisibleIndex();
-            for (int i = childCount - 1; i >= index; i--) {
-                View v = getChildAt(i);
-                detachAndScrapView(v, mRecycler);
-            }
-            mGrid.invalidateItemsAfter(position);
-            if (mPruneChild) {
-                // in regular prune child mode, we just append items up to edge limit
-                appendVisibleItems();
-                if (mFocusPosition >= 0 && mFocusPosition <= savedLastPos) {
-                    // make sure add focus view back:  the view might be outside edge limit
-                    // when there is delta in onLayoutChildren().
-                    while (mGrid.getLastVisibleIndex() < mFocusPosition) {
-                        mGrid.appendOneColumnVisibleItems();
-                    }
-                }
-            } else {
-                // prune disabled(e.g. in RowsFragment transition): append all removed items
-                while (mGrid.appendOneColumnVisibleItems()
-                        && mGrid.getLastVisibleIndex() < savedLastPos);
-            }
-        }
-        updateScrollLimits();
-        updateSecondaryScrollLimits();
-    }
-
-    @Override
-    public void removeAndRecycleAllViews(RecyclerView.Recycler recycler) {
-        if (TRACE) TraceCompat.beginSection("removeAndRecycleAllViews");
-        if (DEBUG) Log.v(TAG, "removeAndRecycleAllViews " + getChildCount());
-        for (int i = getChildCount() - 1; i >= 0; i--) {
-            removeAndRecycleViewAt(i, recycler);
-        }
-        if (TRACE) TraceCompat.endSection();
-    }
-
-    // called by onLayoutChildren, either focus to FocusPosition or declare focusViewAvailable
-    // and scroll to the view if framework focus on it.
-    private void focusToViewInLayout(boolean hadFocus, boolean alignToView, int extraDelta,
-            int extraDeltaSecondary) {
-        View focusView = findViewByPosition(mFocusPosition);
-        if (focusView != null && alignToView) {
-            scrollToView(focusView, false, extraDelta, extraDeltaSecondary);
-        }
-        if (focusView != null && hadFocus && !focusView.hasFocus()) {
-            focusView.requestFocus();
-        } else if (!hadFocus && !mBaseGridView.hasFocus()) {
-            if (focusView != null && focusView.hasFocusable()) {
-                mBaseGridView.focusableViewAvailable(focusView);
-            } else {
-                for (int i = 0, count = getChildCount(); i < count; i++) {
-                    focusView = getChildAt(i);
-                    if (focusView != null && focusView.hasFocusable()) {
-                        mBaseGridView.focusableViewAvailable(focusView);
-                        break;
-                    }
-                }
-            }
-            // focusViewAvailable() might focus to the view, scroll to it if that is the case.
-            if (alignToView && focusView != null && focusView.hasFocus()) {
-                scrollToView(focusView, false, extraDelta, extraDeltaSecondary);
-            }
-        }
-    }
-
-    @VisibleForTesting
-    public static class OnLayoutCompleteListener {
-        public void onLayoutCompleted(RecyclerView.State state) {
-        }
-    }
-
-    @VisibleForTesting
-    OnLayoutCompleteListener mLayoutCompleteListener;
-
-    @Override
-    public void onLayoutCompleted(State state) {
-        if (mLayoutCompleteListener != null) {
-            mLayoutCompleteListener.onLayoutCompleted(state);
-        }
-    }
-
-    @Override
-    public boolean supportsPredictiveItemAnimations() {
-        return true;
-    }
-
-    void updatePositionToRowMapInPostLayout() {
-        mPositionToRowInPostLayout.clear();
-        final int childCount = getChildCount();
-        for (int i = 0;  i < childCount; i++) {
-            // Grid still maps to old positions at this point, use old position to get row infor
-            int position = mBaseGridView.getChildViewHolder(getChildAt(i)).getOldPosition();
-            if (position >= 0) {
-                Grid.Location loc = mGrid.getLocation(position);
-                if (loc != null) {
-                    mPositionToRowInPostLayout.put(position, loc.row);
-                }
-            }
-        }
-    }
-
-    void fillScrapViewsInPostLayout() {
-        List<RecyclerView.ViewHolder> scrapList = mRecycler.getScrapList();
-        final int scrapSize = scrapList.size();
-        if (scrapSize == 0) {
-            return;
-        }
-        // initialize the int array or re-allocate the array.
-        if (mDisappearingPositions == null  || scrapSize > mDisappearingPositions.length) {
-            int length = mDisappearingPositions == null ? 16 : mDisappearingPositions.length;
-            while (length < scrapSize) {
-                length = length << 1;
-            }
-            mDisappearingPositions = new int[length];
-        }
-        int totalItems = 0;
-        for (int i = 0; i < scrapSize; i++) {
-            int pos = scrapList.get(i).getAdapterPosition();
-            if (pos >= 0) {
-                mDisappearingPositions[totalItems++] = pos;
-            }
-        }
-        // totalItems now has the length of disappearing items
-        if (totalItems > 0) {
-            Arrays.sort(mDisappearingPositions, 0, totalItems);
-            mGrid.fillDisappearingItems(mDisappearingPositions, totalItems,
-                    mPositionToRowInPostLayout);
-        }
-        mPositionToRowInPostLayout.clear();
-    }
-
-    // in prelayout, first child's getViewPosition can be smaller than old adapter position
-    // if there were items removed before first visible index. For example:
-    // visible items are 3, 4, 5, 6, deleting 1, 2, 3 from adapter; the view position in
-    // prelayout are not 3(deleted), 4, 5, 6. Instead it's 1(deleted), 2, 3, 4.
-    // So there is a delta (2 in this case) between last cached position and prelayout position.
-    void updatePositionDeltaInPreLayout() {
-        if (getChildCount() > 0) {
-            View view = getChildAt(0);
-            LayoutParams lp = (LayoutParams) view.getLayoutParams();
-            mPositionDeltaInPreLayout = mGrid.getFirstVisibleIndex()
-                    - lp.getViewLayoutPosition();
-        } else {
-            mPositionDeltaInPreLayout = 0;
-        }
-    }
-
-    // Lays out items based on the current scroll position
-    @Override
-    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
-        if (DEBUG) {
-            Log.v(getTag(), "layoutChildren start numRows " + mNumRows
-                    + " inPreLayout " + state.isPreLayout()
-                    + " didStructureChange " + state.didStructureChange()
-                    + " mForceFullLayout " + mForceFullLayout);
-            Log.v(getTag(), "width " + getWidth() + " height " + getHeight());
-        }
-
-        if (mNumRows == 0) {
-            // haven't done measure yet
-            return;
-        }
-        final int itemCount = state.getItemCount();
-        if (itemCount < 0) {
-            return;
-        }
-
-        if (mIsSlidingChildViews) {
-            // if there is already children, delay the layout process until slideIn(), if it's
-            // first time layout children: scroll them offscreen at end of onLayoutChildren()
-            if (getChildCount() > 0) {
-                mLayoutEatenInSliding = true;
-                return;
-            }
-        }
-        if (!mLayoutEnabled) {
-            discardLayoutInfo();
-            removeAndRecycleAllViews(recycler);
-            return;
-        }
-        mInLayout = true;
-
-        saveContext(recycler, state);
-        if (state.isPreLayout()) {
-            updatePositionDeltaInPreLayout();
-            int childCount = getChildCount();
-            if (mGrid != null && childCount > 0) {
-                int minChangedEdge = Integer.MAX_VALUE;
-                int maxChangeEdge = Integer.MIN_VALUE;
-                int minOldAdapterPosition = mBaseGridView.getChildViewHolder(
-                        getChildAt(0)).getOldPosition();
-                int maxOldAdapterPosition = mBaseGridView.getChildViewHolder(
-                        getChildAt(childCount - 1)).getOldPosition();
-                for (int i = 0; i < childCount; i++) {
-                    View view = getChildAt(i);
-                    LayoutParams lp = (LayoutParams) view.getLayoutParams();
-                    int newAdapterPosition = mBaseGridView.getChildAdapterPosition(view);
-                    // if either of following happening
-                    // 1. item itself has changed or layout parameter changed
-                    // 2. item is losing focus
-                    // 3. item is gaining focus
-                    // 4. item is moved out of old adapter position range.
-                    if (lp.isItemChanged() || lp.isItemRemoved() || view.isLayoutRequested()
-                            || (!view.hasFocus() && mFocusPosition == lp.getViewAdapterPosition())
-                            || (view.hasFocus() && mFocusPosition != lp.getViewAdapterPosition())
-                            || newAdapterPosition < minOldAdapterPosition
-                            || newAdapterPosition > maxOldAdapterPosition) {
-                        minChangedEdge = Math.min(minChangedEdge, getViewMin(view));
-                        maxChangeEdge = Math.max(maxChangeEdge, getViewMax(view));
-                    }
-                }
-                if (maxChangeEdge > minChangedEdge) {
-                    mExtraLayoutSpaceInPreLayout = maxChangeEdge - minChangedEdge;
-                }
-                // append items for mExtraLayoutSpaceInPreLayout
-                appendVisibleItems();
-                prependVisibleItems();
-            }
-            mInLayout = false;
-            leaveContext();
-            if (DEBUG) Log.v(getTag(), "layoutChildren end");
-            return;
-        }
-
-        // save all view's row information before detach all views
-        if (state.willRunPredictiveAnimations()) {
-            updatePositionToRowMapInPostLayout();
-        }
-        // check if we need align to mFocusPosition, this is usually true unless in smoothScrolling
-        final boolean scrollToFocus = !isSmoothScrolling()
-                && mFocusScrollStrategy == BaseGridView.FOCUS_SCROLL_ALIGNED;
-        if (mFocusPosition != NO_POSITION && mFocusPositionOffset != Integer.MIN_VALUE) {
-            mFocusPosition = mFocusPosition + mFocusPositionOffset;
-            mSubFocusPosition = 0;
-        }
-        mFocusPositionOffset = 0;
-
-        View savedFocusView = findViewByPosition(mFocusPosition);
-        int savedFocusPos = mFocusPosition;
-        int savedSubFocusPos = mSubFocusPosition;
-        boolean hadFocus = mBaseGridView.hasFocus();
-        final int firstVisibleIndex = mGrid != null ? mGrid.getFirstVisibleIndex() : NO_POSITION;
-        final int lastVisibleIndex = mGrid != null ? mGrid.getLastVisibleIndex() : NO_POSITION;
-        final int deltaPrimary;
-        final int deltaSecondary;
-        if (mOrientation == HORIZONTAL) {
-            deltaPrimary = state.getRemainingScrollHorizontal();
-            deltaSecondary = state.getRemainingScrollVertical();
-        } else {
-            deltaSecondary = state.getRemainingScrollHorizontal();
-            deltaPrimary = state.getRemainingScrollVertical();
-        }
-        if (mInFastRelayout = layoutInit()) {
-            // If grid view is empty, we will start from mFocusPosition
-            mGrid.setStart(mFocusPosition);
-            fastRelayout();
-        } else {
-            // layoutInit() has detached all views, so start from scratch
-            mInLayoutSearchFocus = hadFocus;
-            int startFromPosition, endPos;
-            if (scrollToFocus && (firstVisibleIndex < 0 || mFocusPosition > lastVisibleIndex
-                    || mFocusPosition < firstVisibleIndex)) {
-                startFromPosition = endPos = mFocusPosition;
-            } else {
-                startFromPosition = firstVisibleIndex;
-                endPos = lastVisibleIndex;
-            }
-            mGrid.setStart(startFromPosition);
-            if (endPos != NO_POSITION) {
-                while (appendOneColumnVisibleItems() && findViewByPosition(endPos) == null) {
-                    // continuously append items until endPos
-                }
-            }
-        }
-        // multiple rounds: scrollToView of first round may drag first/last child into
-        // "visible window" and we update scrollMin/scrollMax then run second scrollToView
-        // we must do this for fastRelayout() for the append item case
-        int oldFirstVisible;
-        int oldLastVisible;
-        do {
-            updateScrollLimits();
-            oldFirstVisible = mGrid.getFirstVisibleIndex();
-            oldLastVisible = mGrid.getLastVisibleIndex();
-            focusToViewInLayout(hadFocus, scrollToFocus, -deltaPrimary, -deltaSecondary);
-            appendVisibleItems();
-            prependVisibleItems();
-            // b/67370222: do not removeInvisibleViewsAtFront/End() in the loop, otherwise
-            // loop may bounce between scroll forward and scroll backward forever. Example:
-            // Assuming there are 19 items, child#18 and child#19 are both in RV, we are
-            // trying to focus to child#18 and there are 200px remaining scroll distance.
-            //   1  focusToViewInLayout() tries scroll forward 50 px to align focused child#18 on
-            //      right edge, but there to compensate remaining scroll 200px, also scroll
-            //      backward 200px, 150px pushes last child#19 out side of right edge.
-            //   2  removeInvisibleViewsAtEnd() remove last child#19, updateScrollLimits()
-            //      invalidates scroll max
-            //   3  In next iteration, when scroll max/min is unknown, focusToViewInLayout() will
-            //      align focused child#18 at center of screen.
-            //   4  Because #18 is aligned at center, appendVisibleItems() will fill child#19 to
-            //      the right.
-            //   5  (back to 1 and loop forever)
-        } while (mGrid.getFirstVisibleIndex() != oldFirstVisible
-                || mGrid.getLastVisibleIndex() != oldLastVisible);
-        removeInvisibleViewsAtFront();
-        removeInvisibleViewsAtEnd();
-
-        if (state.willRunPredictiveAnimations()) {
-            fillScrapViewsInPostLayout();
-        }
-
-        if (DEBUG) {
-            StringWriter sw = new StringWriter();
-            PrintWriter pw = new PrintWriter(sw);
-            mGrid.debugPrint(pw);
-            Log.d(getTag(), sw.toString());
-        }
-
-        if (mRowSecondarySizeRefresh) {
-            mRowSecondarySizeRefresh = false;
-        } else {
-            updateRowSecondarySizeRefresh();
-        }
-
-        // For fastRelayout, only dispatch event when focus position changes.
-        if (mInFastRelayout && (mFocusPosition != savedFocusPos || mSubFocusPosition
-                != savedSubFocusPos || findViewByPosition(mFocusPosition) != savedFocusView)) {
-            dispatchChildSelected();
-        } else if (!mInFastRelayout && mInLayoutSearchFocus) {
-            // For full layout we dispatchChildSelected() in createItem() unless searched all
-            // children and found none is focusable then dispatchChildSelected() here.
-            dispatchChildSelected();
-        }
-        dispatchChildSelectedAndPositioned();
-        if (mIsSlidingChildViews) {
-            scrollDirectionPrimary(getSlideOutDistance());
-        }
-
-        mInLayout = false;
-        leaveContext();
-        if (DEBUG) Log.v(getTag(), "layoutChildren end");
-    }
-
-    private void offsetChildrenSecondary(int increment) {
-        final int childCount = getChildCount();
-        if (mOrientation == HORIZONTAL) {
-            for (int i = 0; i < childCount; i++) {
-                getChildAt(i).offsetTopAndBottom(increment);
-            }
-        } else {
-            for (int i = 0; i < childCount; i++) {
-                getChildAt(i).offsetLeftAndRight(increment);
-            }
-        }
-    }
-
-    private void offsetChildrenPrimary(int increment) {
-        final int childCount = getChildCount();
-        if (mOrientation == VERTICAL) {
-            for (int i = 0; i < childCount; i++) {
-                getChildAt(i).offsetTopAndBottom(increment);
-            }
-        } else {
-            for (int i = 0; i < childCount; i++) {
-                getChildAt(i).offsetLeftAndRight(increment);
-            }
-        }
-    }
-
-    @Override
-    public int scrollHorizontallyBy(int dx, Recycler recycler, RecyclerView.State state) {
-        if (DEBUG) Log.v(getTag(), "scrollHorizontallyBy " + dx);
-        if (!mLayoutEnabled || !hasDoneFirstLayout()) {
-            return 0;
-        }
-        saveContext(recycler, state);
-        mInScroll = true;
-        int result;
-        if (mOrientation == HORIZONTAL) {
-            result = scrollDirectionPrimary(dx);
-        } else {
-            result = scrollDirectionSecondary(dx);
-        }
-        leaveContext();
-        mInScroll = false;
-        return result;
-    }
-
-    @Override
-    public int scrollVerticallyBy(int dy, Recycler recycler, RecyclerView.State state) {
-        if (DEBUG) Log.v(getTag(), "scrollVerticallyBy " + dy);
-        if (!mLayoutEnabled || !hasDoneFirstLayout()) {
-            return 0;
-        }
-        mInScroll = true;
-        saveContext(recycler, state);
-        int result;
-        if (mOrientation == VERTICAL) {
-            result = scrollDirectionPrimary(dy);
-        } else {
-            result = scrollDirectionSecondary(dy);
-        }
-        leaveContext();
-        mInScroll = false;
-        return result;
-    }
-
-    // scroll in main direction may add/prune views
-    private int scrollDirectionPrimary(int da) {
-        if (TRACE) TraceCompat.beginSection("scrollPrimary");
-        // We apply the cap of maxScroll/minScroll to the delta, except for two cases:
-        // 1. when children are in sliding out mode
-        // 2. During onLayoutChildren(), it may compensate the remaining scroll delta,
-        //    we should honor the request regardless if it goes over minScroll / maxScroll.
-        //    (see b/64931938 testScrollAndRemove and testScrollAndRemoveSample1)
-        if (!mIsSlidingChildViews && !mInLayout) {
-            if (da > 0) {
-                if (!mWindowAlignment.mainAxis().isMaxUnknown()) {
-                    int maxScroll = mWindowAlignment.mainAxis().getMaxScroll();
-                    if (da > maxScroll) {
-                        da = maxScroll;
-                    }
-                }
-            } else if (da < 0) {
-                if (!mWindowAlignment.mainAxis().isMinUnknown()) {
-                    int minScroll = mWindowAlignment.mainAxis().getMinScroll();
-                    if (da < minScroll) {
-                        da = minScroll;
-                    }
-                }
-            }
-        }
-        if (da == 0) {
-            if (TRACE) TraceCompat.endSection();
-            return 0;
-        }
-        offsetChildrenPrimary(-da);
-        if (mInLayout) {
-            updateScrollLimits();
-            if (TRACE) TraceCompat.endSection();
-            return da;
-        }
-
-        int childCount = getChildCount();
-        boolean updated;
-
-        if (mReverseFlowPrimary ? da > 0 : da < 0) {
-            prependVisibleItems();
-        } else {
-            appendVisibleItems();
-        }
-        updated = getChildCount() > childCount;
-        childCount = getChildCount();
-
-        if (TRACE) TraceCompat.beginSection("remove");
-        if (mReverseFlowPrimary ? da > 0 : da < 0) {
-            removeInvisibleViewsAtEnd();
-        } else {
-            removeInvisibleViewsAtFront();
-        }
-        if (TRACE) TraceCompat.endSection();
-        updated |= getChildCount() < childCount;
-        if (updated) {
-            updateRowSecondarySizeRefresh();
-        }
-
-        mBaseGridView.invalidate();
-        updateScrollLimits();
-        if (TRACE) TraceCompat.endSection();
-        return da;
-    }
-
-    // scroll in second direction will not add/prune views
-    private int scrollDirectionSecondary(int dy) {
-        if (dy == 0) {
-            return 0;
-        }
-        offsetChildrenSecondary(-dy);
-        mScrollOffsetSecondary += dy;
-        updateSecondaryScrollLimits();
-        mBaseGridView.invalidate();
-        return dy;
-    }
-
-    @Override
-    public void collectAdjacentPrefetchPositions(int dx, int dy, State state,
-            LayoutPrefetchRegistry layoutPrefetchRegistry) {
-        try {
-            saveContext(null, state);
-            int da = (mOrientation == HORIZONTAL) ? dx : dy;
-            if (getChildCount() == 0 || da == 0) {
-                // can't support this scroll, so don't bother prefetching
-                return;
-            }
-
-            int fromLimit = da < 0
-                    ? -mExtraLayoutSpace
-                    : mSizePrimary + mExtraLayoutSpace;
-            mGrid.collectAdjacentPrefetchPositions(fromLimit, da, layoutPrefetchRegistry);
-        } finally {
-            leaveContext();
-        }
-    }
-
-    @Override
-    public void collectInitialPrefetchPositions(int adapterItemCount,
-            LayoutPrefetchRegistry layoutPrefetchRegistry) {
-        int numToPrefetch = mBaseGridView.mInitialPrefetchItemCount;
-        if (adapterItemCount != 0 && numToPrefetch != 0) {
-            // prefetch items centered around mFocusPosition
-            int initialPos = Math.max(0, Math.min(mFocusPosition - (numToPrefetch - 1)/ 2,
-                    adapterItemCount - numToPrefetch));
-            for (int i = initialPos; i < adapterItemCount && i < initialPos + numToPrefetch; i++) {
-                layoutPrefetchRegistry.addPosition(i, 0);
-            }
-        }
-    }
-
-    void updateScrollLimits() {
-        if (mState.getItemCount() == 0) {
-            return;
-        }
-        int highVisiblePos, lowVisiblePos;
-        int highMaxPos, lowMinPos;
-        if (!mReverseFlowPrimary) {
-            highVisiblePos = mGrid.getLastVisibleIndex();
-            highMaxPos = mState.getItemCount() - 1;
-            lowVisiblePos = mGrid.getFirstVisibleIndex();
-            lowMinPos = 0;
-        } else {
-            highVisiblePos = mGrid.getFirstVisibleIndex();
-            highMaxPos = 0;
-            lowVisiblePos = mGrid.getLastVisibleIndex();
-            lowMinPos = mState.getItemCount() - 1;
-        }
-        if (highVisiblePos < 0 || lowVisiblePos < 0) {
-            return;
-        }
-        final boolean highAvailable = highVisiblePos == highMaxPos;
-        final boolean lowAvailable = lowVisiblePos == lowMinPos;
-        if (!highAvailable && mWindowAlignment.mainAxis().isMaxUnknown()
-                && !lowAvailable && mWindowAlignment.mainAxis().isMinUnknown()) {
-            return;
-        }
-        int maxEdge, maxViewCenter;
-        if (highAvailable) {
-            maxEdge = mGrid.findRowMax(true, sTwoInts);
-            View maxChild = findViewByPosition(sTwoInts[1]);
-            maxViewCenter = getViewCenter(maxChild);
-            final LayoutParams lp = (LayoutParams) maxChild.getLayoutParams();
-            int[] multipleAligns = lp.getAlignMultiple();
-            if (multipleAligns != null && multipleAligns.length > 0) {
-                maxViewCenter += multipleAligns[multipleAligns.length - 1] - multipleAligns[0];
-            }
-        } else {
-            maxEdge = Integer.MAX_VALUE;
-            maxViewCenter = Integer.MAX_VALUE;
-        }
-        int minEdge, minViewCenter;
-        if (lowAvailable) {
-            minEdge = mGrid.findRowMin(false, sTwoInts);
-            View minChild = findViewByPosition(sTwoInts[1]);
-            minViewCenter = getViewCenter(minChild);
-        } else {
-            minEdge = Integer.MIN_VALUE;
-            minViewCenter = Integer.MIN_VALUE;
-        }
-        mWindowAlignment.mainAxis().updateMinMax(minEdge, maxEdge, minViewCenter, maxViewCenter);
-    }
-
-    /**
-     * Update secondary axis's scroll min/max, should be updated in
-     * {@link #scrollDirectionSecondary(int)}.
-     */
-    private void updateSecondaryScrollLimits() {
-        WindowAlignment.Axis secondAxis = mWindowAlignment.secondAxis();
-        int minEdge = secondAxis.getPaddingMin() - mScrollOffsetSecondary;
-        int maxEdge = minEdge + getSizeSecondary();
-        secondAxis.updateMinMax(minEdge, maxEdge, minEdge, maxEdge);
-    }
-
-    private void initScrollController() {
-        mWindowAlignment.reset();
-        mWindowAlignment.horizontal.setSize(getWidth());
-        mWindowAlignment.vertical.setSize(getHeight());
-        mWindowAlignment.horizontal.setPadding(getPaddingLeft(), getPaddingRight());
-        mWindowAlignment.vertical.setPadding(getPaddingTop(), getPaddingBottom());
-        mSizePrimary = mWindowAlignment.mainAxis().getSize();
-        mScrollOffsetSecondary = 0;
-
-        if (DEBUG) {
-            Log.v(getTag(), "initScrollController mSizePrimary " + mSizePrimary
-                    + " mWindowAlignment " + mWindowAlignment);
-        }
-    }
-
-    private void updateScrollController() {
-        mWindowAlignment.horizontal.setSize(getWidth());
-        mWindowAlignment.vertical.setSize(getHeight());
-        mWindowAlignment.horizontal.setPadding(getPaddingLeft(), getPaddingRight());
-        mWindowAlignment.vertical.setPadding(getPaddingTop(), getPaddingBottom());
-        mSizePrimary = mWindowAlignment.mainAxis().getSize();
-
-        if (DEBUG) {
-            Log.v(getTag(), "updateScrollController mSizePrimary " + mSizePrimary
-                    + " mWindowAlignment " + mWindowAlignment);
-        }
-    }
-
-    @Override
-    public void scrollToPosition(int position) {
-        setSelection(position, 0, false, 0);
-    }
-
-    @Override
-    public void smoothScrollToPosition(RecyclerView recyclerView, State state,
-            int position) {
-        setSelection(position, 0, true, 0);
-    }
-
-    public void setSelection(int position,
-            int primaryScrollExtra) {
-        setSelection(position, 0, false, primaryScrollExtra);
-    }
-
-    public void setSelectionSmooth(int position) {
-        setSelection(position, 0, true, 0);
-    }
-
-    public void setSelectionWithSub(int position, int subposition,
-            int primaryScrollExtra) {
-        setSelection(position, subposition, false, primaryScrollExtra);
-    }
-
-    public void setSelectionSmoothWithSub(int position, int subposition) {
-        setSelection(position, subposition, true, 0);
-    }
-
-    public int getSelection() {
-        return mFocusPosition;
-    }
-
-    public int getSubSelection() {
-        return mSubFocusPosition;
-    }
-
-    public void setSelection(int position, int subposition, boolean smooth,
-            int primaryScrollExtra) {
-        if ((mFocusPosition != position && position != NO_POSITION)
-                || subposition != mSubFocusPosition || primaryScrollExtra != mPrimaryScrollExtra) {
-            scrollToSelection(position, subposition, smooth, primaryScrollExtra);
-        }
-    }
-
-    void scrollToSelection(int position, int subposition,
-            boolean smooth, int primaryScrollExtra) {
-        if (TRACE) TraceCompat.beginSection("scrollToSelection");
-        mPrimaryScrollExtra = primaryScrollExtra;
-        View view = findViewByPosition(position);
-        // scrollToView() is based on Adapter position. Only call scrollToView() when item
-        // is still valid.
-        if (view != null && getAdapterPositionByView(view) == position) {
-            mInSelection = true;
-            scrollToView(view, smooth);
-            mInSelection = false;
-        } else {
-            mFocusPosition = position;
-            mSubFocusPosition = subposition;
-            mFocusPositionOffset = Integer.MIN_VALUE;
-            if (!mLayoutEnabled || mIsSlidingChildViews) {
-                return;
-            }
-            if (smooth) {
-                if (!hasDoneFirstLayout()) {
-                    Log.w(getTag(), "setSelectionSmooth should "
-                            + "not be called before first layout pass");
-                    return;
-                }
-                position = startPositionSmoothScroller(position);
-                if (position != mFocusPosition) {
-                    // gets cropped by adapter size
-                    mFocusPosition = position;
-                    mSubFocusPosition = 0;
-                }
-            } else {
-                mForceFullLayout = true;
-                requestLayout();
-            }
-        }
-        if (TRACE) TraceCompat.endSection();
-    }
-
-    int startPositionSmoothScroller(int position) {
-        LinearSmoothScroller linearSmoothScroller = new GridLinearSmoothScroller() {
-            @Override
-            public PointF computeScrollVectorForPosition(int targetPosition) {
-                if (getChildCount() == 0) {
-                    return null;
-                }
-                final int firstChildPos = getPosition(getChildAt(0));
-                // TODO We should be able to deduce direction from bounds of current and target
-                // focus, rather than making assumptions about positions and directionality
-                final boolean isStart = mReverseFlowPrimary ? targetPosition > firstChildPos
-                        : targetPosition < firstChildPos;
-                final int direction = isStart ? -1 : 1;
-                if (mOrientation == HORIZONTAL) {
-                    return new PointF(direction, 0);
-                } else {
-                    return new PointF(0, direction);
-                }
-            }
-
-        };
-        linearSmoothScroller.setTargetPosition(position);
-        startSmoothScroll(linearSmoothScroller);
-        return linearSmoothScroller.getTargetPosition();
-    }
-
-    private void processPendingMovement(boolean forward) {
-        if (forward ? hasCreatedLastItem() : hasCreatedFirstItem()) {
-            return;
-        }
-        if (mPendingMoveSmoothScroller == null) {
-            // Stop existing scroller and create a new PendingMoveSmoothScroller.
-            mBaseGridView.stopScroll();
-            PendingMoveSmoothScroller linearSmoothScroller = new PendingMoveSmoothScroller(
-                    forward ? 1 : -1, mNumRows > 1);
-            mFocusPositionOffset = 0;
-            startSmoothScroll(linearSmoothScroller);
-            if (linearSmoothScroller.isRunning()) {
-                mPendingMoveSmoothScroller = linearSmoothScroller;
-            }
-        } else {
-            if (forward) {
-                mPendingMoveSmoothScroller.increasePendingMoves();
-            } else {
-                mPendingMoveSmoothScroller.decreasePendingMoves();
-            }
-        }
-    }
-
-    @Override
-    public void onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount) {
-        if (DEBUG) Log.v(getTag(), "onItemsAdded positionStart "
-                + positionStart + " itemCount " + itemCount);
-        if (mFocusPosition != NO_POSITION && mGrid != null && mGrid.getFirstVisibleIndex() >= 0
-                && mFocusPositionOffset != Integer.MIN_VALUE) {
-            int pos = mFocusPosition + mFocusPositionOffset;
-            if (positionStart <= pos) {
-                mFocusPositionOffset += itemCount;
-            }
-        }
-        mChildrenStates.clear();
-    }
-
-    @Override
-    public void onItemsChanged(RecyclerView recyclerView) {
-        if (DEBUG) Log.v(getTag(), "onItemsChanged");
-        mFocusPositionOffset = 0;
-        mChildrenStates.clear();
-    }
-
-    @Override
-    public void onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount) {
-        if (DEBUG) Log.v(getTag(), "onItemsRemoved positionStart "
-                + positionStart + " itemCount " + itemCount);
-        if (mFocusPosition != NO_POSITION  && mGrid != null && mGrid.getFirstVisibleIndex() >= 0
-                && mFocusPositionOffset != Integer.MIN_VALUE) {
-            int pos = mFocusPosition + mFocusPositionOffset;
-            if (positionStart <= pos) {
-                if (positionStart + itemCount > pos) {
-                    // stop updating offset after the focus item was removed
-                    mFocusPositionOffset += positionStart - pos;
-                    mFocusPosition += mFocusPositionOffset;
-                    mFocusPositionOffset = Integer.MIN_VALUE;
-                } else {
-                    mFocusPositionOffset -= itemCount;
-                }
-            }
-        }
-        mChildrenStates.clear();
-    }
-
-    @Override
-    public void onItemsMoved(RecyclerView recyclerView, int fromPosition, int toPosition,
-            int itemCount) {
-        if (DEBUG) Log.v(getTag(), "onItemsMoved fromPosition "
-                + fromPosition + " toPosition " + toPosition);
-        if (mFocusPosition != NO_POSITION && mFocusPositionOffset != Integer.MIN_VALUE) {
-            int pos = mFocusPosition + mFocusPositionOffset;
-            if (fromPosition <= pos && pos < fromPosition + itemCount) {
-                // moved items include focused position
-                mFocusPositionOffset += toPosition - fromPosition;
-            } else if (fromPosition < pos && toPosition > pos - itemCount) {
-                // move items before focus position to after focused position
-                mFocusPositionOffset -= itemCount;
-            } else if (fromPosition > pos && toPosition < pos) {
-                // move items after focus position to before focused position
-                mFocusPositionOffset += itemCount;
-            }
-        }
-        mChildrenStates.clear();
-    }
-
-    @Override
-    public void onItemsUpdated(RecyclerView recyclerView, int positionStart, int itemCount) {
-        if (DEBUG) Log.v(getTag(), "onItemsUpdated positionStart "
-                + positionStart + " itemCount " + itemCount);
-        for (int i = positionStart, end = positionStart + itemCount; i < end; i++) {
-            mChildrenStates.remove(i);
-        }
-    }
-
-    @Override
-    public boolean onRequestChildFocus(RecyclerView parent, View child, View focused) {
-        if (mFocusSearchDisabled) {
-            return true;
-        }
-        if (getAdapterPositionByView(child) == NO_POSITION) {
-            // This is could be the last view in DISAPPEARING animation.
-            return true;
-        }
-        if (!mInLayout && !mInSelection && !mInScroll) {
-            scrollToView(child, focused, true);
-        }
-        return true;
-    }
-
-    @Override
-    public boolean requestChildRectangleOnScreen(RecyclerView parent, View view, Rect rect,
-            boolean immediate) {
-        if (DEBUG) Log.v(getTag(), "requestChildRectangleOnScreen " + view + " " + rect);
-        return false;
-    }
-
-    public void getViewSelectedOffsets(View view, int[] offsets) {
-        if (mOrientation == HORIZONTAL) {
-            offsets[0] = getPrimaryAlignedScrollDistance(view);
-            offsets[1] = getSecondaryScrollDistance(view);
-        } else {
-            offsets[1] = getPrimaryAlignedScrollDistance(view);
-            offsets[0] = getSecondaryScrollDistance(view);
-        }
-    }
-
-    /**
-     * Return the scroll delta on primary direction to make the view selected. If the return value
-     * is 0, there is no need to scroll.
-     */
-    private int getPrimaryAlignedScrollDistance(View view) {
-        return mWindowAlignment.mainAxis().getScroll(getViewCenter(view));
-    }
-
-    /**
-     * Get adjusted primary position for a given childView (if there is multiple ItemAlignment
-     * defined on the view).
-     */
-    private int getAdjustedPrimaryAlignedScrollDistance(int scrollPrimary, View view,
-            View childView) {
-        int subindex = getSubPositionByView(view, childView);
-        if (subindex != 0) {
-            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
-            scrollPrimary += lp.getAlignMultiple()[subindex] - lp.getAlignMultiple()[0];
-        }
-        return scrollPrimary;
-    }
-
-    private int getSecondaryScrollDistance(View view) {
-        int viewCenterSecondary = getViewCenterSecondary(view);
-        return mWindowAlignment.secondAxis().getScroll(viewCenterSecondary);
-    }
-
-    /**
-     * Scroll to a given child view and change mFocusPosition. Ignored when in slideOut() state.
-     */
-    void scrollToView(View view, boolean smooth) {
-        scrollToView(view, view == null ? null : view.findFocus(), smooth);
-    }
-
-    void scrollToView(View view, boolean smooth, int extraDelta, int extraDeltaSecondary) {
-        scrollToView(view, view == null ? null : view.findFocus(), smooth, extraDelta,
-                extraDeltaSecondary);
-    }
-
-    private void scrollToView(View view, View childView, boolean smooth) {
-        scrollToView(view, childView, smooth, 0, 0);
-    }
-    /**
-     * Scroll to a given child view and change mFocusPosition. Ignored when in slideOut() state.
-     */
-    private void scrollToView(View view, View childView, boolean smooth, int extraDelta,
-            int extraDeltaSecondary) {
-        if (mIsSlidingChildViews) {
-            return;
-        }
-        int newFocusPosition = getAdapterPositionByView(view);
-        int newSubFocusPosition = getSubPositionByView(view, childView);
-        if (newFocusPosition != mFocusPosition || newSubFocusPosition != mSubFocusPosition) {
-            mFocusPosition = newFocusPosition;
-            mSubFocusPosition = newSubFocusPosition;
-            mFocusPositionOffset = 0;
-            if (!mInLayout) {
-                dispatchChildSelected();
-            }
-            if (mBaseGridView.isChildrenDrawingOrderEnabledInternal()) {
-                mBaseGridView.invalidate();
-            }
-        }
-        if (view == null) {
-            return;
-        }
-        if (!view.hasFocus() && mBaseGridView.hasFocus()) {
-            // transfer focus to the child if it does not have focus yet (e.g. triggered
-            // by setSelection())
-            view.requestFocus();
-        }
-        if (!mScrollEnabled && smooth) {
-            return;
-        }
-        if (getScrollPosition(view, childView, sTwoInts)
-                || extraDelta != 0 || extraDeltaSecondary != 0) {
-            scrollGrid(sTwoInts[0] + extraDelta, sTwoInts[1] + extraDeltaSecondary, smooth);
-        }
-    }
-
-    boolean getScrollPosition(View view, View childView, int[] deltas) {
-        switch (mFocusScrollStrategy) {
-            case BaseGridView.FOCUS_SCROLL_ALIGNED:
-            default:
-                return getAlignedPosition(view, childView, deltas);
-            case BaseGridView.FOCUS_SCROLL_ITEM:
-            case BaseGridView.FOCUS_SCROLL_PAGE:
-                return getNoneAlignedPosition(view, deltas);
-        }
-    }
-
-    private boolean getNoneAlignedPosition(View view, int[] deltas) {
-        int pos = getAdapterPositionByView(view);
-        int viewMin = getViewMin(view);
-        int viewMax = getViewMax(view);
-        // we either align "firstView" to left/top padding edge
-        // or align "lastView" to right/bottom padding edge
-        View firstView = null;
-        View lastView = null;
-        int paddingMin = mWindowAlignment.mainAxis().getPaddingMin();
-        int clientSize = mWindowAlignment.mainAxis().getClientSize();
-        final int row = mGrid.getRowIndex(pos);
-        if (viewMin < paddingMin) {
-            // view enters low padding area:
-            firstView = view;
-            if (mFocusScrollStrategy == BaseGridView.FOCUS_SCROLL_PAGE) {
-                // scroll one "page" left/top,
-                // align first visible item of the "page" at the low padding edge.
-                while (prependOneColumnVisibleItems()) {
-                    CircularIntArray positions =
-                            mGrid.getItemPositionsInRows(mGrid.getFirstVisibleIndex(), pos)[row];
-                    firstView = findViewByPosition(positions.get(0));
-                    if (viewMax - getViewMin(firstView) > clientSize) {
-                        if (positions.size() > 2) {
-                            firstView = findViewByPosition(positions.get(2));
-                        }
-                        break;
-                    }
-                }
-            }
-        } else if (viewMax > clientSize + paddingMin) {
-            // view enters high padding area:
-            if (mFocusScrollStrategy == BaseGridView.FOCUS_SCROLL_PAGE) {
-                // scroll whole one page right/bottom, align view at the low padding edge.
-                firstView = view;
-                do {
-                    CircularIntArray positions =
-                            mGrid.getItemPositionsInRows(pos, mGrid.getLastVisibleIndex())[row];
-                    lastView = findViewByPosition(positions.get(positions.size() - 1));
-                    if (getViewMax(lastView) - viewMin > clientSize) {
-                        lastView = null;
-                        break;
-                    }
-                } while (appendOneColumnVisibleItems());
-                if (lastView != null) {
-                    // however if we reached end,  we should align last view.
-                    firstView = null;
-                }
-            } else {
-                lastView = view;
-            }
-        }
-        int scrollPrimary = 0;
-        int scrollSecondary = 0;
-        if (firstView != null) {
-            scrollPrimary = getViewMin(firstView) - paddingMin;
-        } else if (lastView != null) {
-            scrollPrimary = getViewMax(lastView) - (paddingMin + clientSize);
-        }
-        View secondaryAlignedView;
-        if (firstView != null) {
-            secondaryAlignedView = firstView;
-        } else if (lastView != null) {
-            secondaryAlignedView = lastView;
-        } else {
-            secondaryAlignedView = view;
-        }
-        scrollSecondary = getSecondaryScrollDistance(secondaryAlignedView);
-        if (scrollPrimary != 0 || scrollSecondary != 0) {
-            deltas[0] = scrollPrimary;
-            deltas[1] = scrollSecondary;
-            return true;
-        }
-        return false;
-    }
-
-    private boolean getAlignedPosition(View view, View childView, int[] deltas) {
-        int scrollPrimary = getPrimaryAlignedScrollDistance(view);
-        if (childView != null) {
-            scrollPrimary = getAdjustedPrimaryAlignedScrollDistance(scrollPrimary, view, childView);
-        }
-        int scrollSecondary = getSecondaryScrollDistance(view);
-        if (DEBUG) {
-            Log.v(getTag(), "getAlignedPosition " + scrollPrimary + " " + scrollSecondary
-                    + " " + mPrimaryScrollExtra + " " + mWindowAlignment);
-        }
-        scrollPrimary += mPrimaryScrollExtra;
-        if (scrollPrimary != 0 || scrollSecondary != 0) {
-            deltas[0] = scrollPrimary;
-            deltas[1] = scrollSecondary;
-            return true;
-        } else {
-            deltas[0] = 0;
-            deltas[1] = 0;
-        }
-        return false;
-    }
-
-    private void scrollGrid(int scrollPrimary, int scrollSecondary, boolean smooth) {
-        if (mInLayout) {
-            scrollDirectionPrimary(scrollPrimary);
-            scrollDirectionSecondary(scrollSecondary);
-        } else {
-            int scrollX;
-            int scrollY;
-            if (mOrientation == HORIZONTAL) {
-                scrollX = scrollPrimary;
-                scrollY = scrollSecondary;
-            } else {
-                scrollX = scrollSecondary;
-                scrollY = scrollPrimary;
-            }
-            if (smooth) {
-                mBaseGridView.smoothScrollBy(scrollX, scrollY);
-            } else {
-                mBaseGridView.scrollBy(scrollX, scrollY);
-                dispatchChildSelectedAndPositioned();
-            }
-        }
-    }
-
-    public void setPruneChild(boolean pruneChild) {
-        if (mPruneChild != pruneChild) {
-            mPruneChild = pruneChild;
-            if (mPruneChild) {
-                requestLayout();
-            }
-        }
-    }
-
-    public boolean getPruneChild() {
-        return mPruneChild;
-    }
-
-    public void setScrollEnabled(boolean scrollEnabled) {
-        if (mScrollEnabled != scrollEnabled) {
-            mScrollEnabled = scrollEnabled;
-            if (mScrollEnabled && mFocusScrollStrategy == BaseGridView.FOCUS_SCROLL_ALIGNED
-                    && mFocusPosition != NO_POSITION) {
-                scrollToSelection(mFocusPosition, mSubFocusPosition,
-                        true, mPrimaryScrollExtra);
-            }
-        }
-    }
-
-    public boolean isScrollEnabled() {
-        return mScrollEnabled;
-    }
-
-    private int findImmediateChildIndex(View view) {
-        if (mBaseGridView != null && view != mBaseGridView) {
-            view = findContainingItemView(view);
-            if (view != null) {
-                for (int i = 0, count = getChildCount(); i < count; i++) {
-                    if (getChildAt(i) == view) {
-                        return i;
-                    }
-                }
-            }
-        }
-        return NO_POSITION;
-    }
-
-    void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
-        if (gainFocus) {
-            // if gridview.requestFocus() is called, select first focusable child.
-            for (int i = mFocusPosition; ;i++) {
-                View view = findViewByPosition(i);
-                if (view == null) {
-                    break;
-                }
-                if (view.getVisibility() == View.VISIBLE && view.hasFocusable()) {
-                    view.requestFocus();
-                    break;
-                }
-            }
-        }
-    }
-
-    void setFocusSearchDisabled(boolean disabled) {
-        mFocusSearchDisabled = disabled;
-    }
-
-    boolean isFocusSearchDisabled() {
-        return mFocusSearchDisabled;
-    }
-
-    @Override
-    public View onInterceptFocusSearch(View focused, int direction) {
-        if (mFocusSearchDisabled) {
-            return focused;
-        }
-
-        final FocusFinder ff = FocusFinder.getInstance();
-        View result = null;
-        if (direction == View.FOCUS_FORWARD || direction == View.FOCUS_BACKWARD) {
-            // convert direction to absolute direction and see if we have a view there and if not
-            // tell LayoutManager to add if it can.
-            if (canScrollVertically()) {
-                final int absDir =
-                        direction == View.FOCUS_FORWARD ? View.FOCUS_DOWN : View.FOCUS_UP;
-                result = ff.findNextFocus(mBaseGridView, focused, absDir);
-            }
-            if (canScrollHorizontally()) {
-                boolean rtl = getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL;
-                final int absDir = (direction == View.FOCUS_FORWARD) ^ rtl
-                        ? View.FOCUS_RIGHT : View.FOCUS_LEFT;
-                result = ff.findNextFocus(mBaseGridView, focused, absDir);
-            }
-        } else {
-            result = ff.findNextFocus(mBaseGridView, focused, direction);
-        }
-        if (result != null) {
-            return result;
-        }
-
-        if (mBaseGridView.getDescendantFocusability() == ViewGroup.FOCUS_BLOCK_DESCENDANTS) {
-            return mBaseGridView.getParent().focusSearch(focused, direction);
-        }
-
-        if (DEBUG) Log.v(getTag(), "regular focusSearch failed direction " + direction);
-        int movement = getMovement(direction);
-        final boolean isScroll = mBaseGridView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE;
-        if (movement == NEXT_ITEM) {
-            if (isScroll || !mFocusOutEnd) {
-                result = focused;
-            }
-            if (mScrollEnabled && !hasCreatedLastItem()) {
-                processPendingMovement(true);
-                result = focused;
-            }
-        } else if (movement == PREV_ITEM) {
-            if (isScroll || !mFocusOutFront) {
-                result = focused;
-            }
-            if (mScrollEnabled && !hasCreatedFirstItem()) {
-                processPendingMovement(false);
-                result = focused;
-            }
-        } else if (movement == NEXT_ROW) {
-            if (isScroll || !mFocusOutSideEnd) {
-                result = focused;
-            }
-        } else if (movement == PREV_ROW) {
-            if (isScroll || !mFocusOutSideStart) {
-                result = focused;
-            }
-        }
-        if (result != null) {
-            return result;
-        }
-
-        if (DEBUG) Log.v(getTag(), "now focusSearch in parent");
-        result = mBaseGridView.getParent().focusSearch(focused, direction);
-        if (result != null) {
-            return result;
-        }
-        return focused != null ? focused : mBaseGridView;
-    }
-
-    boolean hasPreviousViewInSameRow(int pos) {
-        if (mGrid == null || pos == NO_POSITION || mGrid.getFirstVisibleIndex() < 0) {
-            return false;
-        }
-        if (mGrid.getFirstVisibleIndex() > 0) {
-            return true;
-        }
-        final int focusedRow = mGrid.getLocation(pos).row;
-        for (int i = getChildCount() - 1; i >= 0; i--) {
-            int position = getAdapterPositionByIndex(i);
-            Grid.Location loc = mGrid.getLocation(position);
-            if (loc != null && loc.row == focusedRow) {
-                if (position < pos) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
-    @Override
-    public boolean onAddFocusables(RecyclerView recyclerView,
-            ArrayList<View> views, int direction, int focusableMode) {
-        if (mFocusSearchDisabled) {
-            return true;
-        }
-        // If this viewgroup or one of its children currently has focus then we
-        // consider our children for focus searching in main direction on the same row.
-        // If this viewgroup has no focus and using focus align, we want the system
-        // to ignore our children and pass focus to the viewgroup, which will pass
-        // focus on to its children appropriately.
-        // If this viewgroup has no focus and not using focus align, we want to
-        // consider the child that does not overlap with padding area.
-        if (recyclerView.hasFocus()) {
-            if (mPendingMoveSmoothScroller != null) {
-                // don't find next focusable if has pending movement.
-                return true;
-            }
-            final int movement = getMovement(direction);
-            final View focused = recyclerView.findFocus();
-            final int focusedIndex = findImmediateChildIndex(focused);
-            final int focusedPos = getAdapterPositionByIndex(focusedIndex);
-            // Even if focusedPos != NO_POSITION, findViewByPosition could return null if the view
-            // is ignored or getLayoutPosition does not match the adapter position of focused view.
-            final View immediateFocusedChild = (focusedPos == NO_POSITION) ? null
-                    : findViewByPosition(focusedPos);
-            // Add focusables of focused item.
-            if (immediateFocusedChild != null) {
-                immediateFocusedChild.addFocusables(views,  direction, focusableMode);
-            }
-            if (mGrid == null || getChildCount() == 0) {
-                // no grid information, or no child, bail out.
-                return true;
-            }
-            if ((movement == NEXT_ROW || movement == PREV_ROW) && mGrid.getNumRows() <= 1) {
-                // For single row, cannot navigate to previous/next row.
-                return true;
-            }
-            // Add focusables of neighbor depending on the focus search direction.
-            final int focusedRow = mGrid != null && immediateFocusedChild != null
-                    ? mGrid.getLocation(focusedPos).row : NO_POSITION;
-            final int focusableCount = views.size();
-            int inc = movement == NEXT_ITEM || movement == NEXT_ROW ? 1 : -1;
-            int loop_end = inc > 0 ? getChildCount() - 1 : 0;
-            int loop_start;
-            if (focusedIndex == NO_POSITION) {
-                loop_start = inc > 0 ? 0 : getChildCount() - 1;
-            } else {
-                loop_start = focusedIndex + inc;
-            }
-            for (int i = loop_start; inc > 0 ? i <= loop_end : i >= loop_end; i += inc) {
-                final View child = getChildAt(i);
-                if (child.getVisibility() != View.VISIBLE || !child.hasFocusable()) {
-                    continue;
-                }
-                // if there wasn't any focused item, add the very first focusable
-                // items and stop.
-                if (immediateFocusedChild == null) {
-                    child.addFocusables(views,  direction, focusableMode);
-                    if (views.size() > focusableCount) {
-                        break;
-                    }
-                    continue;
-                }
-                int position = getAdapterPositionByIndex(i);
-                Grid.Location loc = mGrid.getLocation(position);
-                if (loc == null) {
-                    continue;
-                }
-                if (movement == NEXT_ITEM) {
-                    // Add first focusable item on the same row
-                    if (loc.row == focusedRow && position > focusedPos) {
-                        child.addFocusables(views,  direction, focusableMode);
-                        if (views.size() > focusableCount) {
-                            break;
-                        }
-                    }
-                } else if (movement == PREV_ITEM) {
-                    // Add first focusable item on the same row
-                    if (loc.row == focusedRow && position < focusedPos) {
-                        child.addFocusables(views,  direction, focusableMode);
-                        if (views.size() > focusableCount) {
-                            break;
-                        }
-                    }
-                } else if (movement == NEXT_ROW) {
-                    // Add all focusable items after this item whose row index is bigger
-                    if (loc.row == focusedRow) {
-                        continue;
-                    } else if (loc.row < focusedRow) {
-                        break;
-                    }
-                    child.addFocusables(views,  direction, focusableMode);
-                } else if (movement == PREV_ROW) {
-                    // Add all focusable items before this item whose row index is smaller
-                    if (loc.row == focusedRow) {
-                        continue;
-                    } else if (loc.row > focusedRow) {
-                        break;
-                    }
-                    child.addFocusables(views,  direction, focusableMode);
-                }
-            }
-        } else {
-            int focusableCount = views.size();
-            if (mFocusScrollStrategy != BaseGridView.FOCUS_SCROLL_ALIGNED) {
-                // adding views not overlapping padding area to avoid scrolling in gaining focus
-                int left = mWindowAlignment.mainAxis().getPaddingMin();
-                int right = mWindowAlignment.mainAxis().getClientSize() + left;
-                for (int i = 0, count = getChildCount(); i < count; i++) {
-                    View child = getChildAt(i);
-                    if (child.getVisibility() == View.VISIBLE) {
-                        if (getViewMin(child) >= left && getViewMax(child) <= right) {
-                            child.addFocusables(views, direction, focusableMode);
-                        }
-                    }
-                }
-                // if we cannot find any, then just add all children.
-                if (views.size() == focusableCount) {
-                    for (int i = 0, count = getChildCount(); i < count; i++) {
-                        View child = getChildAt(i);
-                        if (child.getVisibility() == View.VISIBLE) {
-                            child.addFocusables(views, direction, focusableMode);
-                        }
-                    }
-                }
-            } else {
-                View view = findViewByPosition(mFocusPosition);
-                if (view != null) {
-                    view.addFocusables(views, direction, focusableMode);
-                }
-            }
-            // if still cannot find any, fall through and add itself
-            if (views.size() != focusableCount) {
-                return true;
-            }
-            if (recyclerView.isFocusable()) {
-                views.add(recyclerView);
-            }
-        }
-        return true;
-    }
-
-    boolean hasCreatedLastItem() {
-        int count = getItemCount();
-        return count == 0 || mBaseGridView.findViewHolderForAdapterPosition(count - 1) != null;
-    }
-
-    boolean hasCreatedFirstItem() {
-        int count = getItemCount();
-        return count == 0 || mBaseGridView.findViewHolderForAdapterPosition(0) != null;
-    }
-
-    boolean isItemFullyVisible(int pos) {
-        RecyclerView.ViewHolder vh = mBaseGridView.findViewHolderForAdapterPosition(pos);
-        if (vh == null) {
-            return false;
-        }
-        return vh.itemView.getLeft() >= 0 && vh.itemView.getRight() < mBaseGridView.getWidth()
-                && vh.itemView.getTop() >= 0 && vh.itemView.getBottom() < mBaseGridView.getHeight();
-    }
-
-    boolean canScrollTo(View view) {
-        return view.getVisibility() == View.VISIBLE && (!hasFocus() || view.hasFocusable());
-    }
-
-    boolean gridOnRequestFocusInDescendants(RecyclerView recyclerView, int direction,
-            Rect previouslyFocusedRect) {
-        switch (mFocusScrollStrategy) {
-            case BaseGridView.FOCUS_SCROLL_ALIGNED:
-            default:
-                return gridOnRequestFocusInDescendantsAligned(recyclerView,
-                        direction, previouslyFocusedRect);
-            case BaseGridView.FOCUS_SCROLL_PAGE:
-            case BaseGridView.FOCUS_SCROLL_ITEM:
-                return gridOnRequestFocusInDescendantsUnaligned(recyclerView,
-                        direction, previouslyFocusedRect);
-        }
-    }
-
-    private boolean gridOnRequestFocusInDescendantsAligned(RecyclerView recyclerView,
-            int direction, Rect previouslyFocusedRect) {
-        View view = findViewByPosition(mFocusPosition);
-        if (view != null) {
-            boolean result = view.requestFocus(direction, previouslyFocusedRect);
-            if (!result && DEBUG) {
-                Log.w(getTag(), "failed to request focus on " + view);
-            }
-            return result;
-        }
-        return false;
-    }
-
-    private boolean gridOnRequestFocusInDescendantsUnaligned(RecyclerView recyclerView,
-            int direction, Rect previouslyFocusedRect) {
-        // focus to view not overlapping padding area to avoid scrolling in gaining focus
-        int index;
-        int increment;
-        int end;
-        int count = getChildCount();
-        if ((direction & View.FOCUS_FORWARD) != 0) {
-            index = 0;
-            increment = 1;
-            end = count;
-        } else {
-            index = count - 1;
-            increment = -1;
-            end = -1;
-        }
-        int left = mWindowAlignment.mainAxis().getPaddingMin();
-        int right = mWindowAlignment.mainAxis().getClientSize() + left;
-        for (int i = index; i != end; i += increment) {
-            View child = getChildAt(i);
-            if (child.getVisibility() == View.VISIBLE) {
-                if (getViewMin(child) >= left && getViewMax(child) <= right) {
-                    if (child.requestFocus(direction, previouslyFocusedRect)) {
-                        return true;
-                    }
-                }
-            }
-        }
-        return false;
-    }
-
-    private final static int PREV_ITEM = 0;
-    private final static int NEXT_ITEM = 1;
-    private final static int PREV_ROW = 2;
-    private final static int NEXT_ROW = 3;
-
-    private int getMovement(int direction) {
-        int movement = View.FOCUS_LEFT;
-
-        if (mOrientation == HORIZONTAL) {
-            switch(direction) {
-                case View.FOCUS_LEFT:
-                    movement = (!mReverseFlowPrimary) ? PREV_ITEM : NEXT_ITEM;
-                    break;
-                case View.FOCUS_RIGHT:
-                    movement = (!mReverseFlowPrimary) ? NEXT_ITEM : PREV_ITEM;
-                    break;
-                case View.FOCUS_UP:
-                    movement = PREV_ROW;
-                    break;
-                case View.FOCUS_DOWN:
-                    movement = NEXT_ROW;
-                    break;
-            }
-        } else if (mOrientation == VERTICAL) {
-            switch(direction) {
-                case View.FOCUS_LEFT:
-                    movement = (!mReverseFlowSecondary) ? PREV_ROW : NEXT_ROW;
-                    break;
-                case View.FOCUS_RIGHT:
-                    movement = (!mReverseFlowSecondary) ? NEXT_ROW : PREV_ROW;
-                    break;
-                case View.FOCUS_UP:
-                    movement = PREV_ITEM;
-                    break;
-                case View.FOCUS_DOWN:
-                    movement = NEXT_ITEM;
-                    break;
-            }
-        }
-
-        return movement;
-    }
-
-    int getChildDrawingOrder(RecyclerView recyclerView, int childCount, int i) {
-        View view = findViewByPosition(mFocusPosition);
-        if (view == null) {
-            return i;
-        }
-        int focusIndex = recyclerView.indexOfChild(view);
-        // supposely 0 1 2 3 4 5 6 7 8 9, 4 is the center item
-        // drawing order is 0 1 2 3 9 8 7 6 5 4
-        if (i < focusIndex) {
-            return i;
-        } else if (i < childCount - 1) {
-            return focusIndex + childCount - 1 - i;
-        } else {
-            return focusIndex;
-        }
-    }
-
-    @Override
-    public void onAdapterChanged(RecyclerView.Adapter oldAdapter,
-            RecyclerView.Adapter newAdapter) {
-        if (DEBUG) Log.v(getTag(), "onAdapterChanged to " + newAdapter);
-        if (oldAdapter != null) {
-            discardLayoutInfo();
-            mFocusPosition = NO_POSITION;
-            mFocusPositionOffset = 0;
-            mChildrenStates.clear();
-        }
-        if (newAdapter instanceof FacetProviderAdapter) {
-            mFacetProviderAdapter = (FacetProviderAdapter) newAdapter;
-        } else {
-            mFacetProviderAdapter = null;
-        }
-        super.onAdapterChanged(oldAdapter, newAdapter);
-    }
-
-    private void discardLayoutInfo() {
-        mGrid = null;
-        mRowSizeSecondary = null;
-        mRowSecondarySizeRefresh = false;
-    }
-
-    public void setLayoutEnabled(boolean layoutEnabled) {
-        if (mLayoutEnabled != layoutEnabled) {
-            mLayoutEnabled = layoutEnabled;
-            requestLayout();
-        }
-    }
-
-    void setChildrenVisibility(int visibility) {
-        mChildVisibility = visibility;
-        if (mChildVisibility != -1) {
-            int count = getChildCount();
-            for (int i= 0; i < count; i++) {
-                getChildAt(i).setVisibility(mChildVisibility);
-            }
-        }
-    }
-
-    final static class SavedState implements Parcelable {
-
-        int index; // index inside adapter of the current view
-        Bundle childStates = Bundle.EMPTY;
-
-        @Override
-        public void writeToParcel(Parcel out, int flags) {
-            out.writeInt(index);
-            out.writeBundle(childStates);
-        }
-
-        @SuppressWarnings("hiding")
-        public static final Parcelable.Creator<SavedState> CREATOR =
-                new Parcelable.Creator<SavedState>() {
-                    @Override
-                    public SavedState createFromParcel(Parcel in) {
-                        return new SavedState(in);
-                    }
-
-                    @Override
-                    public SavedState[] newArray(int size) {
-                        return new SavedState[size];
-                    }
-                };
-
-        @Override
-        public int describeContents() {
-            return 0;
-        }
-
-        SavedState(Parcel in) {
-            index = in.readInt();
-            childStates = in.readBundle(GridLayoutManager.class.getClassLoader());
-        }
-
-        SavedState() {
-        }
-    }
-
-    @Override
-    public Parcelable onSaveInstanceState() {
-        if (DEBUG) Log.v(getTag(), "onSaveInstanceState getSelection() " + getSelection());
-        SavedState ss = new SavedState();
-        // save selected index
-        ss.index = getSelection();
-        // save offscreen child (state when they are recycled)
-        Bundle bundle = mChildrenStates.saveAsBundle();
-        // save views currently is on screen (TODO save cached views)
-        for (int i = 0, count = getChildCount(); i < count; i++) {
-            View view = getChildAt(i);
-            int position = getAdapterPositionByView(view);
-            if (position != NO_POSITION) {
-                bundle = mChildrenStates.saveOnScreenView(bundle, view, position);
-            }
-        }
-        ss.childStates = bundle;
-        return ss;
-    }
-
-    void onChildRecycled(RecyclerView.ViewHolder holder) {
-        final int position = holder.getAdapterPosition();
-        if (position != NO_POSITION) {
-            mChildrenStates.saveOffscreenView(holder.itemView, position);
-        }
-    }
-
-    @Override
-    public void onRestoreInstanceState(Parcelable state) {
-        if (!(state instanceof SavedState)) {
-            return;
-        }
-        SavedState loadingState = (SavedState)state;
-        mFocusPosition = loadingState.index;
-        mFocusPositionOffset = 0;
-        mChildrenStates.loadFromBundle(loadingState.childStates);
-        mForceFullLayout = true;
-        requestLayout();
-        if (DEBUG) Log.v(getTag(), "onRestoreInstanceState mFocusPosition " + mFocusPosition);
-    }
-
-    @Override
-    public int getRowCountForAccessibility(RecyclerView.Recycler recycler,
-            RecyclerView.State state) {
-        if (mOrientation == HORIZONTAL && mGrid != null) {
-            return mGrid.getNumRows();
-        }
-        return super.getRowCountForAccessibility(recycler, state);
-    }
-
-    @Override
-    public int getColumnCountForAccessibility(RecyclerView.Recycler recycler,
-            RecyclerView.State state) {
-        if (mOrientation == VERTICAL && mGrid != null) {
-            return mGrid.getNumRows();
-        }
-        return super.getColumnCountForAccessibility(recycler, state);
-    }
-
-    @Override
-    public void onInitializeAccessibilityNodeInfoForItem(RecyclerView.Recycler recycler,
-            RecyclerView.State state, View host, AccessibilityNodeInfoCompat info) {
-        ViewGroup.LayoutParams lp = host.getLayoutParams();
-        if (mGrid == null || !(lp instanceof LayoutParams)) {
-            return;
-        }
-        LayoutParams glp = (LayoutParams) lp;
-        int position = glp.getViewAdapterPosition();
-        int rowIndex = position >= 0 ? mGrid.getRowIndex(position) : -1;
-        if (rowIndex < 0) {
-            return;
-        }
-        int guessSpanIndex = position / mGrid.getNumRows();
-        if (mOrientation == HORIZONTAL) {
-            info.setCollectionItemInfo(AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain(
-                    rowIndex, 1, guessSpanIndex, 1, false, false));
-        } else {
-            info.setCollectionItemInfo(AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain(
-                    guessSpanIndex, 1, rowIndex, 1, false, false));
-        }
-    }
-
-    /*
-     * Leanback widget is different than the default implementation because the "scroll" is driven
-     * by selection change.
-     */
-    @Override
-    public boolean performAccessibilityAction(Recycler recycler, State state, int action,
-            Bundle args) {
-        saveContext(recycler, state);
-        switch (action) {
-            case AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD:
-                processSelectionMoves(false, -1);
-                break;
-            case AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD:
-                processSelectionMoves(false, 1);
-                break;
-        }
-        leaveContext();
-        return true;
-    }
-
-    /*
-     * Move mFocusPosition multiple steps on the same row in main direction.
-     * Stops when moves are all consumed or reach first/last visible item.
-     * Returning remaining moves.
-     */
-    int processSelectionMoves(boolean preventScroll, int moves) {
-        if (mGrid == null) {
-            return moves;
-        }
-        int focusPosition = mFocusPosition;
-        int focusedRow = focusPosition != NO_POSITION
-                ? mGrid.getRowIndex(focusPosition) : NO_POSITION;
-        View newSelected = null;
-        for (int i = 0, count = getChildCount(); i < count && moves != 0; i++) {
-            int index = moves > 0 ? i : count - 1 - i;
-            final View child = getChildAt(index);
-            if (!canScrollTo(child)) {
-                continue;
-            }
-            int position = getAdapterPositionByIndex(index);
-            int rowIndex = mGrid.getRowIndex(position);
-            if (focusedRow == NO_POSITION) {
-                focusPosition = position;
-                newSelected = child;
-                focusedRow = rowIndex;
-            } else if (rowIndex == focusedRow) {
-                if ((moves > 0 && position > focusPosition)
-                        || (moves < 0 && position < focusPosition)) {
-                    focusPosition = position;
-                    newSelected = child;
-                    if (moves > 0) {
-                        moves--;
-                    } else {
-                        moves++;
-                    }
-                }
-            }
-        }
-        if (newSelected != null) {
-            if (preventScroll) {
-                if (hasFocus()) {
-                    mInSelection = true;
-                    newSelected.requestFocus();
-                    mInSelection = false;
-                }
-                mFocusPosition = focusPosition;
-                mSubFocusPosition = 0;
-            } else {
-                scrollToView(newSelected, true);
-            }
-        }
-        return moves;
-    }
-
-    @Override
-    public void onInitializeAccessibilityNodeInfo(Recycler recycler, State state,
-            AccessibilityNodeInfoCompat info) {
-        saveContext(recycler, state);
-        int count = state.getItemCount();
-        if (mScrollEnabled && count > 1 && !isItemFullyVisible(0)) {
-            info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD);
-            info.setScrollable(true);
-        }
-        if (mScrollEnabled && count > 1 && !isItemFullyVisible(count - 1)) {
-            info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD);
-            info.setScrollable(true);
-        }
-        final AccessibilityNodeInfoCompat.CollectionInfoCompat collectionInfo =
-                AccessibilityNodeInfoCompat.CollectionInfoCompat
-                        .obtain(getRowCountForAccessibility(recycler, state),
-                                getColumnCountForAccessibility(recycler, state),
-                                isLayoutHierarchical(recycler, state),
-                                getSelectionModeForAccessibility(recycler, state));
-        info.setCollectionInfo(collectionInfo);
-        leaveContext();
-    }
-}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionAdapter.java b/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionAdapter.java
deleted file mode 100644
index 5b755f5..0000000
--- a/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionAdapter.java
+++ /dev/null
@@ -1,496 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-package android.support.v17.leanback.widget;
-
-import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
-
-import android.support.annotation.RestrictTo;
-import android.support.v7.widget.RecyclerView;
-import android.support.v7.widget.RecyclerView.ViewHolder;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewParent;
-import android.view.inputmethod.EditorInfo;
-import android.widget.EditText;
-import android.widget.TextView;
-import android.widget.TextView.OnEditorActionListener;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * GuidedActionAdapter instantiates views for guided actions, and manages their interactions.
- * Presentation (view creation and state animation) is delegated to a {@link
- * GuidedActionsStylist}, while clients are notified of interactions via
- * {@link GuidedActionAdapter.ClickListener} and {@link GuidedActionAdapter.FocusListener}.
- * @hide
- */
-@RestrictTo(LIBRARY_GROUP)
-public class GuidedActionAdapter extends RecyclerView.Adapter {
-    static final String TAG = "GuidedActionAdapter";
-    static final boolean DEBUG = false;
-
-    static final String TAG_EDIT = "EditableAction";
-    static final boolean DEBUG_EDIT = false;
-
-    /**
-     * Object listening for click events within a {@link GuidedActionAdapter}.
-     */
-    public interface ClickListener {
-
-        /**
-         * Called when the user clicks on an action.
-         */
-        void onGuidedActionClicked(GuidedAction action);
-
-    }
-
-    /**
-     * Object listening for focus events within a {@link GuidedActionAdapter}.
-     */
-    public interface FocusListener {
-
-        /**
-         * Called when the user focuses on an action.
-         */
-        void onGuidedActionFocused(GuidedAction action);
-    }
-
-    /**
-     * Object listening for edit events within a {@link GuidedActionAdapter}.
-     */
-    public interface EditListener {
-
-        /**
-         * Called when the user exits edit mode on an action.
-         */
-        void onGuidedActionEditCanceled(GuidedAction action);
-
-        /**
-         * Called when the user exits edit mode on an action and process confirm button in IME.
-         */
-        long onGuidedActionEditedAndProceed(GuidedAction action);
-
-        /**
-         * Called when Ime Open
-         */
-        void onImeOpen();
-
-        /**
-         * Called when Ime Close
-         */
-        void onImeClose();
-    }
-
-    private final boolean mIsSubAdapter;
-    private final ActionOnKeyListener mActionOnKeyListener;
-    private final ActionOnFocusListener mActionOnFocusListener;
-    private final ActionEditListener mActionEditListener;
-    private final List<GuidedAction> mActions;
-    private ClickListener mClickListener;
-    final GuidedActionsStylist mStylist;
-    GuidedActionAdapterGroup mGroup;
-
-    private final View.OnClickListener mOnClickListener = new View.OnClickListener() {
-        @Override
-        public void onClick(View v) {
-            if (v != null && v.getWindowToken() != null && getRecyclerView() != null) {
-                GuidedActionsStylist.ViewHolder avh = (GuidedActionsStylist.ViewHolder)
-                        getRecyclerView().getChildViewHolder(v);
-                GuidedAction action = avh.getAction();
-                if (action.hasTextEditable()) {
-                    if (DEBUG_EDIT) Log.v(TAG_EDIT, "openIme by click");
-                    mGroup.openIme(GuidedActionAdapter.this, avh);
-                } else if (action.hasEditableActivatorView()) {
-                    if (DEBUG_EDIT) Log.v(TAG_EDIT, "toggle editing mode by click");
-                    performOnActionClick(avh);
-                } else {
-                    handleCheckedActions(avh);
-                    if (action.isEnabled() && !action.infoOnly()) {
-                        performOnActionClick(avh);
-                    }
-                }
-            }
-        }
-    };
-
-    /**
-     * Constructs a GuidedActionAdapter with the given list of guided actions, the given click and
-     * focus listeners, and the given presenter.
-     * @param actions The list of guided actions this adapter will manage.
-     * @param focusListener The focus listener for items in this adapter.
-     * @param presenter The presenter that will manage the display of items in this adapter.
-     */
-    public GuidedActionAdapter(List<GuidedAction> actions, ClickListener clickListener,
-            FocusListener focusListener, GuidedActionsStylist presenter, boolean isSubAdapter) {
-        super();
-        mActions = actions == null ? new ArrayList<GuidedAction>() :
-                new ArrayList<GuidedAction>(actions);
-        mClickListener = clickListener;
-        mStylist = presenter;
-        mActionOnKeyListener = new ActionOnKeyListener();
-        mActionOnFocusListener = new ActionOnFocusListener(focusListener);
-        mActionEditListener = new ActionEditListener();
-        mIsSubAdapter = isSubAdapter;
-    }
-
-    /**
-     * Sets the list of actions managed by this adapter.
-     * @param actions The list of actions to be managed.
-     */
-    public void setActions(List<GuidedAction> actions) {
-        if (!mIsSubAdapter) {
-            mStylist.collapseAction(false);
-        }
-        mActionOnFocusListener.unFocus();
-        mActions.clear();
-        mActions.addAll(actions);
-        notifyDataSetChanged();
-    }
-
-    /**
-     * Returns the count of actions managed by this adapter.
-     * @return The count of actions managed by this adapter.
-     */
-    public int getCount() {
-        return mActions.size();
-    }
-
-    /**
-     * Returns the GuidedAction at the given position in the managed list.
-     * @param position The position of the desired GuidedAction.
-     * @return The GuidedAction at the given position.
-     */
-    public GuidedAction getItem(int position) {
-        return mActions.get(position);
-    }
-
-    /**
-     * Return index of action in array
-     * @param action Action to search index.
-     * @return Index of Action in array.
-     */
-    public int indexOf(GuidedAction action) {
-        return mActions.indexOf(action);
-    }
-
-    /**
-     * @return GuidedActionsStylist used to build the actions list UI.
-     */
-    public GuidedActionsStylist getGuidedActionsStylist() {
-        return mStylist;
-    }
-
-    /**
-     * Sets the click listener for items managed by this adapter.
-     * @param clickListener The click listener for this adapter.
-     */
-    public void setClickListener(ClickListener clickListener) {
-        mClickListener = clickListener;
-    }
-
-    /**
-     * Sets the focus listener for items managed by this adapter.
-     * @param focusListener The focus listener for this adapter.
-     */
-    public void setFocusListener(FocusListener focusListener) {
-        mActionOnFocusListener.setFocusListener(focusListener);
-    }
-
-    /**
-     * Used for serialization only.
-     * @hide
-     */
-    @RestrictTo(LIBRARY_GROUP)
-    public List<GuidedAction> getActions() {
-        return new ArrayList<GuidedAction>(mActions);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public int getItemViewType(int position) {
-        return mStylist.getItemViewType(mActions.get(position));
-    }
-
-    RecyclerView getRecyclerView() {
-        return mIsSubAdapter ? mStylist.getSubActionsGridView() : mStylist.getActionsGridView();
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
-        GuidedActionsStylist.ViewHolder vh = mStylist.onCreateViewHolder(parent, viewType);
-        View v = vh.itemView;
-        v.setOnKeyListener(mActionOnKeyListener);
-        v.setOnClickListener(mOnClickListener);
-        v.setOnFocusChangeListener(mActionOnFocusListener);
-
-        setupListeners(vh.getEditableTitleView());
-        setupListeners(vh.getEditableDescriptionView());
-
-        return vh;
-    }
-
-    private void setupListeners(EditText edit) {
-        if (edit != null) {
-            edit.setPrivateImeOptions("EscapeNorth=1;");
-            edit.setOnEditorActionListener(mActionEditListener);
-            if (edit instanceof ImeKeyMonitor) {
-                ImeKeyMonitor monitor = (ImeKeyMonitor)edit;
-                monitor.setImeKeyListener(mActionEditListener);
-            }
-        }
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public void onBindViewHolder(ViewHolder holder, int position) {
-        if (position >= mActions.size()) {
-            return;
-        }
-        final GuidedActionsStylist.ViewHolder avh = (GuidedActionsStylist.ViewHolder)holder;
-        GuidedAction action = mActions.get(position);
-        mStylist.onBindViewHolder(avh, action);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public int getItemCount() {
-        return mActions.size();
-    }
-
-    private class ActionOnFocusListener implements View.OnFocusChangeListener {
-
-        private FocusListener mFocusListener;
-        private View mSelectedView;
-
-        ActionOnFocusListener(FocusListener focusListener) {
-            mFocusListener = focusListener;
-        }
-
-        public void setFocusListener(FocusListener focusListener) {
-            mFocusListener = focusListener;
-        }
-
-        public void unFocus() {
-            if (mSelectedView != null && getRecyclerView() != null) {
-                ViewHolder vh = getRecyclerView().getChildViewHolder(mSelectedView);
-                if (vh != null) {
-                    GuidedActionsStylist.ViewHolder avh = (GuidedActionsStylist.ViewHolder)vh;
-                    mStylist.onAnimateItemFocused(avh, false);
-                } else {
-                    Log.w(TAG, "RecyclerView returned null view holder",
-                            new Throwable());
-                }
-            }
-        }
-
-        @Override
-        public void onFocusChange(View v, boolean hasFocus) {
-            if (getRecyclerView() == null) {
-                return;
-            }
-            GuidedActionsStylist.ViewHolder avh = (GuidedActionsStylist.ViewHolder)
-                    getRecyclerView().getChildViewHolder(v);
-            if (hasFocus) {
-                mSelectedView = v;
-                if (mFocusListener != null) {
-                    // We still call onGuidedActionFocused so that listeners can clear
-                    // state if they want.
-                    mFocusListener.onGuidedActionFocused(avh.getAction());
-                }
-            } else {
-                if (mSelectedView == v) {
-                    mStylist.onAnimateItemPressedCancelled(avh);
-                    mSelectedView = null;
-                }
-            }
-            mStylist.onAnimateItemFocused(avh, hasFocus);
-        }
-    }
-
-    public GuidedActionsStylist.ViewHolder findSubChildViewHolder(View v) {
-        // Needed because RecyclerView.getChildViewHolder does not traverse the hierarchy
-        if (getRecyclerView() == null) {
-            return null;
-        }
-        GuidedActionsStylist.ViewHolder result = null;
-        ViewParent parent = v.getParent();
-        while (parent != getRecyclerView() && parent != null && v != null) {
-            v = (View)parent;
-            parent = parent.getParent();
-        }
-        if (parent != null && v != null) {
-            result = (GuidedActionsStylist.ViewHolder)getRecyclerView().getChildViewHolder(v);
-        }
-        return result;
-    }
-
-    public void handleCheckedActions(GuidedActionsStylist.ViewHolder avh) {
-        GuidedAction action = avh.getAction();
-        int actionCheckSetId = action.getCheckSetId();
-        if (getRecyclerView() != null && actionCheckSetId != GuidedAction.NO_CHECK_SET) {
-            // Find any actions that are checked and are in the same group
-            // as the selected action. Fade their checkmarks out.
-            if (actionCheckSetId != GuidedAction.CHECKBOX_CHECK_SET_ID) {
-                for (int i = 0, size = mActions.size(); i < size; i++) {
-                    GuidedAction a = mActions.get(i);
-                    if (a != action && a.getCheckSetId() == actionCheckSetId && a.isChecked()) {
-                        a.setChecked(false);
-                        GuidedActionsStylist.ViewHolder vh = (GuidedActionsStylist.ViewHolder)
-                                getRecyclerView().findViewHolderForPosition(i);
-                        if (vh != null) {
-                            mStylist.onAnimateItemChecked(vh, false);
-                        }
-                    }
-                }
-            }
-
-            // If we we'ren't already checked, fade our checkmark in.
-            if (!action.isChecked()) {
-                action.setChecked(true);
-                mStylist.onAnimateItemChecked(avh, true);
-            } else {
-                if (actionCheckSetId == GuidedAction.CHECKBOX_CHECK_SET_ID) {
-                    action.setChecked(false);
-                    mStylist.onAnimateItemChecked(avh, false);
-                }
-            }
-        }
-    }
-
-    public void performOnActionClick(GuidedActionsStylist.ViewHolder avh) {
-        if (mClickListener != null) {
-            mClickListener.onGuidedActionClicked(avh.getAction());
-        }
-    }
-
-    private class ActionOnKeyListener implements View.OnKeyListener {
-
-        private boolean mKeyPressed = false;
-
-        ActionOnKeyListener() {
-        }
-
-        /**
-         * Now only handles KEYCODE_ENTER and KEYCODE_NUMPAD_ENTER key event.
-         */
-        @Override
-        public boolean onKey(View v, int keyCode, KeyEvent event) {
-            if (v == null || event == null || getRecyclerView() == null) {
-                return false;
-            }
-            boolean handled = false;
-            switch (keyCode) {
-                case KeyEvent.KEYCODE_DPAD_CENTER:
-                case KeyEvent.KEYCODE_NUMPAD_ENTER:
-                case KeyEvent.KEYCODE_BUTTON_X:
-                case KeyEvent.KEYCODE_BUTTON_Y:
-                case KeyEvent.KEYCODE_ENTER:
-
-                    GuidedActionsStylist.ViewHolder avh = (GuidedActionsStylist.ViewHolder)
-                            getRecyclerView().getChildViewHolder(v);
-                    GuidedAction action = avh.getAction();
-
-                    if (!action.isEnabled() || action.infoOnly()) {
-                        if (event.getAction() == KeyEvent.ACTION_DOWN) {
-                            // TODO: requires API 19
-                            //playSound(v, AudioManager.FX_KEYPRESS_INVALID);
-                        }
-                        return true;
-                    }
-
-                    switch (event.getAction()) {
-                        case KeyEvent.ACTION_DOWN:
-                            if (DEBUG) {
-                                Log.d(TAG, "Enter Key down");
-                            }
-                            if (!mKeyPressed) {
-                                mKeyPressed = true;
-                                mStylist.onAnimateItemPressed(avh, mKeyPressed);
-                            }
-                            break;
-                        case KeyEvent.ACTION_UP:
-                            if (DEBUG) {
-                                Log.d(TAG, "Enter Key up");
-                            }
-                            // Sometimes we are losing ACTION_DOWN for the first ENTER after pressed
-                            // Escape in IME.
-                            if (mKeyPressed) {
-                                mKeyPressed = false;
-                                mStylist.onAnimateItemPressed(avh, mKeyPressed);
-                            }
-                            break;
-                        default:
-                            break;
-                    }
-                    break;
-                default:
-                    break;
-            }
-            return handled;
-        }
-
-    }
-
-    private class ActionEditListener implements OnEditorActionListener,
-            ImeKeyMonitor.ImeKeyListener {
-
-        ActionEditListener() {
-        }
-
-        @Override
-        public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
-            if (DEBUG_EDIT) Log.v(TAG_EDIT, "IME action: " + actionId);
-            boolean handled = false;
-            if (actionId == EditorInfo.IME_ACTION_NEXT
-                    || actionId == EditorInfo.IME_ACTION_DONE) {
-                mGroup.fillAndGoNext(GuidedActionAdapter.this, v);
-                handled = true;
-            } else if (actionId == EditorInfo.IME_ACTION_NONE) {
-                if (DEBUG_EDIT) Log.v(TAG_EDIT, "closeIme escape north");
-                // Escape north handling: stay on current item, but close editor
-                handled = true;
-                mGroup.fillAndStay(GuidedActionAdapter.this, v);
-            }
-            return handled;
-        }
-
-        @Override
-        public boolean onKeyPreIme(EditText editText, int keyCode, KeyEvent event) {
-            if (DEBUG_EDIT) Log.v(TAG_EDIT, "IME key: " + keyCode);
-            if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) {
-                mGroup.fillAndStay(GuidedActionAdapter.this, editText);
-                return true;
-            } else if (keyCode == KeyEvent.KEYCODE_ENTER
-                    && event.getAction() == KeyEvent.ACTION_UP) {
-                mGroup.fillAndGoNext(GuidedActionAdapter.this, editText);
-                return true;
-            }
-            return false;
-        }
-
-    }
-
-}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ObjectAdapter.java b/v17/leanback/src/android/support/v17/leanback/widget/ObjectAdapter.java
deleted file mode 100644
index 535f81b..0000000
--- a/v17/leanback/src/android/support/v17/leanback/widget/ObjectAdapter.java
+++ /dev/null
@@ -1,337 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-package android.support.v17.leanback.widget;
-
-import android.database.Observable;
-
-/**
- * Base class adapter to be used in leanback activities.  Provides access to a data model and is
- * decoupled from the presentation of the items via {@link PresenterSelector}.
- */
-public abstract class ObjectAdapter {
-
-    /** Indicates that an id has not been set. */
-    public static final int NO_ID = -1;
-
-    /**
-     * A DataObserver can be notified when an ObjectAdapter's underlying data
-     * changes. Separate methods provide notifications about different types of
-     * changes.
-     */
-    public static abstract class DataObserver {
-        /**
-         * Called whenever the ObjectAdapter's data has changed in some manner
-         * outside of the set of changes covered by the other range-based change
-         * notification methods.
-         */
-        public void onChanged() {
-        }
-
-        /**
-         * Called when a range of items in the ObjectAdapter has changed. The
-         * basic ordering and structure of the ObjectAdapter has not changed.
-         *
-         * @param positionStart The position of the first item that changed.
-         * @param itemCount     The number of items changed.
-         */
-        public void onItemRangeChanged(int positionStart, int itemCount) {
-            onChanged();
-        }
-
-        /**
-         * Called when a range of items in the ObjectAdapter has changed. The
-         * basic ordering and structure of the ObjectAdapter has not changed.
-         *
-         * @param positionStart The position of the first item that changed.
-         * @param itemCount     The number of items changed.
-         * @param payload       Optional parameter, use null to identify a "full" update.
-         */
-        public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
-            onChanged();
-        }
-
-        /**
-         * Called when a range of items is inserted into the ObjectAdapter.
-         *
-         * @param positionStart The position of the first inserted item.
-         * @param itemCount     The number of items inserted.
-         */
-        public void onItemRangeInserted(int positionStart, int itemCount) {
-            onChanged();
-        }
-
-        /**
-         * Called when an item is moved from one position to another position
-         *
-         * @param fromPosition Previous position of the item.
-         * @param toPosition   New position of the item.
-         */
-        public void onItemMoved(int fromPosition, int toPosition) {
-            onChanged();
-        }
-
-        /**
-         * Called when a range of items is removed from the ObjectAdapter.
-         *
-         * @param positionStart The position of the first removed item.
-         * @param itemCount     The number of items removed.
-         */
-        public void onItemRangeRemoved(int positionStart, int itemCount) {
-            onChanged();
-        }
-    }
-
-    private static final class DataObservable extends Observable<DataObserver> {
-
-        DataObservable() {
-        }
-
-        public void notifyChanged() {
-            for (int i = mObservers.size() - 1; i >= 0; i--) {
-                mObservers.get(i).onChanged();
-            }
-        }
-
-        public void notifyItemRangeChanged(int positionStart, int itemCount) {
-            for (int i = mObservers.size() - 1; i >= 0; i--) {
-                mObservers.get(i).onItemRangeChanged(positionStart, itemCount);
-            }
-        }
-
-        public void notifyItemRangeChanged(int positionStart, int itemCount, Object payload) {
-            for (int i = mObservers.size() - 1; i >= 0; i--) {
-                mObservers.get(i).onItemRangeChanged(positionStart, itemCount, payload);
-            }
-        }
-
-        public void notifyItemRangeInserted(int positionStart, int itemCount) {
-            for (int i = mObservers.size() - 1; i >= 0; i--) {
-                mObservers.get(i).onItemRangeInserted(positionStart, itemCount);
-            }
-        }
-
-        public void notifyItemRangeRemoved(int positionStart, int itemCount) {
-            for (int i = mObservers.size() - 1; i >= 0; i--) {
-                mObservers.get(i).onItemRangeRemoved(positionStart, itemCount);
-            }
-        }
-
-        public void notifyItemMoved(int positionStart, int toPosition) {
-            for (int i = mObservers.size() - 1; i >= 0; i--) {
-                mObservers.get(i).onItemMoved(positionStart, toPosition);
-            }
-        }
-    }
-
-    private final DataObservable mObservable = new DataObservable();
-    private boolean mHasStableIds;
-    private PresenterSelector mPresenterSelector;
-
-    /**
-     * Constructs an adapter with the given {@link PresenterSelector}.
-     */
-    public ObjectAdapter(PresenterSelector presenterSelector) {
-        setPresenterSelector(presenterSelector);
-    }
-
-    /**
-     * Constructs an adapter that uses the given {@link Presenter} for all items.
-     */
-    public ObjectAdapter(Presenter presenter) {
-        setPresenterSelector(new SinglePresenterSelector(presenter));
-    }
-
-    /**
-     * Constructs an adapter.
-     */
-    public ObjectAdapter() {
-    }
-
-    /**
-     * Sets the presenter selector.  May not be null.
-     */
-    public final void setPresenterSelector(PresenterSelector presenterSelector) {
-        if (presenterSelector == null) {
-            throw new IllegalArgumentException("Presenter selector must not be null");
-        }
-        final boolean update = (mPresenterSelector != null);
-        final boolean selectorChanged = update && mPresenterSelector != presenterSelector;
-
-        mPresenterSelector = presenterSelector;
-
-        if (selectorChanged) {
-            onPresenterSelectorChanged();
-        }
-        if (update) {
-            notifyChanged();
-        }
-    }
-
-    /**
-     * Called when {@link #setPresenterSelector(PresenterSelector)} is called
-     * and the PresenterSelector differs from the previous one.
-     */
-    protected void onPresenterSelectorChanged() {
-    }
-
-    /**
-     * Returns the presenter selector for this ObjectAdapter.
-     */
-    public final PresenterSelector getPresenterSelector() {
-        return mPresenterSelector;
-    }
-
-    /**
-     * Registers a DataObserver for data change notifications.
-     */
-    public final void registerObserver(DataObserver observer) {
-        mObservable.registerObserver(observer);
-    }
-
-    /**
-     * Unregisters a DataObserver for data change notifications.
-     */
-    public final void unregisterObserver(DataObserver observer) {
-        mObservable.unregisterObserver(observer);
-    }
-
-    /**
-     * Unregisters all DataObservers for this ObjectAdapter.
-     */
-    public final void unregisterAllObservers() {
-        mObservable.unregisterAll();
-    }
-
-    /**
-     * Notifies UI that some items has changed.
-     *
-     * @param positionStart Starting position of the changed items.
-     * @param itemCount     Total number of items that changed.
-     */
-    public final void notifyItemRangeChanged(int positionStart, int itemCount) {
-        mObservable.notifyItemRangeChanged(positionStart, itemCount);
-    }
-
-    /**
-     * Notifies UI that some items has changed.
-     *
-     * @param positionStart Starting position of the changed items.
-     * @param itemCount     Total number of items that changed.
-     * @param payload       Optional parameter, use null to identify a "full" update.
-     */
-    public final void notifyItemRangeChanged(int positionStart, int itemCount, Object payload) {
-        mObservable.notifyItemRangeChanged(positionStart, itemCount, payload);
-    }
-
-    /**
-     * Notifies UI that new items has been inserted.
-     *
-     * @param positionStart Position where new items has been inserted.
-     * @param itemCount     Count of the new items has been inserted.
-     */
-    final protected void notifyItemRangeInserted(int positionStart, int itemCount) {
-        mObservable.notifyItemRangeInserted(positionStart, itemCount);
-    }
-
-    /**
-     * Notifies UI that some items that has been removed.
-     *
-     * @param positionStart Starting position of the removed items.
-     * @param itemCount     Total number of items that has been removed.
-     */
-    final protected void notifyItemRangeRemoved(int positionStart, int itemCount) {
-        mObservable.notifyItemRangeRemoved(positionStart, itemCount);
-    }
-
-    /**
-     * Notifies UI that item at fromPosition has been moved to toPosition.
-     *
-     * @param fromPosition Previous position of the item.
-     * @param toPosition   New position of the item.
-     */
-    protected final void notifyItemMoved(int fromPosition, int toPosition) {
-        mObservable.notifyItemMoved(fromPosition, toPosition);
-    }
-
-    /**
-     * Notifies UI that the underlying data has changed.
-     */
-    final protected void notifyChanged() {
-        mObservable.notifyChanged();
-    }
-
-    /**
-     * Returns true if the item ids are stable across changes to the
-     * underlying data.  When this is true, clients of the ObjectAdapter can use
-     * {@link #getId(int)} to correlate Objects across changes.
-     */
-    public final boolean hasStableIds() {
-        return mHasStableIds;
-    }
-
-    /**
-     * Sets whether the item ids are stable across changes to the underlying
-     * data.
-     */
-    public final void setHasStableIds(boolean hasStableIds) {
-        boolean changed = mHasStableIds != hasStableIds;
-        mHasStableIds = hasStableIds;
-
-        if (changed) {
-            onHasStableIdsChanged();
-        }
-    }
-
-    /**
-     * Called when {@link #setHasStableIds(boolean)} is called and the status
-     * of stable ids has changed.
-     */
-    protected void onHasStableIdsChanged() {
-    }
-
-    /**
-     * Returns the {@link Presenter} for the given item from the adapter.
-     */
-    public final Presenter getPresenter(Object item) {
-        if (mPresenterSelector == null) {
-            throw new IllegalStateException("Presenter selector must not be null");
-        }
-        return mPresenterSelector.getPresenter(item);
-    }
-
-    /**
-     * Returns the number of items in the adapter.
-     */
-    public abstract int size();
-
-    /**
-     * Returns the item for the given position.
-     */
-    public abstract Object get(int position);
-
-    /**
-     * Returns the id for the given position.
-     */
-    public long getId(int position) {
-        return NO_ID;
-    }
-
-    /**
-     * Returns true if the adapter pairs each underlying data change with a call to notify and
-     * false otherwise.
-     */
-    public boolean isImmediateNotifySupported() {
-        return false;
-    }
-}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/WindowAlignment.java b/v17/leanback/src/android/support/v17/leanback/widget/WindowAlignment.java
deleted file mode 100644
index 55fa758..0000000
--- a/v17/leanback/src/android/support/v17/leanback/widget/WindowAlignment.java
+++ /dev/null
@@ -1,400 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package android.support.v17.leanback.widget;
-
-import static android.support.v17.leanback.widget.BaseGridView.WINDOW_ALIGN_BOTH_EDGE;
-import static android.support.v17.leanback.widget.BaseGridView.WINDOW_ALIGN_HIGH_EDGE;
-import static android.support.v17.leanback.widget.BaseGridView.WINDOW_ALIGN_LOW_EDGE;
-import static android.support.v17.leanback.widget.BaseGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED;
-import static android.support.v7.widget.RecyclerView.HORIZONTAL;
-
-/**
- * Maintains Window Alignment information of two axis.
- */
-class WindowAlignment {
-
-    /**
-     * Maintains alignment information in one direction.
-     */
-    public static class Axis {
-        /**
-         * Right or bottom edge of last child.
-         */
-        private int mMaxEdge;
-        /**
-         * Left or top edge of first child
-         */
-        private int mMinEdge;
-        /**
-         * Scroll distance to align last child, it defines limit of scroll.
-         */
-        private int mMaxScroll;
-        /**
-         * Scroll distance to align first child, it defines limit of scroll.
-         */
-        private int mMinScroll;
-
-        static final int PF_KEYLINE_OVER_LOW_EDGE = 1;
-        static final int PF_KEYLINE_OVER_HIGH_EDGE = 1 << 1;
-
-        /**
-         * By default we prefer low edge over keyline, prefer keyline over high edge.
-         */
-        private int mPreferredKeyLine = PF_KEYLINE_OVER_HIGH_EDGE;
-
-        private int mWindowAlignment = WINDOW_ALIGN_BOTH_EDGE;
-
-        private int mWindowAlignmentOffset = 0;
-
-        private float mWindowAlignmentOffsetPercent = 50f;
-
-        private int mSize;
-
-        /**
-         * Padding at the min edge, it is the left or top padding.
-         */
-        private int mPaddingMin;
-
-        /**
-         * Padding at the max edge, it is the right or bottom padding.
-         */
-        private int mPaddingMax;
-
-        private boolean mReversedFlow;
-
-        private String mName; // for debugging
-
-        public Axis(String name) {
-            reset();
-            mName = name;
-        }
-
-        public final int getWindowAlignment() {
-            return mWindowAlignment;
-        }
-
-        public final void setWindowAlignment(int windowAlignment) {
-            mWindowAlignment = windowAlignment;
-        }
-
-        final void setPreferKeylineOverLowEdge(boolean keylineOverLowEdge) {
-            mPreferredKeyLine = keylineOverLowEdge
-                    ? mPreferredKeyLine | PF_KEYLINE_OVER_LOW_EDGE
-                    : mPreferredKeyLine & ~PF_KEYLINE_OVER_LOW_EDGE;
-        }
-
-        final void setPreferKeylineOverHighEdge(boolean keylineOverHighEdge) {
-            mPreferredKeyLine = keylineOverHighEdge
-                    ? mPreferredKeyLine | PF_KEYLINE_OVER_HIGH_EDGE
-                    : mPreferredKeyLine & ~PF_KEYLINE_OVER_HIGH_EDGE;
-        }
-
-        final boolean isPreferKeylineOverHighEdge() {
-            return (mPreferredKeyLine & PF_KEYLINE_OVER_HIGH_EDGE) != 0;
-        }
-
-        final boolean isPreferKeylineOverLowEdge() {
-            return (mPreferredKeyLine & PF_KEYLINE_OVER_LOW_EDGE) != 0;
-        }
-
-        public final int getWindowAlignmentOffset() {
-            return mWindowAlignmentOffset;
-        }
-
-        public final void setWindowAlignmentOffset(int offset) {
-            mWindowAlignmentOffset = offset;
-        }
-
-        public final void setWindowAlignmentOffsetPercent(float percent) {
-            if ((percent < 0 || percent > 100)
-                    && percent != WINDOW_ALIGN_OFFSET_PERCENT_DISABLED) {
-                throw new IllegalArgumentException();
-            }
-            mWindowAlignmentOffsetPercent = percent;
-        }
-
-        public final float getWindowAlignmentOffsetPercent() {
-            return mWindowAlignmentOffsetPercent;
-        }
-
-        /**
-         * Returns scroll distance to align min child.
-         */
-        public final int getMinScroll() {
-            return mMinScroll;
-        }
-
-        public final void invalidateScrollMin() {
-            mMinEdge = Integer.MIN_VALUE;
-            mMinScroll = Integer.MIN_VALUE;
-        }
-
-        /**
-         * Returns scroll distance to align max child.
-         */
-        public final int getMaxScroll() {
-            return mMaxScroll;
-        }
-
-        public final void invalidateScrollMax() {
-            mMaxEdge = Integer.MAX_VALUE;
-            mMaxScroll = Integer.MAX_VALUE;
-        }
-
-        void reset() {
-            mMinEdge = Integer.MIN_VALUE;
-            mMaxEdge = Integer.MAX_VALUE;
-        }
-
-        public final boolean isMinUnknown() {
-            return mMinEdge == Integer.MIN_VALUE;
-        }
-
-        public final boolean isMaxUnknown() {
-            return mMaxEdge == Integer.MAX_VALUE;
-        }
-
-        public final void setSize(int size) {
-            mSize = size;
-        }
-
-        public final int getSize() {
-            return mSize;
-        }
-
-        public final void setPadding(int paddingMin, int paddingMax) {
-            mPaddingMin = paddingMin;
-            mPaddingMax = paddingMax;
-        }
-
-        public final int getPaddingMin() {
-            return mPaddingMin;
-        }
-
-        public final int getPaddingMax() {
-            return mPaddingMax;
-        }
-
-        public final int getClientSize() {
-            return mSize - mPaddingMin - mPaddingMax;
-        }
-
-        final int calculateKeyline() {
-            int keyLine;
-            if (!mReversedFlow) {
-                if (mWindowAlignmentOffset >= 0) {
-                    keyLine = mWindowAlignmentOffset;
-                } else {
-                    keyLine = mSize + mWindowAlignmentOffset;
-                }
-                if (mWindowAlignmentOffsetPercent != WINDOW_ALIGN_OFFSET_PERCENT_DISABLED) {
-                    keyLine += (int) (mSize * mWindowAlignmentOffsetPercent / 100);
-                }
-            } else {
-                if (mWindowAlignmentOffset >= 0) {
-                    keyLine = mSize - mWindowAlignmentOffset;
-                } else {
-                    keyLine = -mWindowAlignmentOffset;
-                }
-                if (mWindowAlignmentOffsetPercent != WINDOW_ALIGN_OFFSET_PERCENT_DISABLED) {
-                    keyLine -= (int) (mSize * mWindowAlignmentOffsetPercent / 100);
-                }
-            }
-            return keyLine;
-        }
-
-        /**
-         * Returns scroll distance to move viewCenterPosition to keyLine.
-         */
-        final int calculateScrollToKeyLine(int viewCenterPosition, int keyLine) {
-            return viewCenterPosition - keyLine;
-        }
-
-        /**
-         * Update {@link #getMinScroll()} and {@link #getMaxScroll()}
-         */
-        public final void updateMinMax(int minEdge, int maxEdge,
-                int minChildViewCenter, int maxChildViewCenter) {
-            mMinEdge = minEdge;
-            mMaxEdge = maxEdge;
-            final int clientSize = getClientSize();
-            final int keyLine = calculateKeyline();
-            final boolean isMinUnknown = isMinUnknown();
-            final boolean isMaxUnknown = isMaxUnknown();
-            if (!isMinUnknown) {
-                if (!mReversedFlow ? (mWindowAlignment & WINDOW_ALIGN_LOW_EDGE) != 0
-                        : (mWindowAlignment & WINDOW_ALIGN_HIGH_EDGE) != 0) {
-                    // calculate scroll distance to move current mMinEdge to padding at min edge
-                    mMinScroll = mMinEdge - mPaddingMin;
-                } else  {
-                    // calculate scroll distance to move min child center to key line
-                    mMinScroll = calculateScrollToKeyLine(minChildViewCenter, keyLine);
-                }
-            }
-            if (!isMaxUnknown) {
-                if (!mReversedFlow ? (mWindowAlignment & WINDOW_ALIGN_HIGH_EDGE) != 0
-                        : (mWindowAlignment & WINDOW_ALIGN_LOW_EDGE) != 0) {
-                    // calculate scroll distance to move current mMaxEdge to padding at max edge
-                    mMaxScroll = mMaxEdge - mPaddingMin - clientSize;
-                } else  {
-                    // calculate scroll distance to move max child center to key line
-                    mMaxScroll = calculateScrollToKeyLine(maxChildViewCenter, keyLine);
-                }
-            }
-            if (!isMaxUnknown && !isMinUnknown) {
-                if (!mReversedFlow) {
-                    if ((mWindowAlignment & WINDOW_ALIGN_LOW_EDGE) != 0) {
-                        if (isPreferKeylineOverLowEdge()) {
-                            // if we prefer key line, might align max child to key line for
-                            // minScroll
-                            mMinScroll = Math.min(mMinScroll,
-                                    calculateScrollToKeyLine(maxChildViewCenter, keyLine));
-                        }
-                        // don't over scroll max
-                        mMaxScroll = Math.max(mMinScroll, mMaxScroll);
-                    } else if ((mWindowAlignment & WINDOW_ALIGN_HIGH_EDGE) != 0) {
-                        if (isPreferKeylineOverHighEdge()) {
-                            // if we prefer key line, might align min child to key line for
-                            // maxScroll
-                            mMaxScroll = Math.max(mMaxScroll,
-                                    calculateScrollToKeyLine(minChildViewCenter, keyLine));
-                        }
-                        // don't over scroll min
-                        mMinScroll = Math.min(mMinScroll, mMaxScroll);
-                    }
-                } else {
-                    if ((mWindowAlignment & WINDOW_ALIGN_LOW_EDGE) != 0) {
-                        if (isPreferKeylineOverLowEdge()) {
-                            // if we prefer key line, might align min child to key line for
-                            // maxScroll
-                            mMaxScroll = Math.max(mMaxScroll,
-                                    calculateScrollToKeyLine(minChildViewCenter, keyLine));
-                        }
-                        // don't over scroll min
-                        mMinScroll = Math.min(mMinScroll, mMaxScroll);
-                    } else if ((mWindowAlignment & WINDOW_ALIGN_HIGH_EDGE) != 0) {
-                        if (isPreferKeylineOverHighEdge()) {
-                            // if we prefer key line, might align max child to key line for
-                            // minScroll
-                            mMinScroll = Math.min(mMinScroll,
-                                    calculateScrollToKeyLine(maxChildViewCenter, keyLine));
-                        }
-                        // don't over scroll max
-                        mMaxScroll = Math.max(mMinScroll, mMaxScroll);
-                    }
-                }
-            }
-        }
-
-        /**
-         * Get scroll distance of align an item (depends on ALIGN_LOW_EDGE, ALIGN_HIGH_EDGE or the
-         * item should be aligned to key line). The scroll distance will be capped by
-         * {@link #getMinScroll()} and {@link #getMaxScroll()}.
-         */
-        public final int getScroll(int viewCenter) {
-            final int size = getSize();
-            final int keyLine = calculateKeyline();
-            final boolean isMinUnknown = isMinUnknown();
-            final boolean isMaxUnknown = isMaxUnknown();
-            if (!isMinUnknown) {
-                final int keyLineToMinEdge = keyLine - mPaddingMin;
-                if ((!mReversedFlow ? (mWindowAlignment & WINDOW_ALIGN_LOW_EDGE) != 0
-                     : (mWindowAlignment & WINDOW_ALIGN_HIGH_EDGE) != 0)
-                        && (viewCenter - mMinEdge <= keyLineToMinEdge)) {
-                    // view center is before key line: align the min edge (first child) to padding.
-                    int alignToMin = mMinEdge - mPaddingMin;
-                    // Also we need make sure don't over scroll
-                    if (!isMaxUnknown && alignToMin > mMaxScroll) {
-                        alignToMin = mMaxScroll;
-                    }
-                    return alignToMin;
-                }
-            }
-            if (!isMaxUnknown) {
-                final int keyLineToMaxEdge = size - keyLine - mPaddingMax;
-                if ((!mReversedFlow ? (mWindowAlignment & WINDOW_ALIGN_HIGH_EDGE) != 0
-                        : (mWindowAlignment & WINDOW_ALIGN_LOW_EDGE) != 0)
-                        && (mMaxEdge - viewCenter <= keyLineToMaxEdge)) {
-                    // view center is after key line: align the max edge (last child) to padding.
-                    int alignToMax = mMaxEdge - (size - mPaddingMax);
-                    // Also we need make sure don't over scroll
-                    if (!isMinUnknown && alignToMax < mMinScroll) {
-                        alignToMax = mMinScroll;
-                    }
-                    return alignToMax;
-                }
-            }
-            // else put view center at key line.
-            return calculateScrollToKeyLine(viewCenter, keyLine);
-        }
-
-        public final void setReversedFlow(boolean reversedFlow) {
-            mReversedFlow = reversedFlow;
-        }
-
-        @Override
-        public String toString() {
-            return " min:" + mMinEdge + " " + mMinScroll + " max:" + mMaxEdge + " " + mMaxScroll;
-        }
-
-    }
-
-    private int mOrientation = HORIZONTAL;
-
-    public final Axis vertical = new Axis("vertical");
-
-    public final Axis horizontal = new Axis("horizontal");
-
-    private Axis mMainAxis = horizontal;
-
-    private Axis mSecondAxis = vertical;
-
-    public final Axis mainAxis() {
-        return mMainAxis;
-    }
-
-    public final Axis secondAxis() {
-        return mSecondAxis;
-    }
-
-    public final void setOrientation(int orientation) {
-        mOrientation = orientation;
-        if (mOrientation == HORIZONTAL) {
-            mMainAxis = horizontal;
-            mSecondAxis = vertical;
-        } else {
-            mMainAxis = vertical;
-            mSecondAxis = horizontal;
-        }
-    }
-
-    public final int getOrientation() {
-        return mOrientation;
-    }
-
-    public final void reset() {
-        mainAxis().reset();
-    }
-
-    @Override
-    public String toString() {
-        return new StringBuffer().append("horizontal=")
-                .append(horizontal.toString())
-                .append("; vertical=")
-                .append(vertical.toString())
-                .toString();
-    }
-
-}
diff --git a/v17/leanback/tests/Android.mk b/v17/leanback/tests/Android.mk
deleted file mode 100644
index 6c1a709..0000000
--- a/v17/leanback/tests/Android.mk
+++ /dev/null
@@ -1,43 +0,0 @@
-# Copyright (C) 2015 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_RESOURCE_DIR = \
-        $(LOCAL_PATH)/res \
-        $(LOCAL_PATH)/../res \
-        $(LOCAL_PATH)/../../v7/recyclerview/res
-LOCAL_AAPT_FLAGS := \
-        --auto-add-overlay \
-        --extra-packages android.support.v17.leanback \
-        --extra-packages android.support.v7.recyclerview
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
-        android-support-v4 \
-        android-support-v7-recyclerview \
-        android-support-v17-leanback \
-        android-support-test \
-        mockito-target-minus-junit4
-
-LOCAL_PACKAGE_NAME := AndroidLeanbackTests
-
-include $(BUILD_PACKAGE)
diff --git a/v17/leanback/tests/generatev4.py b/v17/leanback/tests/generatev4.py
deleted file mode 100755
index d87ff6f..0000000
--- a/v17/leanback/tests/generatev4.py
+++ /dev/null
@@ -1,168 +0,0 @@
-#!/usr/bin/python
-
-# Copyright (C) 2015 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import os
-import sys
-
-print "Generate v4 fragment related code for leanback"
-
-####### generate XXXTestFragment classes #######
-
-files = ['BrowseTest', 'GuidedStepTest', 'PlaybackTest', 'DetailsTest']
-
-cls = ['BrowseTest', 'Background', 'Base', 'BaseRow', 'Browse', 'Details', 'Error', 'Headers',
-      'PlaybackOverlay', 'Rows', 'Search', 'VerticalGrid', 'Branded',
-      'GuidedStepTest', 'GuidedStep', 'RowsTest', 'PlaybackTest', 'Playback', 'Video',
-      'DetailsTest']
-
-for w in files:
-    print "copy {}Fragment to {}SupportFragment".format(w, w)
-
-    file = open('java/android/support/v17/leanback/app/{}Fragment.java'.format(w), 'r')
-    outfile = open('java/android/support/v17/leanback/app/{}SupportFragment.java'.format(w), 'w')
-
-    outfile.write("// CHECKSTYLE:OFF Generated code\n")
-    outfile.write("/* This file is auto-generated from {}Fragment.java.  DO NOT MODIFY. */\n\n".format(w))
-
-    for line in file:
-        for w in cls:
-            line = line.replace('{}Fragment'.format(w), '{}SupportFragment'.format(w))
-        line = line.replace('android.app.Fragment', 'android.support.v4.app.Fragment')
-        line = line.replace('android.app.Activity', 'android.support.v4.app.FragmentActivity')
-        line = line.replace('Activity getActivity()', 'FragmentActivity getActivity()')
-        outfile.write(line)
-    file.close()
-    outfile.close()
-
-####### generate XXXFragmentTestBase classes #######
-
-testcls = ['GuidedStep', 'Single']
-
-for w in testcls:
-    print "copy {}FrgamentTestBase to {}SupportFragmentTestBase".format(w, w)
-
-    file = open('java/android/support/v17/leanback/app/{}FragmentTestBase.java'.format(w), 'r')
-    outfile = open('java/android/support/v17/leanback/app/{}SupportFragmentTestBase.java'.format(w), 'w')
-
-    outfile.write("// CHECKSTYLE:OFF Generated code\n")
-    outfile.write("/* This file is auto-generated from {}FrgamentTestBase.java.  DO NOT MODIFY. */\n\n".format(w))
-
-    for line in file:
-        for w in cls:
-            line = line.replace('{}Fragment'.format(w), '{}SupportFragment'.format(w))
-        for w in testcls:
-            line = line.replace('{}FragmentTestBase'.format(w), '{}SupportFragmentTestBase'.format(w))
-            line = line.replace('{}FragmentTestActivity'.format(w), '{}SupportFragmentTestActivity'.format(w))
-            line = line.replace('{}TestFragment'.format(w), '{}TestSupportFragment'.format(w))
-        line = line.replace('android.app.Fragment', 'android.support.v4.app.Fragment')
-        line = line.replace('android.app.Activity', 'android.support.v4.app.FragmentActivity')
-        outfile.write(line)
-    file.close()
-    outfile.close()
-
-####### generate XXXFragmentTest classes #######
-
-testcls = ['Browse', 'GuidedStep', 'VerticalGrid', 'Playback', 'Video', 'Details', 'Rows', 'Headers']
-
-for w in testcls:
-    print "copy {}FrgamentTest to {}SupportFragmentTest".format(w, w)
-
-    file = open('java/android/support/v17/leanback/app/{}FragmentTest.java'.format(w), 'r')
-    outfile = open('java/android/support/v17/leanback/app/{}SupportFragmentTest.java'.format(w), 'w')
-
-    outfile.write("// CHECKSTYLE:OFF Generated code\n")
-    outfile.write("/* This file is auto-generated from {}FragmentTest.java.  DO NOT MODIFY. */\n\n".format(w))
-
-    for line in file:
-        for w in cls:
-            line = line.replace('{}Fragment'.format(w), '{}SupportFragment'.format(w))
-        for w in testcls:
-            line = line.replace('SingleFragmentTestBase', 'SingleSupportFragmentTestBase')
-            line = line.replace('SingleFragmentTestActivity', 'SingleSupportFragmentTestActivity')
-            line = line.replace('{}FragmentTestBase'.format(w), '{}SupportFragmentTestBase'.format(w))
-            line = line.replace('{}FragmentTest'.format(w), '{}SupportFragmentTest'.format(w))
-            line = line.replace('{}FragmentTestActivity'.format(w), '{}SupportFragmentTestActivity'.format(w))
-            line = line.replace('{}TestFragment'.format(w), '{}TestSupportFragment'.format(w))
-        line = line.replace('android.app.Fragment', 'android.support.v4.app.Fragment')
-        line = line.replace('android.app.Activity', 'android.support.v4.app.FragmentActivity')
-	line = line.replace('extends Activity', 'extends FragmentActivity')
-	line = line.replace('Activity.this.getFragmentManager', 'Activity.this.getSupportFragmentManager')
-	line = line.replace('tivity.getFragmentManager', 'tivity.getSupportFragmentManager')
-        outfile.write(line)
-    file.close()
-    outfile.close()
-
-
-####### generate XXXTestActivity classes #######
-testcls = ['Browse', 'GuidedStep', 'Single']
-
-for w in testcls:
-    print "copy {}FragmentTestActivity to {}SupportFragmentTestActivity".format(w, w)
-    file = open('java/android/support/v17/leanback/app/{}FragmentTestActivity.java'.format(w), 'r')
-    outfile = open('java/android/support/v17/leanback/app/{}SupportFragmentTestActivity.java'.format(w), 'w')
-    outfile.write("// CHECKSTYLE:OFF Generated code\n")
-    outfile.write("/* This file is auto-generated from {}FragmentTestActivity.java.  DO NOT MODIFY. */\n\n".format(w))
-    for line in file:
-        line = line.replace('{}TestFragment'.format(w), '{}TestSupportFragment'.format(w))
-        line = line.replace('{}FragmentTestActivity'.format(w), '{}SupportFragmentTestActivity'.format(w))
-        line = line.replace('android.app.Fragment', 'android.support.v4.app.Fragment')
-        line = line.replace('android.app.Activity', 'android.support.v4.app.FragmentActivity')
-        line = line.replace('extends Activity', 'extends FragmentActivity')
-        line = line.replace('getFragmentManager', 'getSupportFragmentManager')
-        outfile.write(line)
-    file.close()
-    outfile.close()
-
-####### generate Float parallax test #######
-
-print "copy ParallaxIntEffectTest to ParallaxFloatEffectTest"
-file = open('java/android/support/v17/leanback/widget/ParallaxIntEffectTest.java', 'r')
-outfile = open('java/android/support/v17/leanback/widget/ParallaxFloatEffectTest.java', 'w')
-outfile.write("// CHECKSTYLE:OFF Generated code\n")
-outfile.write("/* This file is auto-generated from ParallaxIntEffectTest.java.  DO NOT MODIFY. */\n\n")
-for line in file:
-    line = line.replace('IntEffect', 'FloatEffect')
-    line = line.replace('IntParallax', 'FloatParallax')
-    line = line.replace('IntProperty', 'FloatProperty')
-    line = line.replace('intValue()', 'floatValue()')
-    line = line.replace('int screenMax', 'float screenMax')
-    line = line.replace('assertEquals((int)', 'assertFloatEquals((float)')
-    line = line.replace('(int)', '(float)')
-    line = line.replace('int[', 'float[')
-    line = line.replace('Integer', 'Float');
-    outfile.write(line)
-file.close()
-outfile.close()
-
-
-print "copy ParallaxIntTest to ParallaxFloatTest"
-file = open('java/android/support/v17/leanback/widget/ParallaxIntTest.java', 'r')
-outfile = open('java/android/support/v17/leanback/widget/ParallaxFloatTest.java', 'w')
-outfile.write("// CHECKSTYLE:OFF Generated code\n")
-outfile.write("/* This file is auto-generated from ParallaxIntTest.java.  DO NOT MODIFY. */\n\n")
-for line in file:
-    line = line.replace('ParallaxIntTest', 'ParallaxFloatTest')
-    line = line.replace('IntParallax', 'FloatParallax')
-    line = line.replace('IntProperty', 'FloatProperty')
-    line = line.replace('verifyIntProperties', 'verifyFloatProperties')
-    line = line.replace('intValue()', 'floatValue()')
-    line = line.replace('int screenMax', 'float screenMax')
-    line = line.replace('assertEquals((int)', 'assertFloatEquals((float)')
-    line = line.replace('(int)', '(float)')
-    outfile.write(line)
-file.close()
-outfile.close()
-
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/BrowseFragmentTest.java b/v17/leanback/tests/java/android/support/v17/leanback/app/BrowseFragmentTest.java
deleted file mode 100644
index 06a1217..0000000
--- a/v17/leanback/tests/java/android/support/v17/leanback/app/BrowseFragmentTest.java
+++ /dev/null
@@ -1,254 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.support.v17.leanback.app;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.verify;
-
-import android.content.Intent;
-import android.os.Build;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.LargeTest;
-import android.support.test.rule.ActivityTestRule;
-import android.support.test.runner.AndroidJUnit4;
-import android.support.v17.leanback.testutils.PollingCheck;
-import android.support.v17.leanback.widget.ItemBridgeAdapter;
-import android.support.v17.leanback.widget.ListRowPresenter;
-import android.support.v17.leanback.widget.Presenter;
-import android.support.v7.widget.RecyclerView;
-import android.view.KeyEvent;
-import android.view.View;
-
-import org.junit.After;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mockito;
-
-@LargeTest
-@RunWith(AndroidJUnit4.class)
-public class BrowseFragmentTest {
-
-    static final String TAG = "BrowseFragmentTest";
-    static final long WAIT_TRANSIITON_TIMEOUT = 10000;
-
-    @Rule
-    public ActivityTestRule<BrowseFragmentTestActivity> activityTestRule =
-            new ActivityTestRule<>(BrowseFragmentTestActivity.class, false, false);
-    private BrowseFragmentTestActivity mActivity;
-
-    @After
-    public void afterTest() throws Throwable {
-        activityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                if (mActivity != null) {
-                    mActivity.finish();
-                    mActivity = null;
-                }
-            }
-        });
-    }
-
-    void waitForEntranceTransitionFinished() {
-        PollingCheck.waitFor(WAIT_TRANSIITON_TIMEOUT, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                if (Build.VERSION.SDK_INT >= 21) {
-                    return mActivity.getBrowseTestFragment() != null
-                            && mActivity.getBrowseTestFragment().mEntranceTransitionEnded;
-                } else {
-                    // when entrance transition not supported, wait main fragment loaded.
-                    return mActivity.getBrowseTestFragment() != null
-                            && mActivity.getBrowseTestFragment().getMainFragment() != null;
-                }
-            }
-        });
-    }
-
-    void waitForHeaderTransitionFinished() {
-        View row = mActivity.getBrowseTestFragment().getRowsFragment().getRowViewHolder(
-                mActivity.getBrowseTestFragment().getSelectedPosition()).view;
-        PollingCheck.waitFor(WAIT_TRANSIITON_TIMEOUT, new PollingCheck.ViewStableOnScreen(row));
-    }
-
-    @Test
-    public void testTwoBackKeysWithBackStack() throws Throwable {
-        final long dataLoadingDelay = 1000;
-        Intent intent = new Intent();
-        intent.putExtra(BrowseFragmentTestActivity.EXTRA_LOAD_DATA_DELAY, dataLoadingDelay);
-        intent.putExtra(BrowseFragmentTestActivity.EXTRA_ADD_TO_BACKSTACK , true);
-        mActivity = activityTestRule.launchActivity(intent);
-
-        waitForEntranceTransitionFinished();
-
-        assertNotNull(mActivity.getBrowseTestFragment().getMainFragment());
-        sendKeys(KeyEvent.KEYCODE_DPAD_RIGHT);
-        waitForHeaderTransitionFinished();
-        sendKeys(KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_BACK);
-    }
-
-    @Test
-    public void testTwoBackKeysWithoutBackStack() throws Throwable {
-        final long dataLoadingDelay = 1000;
-        Intent intent = new Intent();
-        intent.putExtra(BrowseFragmentTestActivity.EXTRA_LOAD_DATA_DELAY, dataLoadingDelay);
-        intent.putExtra(BrowseFragmentTestActivity.EXTRA_ADD_TO_BACKSTACK , false);
-        mActivity = activityTestRule.launchActivity(intent);
-
-        waitForEntranceTransitionFinished();
-
-        assertNotNull(mActivity.getBrowseTestFragment().getMainFragment());
-        sendKeys(KeyEvent.KEYCODE_DPAD_RIGHT);
-        waitForHeaderTransitionFinished();
-        sendKeys(KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_BACK);
-    }
-
-    @Test
-    public void testPressRightBeforeMainFragmentCreated() throws Throwable {
-        final long dataLoadingDelay = 1000;
-        Intent intent = new Intent();
-        intent.putExtra(BrowseFragmentTestActivity.EXTRA_LOAD_DATA_DELAY, dataLoadingDelay);
-        intent.putExtra(BrowseFragmentTestActivity.EXTRA_ADD_TO_BACKSTACK , false);
-        mActivity = activityTestRule.launchActivity(intent);
-
-        assertNull(mActivity.getBrowseTestFragment().getMainFragment());
-        sendKeys(KeyEvent.KEYCODE_DPAD_RIGHT);
-    }
-
-    @Test
-    public void testSelectCardOnARow() throws Throwable {
-        final int selectRow = 10;
-        final int selectItem = 20;
-        Intent intent = new Intent();
-        final long dataLoadingDelay = 1000;
-        intent.putExtra(BrowseFragmentTestActivity.EXTRA_LOAD_DATA_DELAY, dataLoadingDelay);
-        intent.putExtra(BrowseFragmentTestActivity.EXTRA_ADD_TO_BACKSTACK , true);
-        mActivity = activityTestRule.launchActivity(intent);
-
-        waitForEntranceTransitionFinished();
-
-        Presenter.ViewHolderTask itemTask = Mockito.spy(
-                new ItemSelectionTask(mActivity, selectRow));
-
-        final ListRowPresenter.SelectItemViewHolderTask task =
-                new ListRowPresenter.SelectItemViewHolderTask(selectItem);
-        task.setItemTask(itemTask);
-
-        mActivity.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mActivity.getBrowseTestFragment().setSelectedPosition(selectRow, true, task);
-            }
-        });
-
-        verify(itemTask, timeout(5000).times(1)).run(any(Presenter.ViewHolder.class));
-
-        activityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                ListRowPresenter.ViewHolder row = (ListRowPresenter.ViewHolder) mActivity
-                        .getBrowseTestFragment().getRowsFragment().getRowViewHolder(selectRow);
-                assertNotNull(dumpRecyclerView(mActivity.getBrowseTestFragment().getGridView()), row);
-                assertNotNull(row.getGridView());
-                assertEquals(selectItem, row.getGridView().getSelectedPosition());
-            }
-        });
-    }
-
-    @Test
-    public void activityRecreate_notCrash() throws Throwable {
-        final long dataLoadingDelay = 1000;
-        Intent intent = new Intent();
-        intent.putExtra(BrowseFragmentTestActivity.EXTRA_LOAD_DATA_DELAY, dataLoadingDelay);
-        intent.putExtra(BrowseFragmentTestActivity.EXTRA_ADD_TO_BACKSTACK , false);
-        intent.putExtra(BrowseFragmentTestActivity.EXTRA_SET_ADAPTER_AFTER_DATA_LOAD, true);
-        mActivity = activityTestRule.launchActivity(intent);
-
-        waitForEntranceTransitionFinished();
-
-        InstrumentationRegistry.getInstrumentation().callActivityOnRestart(mActivity);
-        activityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mActivity.recreate();
-            }
-        });
-    }
-
-
-    @Test
-    public void lateLoadingHeaderDisabled() throws Throwable {
-        final long dataLoadingDelay = 1000;
-        Intent intent = new Intent();
-        intent.putExtra(BrowseFragmentTestActivity.EXTRA_LOAD_DATA_DELAY, dataLoadingDelay);
-        intent.putExtra(BrowseFragmentTestActivity.EXTRA_HEADERS_STATE,
-                BrowseFragment.HEADERS_DISABLED);
-        mActivity = activityTestRule.launchActivity(intent);
-        waitForEntranceTransitionFinished();
-        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return mActivity.getBrowseTestFragment().getGridView() != null
-                        && mActivity.getBrowseTestFragment().getGridView().getChildCount() > 0;
-            }
-        });
-    }
-
-    private void sendKeys(int ...keys) {
-        for (int i = 0; i < keys.length; i++) {
-            InstrumentationRegistry.getInstrumentation().sendKeyDownUpSync(keys[i]);
-        }
-    }
-
-    public static class ItemSelectionTask extends Presenter.ViewHolderTask {
-
-        private final BrowseFragmentTestActivity activity;
-        private final int expectedRow;
-
-        public ItemSelectionTask(BrowseFragmentTestActivity activity, int expectedRow) {
-            this.activity = activity;
-            this.expectedRow = expectedRow;
-        }
-
-        @Override
-        public void run(Presenter.ViewHolder holder) {
-            android.util.Log.d(TAG, dumpRecyclerView(activity.getBrowseTestFragment()
-                    .getGridView()));
-            android.util.Log.d(TAG, "Row " + expectedRow + " " + activity.getBrowseTestFragment()
-                    .getRowsFragment().getRowViewHolder(expectedRow), new Exception());
-        }
-    }
-
-    static String dumpRecyclerView(RecyclerView recyclerView) {
-        StringBuffer b = new StringBuffer();
-        for (int i = 0; i < recyclerView.getChildCount(); i++) {
-            View child = recyclerView.getChildAt(i);
-            ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder)
-                    recyclerView.getChildViewHolder(child);
-            b.append("child").append(i).append(":").append(vh);
-            if (vh != null) {
-                b.append(",").append(vh.getViewHolder());
-            }
-            b.append(";");
-        }
-        return b.toString();
-    }
-}
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/BrowseFragmentTestActivity.java b/v17/leanback/tests/java/android/support/v17/leanback/app/BrowseFragmentTestActivity.java
deleted file mode 100644
index 605a9ca..0000000
--- a/v17/leanback/tests/java/android/support/v17/leanback/app/BrowseFragmentTestActivity.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.support.v17.leanback.app;
-
-import android.app.Activity;
-import android.app.FragmentTransaction;
-import android.content.Intent;
-import android.os.Bundle;
-import android.support.v17.leanback.test.R;
-
-public class BrowseFragmentTestActivity extends Activity {
-
-    public static final String EXTRA_ADD_TO_BACKSTACK = "addToBackStack";
-    public static final String EXTRA_NUM_ROWS = "numRows";
-    public static final String EXTRA_REPEAT_PER_ROW = "repeatPerRow";
-    public static final String EXTRA_LOAD_DATA_DELAY = "loadDataDelay";
-    public static final String EXTRA_TEST_ENTRANCE_TRANSITION = "testEntranceTransition";
-    public static final String EXTRA_SET_ADAPTER_AFTER_DATA_LOAD = "set_adapter_after_data_load";
-    public static final String EXTRA_HEADERS_STATE = "headers_state";
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        Intent intent = getIntent();
-
-        setContentView(R.layout.browse);
-        if (savedInstanceState == null) {
-            Bundle arguments = new Bundle();
-            arguments.putAll(intent.getExtras());
-            BrowseTestFragment fragment = new BrowseTestFragment();
-            fragment.setArguments(arguments);
-            FragmentTransaction ft = getFragmentManager().beginTransaction();
-            ft.replace(R.id.main_frame, fragment);
-            if (intent.getBooleanExtra(EXTRA_ADD_TO_BACKSTACK, false)) {
-                ft.addToBackStack(null);
-            }
-            ft.commit();
-        }
-    }
-
-    public BrowseTestFragment getBrowseTestFragment() {
-        return (BrowseTestFragment) getFragmentManager().findFragmentById(R.id.main_frame);
-    }
-}
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/BrowseSupportFragmentTest.java b/v17/leanback/tests/java/android/support/v17/leanback/app/BrowseSupportFragmentTest.java
deleted file mode 100644
index f578874..0000000
--- a/v17/leanback/tests/java/android/support/v17/leanback/app/BrowseSupportFragmentTest.java
+++ /dev/null
@@ -1,257 +0,0 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from BrowseFragmentTest.java.  DO NOT MODIFY. */
-
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.support.v17.leanback.app;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.verify;
-
-import android.content.Intent;
-import android.os.Build;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.LargeTest;
-import android.support.test.rule.ActivityTestRule;
-import android.support.test.runner.AndroidJUnit4;
-import android.support.v17.leanback.testutils.PollingCheck;
-import android.support.v17.leanback.widget.ItemBridgeAdapter;
-import android.support.v17.leanback.widget.ListRowPresenter;
-import android.support.v17.leanback.widget.Presenter;
-import android.support.v7.widget.RecyclerView;
-import android.view.KeyEvent;
-import android.view.View;
-
-import org.junit.After;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mockito;
-
-@LargeTest
-@RunWith(AndroidJUnit4.class)
-public class BrowseSupportFragmentTest {
-
-    static final String TAG = "BrowseSupportFragmentTest";
-    static final long WAIT_TRANSIITON_TIMEOUT = 10000;
-
-    @Rule
-    public ActivityTestRule<BrowseSupportFragmentTestActivity> activityTestRule =
-            new ActivityTestRule<>(BrowseSupportFragmentTestActivity.class, false, false);
-    private BrowseSupportFragmentTestActivity mActivity;
-
-    @After
-    public void afterTest() throws Throwable {
-        activityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                if (mActivity != null) {
-                    mActivity.finish();
-                    mActivity = null;
-                }
-            }
-        });
-    }
-
-    void waitForEntranceTransitionFinished() {
-        PollingCheck.waitFor(WAIT_TRANSIITON_TIMEOUT, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                if (Build.VERSION.SDK_INT >= 21) {
-                    return mActivity.getBrowseTestSupportFragment() != null
-                            && mActivity.getBrowseTestSupportFragment().mEntranceTransitionEnded;
-                } else {
-                    // when entrance transition not supported, wait main fragment loaded.
-                    return mActivity.getBrowseTestSupportFragment() != null
-                            && mActivity.getBrowseTestSupportFragment().getMainFragment() != null;
-                }
-            }
-        });
-    }
-
-    void waitForHeaderTransitionFinished() {
-        View row = mActivity.getBrowseTestSupportFragment().getRowsSupportFragment().getRowViewHolder(
-                mActivity.getBrowseTestSupportFragment().getSelectedPosition()).view;
-        PollingCheck.waitFor(WAIT_TRANSIITON_TIMEOUT, new PollingCheck.ViewStableOnScreen(row));
-    }
-
-    @Test
-    public void testTwoBackKeysWithBackStack() throws Throwable {
-        final long dataLoadingDelay = 1000;
-        Intent intent = new Intent();
-        intent.putExtra(BrowseSupportFragmentTestActivity.EXTRA_LOAD_DATA_DELAY, dataLoadingDelay);
-        intent.putExtra(BrowseSupportFragmentTestActivity.EXTRA_ADD_TO_BACKSTACK , true);
-        mActivity = activityTestRule.launchActivity(intent);
-
-        waitForEntranceTransitionFinished();
-
-        assertNotNull(mActivity.getBrowseTestSupportFragment().getMainFragment());
-        sendKeys(KeyEvent.KEYCODE_DPAD_RIGHT);
-        waitForHeaderTransitionFinished();
-        sendKeys(KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_BACK);
-    }
-
-    @Test
-    public void testTwoBackKeysWithoutBackStack() throws Throwable {
-        final long dataLoadingDelay = 1000;
-        Intent intent = new Intent();
-        intent.putExtra(BrowseSupportFragmentTestActivity.EXTRA_LOAD_DATA_DELAY, dataLoadingDelay);
-        intent.putExtra(BrowseSupportFragmentTestActivity.EXTRA_ADD_TO_BACKSTACK , false);
-        mActivity = activityTestRule.launchActivity(intent);
-
-        waitForEntranceTransitionFinished();
-
-        assertNotNull(mActivity.getBrowseTestSupportFragment().getMainFragment());
-        sendKeys(KeyEvent.KEYCODE_DPAD_RIGHT);
-        waitForHeaderTransitionFinished();
-        sendKeys(KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_BACK);
-    }
-
-    @Test
-    public void testPressRightBeforeMainFragmentCreated() throws Throwable {
-        final long dataLoadingDelay = 1000;
-        Intent intent = new Intent();
-        intent.putExtra(BrowseSupportFragmentTestActivity.EXTRA_LOAD_DATA_DELAY, dataLoadingDelay);
-        intent.putExtra(BrowseSupportFragmentTestActivity.EXTRA_ADD_TO_BACKSTACK , false);
-        mActivity = activityTestRule.launchActivity(intent);
-
-        assertNull(mActivity.getBrowseTestSupportFragment().getMainFragment());
-        sendKeys(KeyEvent.KEYCODE_DPAD_RIGHT);
-    }
-
-    @Test
-    public void testSelectCardOnARow() throws Throwable {
-        final int selectRow = 10;
-        final int selectItem = 20;
-        Intent intent = new Intent();
-        final long dataLoadingDelay = 1000;
-        intent.putExtra(BrowseSupportFragmentTestActivity.EXTRA_LOAD_DATA_DELAY, dataLoadingDelay);
-        intent.putExtra(BrowseSupportFragmentTestActivity.EXTRA_ADD_TO_BACKSTACK , true);
-        mActivity = activityTestRule.launchActivity(intent);
-
-        waitForEntranceTransitionFinished();
-
-        Presenter.ViewHolderTask itemTask = Mockito.spy(
-                new ItemSelectionTask(mActivity, selectRow));
-
-        final ListRowPresenter.SelectItemViewHolderTask task =
-                new ListRowPresenter.SelectItemViewHolderTask(selectItem);
-        task.setItemTask(itemTask);
-
-        mActivity.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mActivity.getBrowseTestSupportFragment().setSelectedPosition(selectRow, true, task);
-            }
-        });
-
-        verify(itemTask, timeout(5000).times(1)).run(any(Presenter.ViewHolder.class));
-
-        activityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                ListRowPresenter.ViewHolder row = (ListRowPresenter.ViewHolder) mActivity
-                        .getBrowseTestSupportFragment().getRowsSupportFragment().getRowViewHolder(selectRow);
-                assertNotNull(dumpRecyclerView(mActivity.getBrowseTestSupportFragment().getGridView()), row);
-                assertNotNull(row.getGridView());
-                assertEquals(selectItem, row.getGridView().getSelectedPosition());
-            }
-        });
-    }
-
-    @Test
-    public void activityRecreate_notCrash() throws Throwable {
-        final long dataLoadingDelay = 1000;
-        Intent intent = new Intent();
-        intent.putExtra(BrowseSupportFragmentTestActivity.EXTRA_LOAD_DATA_DELAY, dataLoadingDelay);
-        intent.putExtra(BrowseSupportFragmentTestActivity.EXTRA_ADD_TO_BACKSTACK , false);
-        intent.putExtra(BrowseSupportFragmentTestActivity.EXTRA_SET_ADAPTER_AFTER_DATA_LOAD, true);
-        mActivity = activityTestRule.launchActivity(intent);
-
-        waitForEntranceTransitionFinished();
-
-        InstrumentationRegistry.getInstrumentation().callActivityOnRestart(mActivity);
-        activityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mActivity.recreate();
-            }
-        });
-    }
-
-
-    @Test
-    public void lateLoadingHeaderDisabled() throws Throwable {
-        final long dataLoadingDelay = 1000;
-        Intent intent = new Intent();
-        intent.putExtra(BrowseSupportFragmentTestActivity.EXTRA_LOAD_DATA_DELAY, dataLoadingDelay);
-        intent.putExtra(BrowseSupportFragmentTestActivity.EXTRA_HEADERS_STATE,
-                BrowseSupportFragment.HEADERS_DISABLED);
-        mActivity = activityTestRule.launchActivity(intent);
-        waitForEntranceTransitionFinished();
-        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return mActivity.getBrowseTestSupportFragment().getGridView() != null
-                        && mActivity.getBrowseTestSupportFragment().getGridView().getChildCount() > 0;
-            }
-        });
-    }
-
-    private void sendKeys(int ...keys) {
-        for (int i = 0; i < keys.length; i++) {
-            InstrumentationRegistry.getInstrumentation().sendKeyDownUpSync(keys[i]);
-        }
-    }
-
-    public static class ItemSelectionTask extends Presenter.ViewHolderTask {
-
-        private final BrowseSupportFragmentTestActivity activity;
-        private final int expectedRow;
-
-        public ItemSelectionTask(BrowseSupportFragmentTestActivity activity, int expectedRow) {
-            this.activity = activity;
-            this.expectedRow = expectedRow;
-        }
-
-        @Override
-        public void run(Presenter.ViewHolder holder) {
-            android.util.Log.d(TAG, dumpRecyclerView(activity.getBrowseTestSupportFragment()
-                    .getGridView()));
-            android.util.Log.d(TAG, "Row " + expectedRow + " " + activity.getBrowseTestSupportFragment()
-                    .getRowsSupportFragment().getRowViewHolder(expectedRow), new Exception());
-        }
-    }
-
-    static String dumpRecyclerView(RecyclerView recyclerView) {
-        StringBuffer b = new StringBuffer();
-        for (int i = 0; i < recyclerView.getChildCount(); i++) {
-            View child = recyclerView.getChildAt(i);
-            ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder)
-                    recyclerView.getChildViewHolder(child);
-            b.append("child").append(i).append(":").append(vh);
-            if (vh != null) {
-                b.append(",").append(vh.getViewHolder());
-            }
-            b.append(";");
-        }
-        return b.toString();
-    }
-}
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/BrowseSupportFragmentTestActivity.java b/v17/leanback/tests/java/android/support/v17/leanback/app/BrowseSupportFragmentTestActivity.java
deleted file mode 100644
index 9df846f..0000000
--- a/v17/leanback/tests/java/android/support/v17/leanback/app/BrowseSupportFragmentTestActivity.java
+++ /dev/null
@@ -1,60 +0,0 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from BrowseFragmentTestActivity.java.  DO NOT MODIFY. */
-
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.support.v17.leanback.app;
-
-import android.support.v4.app.FragmentActivity;
-import android.support.v4.app.FragmentTransaction;
-import android.content.Intent;
-import android.os.Bundle;
-import android.support.v17.leanback.test.R;
-
-public class BrowseSupportFragmentTestActivity extends FragmentActivity {
-
-    public static final String EXTRA_ADD_TO_BACKSTACK = "addToBackStack";
-    public static final String EXTRA_NUM_ROWS = "numRows";
-    public static final String EXTRA_REPEAT_PER_ROW = "repeatPerRow";
-    public static final String EXTRA_LOAD_DATA_DELAY = "loadDataDelay";
-    public static final String EXTRA_TEST_ENTRANCE_TRANSITION = "testEntranceTransition";
-    public static final String EXTRA_SET_ADAPTER_AFTER_DATA_LOAD = "set_adapter_after_data_load";
-    public static final String EXTRA_HEADERS_STATE = "headers_state";
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        Intent intent = getIntent();
-
-        setContentView(R.layout.browse);
-        if (savedInstanceState == null) {
-            Bundle arguments = new Bundle();
-            arguments.putAll(intent.getExtras());
-            BrowseTestSupportFragment fragment = new BrowseTestSupportFragment();
-            fragment.setArguments(arguments);
-            FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
-            ft.replace(R.id.main_frame, fragment);
-            if (intent.getBooleanExtra(EXTRA_ADD_TO_BACKSTACK, false)) {
-                ft.addToBackStack(null);
-            }
-            ft.commit();
-        }
-    }
-
-    public BrowseTestSupportFragment getBrowseTestSupportFragment() {
-        return (BrowseTestSupportFragment) getSupportFragmentManager().findFragmentById(R.id.main_frame);
-    }
-}
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/BrowseTestFragment.java b/v17/leanback/tests/java/android/support/v17/leanback/app/BrowseTestFragment.java
deleted file mode 100644
index 4fe79f0..0000000
--- a/v17/leanback/tests/java/android/support/v17/leanback/app/BrowseTestFragment.java
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-package android.support.v17.leanback.app;
-
-import static android.support.v17.leanback.app.BrowseFragmentTestActivity.EXTRA_HEADERS_STATE;
-import static android.support.v17.leanback.app.BrowseFragmentTestActivity.EXTRA_LOAD_DATA_DELAY;
-import static android.support.v17.leanback.app.BrowseFragmentTestActivity.EXTRA_NUM_ROWS;
-import static android.support.v17.leanback.app.BrowseFragmentTestActivity.EXTRA_REPEAT_PER_ROW;
-import static android.support.v17.leanback.app.BrowseFragmentTestActivity.EXTRA_SET_ADAPTER_AFTER_DATA_LOAD;
-import static android.support.v17.leanback.app.BrowseFragmentTestActivity.EXTRA_TEST_ENTRANCE_TRANSITION;
-
-import android.os.Bundle;
-import android.os.Handler;
-import android.support.v17.leanback.widget.ArrayObjectAdapter;
-import android.support.v17.leanback.widget.HeaderItem;
-import android.support.v17.leanback.widget.ListRow;
-import android.support.v17.leanback.widget.ListRowPresenter;
-import android.support.v17.leanback.widget.OnItemViewClickedListener;
-import android.support.v17.leanback.widget.OnItemViewSelectedListener;
-import android.support.v17.leanback.widget.Presenter;
-import android.support.v17.leanback.widget.Row;
-import android.support.v17.leanback.widget.RowPresenter;
-import android.support.v17.leanback.widget.VerticalGridView;
-import android.util.Log;
-import android.view.View;
-
-public class BrowseTestFragment extends BrowseFragment {
-    private static final String TAG = "BrowseTestFragment";
-
-    final static int DEFAULT_NUM_ROWS = 100;
-    final static int DEFAULT_REPEAT_PER_ROW = 20;
-    final static long DEFAULT_LOAD_DATA_DELAY = 2000;
-    final static boolean DEFAULT_TEST_ENTRANCE_TRANSITION = true;
-    final static boolean DEFAULT_SET_ADAPTER_AFTER_DATA_LOAD = false;
-
-    private ArrayObjectAdapter mRowsAdapter;
-
-    // For good performance, it's important to use a single instance of
-    // a card presenter for all rows using that presenter.
-    final static StringPresenter sCardPresenter = new StringPresenter();
-
-    int NUM_ROWS;
-    int REPEAT_PER_ROW;
-    boolean mEntranceTransitionStarted;
-    boolean mEntranceTransitionEnded;
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        Log.i(TAG, "onCreate");
-        super.onCreate(savedInstanceState);
-
-        Bundle arguments = getArguments();
-        NUM_ROWS = arguments.getInt(EXTRA_NUM_ROWS, BrowseTestFragment.DEFAULT_NUM_ROWS);
-        REPEAT_PER_ROW = arguments.getInt(EXTRA_REPEAT_PER_ROW,
-                DEFAULT_REPEAT_PER_ROW);
-        long LOAD_DATA_DELAY = arguments.getLong(EXTRA_LOAD_DATA_DELAY,
-                DEFAULT_LOAD_DATA_DELAY);
-        boolean TEST_ENTRANCE_TRANSITION = arguments.getBoolean(
-                EXTRA_TEST_ENTRANCE_TRANSITION,
-                DEFAULT_TEST_ENTRANCE_TRANSITION);
-        final boolean SET_ADAPTER_AFTER_DATA_LOAD = arguments.getBoolean(
-                EXTRA_SET_ADAPTER_AFTER_DATA_LOAD,
-                DEFAULT_SET_ADAPTER_AFTER_DATA_LOAD);
-
-        if (!SET_ADAPTER_AFTER_DATA_LOAD) {
-            setupRows();
-        }
-
-        setTitle("BrowseTestFragment");
-        setHeadersState(arguments.getInt(EXTRA_HEADERS_STATE, HEADERS_ENABLED));
-
-        setOnSearchClickedListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View view) {
-                Log.i(TAG, "onSearchClicked");
-            }
-        });
-
-        setOnItemViewClickedListener(new ItemViewClickedListener());
-        setOnItemViewSelectedListener(new OnItemViewSelectedListener() {
-            @Override
-            public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
-                    RowPresenter.ViewHolder rowViewHolder, Row row) {
-                Log.i(TAG, "onItemSelected: " + item + " row " + row.getHeaderItem().getName()
-                        + " " + rowViewHolder
-                        + " " + ((ListRowPresenter.ViewHolder) rowViewHolder).getGridView());
-            }
-        });
-        if (TEST_ENTRANCE_TRANSITION) {
-            // don't run entrance transition if fragment is restored.
-            if (savedInstanceState == null) {
-                prepareEntranceTransition();
-            }
-        }
-        // simulates in a real world use case  data being loaded two seconds later
-        new Handler().postDelayed(new Runnable() {
-            @Override
-            public void run() {
-                if (getActivity() == null || getActivity().isDestroyed()) {
-                    return;
-                }
-                if (SET_ADAPTER_AFTER_DATA_LOAD) {
-                    setupRows();
-                }
-                loadData();
-                startEntranceTransition();
-            }
-        }, LOAD_DATA_DELAY);
-    }
-
-    private void setupRows() {
-        ListRowPresenter lrp = new ListRowPresenter();
-
-        mRowsAdapter = new ArrayObjectAdapter(lrp);
-
-        setAdapter(mRowsAdapter);
-    }
-
-    @Override
-    protected void onEntranceTransitionStart() {
-        super.onEntranceTransitionStart();
-        mEntranceTransitionStarted = true;
-    }
-
-    @Override
-    protected void onEntranceTransitionEnd() {
-        super.onEntranceTransitionEnd();
-        mEntranceTransitionEnded = true;
-    }
-
-    private void loadData() {
-        for (int i = 0; i < NUM_ROWS; ++i) {
-            ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(sCardPresenter);
-            int index = 0;
-            for (int j = 0; j < REPEAT_PER_ROW; ++j) {
-                listRowAdapter.add("Hello world-" + (index++));
-                listRowAdapter.add("This is a test-" + (index++));
-                listRowAdapter.add("Android TV-" + (index++));
-                listRowAdapter.add("Leanback-" + (index++));
-                listRowAdapter.add("Hello world-" + (index++));
-                listRowAdapter.add("Android TV-" + (index++));
-                listRowAdapter.add("Leanback-" + (index++));
-                listRowAdapter.add("GuidedStepFragment-" + (index++));
-            }
-            HeaderItem header = new HeaderItem(i, "Row " + i);
-            mRowsAdapter.add(new ListRow(header, listRowAdapter));
-        }
-    }
-
-    private final class ItemViewClickedListener implements OnItemViewClickedListener {
-        @Override
-        public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
-                RowPresenter.ViewHolder rowViewHolder, Row row) {
-            Log.i(TAG, "onItemClicked: " + item + " row " + row);
-        }
-    }
-
-    public VerticalGridView getGridView() {
-        return getRowsFragment().getVerticalGridView();
-    }
-}
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/BrowseTestSupportFragment.java b/v17/leanback/tests/java/android/support/v17/leanback/app/BrowseTestSupportFragment.java
deleted file mode 100644
index 2acc530..0000000
--- a/v17/leanback/tests/java/android/support/v17/leanback/app/BrowseTestSupportFragment.java
+++ /dev/null
@@ -1,175 +0,0 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from BrowseTestFragment.java.  DO NOT MODIFY. */
-
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-package android.support.v17.leanback.app;
-
-import static android.support.v17.leanback.app.BrowseSupportFragmentTestActivity.EXTRA_HEADERS_STATE;
-import static android.support.v17.leanback.app.BrowseSupportFragmentTestActivity.EXTRA_LOAD_DATA_DELAY;
-import static android.support.v17.leanback.app.BrowseSupportFragmentTestActivity.EXTRA_NUM_ROWS;
-import static android.support.v17.leanback.app.BrowseSupportFragmentTestActivity.EXTRA_REPEAT_PER_ROW;
-import static android.support.v17.leanback.app.BrowseSupportFragmentTestActivity.EXTRA_SET_ADAPTER_AFTER_DATA_LOAD;
-import static android.support.v17.leanback.app.BrowseSupportFragmentTestActivity.EXTRA_TEST_ENTRANCE_TRANSITION;
-
-import android.os.Bundle;
-import android.os.Handler;
-import android.support.v17.leanback.widget.ArrayObjectAdapter;
-import android.support.v17.leanback.widget.HeaderItem;
-import android.support.v17.leanback.widget.ListRow;
-import android.support.v17.leanback.widget.ListRowPresenter;
-import android.support.v17.leanback.widget.OnItemViewClickedListener;
-import android.support.v17.leanback.widget.OnItemViewSelectedListener;
-import android.support.v17.leanback.widget.Presenter;
-import android.support.v17.leanback.widget.Row;
-import android.support.v17.leanback.widget.RowPresenter;
-import android.support.v17.leanback.widget.VerticalGridView;
-import android.util.Log;
-import android.view.View;
-
-public class BrowseTestSupportFragment extends BrowseSupportFragment {
-    private static final String TAG = "BrowseTestSupportFragment";
-
-    final static int DEFAULT_NUM_ROWS = 100;
-    final static int DEFAULT_REPEAT_PER_ROW = 20;
-    final static long DEFAULT_LOAD_DATA_DELAY = 2000;
-    final static boolean DEFAULT_TEST_ENTRANCE_TRANSITION = true;
-    final static boolean DEFAULT_SET_ADAPTER_AFTER_DATA_LOAD = false;
-
-    private ArrayObjectAdapter mRowsAdapter;
-
-    // For good performance, it's important to use a single instance of
-    // a card presenter for all rows using that presenter.
-    final static StringPresenter sCardPresenter = new StringPresenter();
-
-    int NUM_ROWS;
-    int REPEAT_PER_ROW;
-    boolean mEntranceTransitionStarted;
-    boolean mEntranceTransitionEnded;
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        Log.i(TAG, "onCreate");
-        super.onCreate(savedInstanceState);
-
-        Bundle arguments = getArguments();
-        NUM_ROWS = arguments.getInt(EXTRA_NUM_ROWS, BrowseTestSupportFragment.DEFAULT_NUM_ROWS);
-        REPEAT_PER_ROW = arguments.getInt(EXTRA_REPEAT_PER_ROW,
-                DEFAULT_REPEAT_PER_ROW);
-        long LOAD_DATA_DELAY = arguments.getLong(EXTRA_LOAD_DATA_DELAY,
-                DEFAULT_LOAD_DATA_DELAY);
-        boolean TEST_ENTRANCE_TRANSITION = arguments.getBoolean(
-                EXTRA_TEST_ENTRANCE_TRANSITION,
-                DEFAULT_TEST_ENTRANCE_TRANSITION);
-        final boolean SET_ADAPTER_AFTER_DATA_LOAD = arguments.getBoolean(
-                EXTRA_SET_ADAPTER_AFTER_DATA_LOAD,
-                DEFAULT_SET_ADAPTER_AFTER_DATA_LOAD);
-
-        if (!SET_ADAPTER_AFTER_DATA_LOAD) {
-            setupRows();
-        }
-
-        setTitle("BrowseTestSupportFragment");
-        setHeadersState(arguments.getInt(EXTRA_HEADERS_STATE, HEADERS_ENABLED));
-
-        setOnSearchClickedListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View view) {
-                Log.i(TAG, "onSearchClicked");
-            }
-        });
-
-        setOnItemViewClickedListener(new ItemViewClickedListener());
-        setOnItemViewSelectedListener(new OnItemViewSelectedListener() {
-            @Override
-            public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
-                    RowPresenter.ViewHolder rowViewHolder, Row row) {
-                Log.i(TAG, "onItemSelected: " + item + " row " + row.getHeaderItem().getName()
-                        + " " + rowViewHolder
-                        + " " + ((ListRowPresenter.ViewHolder) rowViewHolder).getGridView());
-            }
-        });
-        if (TEST_ENTRANCE_TRANSITION) {
-            // don't run entrance transition if fragment is restored.
-            if (savedInstanceState == null) {
-                prepareEntranceTransition();
-            }
-        }
-        // simulates in a real world use case  data being loaded two seconds later
-        new Handler().postDelayed(new Runnable() {
-            @Override
-            public void run() {
-                if (getActivity() == null || getActivity().isDestroyed()) {
-                    return;
-                }
-                if (SET_ADAPTER_AFTER_DATA_LOAD) {
-                    setupRows();
-                }
-                loadData();
-                startEntranceTransition();
-            }
-        }, LOAD_DATA_DELAY);
-    }
-
-    private void setupRows() {
-        ListRowPresenter lrp = new ListRowPresenter();
-
-        mRowsAdapter = new ArrayObjectAdapter(lrp);
-
-        setAdapter(mRowsAdapter);
-    }
-
-    @Override
-    protected void onEntranceTransitionStart() {
-        super.onEntranceTransitionStart();
-        mEntranceTransitionStarted = true;
-    }
-
-    @Override
-    protected void onEntranceTransitionEnd() {
-        super.onEntranceTransitionEnd();
-        mEntranceTransitionEnded = true;
-    }
-
-    private void loadData() {
-        for (int i = 0; i < NUM_ROWS; ++i) {
-            ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(sCardPresenter);
-            int index = 0;
-            for (int j = 0; j < REPEAT_PER_ROW; ++j) {
-                listRowAdapter.add("Hello world-" + (index++));
-                listRowAdapter.add("This is a test-" + (index++));
-                listRowAdapter.add("Android TV-" + (index++));
-                listRowAdapter.add("Leanback-" + (index++));
-                listRowAdapter.add("Hello world-" + (index++));
-                listRowAdapter.add("Android TV-" + (index++));
-                listRowAdapter.add("Leanback-" + (index++));
-                listRowAdapter.add("GuidedStepSupportFragment-" + (index++));
-            }
-            HeaderItem header = new HeaderItem(i, "Row " + i);
-            mRowsAdapter.add(new ListRow(header, listRowAdapter));
-        }
-    }
-
-    private final class ItemViewClickedListener implements OnItemViewClickedListener {
-        @Override
-        public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
-                RowPresenter.ViewHolder rowViewHolder, Row row) {
-            Log.i(TAG, "onItemClicked: " + item + " row " + row);
-        }
-    }
-
-    public VerticalGridView getGridView() {
-        return getRowsSupportFragment().getVerticalGridView();
-    }
-}
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/DetailsFragmentTest.java b/v17/leanback/tests/java/android/support/v17/leanback/app/DetailsFragmentTest.java
deleted file mode 100644
index 38d08c7..0000000
--- a/v17/leanback/tests/java/android/support/v17/leanback/app/DetailsFragmentTest.java
+++ /dev/null
@@ -1,1216 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.support.v17.leanback.app;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-
-import android.animation.PropertyValuesHolder;
-import android.app.Fragment;
-import android.content.Intent;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Rect;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.SystemClock;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.LargeTest;
-import android.support.test.filters.SdkSuppress;
-import android.support.v17.leanback.R;
-import android.support.v17.leanback.graphics.FitWidthBitmapDrawable;
-import android.support.v17.leanback.media.MediaPlayerGlue;
-import android.support.v17.leanback.media.PlaybackGlueHost;
-import android.support.v17.leanback.testutils.PollingCheck;
-import android.support.v17.leanback.transition.TransitionHelper;
-import android.support.v17.leanback.util.StateMachine;
-import android.support.v17.leanback.widget.DetailsParallax;
-import android.support.v17.leanback.widget.DetailsParallaxDrawable;
-import android.support.v17.leanback.widget.ParallaxTarget;
-import android.support.v17.leanback.widget.RecyclerViewParallax;
-import android.support.v17.leanback.widget.VerticalGridView;
-import android.view.KeyEvent;
-import android.view.View;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-/**
- * Unit tests for {@link DetailsFragment}.
- */
-@RunWith(JUnit4.class)
-@LargeTest
-public class DetailsFragmentTest extends SingleFragmentTestBase {
-
-    static final int PARALLAX_VERTICAL_OFFSET = -300;
-
-    static int getCoverDrawableAlpha(DetailsFragmentBackgroundController controller) {
-        return ((FitWidthBitmapDrawable) controller.mParallaxDrawable.getCoverDrawable())
-                .getAlpha();
-    }
-
-    public static class DetailsFragmentParallax extends DetailsTestFragment {
-
-        private DetailsParallaxDrawable mParallaxDrawable;
-
-        public DetailsFragmentParallax() {
-            super();
-            mMinVerticalOffset = PARALLAX_VERTICAL_OFFSET;
-        }
-
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            Drawable coverDrawable = new FitWidthBitmapDrawable();
-            mParallaxDrawable = new DetailsParallaxDrawable(
-                    getActivity(),
-                    getParallax(),
-                    coverDrawable,
-                    new ParallaxTarget.PropertyValuesHolderTarget(
-                            coverDrawable,
-                            PropertyValuesHolder.ofInt("verticalOffset", 0, mMinVerticalOffset)
-                    )
-            );
-
-            BackgroundManager backgroundManager = BackgroundManager.getInstance(getActivity());
-            backgroundManager.attach(getActivity().getWindow());
-            backgroundManager.setDrawable(mParallaxDrawable);
-        }
-
-        @Override
-        public void onStart() {
-            super.onStart();
-            setItem(new PhotoItem("Hello world", "Fake content goes here",
-                    android.support.v17.leanback.test.R.drawable.spiderman));
-        }
-
-        @Override
-        public void onResume() {
-            super.onResume();
-            Bitmap bitmap = BitmapFactory.decodeResource(getActivity().getResources(),
-                    android.support.v17.leanback.test.R.drawable.spiderman);
-            ((FitWidthBitmapDrawable) mParallaxDrawable.getCoverDrawable()).setBitmap(bitmap);
-        }
-
-        DetailsParallaxDrawable getParallaxDrawable() {
-            return mParallaxDrawable;
-        }
-    }
-
-    @Test
-    public void parallaxSetupTest() {
-        SingleFragmentTestActivity activity =
-                launchAndWaitActivity(DetailsFragmentTest.DetailsFragmentParallax.class,
-                new SingleFragmentTestBase.Options().uiVisibility(
-                        View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN), 0);
-
-        double delta = 0.0002;
-        DetailsParallax dpm = ((DetailsFragment) activity.getTestFragment()).getParallax();
-
-        RecyclerViewParallax.ChildPositionProperty frameTop =
-                (RecyclerViewParallax.ChildPositionProperty) dpm.getOverviewRowTop();
-        assertEquals(0f, frameTop.getFraction(), delta);
-        assertEquals(0f, frameTop.getAdapterPosition(), delta);
-
-
-        RecyclerViewParallax.ChildPositionProperty frameBottom =
-                (RecyclerViewParallax.ChildPositionProperty) dpm.getOverviewRowBottom();
-        assertEquals(1f, frameBottom.getFraction(), delta);
-        assertEquals(0f, frameBottom.getAdapterPosition(), delta);
-    }
-
-    @Test
-    public void parallaxTest() throws Throwable {
-        SingleFragmentTestActivity activity = launchAndWaitActivity(DetailsFragmentParallax.class,
-                new Options().uiVisibility(
-                        View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN), 0);
-
-        final DetailsFragmentParallax detailsFragment =
-                (DetailsFragmentParallax) activity.getTestFragment();
-        DetailsParallaxDrawable drawable =
-                detailsFragment.getParallaxDrawable();
-        final FitWidthBitmapDrawable bitmapDrawable = (FitWidthBitmapDrawable)
-                drawable.getCoverDrawable();
-
-        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return detailsFragment.getRowsFragment().getAdapter() != null
-                        && detailsFragment.getRowsFragment().getAdapter().size() > 1;
-            }
-        });
-
-        final VerticalGridView verticalGridView = detailsFragment.getRowsFragment()
-                .getVerticalGridView();
-        final int windowHeight = verticalGridView.getHeight();
-        final int windowWidth = verticalGridView.getWidth();
-        // make sure background manager attached to window is same size as VerticalGridView
-        // i.e. no status bar.
-        assertEquals(windowHeight, activity.getWindow().getDecorView().getHeight());
-        assertEquals(windowWidth, activity.getWindow().getDecorView().getWidth());
-
-        final View detailsFrame = verticalGridView.findViewById(R.id.details_frame);
-
-        assertEquals(windowWidth, bitmapDrawable.getBounds().width());
-
-        final Rect detailsFrameRect = new Rect();
-        detailsFrameRect.set(0, 0, detailsFrame.getWidth(), detailsFrame.getHeight());
-        verticalGridView.offsetDescendantRectToMyCoords(detailsFrame, detailsFrameRect);
-
-        assertEquals(Math.min(windowHeight, detailsFrameRect.top),
-                bitmapDrawable.getBounds().height());
-        assertEquals(0, bitmapDrawable.getVerticalOffset());
-
-        assertTrue("TitleView is visible", detailsFragment.getView()
-                .findViewById(R.id.browse_title_group).getVisibility() == View.VISIBLE);
-
-        activityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                verticalGridView.scrollToPosition(1);
-            }
-        });
-
-        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return bitmapDrawable.getVerticalOffset() == PARALLAX_VERTICAL_OFFSET
-                        && detailsFragment.getView()
-                        .findViewById(R.id.browse_title_group).getVisibility() != View.VISIBLE;
-            }
-        });
-
-        detailsFrameRect.set(0, 0, detailsFrame.getWidth(), detailsFrame.getHeight());
-        verticalGridView.offsetDescendantRectToMyCoords(detailsFrame, detailsFrameRect);
-
-        assertEquals(0, bitmapDrawable.getBounds().top);
-        assertEquals(Math.max(detailsFrameRect.top, 0), bitmapDrawable.getBounds().bottom);
-        assertEquals(windowWidth, bitmapDrawable.getBounds().width());
-
-        ColorDrawable colorDrawable = (ColorDrawable) (drawable.getChildAt(1).getDrawable());
-        assertEquals(windowWidth, colorDrawable.getBounds().width());
-        assertEquals(detailsFrameRect.bottom, colorDrawable.getBounds().top);
-        assertEquals(windowHeight, colorDrawable.getBounds().bottom);
-    }
-
-    public static class DetailsFragmentWithVideo extends DetailsTestFragment {
-
-        final DetailsFragmentBackgroundController mDetailsBackground =
-                new DetailsFragmentBackgroundController(this);
-        MediaPlayerGlue mGlue;
-
-        public DetailsFragmentWithVideo() {
-            mTimeToLoadOverviewRow = mTimeToLoadRelatedRow = 100;
-        }
-
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            mDetailsBackground.enableParallax();
-            mGlue = new MediaPlayerGlue(getActivity());
-            mDetailsBackground.setupVideoPlayback(mGlue);
-
-            mGlue.setMode(MediaPlayerGlue.REPEAT_ALL);
-            mGlue.setArtist("A Googleer");
-            mGlue.setTitle("Diving with Sharks");
-            mGlue.setMediaSource(
-                    Uri.parse("android.resource://android.support.v17.leanback.test/raw/video"));
-        }
-
-        @Override
-        public void onStart() {
-            super.onStart();
-            Bitmap bitmap = BitmapFactory.decodeResource(getActivity().getResources(),
-                    android.support.v17.leanback.test.R.drawable.spiderman);
-            mDetailsBackground.setCoverBitmap(bitmap);
-        }
-
-        @Override
-        public void onStop() {
-            mDetailsBackground.setCoverBitmap(null);
-            super.onStop();
-        }
-    }
-
-    public static class DetailsFragmentWithVideo1 extends DetailsFragmentWithVideo {
-
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            setItem(new PhotoItem("Hello world", "Fake content goes here",
-                    android.support.v17.leanback.test.R.drawable.spiderman));
-        }
-    }
-
-    public static class DetailsFragmentWithVideo2 extends DetailsFragmentWithVideo {
-
-        @Override
-        public void onStart() {
-            super.onStart();
-            setItem(new PhotoItem("Hello world", "Fake content goes here",
-                    android.support.v17.leanback.test.R.drawable.spiderman));
-        }
-    }
-
-    private void navigateBetweenRowsAndVideoUsingRequestFocusInternal(Class cls)
-            throws Throwable {
-        SingleFragmentTestActivity activity = launchAndWaitActivity(cls,
-                new Options().uiVisibility(
-                        View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN), 0);
-
-        final DetailsFragmentWithVideo detailsFragment =
-                (DetailsFragmentWithVideo) activity.getTestFragment();
-        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return detailsFragment.mVideoFragment != null
-                        && detailsFragment.mVideoFragment.getView() != null
-                        && detailsFragment.mGlue.isMediaPlaying();
-            }
-        });
-
-        final int screenHeight = detailsFragment.getRowsFragment().getVerticalGridView()
-                .getHeight();
-        final View firstRow = detailsFragment.getRowsFragment().getVerticalGridView().getChildAt(0);
-        final int originalFirstRowTop = firstRow.getTop();
-        assertTrue(firstRow.hasFocus());
-        assertTrue(firstRow.getTop() > 0 && firstRow.getTop() < screenHeight);
-        assertTrue(detailsFragment.isShowingTitle());
-
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                detailsFragment.mVideoFragment.getView().requestFocus();
-            }
-        });
-        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return firstRow.getTop() >= screenHeight;
-            }
-        });
-        assertFalse(detailsFragment.isShowingTitle());
-
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                detailsFragment.getRowsFragment().getVerticalGridView().requestFocus();
-            }
-        });
-        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return firstRow.getTop() == originalFirstRowTop;
-            }
-        });
-        assertTrue(detailsFragment.isShowingTitle());
-    }
-
-    @Test
-    public void navigateBetweenRowsAndVideoUsingRequestFocus1() throws Throwable {
-        navigateBetweenRowsAndVideoUsingRequestFocusInternal(DetailsFragmentWithVideo1.class);
-    }
-
-    @Test
-    public void navigateBetweenRowsAndVideoUsingRequestFocus2() throws Throwable {
-        navigateBetweenRowsAndVideoUsingRequestFocusInternal(DetailsFragmentWithVideo2.class);
-    }
-
-    private void navigateBetweenRowsAndVideoUsingDPADInternal(Class cls) throws Throwable {
-        SingleFragmentTestActivity activity = launchAndWaitActivity(cls,
-                new Options().uiVisibility(
-                        View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN), 0);
-
-        final DetailsFragmentWithVideo detailsFragment =
-                (DetailsFragmentWithVideo) activity.getTestFragment();
-        // wait video playing
-        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return detailsFragment.mVideoFragment != null
-                        && detailsFragment.mVideoFragment.getView() != null
-                        && detailsFragment.mGlue.isMediaPlaying();
-            }
-        });
-
-        final int screenHeight = detailsFragment.getRowsFragment().getVerticalGridView()
-                .getHeight();
-        final View firstRow = detailsFragment.getRowsFragment().getVerticalGridView().getChildAt(0);
-        final int originalFirstRowTop = firstRow.getTop();
-        assertTrue(firstRow.hasFocus());
-        assertTrue(firstRow.getTop() > 0 && firstRow.getTop() < screenHeight);
-        assertTrue(detailsFragment.isShowingTitle());
-
-        // navigate to video
-        sendKeys(KeyEvent.KEYCODE_DPAD_UP);
-        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return firstRow.getTop() >= screenHeight;
-            }
-        });
-
-        // wait auto hide play controls done:
-        PollingCheck.waitFor(8000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return ((PlaybackFragment) detailsFragment.mVideoFragment).mBgAlpha == 0;
-            }
-        });
-
-        // navigate to details
-        sendKeys(KeyEvent.KEYCODE_BACK);
-        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return firstRow.getTop() == originalFirstRowTop;
-            }
-        });
-        assertTrue(detailsFragment.isShowingTitle());
-    }
-
-    @Test
-    public void navigateBetweenRowsAndVideoUsingDPAD1() throws Throwable {
-        navigateBetweenRowsAndVideoUsingDPADInternal(DetailsFragmentWithVideo1.class);
-    }
-
-    @Test
-    public void navigateBetweenRowsAndVideoUsingDPAD2() throws Throwable {
-        navigateBetweenRowsAndVideoUsingDPADInternal(DetailsFragmentWithVideo2.class);
-    }
-
-    public static class EmptyFragmentClass extends Fragment {
-        @Override
-        public void onStart() {
-            super.onStart();
-            getActivity().finish();
-        }
-    }
-
-    private void fragmentOnStartWithVideoInternal(Class cls) throws Throwable {
-        final SingleFragmentTestActivity activity = launchAndWaitActivity(cls,
-                new Options().uiVisibility(
-                        View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN), 0);
-
-        final DetailsFragmentWithVideo detailsFragment =
-                (DetailsFragmentWithVideo) activity.getTestFragment();
-        // wait video playing
-        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return detailsFragment.mVideoFragment != null
-                        && detailsFragment.mVideoFragment.getView() != null
-                        && detailsFragment.mGlue.isMediaPlaying();
-            }
-        });
-
-        final int screenHeight = detailsFragment.getRowsFragment().getVerticalGridView()
-                .getHeight();
-        final View firstRow = detailsFragment.getRowsFragment().getVerticalGridView().getChildAt(0);
-        final int originalFirstRowTop = firstRow.getTop();
-        assertTrue(firstRow.hasFocus());
-        assertTrue(firstRow.getTop() > 0 && firstRow.getTop() < screenHeight);
-        assertTrue(detailsFragment.isShowingTitle());
-
-        // navigate to video
-        sendKeys(KeyEvent.KEYCODE_DPAD_UP);
-        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return firstRow.getTop() >= screenHeight;
-            }
-        });
-
-        // start an empty activity
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(
-                new Runnable() {
-                    @Override
-                    public void run() {
-                        Intent intent = new Intent(activity, SingleFragmentTestActivity.class);
-                        intent.putExtra(SingleFragmentTestActivity.EXTRA_FRAGMENT_NAME,
-                                EmptyFragmentClass.class.getName());
-                        activity.startActivity(intent);
-                    }
-                }
-        );
-        PollingCheck.waitFor(2000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return detailsFragment.isResumed();
-            }
-        });
-        assertTrue(detailsFragment.mVideoFragment.getView().hasFocus());
-    }
-
-    @Test
-    public void fragmentOnStartWithVideo1() throws Throwable {
-        fragmentOnStartWithVideoInternal(DetailsFragmentWithVideo1.class);
-    }
-
-    @Test
-    public void fragmentOnStartWithVideo2() throws Throwable {
-        fragmentOnStartWithVideoInternal(DetailsFragmentWithVideo2.class);
-    }
-
-    @Test
-    public void navigateBetweenRowsAndTitle() throws Throwable {
-        SingleFragmentTestActivity activity =
-                launchAndWaitActivity(DetailsTestFragment.class, new Options().uiVisibility(
-                View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN), 0);
-        final DetailsTestFragment detailsFragment =
-                (DetailsTestFragment) activity.getTestFragment();
-
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                detailsFragment.setOnSearchClickedListener(new View.OnClickListener() {
-                    @Override
-                    public void onClick(View view) {
-                    }
-                });
-                detailsFragment.setItem(new PhotoItem("Hello world", "Fake content goes here",
-                        android.support.v17.leanback.test.R.drawable.spiderman));
-            }
-        });
-
-        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return detailsFragment.getRowsFragment().getVerticalGridView().getChildCount() > 0;
-            }
-        });
-        final View firstRow = detailsFragment.getRowsFragment().getVerticalGridView().getChildAt(0);
-        final int originalFirstRowTop = firstRow.getTop();
-        final int screenHeight = detailsFragment.getRowsFragment().getVerticalGridView()
-                .getHeight();
-
-        assertTrue(firstRow.hasFocus());
-        assertTrue(detailsFragment.isShowingTitle());
-        assertTrue(firstRow.getTop() > 0 && firstRow.getTop() < screenHeight);
-
-        sendKeys(KeyEvent.KEYCODE_DPAD_UP);
-        PollingCheck.waitFor(new PollingCheck.ViewStableOnScreen(firstRow));
-        assertTrue(detailsFragment.isShowingTitle());
-        assertTrue(detailsFragment.getTitleView().hasFocus());
-        assertEquals(originalFirstRowTop, firstRow.getTop());
-
-        sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
-        PollingCheck.waitFor(new PollingCheck.ViewStableOnScreen(firstRow));
-        assertTrue(detailsFragment.isShowingTitle());
-        assertTrue(firstRow.hasFocus());
-        assertEquals(originalFirstRowTop, firstRow.getTop());
-    }
-
-    public static class DetailsFragmentWithNoVideo extends DetailsTestFragment {
-
-        final DetailsFragmentBackgroundController mDetailsBackground =
-                new DetailsFragmentBackgroundController(this);
-
-        public DetailsFragmentWithNoVideo() {
-            mTimeToLoadOverviewRow = mTimeToLoadRelatedRow = 100;
-        }
-
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            mDetailsBackground.enableParallax();
-
-            setItem(new PhotoItem("Hello world", "Fake content goes here",
-                    android.support.v17.leanback.test.R.drawable.spiderman));
-        }
-
-        @Override
-        public void onStart() {
-            super.onStart();
-            Bitmap bitmap = BitmapFactory.decodeResource(getActivity().getResources(),
-                    android.support.v17.leanback.test.R.drawable.spiderman);
-            mDetailsBackground.setCoverBitmap(bitmap);
-        }
-
-        @Override
-        public void onStop() {
-            mDetailsBackground.setCoverBitmap(null);
-            super.onStop();
-        }
-    }
-
-    @Test
-    public void lateSetupVideo() {
-        final SingleFragmentTestActivity activity =
-                launchAndWaitActivity(DetailsFragmentWithNoVideo.class, new Options().uiVisibility(
-                View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN), 0);
-        final DetailsFragmentWithNoVideo detailsFragment =
-                (DetailsFragmentWithNoVideo) activity.getTestFragment();
-
-        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return detailsFragment.getRowsFragment().getVerticalGridView().getChildCount() > 0;
-            }
-        });
-        final View firstRow = detailsFragment.getRowsFragment().getVerticalGridView().getChildAt(0);
-        final int screenHeight = detailsFragment.getRowsFragment().getVerticalGridView()
-                .getHeight();
-
-        assertTrue(firstRow.hasFocus());
-        assertTrue(detailsFragment.isShowingTitle());
-        assertTrue(firstRow.getTop() > 0 && firstRow.getTop() < screenHeight);
-
-        sendKeys(KeyEvent.KEYCODE_DPAD_UP);
-        assertTrue(firstRow.hasFocus());
-
-        SystemClock.sleep(1000);
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(
-                new Runnable() {
-                    @Override
-                    public void run() {
-                        final MediaPlayerGlue glue = new MediaPlayerGlue(activity);
-                        detailsFragment.mDetailsBackgroundController.setupVideoPlayback(glue);
-                        glue.setMode(MediaPlayerGlue.REPEAT_ALL);
-                        glue.setArtist("A Googleer");
-                        glue.setTitle("Diving with Sharks");
-                        glue.setMediaSource(Uri.parse(
-                                "android.resource://android.support.v17.leanback.test/raw/video"));
-                    }
-                }
-        );
-
-        // after setup Video Playback the DPAD up will navigate to Video Fragment.
-        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
-                @Override
-                    public boolean canProceed() {
-                        return detailsFragment.mVideoFragment != null
-                                && detailsFragment.mVideoFragment.getView() != null;
-                }
-        });
-        sendKeys(KeyEvent.KEYCODE_DPAD_UP);
-        assertTrue(detailsFragment.mVideoFragment.getView().hasFocus());
-        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return ((MediaPlayerGlue) detailsFragment.mDetailsBackgroundController
-                        .getPlaybackGlue()).isMediaPlaying();
-            }
-        });
-        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return 0 == getCoverDrawableAlpha(detailsFragment.mDetailsBackgroundController);
-            }
-        });
-
-        // wait a little bit to replace with new Glue
-        SystemClock.sleep(1000);
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(
-                new Runnable() {
-                    @Override
-                    public void run() {
-                        final MediaPlayerGlue glue2 = new MediaPlayerGlue(activity);
-                        detailsFragment.mDetailsBackgroundController.setupVideoPlayback(glue2);
-                        glue2.setMode(MediaPlayerGlue.REPEAT_ALL);
-                        glue2.setArtist("A Googleer");
-                        glue2.setTitle("Diving with Sharks");
-                        glue2.setMediaSource(Uri.parse(
-                                "android.resource://android.support.v17.leanback.test/raw/video"));
-                    }
-                }
-        );
-
-        // test switchToRows() and switchToVideo()
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(
-                new Runnable() {
-                    @Override
-                    public void run() {
-                        detailsFragment.mDetailsBackgroundController.switchToRows();
-                    }
-                }
-        );
-        assertTrue(detailsFragment.mRowsFragment.getView().hasFocus());
-        PollingCheck.waitFor(new PollingCheck.ViewStableOnScreen(firstRow));
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(
-                new Runnable() {
-                    @Override
-                    public void run() {
-                        detailsFragment.mDetailsBackgroundController.switchToVideo();
-                    }
-                }
-        );
-        assertTrue(detailsFragment.mVideoFragment.getView().hasFocus());
-        PollingCheck.waitFor(new PollingCheck.ViewStableOnScreen(firstRow));
-    }
-
-    @Test
-    public void sharedGlueHost() {
-        final SingleFragmentTestActivity activity =
-                launchAndWaitActivity(DetailsFragmentWithNoVideo.class, new Options().uiVisibility(
-                        View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN), 0);
-        final DetailsFragmentWithNoVideo detailsFragment =
-                (DetailsFragmentWithNoVideo) activity.getTestFragment();
-
-        SystemClock.sleep(1000);
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(
-                new Runnable() {
-                    @Override
-                    public void run() {
-                        final MediaPlayerGlue glue1 = new MediaPlayerGlue(activity);
-                        detailsFragment.mDetailsBackgroundController.setupVideoPlayback(glue1);
-                        glue1.setArtist("A Googleer");
-                        glue1.setTitle("Diving with Sharks");
-                        glue1.setMediaSource(Uri.parse(
-                                "android.resource://android.support.v17.leanback.test/raw/video"));
-                    }
-                }
-        );
-
-        // after setup Video Playback the DPAD up will navigate to Video Fragment.
-        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return detailsFragment.mVideoFragment != null
-                        && detailsFragment.mVideoFragment.getView() != null;
-            }
-        });
-
-        final MediaPlayerGlue glue1 = (MediaPlayerGlue) detailsFragment
-                .mDetailsBackgroundController
-                .getPlaybackGlue();
-        PlaybackGlueHost playbackGlueHost = glue1.getHost();
-
-        // wait a little bit to replace with new Glue
-        SystemClock.sleep(1000);
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(
-                new Runnable() {
-                    @Override
-                    public void run() {
-                        final MediaPlayerGlue glue2 = new MediaPlayerGlue(activity);
-                        detailsFragment.mDetailsBackgroundController.setupVideoPlayback(glue2);
-                        glue2.setArtist("A Googleer");
-                        glue2.setTitle("Diving with Sharks");
-                        glue2.setMediaSource(Uri.parse(
-                                "android.resource://android.support.v17.leanback.test/raw/video"));
-                    }
-                }
-        );
-
-        // wait for new glue to get its glue host
-        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                MediaPlayerGlue mediaPlayerGlue = (MediaPlayerGlue) detailsFragment
-                        .mDetailsBackgroundController
-                        .getPlaybackGlue();
-                return mediaPlayerGlue != null && mediaPlayerGlue != glue1
-                        && mediaPlayerGlue.getHost() != null;
-            }
-        });
-
-        final MediaPlayerGlue glue2 = (MediaPlayerGlue) detailsFragment
-                .mDetailsBackgroundController
-                .getPlaybackGlue();
-
-        assertTrue(glue1.getHost() == null);
-        assertTrue(glue2.getHost() == playbackGlueHost);
-    }
-
-    @Test
-    public void clearVideo() {
-        final SingleFragmentTestActivity activity =
-                launchAndWaitActivity(DetailsFragmentWithNoVideo.class, new Options().uiVisibility(
-                View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN), 0);
-        final DetailsFragmentWithNoVideo detailsFragment =
-                (DetailsFragmentWithNoVideo) activity.getTestFragment();
-
-        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return detailsFragment.getRowsFragment().getVerticalGridView().getChildCount() > 0;
-            }
-        });
-        final View firstRow = detailsFragment.getRowsFragment().getVerticalGridView().getChildAt(0);
-        final int screenHeight = detailsFragment.getRowsFragment().getVerticalGridView()
-                .getHeight();
-
-        assertTrue(firstRow.hasFocus());
-        assertTrue(detailsFragment.isShowingTitle());
-        assertTrue(firstRow.getTop() > 0 && firstRow.getTop() < screenHeight);
-
-        SystemClock.sleep(1000);
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(
-                new Runnable() {
-                    @Override
-                    public void run() {
-                        final MediaPlayerGlue glue = new MediaPlayerGlue(activity);
-                        detailsFragment.mDetailsBackgroundController.setupVideoPlayback(glue);
-                        glue.setMode(MediaPlayerGlue.REPEAT_ALL);
-                        glue.setArtist("A Googleer");
-                        glue.setTitle("Diving with Sharks");
-                        glue.setMediaSource(Uri.parse(
-                                "android.resource://android.support.v17.leanback.test/raw/video"));
-                    }
-                }
-        );
-
-        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return ((MediaPlayerGlue) detailsFragment.mDetailsBackgroundController
-                        .getPlaybackGlue()).isMediaPlaying();
-            }
-        });
-        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return 0 == getCoverDrawableAlpha(detailsFragment.mDetailsBackgroundController);
-            }
-        });
-
-        // wait a little bit then reset glue
-        SystemClock.sleep(1000);
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(
-                new Runnable() {
-                    @Override
-                    public void run() {
-                        detailsFragment.mDetailsBackgroundController.setupVideoPlayback(null);
-                    }
-                }
-        );
-        // background should fade in upon reset playback
-        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return 255 == getCoverDrawableAlpha(detailsFragment.mDetailsBackgroundController);
-            }
-        });
-    }
-
-    public static class DetailsFragmentWithNoItem extends DetailsTestFragment {
-
-        final DetailsFragmentBackgroundController mDetailsBackground =
-                new DetailsFragmentBackgroundController(this);
-
-        public DetailsFragmentWithNoItem() {
-            mTimeToLoadOverviewRow = mTimeToLoadRelatedRow = 100;
-        }
-
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            mDetailsBackground.enableParallax();
-        }
-
-        @Override
-        public void onStart() {
-            super.onStart();
-            Bitmap bitmap = BitmapFactory.decodeResource(getActivity().getResources(),
-                    android.support.v17.leanback.test.R.drawable.spiderman);
-            mDetailsBackground.setCoverBitmap(bitmap);
-        }
-
-        @Override
-        public void onStop() {
-            mDetailsBackground.setCoverBitmap(null);
-            super.onStop();
-        }
-    }
-
-    @Test
-    public void noInitialItem() {
-        SingleFragmentTestActivity activity =
-                launchAndWaitActivity(DetailsFragmentWithNoItem.class, new Options().uiVisibility(
-                View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN), 0);
-        final DetailsFragmentWithNoItem detailsFragment =
-                (DetailsFragmentWithNoItem) activity.getTestFragment();
-
-        final int recyclerViewHeight = detailsFragment.getRowsFragment().getVerticalGridView()
-                .getHeight();
-        assertTrue(recyclerViewHeight > 0);
-
-        assertEquals(255, getCoverDrawableAlpha(detailsFragment.mDetailsBackgroundController));
-        Drawable coverDrawable = detailsFragment.mDetailsBackgroundController.getCoverDrawable();
-        assertEquals(0, coverDrawable.getBounds().top);
-        assertEquals(recyclerViewHeight, coverDrawable.getBounds().bottom);
-        Drawable bottomDrawable = detailsFragment.mDetailsBackgroundController.getBottomDrawable();
-        assertEquals(recyclerViewHeight, bottomDrawable.getBounds().top);
-        assertEquals(recyclerViewHeight, bottomDrawable.getBounds().bottom);
-    }
-
-    public static class DetailsFragmentSwitchToVideoInOnCreate extends DetailsTestFragment {
-
-        final DetailsFragmentBackgroundController mDetailsBackground =
-                new DetailsFragmentBackgroundController(this);
-
-        public DetailsFragmentSwitchToVideoInOnCreate() {
-            mTimeToLoadOverviewRow = mTimeToLoadRelatedRow = 100;
-        }
-
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            mDetailsBackground.enableParallax();
-            mDetailsBackground.switchToVideo();
-        }
-
-        @Override
-        public void onStart() {
-            super.onStart();
-            Bitmap bitmap = BitmapFactory.decodeResource(getActivity().getResources(),
-                    android.support.v17.leanback.test.R.drawable.spiderman);
-            mDetailsBackground.setCoverBitmap(bitmap);
-        }
-
-        @Override
-        public void onStop() {
-            mDetailsBackground.setCoverBitmap(null);
-            super.onStop();
-        }
-    }
-
-    @Test
-    public void switchToVideoInOnCreate() {
-        final SingleFragmentTestActivity activity =
-                launchAndWaitActivity(DetailsFragmentSwitchToVideoInOnCreate.class,
-                new Options().uiVisibility(
-                        View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN), 0);
-        final DetailsFragmentSwitchToVideoInOnCreate detailsFragment =
-                (DetailsFragmentSwitchToVideoInOnCreate) activity.getTestFragment();
-
-        // the pending enter transition flag should be automatically cleared
-        assertEquals(StateMachine.STATUS_INVOKED,
-                detailsFragment.STATE_ENTER_TRANSITION_COMPLETE.getStatus());
-        assertNull(TransitionHelper.getEnterTransition(activity.getWindow()));
-        assertEquals(0, getCoverDrawableAlpha(detailsFragment.mDetailsBackgroundController));
-        assertTrue(detailsFragment.getRowsFragment().getView().hasFocus());
-        //SystemClock.sleep(5000);
-        assertFalse(detailsFragment.isShowingTitle());
-
-        SystemClock.sleep(1000);
-        assertNull(detailsFragment.mVideoFragment);
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(
-                new Runnable() {
-                    @Override
-                    public void run() {
-                        final MediaPlayerGlue glue = new MediaPlayerGlue(activity);
-                        detailsFragment.mDetailsBackgroundController.setupVideoPlayback(glue);
-                        glue.setMode(MediaPlayerGlue.REPEAT_ALL);
-                        glue.setArtist("A Googleer");
-                        glue.setTitle("Diving with Sharks");
-                        glue.setMediaSource(Uri.parse(
-                                "android.resource://android.support.v17.leanback.test/raw/video"));
-                    }
-                }
-        );
-        // once the video fragment is created it would be immediately assigned focus
-        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return detailsFragment.mVideoFragment != null
-                        && detailsFragment.mVideoFragment.getView() != null
-                        && detailsFragment.mVideoFragment.getView().hasFocus();
-            }
-        });
-        // wait auto hide play controls done:
-        PollingCheck.waitFor(8000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return ((PlaybackFragment) detailsFragment.mVideoFragment).mBgAlpha == 0;
-            }
-        });
-
-        // switchToRows does nothing if there is no row
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(
-                new Runnable() {
-                    @Override
-                    public void run() {
-                        detailsFragment.mDetailsBackgroundController.switchToRows();
-                    }
-                }
-        );
-        assertTrue(detailsFragment.mVideoFragment.getView().hasFocus());
-
-        // create item, it should be layout outside screen
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(
-                new Runnable() {
-                    @Override
-                    public void run() {
-                        detailsFragment.setItem(new PhotoItem("Hello world",
-                                "Fake content goes here",
-                                android.support.v17.leanback.test.R.drawable.spiderman));
-                    }
-                }
-        );
-        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return detailsFragment.getVerticalGridView().getChildCount() > 0
-                        && detailsFragment.getVerticalGridView().getChildAt(0).getTop()
-                        >= detailsFragment.getVerticalGridView().getHeight();
-            }
-        });
-
-        // pressing BACK will return to details row
-        sendKeys(KeyEvent.KEYCODE_BACK);
-        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return detailsFragment.getVerticalGridView().getChildAt(0).getTop()
-                        < (detailsFragment.getVerticalGridView().getHeight() * 0.7f);
-            }
-        });
-        assertTrue(detailsFragment.getVerticalGridView().getChildAt(0).hasFocus());
-    }
-
-    @Test
-    public void switchToVideoBackToQuit() {
-        final SingleFragmentTestActivity activity =
-                launchAndWaitActivity(DetailsFragmentSwitchToVideoInOnCreate.class,
-                new Options().uiVisibility(
-                        View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN), 0);
-        final DetailsFragmentSwitchToVideoInOnCreate detailsFragment =
-                (DetailsFragmentSwitchToVideoInOnCreate) activity.getTestFragment();
-
-        // the pending enter transition flag should be automatically cleared
-        assertEquals(StateMachine.STATUS_INVOKED,
-                detailsFragment.STATE_ENTER_TRANSITION_COMPLETE.getStatus());
-        assertNull(TransitionHelper.getEnterTransition(activity.getWindow()));
-        assertEquals(0, getCoverDrawableAlpha(detailsFragment.mDetailsBackgroundController));
-        assertTrue(detailsFragment.getRowsFragment().getView().hasFocus());
-        assertFalse(detailsFragment.isShowingTitle());
-
-        SystemClock.sleep(1000);
-        assertNull(detailsFragment.mVideoFragment);
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(
-                new Runnable() {
-                    @Override
-                    public void run() {
-                        final MediaPlayerGlue glue = new MediaPlayerGlue(activity);
-                        detailsFragment.mDetailsBackgroundController.setupVideoPlayback(glue);
-                        glue.setMode(MediaPlayerGlue.REPEAT_ALL);
-                        glue.setArtist("A Googleer");
-                        glue.setTitle("Diving with Sharks");
-                        glue.setMediaSource(Uri.parse(
-                                "android.resource://android.support.v17.leanback.test/raw/video"));
-                    }
-                }
-        );
-        // once the video fragment is created it would be immediately assigned focus
-        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return detailsFragment.mVideoFragment != null
-                        && detailsFragment.mVideoFragment.getView() != null
-                        && detailsFragment.mVideoFragment.getView().hasFocus();
-            }
-        });
-        // wait auto hide play controls done:
-        PollingCheck.waitFor(8000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return ((PlaybackFragment) detailsFragment.mVideoFragment).mBgAlpha == 0;
-            }
-        });
-
-        // before any details row is presented, pressing BACK will quit the activity
-        sendKeys(KeyEvent.KEYCODE_BACK);
-        PollingCheck.waitFor(4000, new PollingCheck.ActivityDestroy(activity));
-    }
-
-    public static class DetailsFragmentSwitchToVideoAndPrepareEntranceTransition
-            extends DetailsTestFragment {
-
-        final DetailsFragmentBackgroundController mDetailsBackground =
-                new DetailsFragmentBackgroundController(this);
-
-        public DetailsFragmentSwitchToVideoAndPrepareEntranceTransition() {
-            mTimeToLoadOverviewRow = mTimeToLoadRelatedRow = 100;
-        }
-
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            mDetailsBackground.enableParallax();
-            mDetailsBackground.switchToVideo();
-            prepareEntranceTransition();
-        }
-
-        @Override
-        public void onViewCreated(View view, Bundle savedInstanceState) {
-            super.onViewCreated(view, savedInstanceState);
-        }
-
-        @Override
-        public void onStart() {
-            super.onStart();
-            Bitmap bitmap = BitmapFactory.decodeResource(getActivity().getResources(),
-                    android.support.v17.leanback.test.R.drawable.spiderman);
-            mDetailsBackground.setCoverBitmap(bitmap);
-        }
-
-        @Override
-        public void onStop() {
-            mDetailsBackground.setCoverBitmap(null);
-            super.onStop();
-        }
-    }
-
-    @Test
-    public void switchToVideoInOnCreateAndPrepareEntranceTransition() {
-        SingleFragmentTestActivity activity = launchAndWaitActivity(
-                DetailsFragmentSwitchToVideoAndPrepareEntranceTransition.class,
-                new Options().uiVisibility(
-                        View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN), 0);
-        final DetailsFragmentSwitchToVideoAndPrepareEntranceTransition detailsFragment =
-                (DetailsFragmentSwitchToVideoAndPrepareEntranceTransition)
-                        activity.getTestFragment();
-
-        assertEquals(StateMachine.STATUS_INVOKED,
-                detailsFragment.STATE_ENTRANCE_COMPLETE.getStatus());
-    }
-
-    public static class DetailsFragmentEntranceTransition
-            extends DetailsTestFragment {
-
-        final DetailsFragmentBackgroundController mDetailsBackground =
-                new DetailsFragmentBackgroundController(this);
-
-        public DetailsFragmentEntranceTransition() {
-            mTimeToLoadOverviewRow = mTimeToLoadRelatedRow = 100;
-        }
-
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            mDetailsBackground.enableParallax();
-            prepareEntranceTransition();
-        }
-
-        @Override
-        public void onStart() {
-            super.onStart();
-            Bitmap bitmap = BitmapFactory.decodeResource(getActivity().getResources(),
-                    android.support.v17.leanback.test.R.drawable.spiderman);
-            mDetailsBackground.setCoverBitmap(bitmap);
-        }
-
-        @Override
-        public void onStop() {
-            mDetailsBackground.setCoverBitmap(null);
-            super.onStop();
-        }
-    }
-
-    @Test
-    public void entranceTransitionBlocksSwitchToVideo() {
-        SingleFragmentTestActivity activity =
-                launchAndWaitActivity(DetailsFragmentEntranceTransition.class,
-                new Options().uiVisibility(
-                        View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN), 0);
-        final DetailsFragmentEntranceTransition detailsFragment =
-                (DetailsFragmentEntranceTransition)
-                        activity.getTestFragment();
-
-        if (Build.VERSION.SDK_INT < 21) {
-            // when enter transition is not supported, mCanUseHost is immmediately true
-            assertTrue(detailsFragment.mDetailsBackgroundController.mCanUseHost);
-        } else {
-            // calling switchToVideo() between prepareEntranceTransition and entrance transition
-            // finishes will be ignored.
-            InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
-                @Override
-                public void run() {
-                    detailsFragment.mDetailsBackgroundController.switchToVideo();
-                }
-            });
-            assertFalse(detailsFragment.mDetailsBackgroundController.mCanUseHost);
-        }
-        assertEquals(255, getCoverDrawableAlpha(detailsFragment.mDetailsBackgroundController));
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                detailsFragment.setItem(new PhotoItem("Hello world", "Fake content goes here",
-                        android.support.v17.leanback.test.R.drawable.spiderman));
-                detailsFragment.startEntranceTransition();
-            }
-        });
-        // once Entrance transition is finished, mCanUseHost will be true
-        // and we can switchToVideo and fade out the background.
-        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return detailsFragment.mDetailsBackgroundController.mCanUseHost;
-            }
-        });
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                detailsFragment.mDetailsBackgroundController.switchToVideo();
-            }
-        });
-        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return 0 == getCoverDrawableAlpha(detailsFragment.mDetailsBackgroundController);
-            }
-        });
-    }
-
-    public static class DetailsFragmentEntranceTransitionTimeout extends DetailsTestFragment {
-
-        public DetailsFragmentEntranceTransitionTimeout() {
-        }
-
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            prepareEntranceTransition();
-        }
-
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
-    public void startEntranceTransitionAfterDestroyed() {
-        SingleFragmentTestActivity activity = launchAndWaitActivity(
-                DetailsFragmentEntranceTransition.class, new Options().uiVisibility(
-                        View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN),
-                1000);
-        final DetailsFragmentEntranceTransition detailsFragment =
-                (DetailsFragmentEntranceTransition)
-                        activity.getTestFragment();
-
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                detailsFragment.setItem(new PhotoItem("Hello world", "Fake content goes here",
-                        android.support.v17.leanback.test.R.drawable.spiderman));
-            }
-        });
-        SystemClock.sleep(100);
-        activity.finish();
-        PollingCheck.waitFor(new PollingCheck.ActivityDestroy(activity));
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                detailsFragment.startEntranceTransition();
-            }
-        });
-    }
-}
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/DetailsSupportFragmentTest.java b/v17/leanback/tests/java/android/support/v17/leanback/app/DetailsSupportFragmentTest.java
deleted file mode 100644
index 04f20bc..0000000
--- a/v17/leanback/tests/java/android/support/v17/leanback/app/DetailsSupportFragmentTest.java
+++ /dev/null
@@ -1,1219 +0,0 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from DetailsFragmentTest.java.  DO NOT MODIFY. */
-
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.support.v17.leanback.app;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-
-import android.animation.PropertyValuesHolder;
-import android.support.v4.app.Fragment;
-import android.content.Intent;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Rect;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.SystemClock;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.LargeTest;
-import android.support.test.filters.SdkSuppress;
-import android.support.v17.leanback.R;
-import android.support.v17.leanback.graphics.FitWidthBitmapDrawable;
-import android.support.v17.leanback.media.MediaPlayerGlue;
-import android.support.v17.leanback.media.PlaybackGlueHost;
-import android.support.v17.leanback.testutils.PollingCheck;
-import android.support.v17.leanback.transition.TransitionHelper;
-import android.support.v17.leanback.util.StateMachine;
-import android.support.v17.leanback.widget.DetailsParallax;
-import android.support.v17.leanback.widget.DetailsParallaxDrawable;
-import android.support.v17.leanback.widget.ParallaxTarget;
-import android.support.v17.leanback.widget.RecyclerViewParallax;
-import android.support.v17.leanback.widget.VerticalGridView;
-import android.view.KeyEvent;
-import android.view.View;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-/**
- * Unit tests for {@link DetailsSupportFragment}.
- */
-@RunWith(JUnit4.class)
-@LargeTest
-public class DetailsSupportFragmentTest extends SingleSupportFragmentTestBase {
-
-    static final int PARALLAX_VERTICAL_OFFSET = -300;
-
-    static int getCoverDrawableAlpha(DetailsSupportFragmentBackgroundController controller) {
-        return ((FitWidthBitmapDrawable) controller.mParallaxDrawable.getCoverDrawable())
-                .getAlpha();
-    }
-
-    public static class DetailsSupportFragmentParallax extends DetailsTestSupportFragment {
-
-        private DetailsParallaxDrawable mParallaxDrawable;
-
-        public DetailsSupportFragmentParallax() {
-            super();
-            mMinVerticalOffset = PARALLAX_VERTICAL_OFFSET;
-        }
-
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            Drawable coverDrawable = new FitWidthBitmapDrawable();
-            mParallaxDrawable = new DetailsParallaxDrawable(
-                    getActivity(),
-                    getParallax(),
-                    coverDrawable,
-                    new ParallaxTarget.PropertyValuesHolderTarget(
-                            coverDrawable,
-                            PropertyValuesHolder.ofInt("verticalOffset", 0, mMinVerticalOffset)
-                    )
-            );
-
-            BackgroundManager backgroundManager = BackgroundManager.getInstance(getActivity());
-            backgroundManager.attach(getActivity().getWindow());
-            backgroundManager.setDrawable(mParallaxDrawable);
-        }
-
-        @Override
-        public void onStart() {
-            super.onStart();
-            setItem(new PhotoItem("Hello world", "Fake content goes here",
-                    android.support.v17.leanback.test.R.drawable.spiderman));
-        }
-
-        @Override
-        public void onResume() {
-            super.onResume();
-            Bitmap bitmap = BitmapFactory.decodeResource(getActivity().getResources(),
-                    android.support.v17.leanback.test.R.drawable.spiderman);
-            ((FitWidthBitmapDrawable) mParallaxDrawable.getCoverDrawable()).setBitmap(bitmap);
-        }
-
-        DetailsParallaxDrawable getParallaxDrawable() {
-            return mParallaxDrawable;
-        }
-    }
-
-    @Test
-    public void parallaxSetupTest() {
-        SingleSupportFragmentTestActivity activity =
-                launchAndWaitActivity(DetailsSupportFragmentTest.DetailsSupportFragmentParallax.class,
-                new SingleSupportFragmentTestBase.Options().uiVisibility(
-                        View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN), 0);
-
-        double delta = 0.0002;
-        DetailsParallax dpm = ((DetailsSupportFragment) activity.getTestFragment()).getParallax();
-
-        RecyclerViewParallax.ChildPositionProperty frameTop =
-                (RecyclerViewParallax.ChildPositionProperty) dpm.getOverviewRowTop();
-        assertEquals(0f, frameTop.getFraction(), delta);
-        assertEquals(0f, frameTop.getAdapterPosition(), delta);
-
-
-        RecyclerViewParallax.ChildPositionProperty frameBottom =
-                (RecyclerViewParallax.ChildPositionProperty) dpm.getOverviewRowBottom();
-        assertEquals(1f, frameBottom.getFraction(), delta);
-        assertEquals(0f, frameBottom.getAdapterPosition(), delta);
-    }
-
-    @Test
-    public void parallaxTest() throws Throwable {
-        SingleSupportFragmentTestActivity activity = launchAndWaitActivity(DetailsSupportFragmentParallax.class,
-                new Options().uiVisibility(
-                        View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN), 0);
-
-        final DetailsSupportFragmentParallax detailsFragment =
-                (DetailsSupportFragmentParallax) activity.getTestFragment();
-        DetailsParallaxDrawable drawable =
-                detailsFragment.getParallaxDrawable();
-        final FitWidthBitmapDrawable bitmapDrawable = (FitWidthBitmapDrawable)
-                drawable.getCoverDrawable();
-
-        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return detailsFragment.getRowsSupportFragment().getAdapter() != null
-                        && detailsFragment.getRowsSupportFragment().getAdapter().size() > 1;
-            }
-        });
-
-        final VerticalGridView verticalGridView = detailsFragment.getRowsSupportFragment()
-                .getVerticalGridView();
-        final int windowHeight = verticalGridView.getHeight();
-        final int windowWidth = verticalGridView.getWidth();
-        // make sure background manager attached to window is same size as VerticalGridView
-        // i.e. no status bar.
-        assertEquals(windowHeight, activity.getWindow().getDecorView().getHeight());
-        assertEquals(windowWidth, activity.getWindow().getDecorView().getWidth());
-
-        final View detailsFrame = verticalGridView.findViewById(R.id.details_frame);
-
-        assertEquals(windowWidth, bitmapDrawable.getBounds().width());
-
-        final Rect detailsFrameRect = new Rect();
-        detailsFrameRect.set(0, 0, detailsFrame.getWidth(), detailsFrame.getHeight());
-        verticalGridView.offsetDescendantRectToMyCoords(detailsFrame, detailsFrameRect);
-
-        assertEquals(Math.min(windowHeight, detailsFrameRect.top),
-                bitmapDrawable.getBounds().height());
-        assertEquals(0, bitmapDrawable.getVerticalOffset());
-
-        assertTrue("TitleView is visible", detailsFragment.getView()
-                .findViewById(R.id.browse_title_group).getVisibility() == View.VISIBLE);
-
-        activityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                verticalGridView.scrollToPosition(1);
-            }
-        });
-
-        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return bitmapDrawable.getVerticalOffset() == PARALLAX_VERTICAL_OFFSET
-                        && detailsFragment.getView()
-                        .findViewById(R.id.browse_title_group).getVisibility() != View.VISIBLE;
-            }
-        });
-
-        detailsFrameRect.set(0, 0, detailsFrame.getWidth(), detailsFrame.getHeight());
-        verticalGridView.offsetDescendantRectToMyCoords(detailsFrame, detailsFrameRect);
-
-        assertEquals(0, bitmapDrawable.getBounds().top);
-        assertEquals(Math.max(detailsFrameRect.top, 0), bitmapDrawable.getBounds().bottom);
-        assertEquals(windowWidth, bitmapDrawable.getBounds().width());
-
-        ColorDrawable colorDrawable = (ColorDrawable) (drawable.getChildAt(1).getDrawable());
-        assertEquals(windowWidth, colorDrawable.getBounds().width());
-        assertEquals(detailsFrameRect.bottom, colorDrawable.getBounds().top);
-        assertEquals(windowHeight, colorDrawable.getBounds().bottom);
-    }
-
-    public static class DetailsSupportFragmentWithVideo extends DetailsTestSupportFragment {
-
-        final DetailsSupportFragmentBackgroundController mDetailsBackground =
-                new DetailsSupportFragmentBackgroundController(this);
-        MediaPlayerGlue mGlue;
-
-        public DetailsSupportFragmentWithVideo() {
-            mTimeToLoadOverviewRow = mTimeToLoadRelatedRow = 100;
-        }
-
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            mDetailsBackground.enableParallax();
-            mGlue = new MediaPlayerGlue(getActivity());
-            mDetailsBackground.setupVideoPlayback(mGlue);
-
-            mGlue.setMode(MediaPlayerGlue.REPEAT_ALL);
-            mGlue.setArtist("A Googleer");
-            mGlue.setTitle("Diving with Sharks");
-            mGlue.setMediaSource(
-                    Uri.parse("android.resource://android.support.v17.leanback.test/raw/video"));
-        }
-
-        @Override
-        public void onStart() {
-            super.onStart();
-            Bitmap bitmap = BitmapFactory.decodeResource(getActivity().getResources(),
-                    android.support.v17.leanback.test.R.drawable.spiderman);
-            mDetailsBackground.setCoverBitmap(bitmap);
-        }
-
-        @Override
-        public void onStop() {
-            mDetailsBackground.setCoverBitmap(null);
-            super.onStop();
-        }
-    }
-
-    public static class DetailsSupportFragmentWithVideo1 extends DetailsSupportFragmentWithVideo {
-
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            setItem(new PhotoItem("Hello world", "Fake content goes here",
-                    android.support.v17.leanback.test.R.drawable.spiderman));
-        }
-    }
-
-    public static class DetailsSupportFragmentWithVideo2 extends DetailsSupportFragmentWithVideo {
-
-        @Override
-        public void onStart() {
-            super.onStart();
-            setItem(new PhotoItem("Hello world", "Fake content goes here",
-                    android.support.v17.leanback.test.R.drawable.spiderman));
-        }
-    }
-
-    private void navigateBetweenRowsAndVideoUsingRequestFocusInternal(Class cls)
-            throws Throwable {
-        SingleSupportFragmentTestActivity activity = launchAndWaitActivity(cls,
-                new Options().uiVisibility(
-                        View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN), 0);
-
-        final DetailsSupportFragmentWithVideo detailsFragment =
-                (DetailsSupportFragmentWithVideo) activity.getTestFragment();
-        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return detailsFragment.mVideoSupportFragment != null
-                        && detailsFragment.mVideoSupportFragment.getView() != null
-                        && detailsFragment.mGlue.isMediaPlaying();
-            }
-        });
-
-        final int screenHeight = detailsFragment.getRowsSupportFragment().getVerticalGridView()
-                .getHeight();
-        final View firstRow = detailsFragment.getRowsSupportFragment().getVerticalGridView().getChildAt(0);
-        final int originalFirstRowTop = firstRow.getTop();
-        assertTrue(firstRow.hasFocus());
-        assertTrue(firstRow.getTop() > 0 && firstRow.getTop() < screenHeight);
-        assertTrue(detailsFragment.isShowingTitle());
-
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                detailsFragment.mVideoSupportFragment.getView().requestFocus();
-            }
-        });
-        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return firstRow.getTop() >= screenHeight;
-            }
-        });
-        assertFalse(detailsFragment.isShowingTitle());
-
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                detailsFragment.getRowsSupportFragment().getVerticalGridView().requestFocus();
-            }
-        });
-        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return firstRow.getTop() == originalFirstRowTop;
-            }
-        });
-        assertTrue(detailsFragment.isShowingTitle());
-    }
-
-    @Test
-    public void navigateBetweenRowsAndVideoUsingRequestFocus1() throws Throwable {
-        navigateBetweenRowsAndVideoUsingRequestFocusInternal(DetailsSupportFragmentWithVideo1.class);
-    }
-
-    @Test
-    public void navigateBetweenRowsAndVideoUsingRequestFocus2() throws Throwable {
-        navigateBetweenRowsAndVideoUsingRequestFocusInternal(DetailsSupportFragmentWithVideo2.class);
-    }
-
-    private void navigateBetweenRowsAndVideoUsingDPADInternal(Class cls) throws Throwable {
-        SingleSupportFragmentTestActivity activity = launchAndWaitActivity(cls,
-                new Options().uiVisibility(
-                        View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN), 0);
-
-        final DetailsSupportFragmentWithVideo detailsFragment =
-                (DetailsSupportFragmentWithVideo) activity.getTestFragment();
-        // wait video playing
-        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return detailsFragment.mVideoSupportFragment != null
-                        && detailsFragment.mVideoSupportFragment.getView() != null
-                        && detailsFragment.mGlue.isMediaPlaying();
-            }
-        });
-
-        final int screenHeight = detailsFragment.getRowsSupportFragment().getVerticalGridView()
-                .getHeight();
-        final View firstRow = detailsFragment.getRowsSupportFragment().getVerticalGridView().getChildAt(0);
-        final int originalFirstRowTop = firstRow.getTop();
-        assertTrue(firstRow.hasFocus());
-        assertTrue(firstRow.getTop() > 0 && firstRow.getTop() < screenHeight);
-        assertTrue(detailsFragment.isShowingTitle());
-
-        // navigate to video
-        sendKeys(KeyEvent.KEYCODE_DPAD_UP);
-        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return firstRow.getTop() >= screenHeight;
-            }
-        });
-
-        // wait auto hide play controls done:
-        PollingCheck.waitFor(8000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return ((PlaybackSupportFragment) detailsFragment.mVideoSupportFragment).mBgAlpha == 0;
-            }
-        });
-
-        // navigate to details
-        sendKeys(KeyEvent.KEYCODE_BACK);
-        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return firstRow.getTop() == originalFirstRowTop;
-            }
-        });
-        assertTrue(detailsFragment.isShowingTitle());
-    }
-
-    @Test
-    public void navigateBetweenRowsAndVideoUsingDPAD1() throws Throwable {
-        navigateBetweenRowsAndVideoUsingDPADInternal(DetailsSupportFragmentWithVideo1.class);
-    }
-
-    @Test
-    public void navigateBetweenRowsAndVideoUsingDPAD2() throws Throwable {
-        navigateBetweenRowsAndVideoUsingDPADInternal(DetailsSupportFragmentWithVideo2.class);
-    }
-
-    public static class EmptyFragmentClass extends Fragment {
-        @Override
-        public void onStart() {
-            super.onStart();
-            getActivity().finish();
-        }
-    }
-
-    private void fragmentOnStartWithVideoInternal(Class cls) throws Throwable {
-        final SingleSupportFragmentTestActivity activity = launchAndWaitActivity(cls,
-                new Options().uiVisibility(
-                        View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN), 0);
-
-        final DetailsSupportFragmentWithVideo detailsFragment =
-                (DetailsSupportFragmentWithVideo) activity.getTestFragment();
-        // wait video playing
-        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return detailsFragment.mVideoSupportFragment != null
-                        && detailsFragment.mVideoSupportFragment.getView() != null
-                        && detailsFragment.mGlue.isMediaPlaying();
-            }
-        });
-
-        final int screenHeight = detailsFragment.getRowsSupportFragment().getVerticalGridView()
-                .getHeight();
-        final View firstRow = detailsFragment.getRowsSupportFragment().getVerticalGridView().getChildAt(0);
-        final int originalFirstRowTop = firstRow.getTop();
-        assertTrue(firstRow.hasFocus());
-        assertTrue(firstRow.getTop() > 0 && firstRow.getTop() < screenHeight);
-        assertTrue(detailsFragment.isShowingTitle());
-
-        // navigate to video
-        sendKeys(KeyEvent.KEYCODE_DPAD_UP);
-        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return firstRow.getTop() >= screenHeight;
-            }
-        });
-
-        // start an empty activity
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(
-                new Runnable() {
-                    @Override
-                    public void run() {
-                        Intent intent = new Intent(activity, SingleSupportFragmentTestActivity.class);
-                        intent.putExtra(SingleSupportFragmentTestActivity.EXTRA_FRAGMENT_NAME,
-                                EmptyFragmentClass.class.getName());
-                        activity.startActivity(intent);
-                    }
-                }
-        );
-        PollingCheck.waitFor(2000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return detailsFragment.isResumed();
-            }
-        });
-        assertTrue(detailsFragment.mVideoSupportFragment.getView().hasFocus());
-    }
-
-    @Test
-    public void fragmentOnStartWithVideo1() throws Throwable {
-        fragmentOnStartWithVideoInternal(DetailsSupportFragmentWithVideo1.class);
-    }
-
-    @Test
-    public void fragmentOnStartWithVideo2() throws Throwable {
-        fragmentOnStartWithVideoInternal(DetailsSupportFragmentWithVideo2.class);
-    }
-
-    @Test
-    public void navigateBetweenRowsAndTitle() throws Throwable {
-        SingleSupportFragmentTestActivity activity =
-                launchAndWaitActivity(DetailsTestSupportFragment.class, new Options().uiVisibility(
-                View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN), 0);
-        final DetailsTestSupportFragment detailsFragment =
-                (DetailsTestSupportFragment) activity.getTestFragment();
-
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                detailsFragment.setOnSearchClickedListener(new View.OnClickListener() {
-                    @Override
-                    public void onClick(View view) {
-                    }
-                });
-                detailsFragment.setItem(new PhotoItem("Hello world", "Fake content goes here",
-                        android.support.v17.leanback.test.R.drawable.spiderman));
-            }
-        });
-
-        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return detailsFragment.getRowsSupportFragment().getVerticalGridView().getChildCount() > 0;
-            }
-        });
-        final View firstRow = detailsFragment.getRowsSupportFragment().getVerticalGridView().getChildAt(0);
-        final int originalFirstRowTop = firstRow.getTop();
-        final int screenHeight = detailsFragment.getRowsSupportFragment().getVerticalGridView()
-                .getHeight();
-
-        assertTrue(firstRow.hasFocus());
-        assertTrue(detailsFragment.isShowingTitle());
-        assertTrue(firstRow.getTop() > 0 && firstRow.getTop() < screenHeight);
-
-        sendKeys(KeyEvent.KEYCODE_DPAD_UP);
-        PollingCheck.waitFor(new PollingCheck.ViewStableOnScreen(firstRow));
-        assertTrue(detailsFragment.isShowingTitle());
-        assertTrue(detailsFragment.getTitleView().hasFocus());
-        assertEquals(originalFirstRowTop, firstRow.getTop());
-
-        sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
-        PollingCheck.waitFor(new PollingCheck.ViewStableOnScreen(firstRow));
-        assertTrue(detailsFragment.isShowingTitle());
-        assertTrue(firstRow.hasFocus());
-        assertEquals(originalFirstRowTop, firstRow.getTop());
-    }
-
-    public static class DetailsSupportFragmentWithNoVideo extends DetailsTestSupportFragment {
-
-        final DetailsSupportFragmentBackgroundController mDetailsBackground =
-                new DetailsSupportFragmentBackgroundController(this);
-
-        public DetailsSupportFragmentWithNoVideo() {
-            mTimeToLoadOverviewRow = mTimeToLoadRelatedRow = 100;
-        }
-
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            mDetailsBackground.enableParallax();
-
-            setItem(new PhotoItem("Hello world", "Fake content goes here",
-                    android.support.v17.leanback.test.R.drawable.spiderman));
-        }
-
-        @Override
-        public void onStart() {
-            super.onStart();
-            Bitmap bitmap = BitmapFactory.decodeResource(getActivity().getResources(),
-                    android.support.v17.leanback.test.R.drawable.spiderman);
-            mDetailsBackground.setCoverBitmap(bitmap);
-        }
-
-        @Override
-        public void onStop() {
-            mDetailsBackground.setCoverBitmap(null);
-            super.onStop();
-        }
-    }
-
-    @Test
-    public void lateSetupVideo() {
-        final SingleSupportFragmentTestActivity activity =
-                launchAndWaitActivity(DetailsSupportFragmentWithNoVideo.class, new Options().uiVisibility(
-                View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN), 0);
-        final DetailsSupportFragmentWithNoVideo detailsFragment =
-                (DetailsSupportFragmentWithNoVideo) activity.getTestFragment();
-
-        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return detailsFragment.getRowsSupportFragment().getVerticalGridView().getChildCount() > 0;
-            }
-        });
-        final View firstRow = detailsFragment.getRowsSupportFragment().getVerticalGridView().getChildAt(0);
-        final int screenHeight = detailsFragment.getRowsSupportFragment().getVerticalGridView()
-                .getHeight();
-
-        assertTrue(firstRow.hasFocus());
-        assertTrue(detailsFragment.isShowingTitle());
-        assertTrue(firstRow.getTop() > 0 && firstRow.getTop() < screenHeight);
-
-        sendKeys(KeyEvent.KEYCODE_DPAD_UP);
-        assertTrue(firstRow.hasFocus());
-
-        SystemClock.sleep(1000);
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(
-                new Runnable() {
-                    @Override
-                    public void run() {
-                        final MediaPlayerGlue glue = new MediaPlayerGlue(activity);
-                        detailsFragment.mDetailsBackgroundController.setupVideoPlayback(glue);
-                        glue.setMode(MediaPlayerGlue.REPEAT_ALL);
-                        glue.setArtist("A Googleer");
-                        glue.setTitle("Diving with Sharks");
-                        glue.setMediaSource(Uri.parse(
-                                "android.resource://android.support.v17.leanback.test/raw/video"));
-                    }
-                }
-        );
-
-        // after setup Video Playback the DPAD up will navigate to Video Fragment.
-        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
-                @Override
-                    public boolean canProceed() {
-                        return detailsFragment.mVideoSupportFragment != null
-                                && detailsFragment.mVideoSupportFragment.getView() != null;
-                }
-        });
-        sendKeys(KeyEvent.KEYCODE_DPAD_UP);
-        assertTrue(detailsFragment.mVideoSupportFragment.getView().hasFocus());
-        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return ((MediaPlayerGlue) detailsFragment.mDetailsBackgroundController
-                        .getPlaybackGlue()).isMediaPlaying();
-            }
-        });
-        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return 0 == getCoverDrawableAlpha(detailsFragment.mDetailsBackgroundController);
-            }
-        });
-
-        // wait a little bit to replace with new Glue
-        SystemClock.sleep(1000);
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(
-                new Runnable() {
-                    @Override
-                    public void run() {
-                        final MediaPlayerGlue glue2 = new MediaPlayerGlue(activity);
-                        detailsFragment.mDetailsBackgroundController.setupVideoPlayback(glue2);
-                        glue2.setMode(MediaPlayerGlue.REPEAT_ALL);
-                        glue2.setArtist("A Googleer");
-                        glue2.setTitle("Diving with Sharks");
-                        glue2.setMediaSource(Uri.parse(
-                                "android.resource://android.support.v17.leanback.test/raw/video"));
-                    }
-                }
-        );
-
-        // test switchToRows() and switchToVideo()
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(
-                new Runnable() {
-                    @Override
-                    public void run() {
-                        detailsFragment.mDetailsBackgroundController.switchToRows();
-                    }
-                }
-        );
-        assertTrue(detailsFragment.mRowsSupportFragment.getView().hasFocus());
-        PollingCheck.waitFor(new PollingCheck.ViewStableOnScreen(firstRow));
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(
-                new Runnable() {
-                    @Override
-                    public void run() {
-                        detailsFragment.mDetailsBackgroundController.switchToVideo();
-                    }
-                }
-        );
-        assertTrue(detailsFragment.mVideoSupportFragment.getView().hasFocus());
-        PollingCheck.waitFor(new PollingCheck.ViewStableOnScreen(firstRow));
-    }
-
-    @Test
-    public void sharedGlueHost() {
-        final SingleSupportFragmentTestActivity activity =
-                launchAndWaitActivity(DetailsSupportFragmentWithNoVideo.class, new Options().uiVisibility(
-                        View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN), 0);
-        final DetailsSupportFragmentWithNoVideo detailsFragment =
-                (DetailsSupportFragmentWithNoVideo) activity.getTestFragment();
-
-        SystemClock.sleep(1000);
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(
-                new Runnable() {
-                    @Override
-                    public void run() {
-                        final MediaPlayerGlue glue1 = new MediaPlayerGlue(activity);
-                        detailsFragment.mDetailsBackgroundController.setupVideoPlayback(glue1);
-                        glue1.setArtist("A Googleer");
-                        glue1.setTitle("Diving with Sharks");
-                        glue1.setMediaSource(Uri.parse(
-                                "android.resource://android.support.v17.leanback.test/raw/video"));
-                    }
-                }
-        );
-
-        // after setup Video Playback the DPAD up will navigate to Video Fragment.
-        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return detailsFragment.mVideoSupportFragment != null
-                        && detailsFragment.mVideoSupportFragment.getView() != null;
-            }
-        });
-
-        final MediaPlayerGlue glue1 = (MediaPlayerGlue) detailsFragment
-                .mDetailsBackgroundController
-                .getPlaybackGlue();
-        PlaybackGlueHost playbackGlueHost = glue1.getHost();
-
-        // wait a little bit to replace with new Glue
-        SystemClock.sleep(1000);
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(
-                new Runnable() {
-                    @Override
-                    public void run() {
-                        final MediaPlayerGlue glue2 = new MediaPlayerGlue(activity);
-                        detailsFragment.mDetailsBackgroundController.setupVideoPlayback(glue2);
-                        glue2.setArtist("A Googleer");
-                        glue2.setTitle("Diving with Sharks");
-                        glue2.setMediaSource(Uri.parse(
-                                "android.resource://android.support.v17.leanback.test/raw/video"));
-                    }
-                }
-        );
-
-        // wait for new glue to get its glue host
-        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                MediaPlayerGlue mediaPlayerGlue = (MediaPlayerGlue) detailsFragment
-                        .mDetailsBackgroundController
-                        .getPlaybackGlue();
-                return mediaPlayerGlue != null && mediaPlayerGlue != glue1
-                        && mediaPlayerGlue.getHost() != null;
-            }
-        });
-
-        final MediaPlayerGlue glue2 = (MediaPlayerGlue) detailsFragment
-                .mDetailsBackgroundController
-                .getPlaybackGlue();
-
-        assertTrue(glue1.getHost() == null);
-        assertTrue(glue2.getHost() == playbackGlueHost);
-    }
-
-    @Test
-    public void clearVideo() {
-        final SingleSupportFragmentTestActivity activity =
-                launchAndWaitActivity(DetailsSupportFragmentWithNoVideo.class, new Options().uiVisibility(
-                View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN), 0);
-        final DetailsSupportFragmentWithNoVideo detailsFragment =
-                (DetailsSupportFragmentWithNoVideo) activity.getTestFragment();
-
-        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return detailsFragment.getRowsSupportFragment().getVerticalGridView().getChildCount() > 0;
-            }
-        });
-        final View firstRow = detailsFragment.getRowsSupportFragment().getVerticalGridView().getChildAt(0);
-        final int screenHeight = detailsFragment.getRowsSupportFragment().getVerticalGridView()
-                .getHeight();
-
-        assertTrue(firstRow.hasFocus());
-        assertTrue(detailsFragment.isShowingTitle());
-        assertTrue(firstRow.getTop() > 0 && firstRow.getTop() < screenHeight);
-
-        SystemClock.sleep(1000);
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(
-                new Runnable() {
-                    @Override
-                    public void run() {
-                        final MediaPlayerGlue glue = new MediaPlayerGlue(activity);
-                        detailsFragment.mDetailsBackgroundController.setupVideoPlayback(glue);
-                        glue.setMode(MediaPlayerGlue.REPEAT_ALL);
-                        glue.setArtist("A Googleer");
-                        glue.setTitle("Diving with Sharks");
-                        glue.setMediaSource(Uri.parse(
-                                "android.resource://android.support.v17.leanback.test/raw/video"));
-                    }
-                }
-        );
-
-        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return ((MediaPlayerGlue) detailsFragment.mDetailsBackgroundController
-                        .getPlaybackGlue()).isMediaPlaying();
-            }
-        });
-        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return 0 == getCoverDrawableAlpha(detailsFragment.mDetailsBackgroundController);
-            }
-        });
-
-        // wait a little bit then reset glue
-        SystemClock.sleep(1000);
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(
-                new Runnable() {
-                    @Override
-                    public void run() {
-                        detailsFragment.mDetailsBackgroundController.setupVideoPlayback(null);
-                    }
-                }
-        );
-        // background should fade in upon reset playback
-        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return 255 == getCoverDrawableAlpha(detailsFragment.mDetailsBackgroundController);
-            }
-        });
-    }
-
-    public static class DetailsSupportFragmentWithNoItem extends DetailsTestSupportFragment {
-
-        final DetailsSupportFragmentBackgroundController mDetailsBackground =
-                new DetailsSupportFragmentBackgroundController(this);
-
-        public DetailsSupportFragmentWithNoItem() {
-            mTimeToLoadOverviewRow = mTimeToLoadRelatedRow = 100;
-        }
-
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            mDetailsBackground.enableParallax();
-        }
-
-        @Override
-        public void onStart() {
-            super.onStart();
-            Bitmap bitmap = BitmapFactory.decodeResource(getActivity().getResources(),
-                    android.support.v17.leanback.test.R.drawable.spiderman);
-            mDetailsBackground.setCoverBitmap(bitmap);
-        }
-
-        @Override
-        public void onStop() {
-            mDetailsBackground.setCoverBitmap(null);
-            super.onStop();
-        }
-    }
-
-    @Test
-    public void noInitialItem() {
-        SingleSupportFragmentTestActivity activity =
-                launchAndWaitActivity(DetailsSupportFragmentWithNoItem.class, new Options().uiVisibility(
-                View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN), 0);
-        final DetailsSupportFragmentWithNoItem detailsFragment =
-                (DetailsSupportFragmentWithNoItem) activity.getTestFragment();
-
-        final int recyclerViewHeight = detailsFragment.getRowsSupportFragment().getVerticalGridView()
-                .getHeight();
-        assertTrue(recyclerViewHeight > 0);
-
-        assertEquals(255, getCoverDrawableAlpha(detailsFragment.mDetailsBackgroundController));
-        Drawable coverDrawable = detailsFragment.mDetailsBackgroundController.getCoverDrawable();
-        assertEquals(0, coverDrawable.getBounds().top);
-        assertEquals(recyclerViewHeight, coverDrawable.getBounds().bottom);
-        Drawable bottomDrawable = detailsFragment.mDetailsBackgroundController.getBottomDrawable();
-        assertEquals(recyclerViewHeight, bottomDrawable.getBounds().top);
-        assertEquals(recyclerViewHeight, bottomDrawable.getBounds().bottom);
-    }
-
-    public static class DetailsSupportFragmentSwitchToVideoInOnCreate extends DetailsTestSupportFragment {
-
-        final DetailsSupportFragmentBackgroundController mDetailsBackground =
-                new DetailsSupportFragmentBackgroundController(this);
-
-        public DetailsSupportFragmentSwitchToVideoInOnCreate() {
-            mTimeToLoadOverviewRow = mTimeToLoadRelatedRow = 100;
-        }
-
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            mDetailsBackground.enableParallax();
-            mDetailsBackground.switchToVideo();
-        }
-
-        @Override
-        public void onStart() {
-            super.onStart();
-            Bitmap bitmap = BitmapFactory.decodeResource(getActivity().getResources(),
-                    android.support.v17.leanback.test.R.drawable.spiderman);
-            mDetailsBackground.setCoverBitmap(bitmap);
-        }
-
-        @Override
-        public void onStop() {
-            mDetailsBackground.setCoverBitmap(null);
-            super.onStop();
-        }
-    }
-
-    @Test
-    public void switchToVideoInOnCreate() {
-        final SingleSupportFragmentTestActivity activity =
-                launchAndWaitActivity(DetailsSupportFragmentSwitchToVideoInOnCreate.class,
-                new Options().uiVisibility(
-                        View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN), 0);
-        final DetailsSupportFragmentSwitchToVideoInOnCreate detailsFragment =
-                (DetailsSupportFragmentSwitchToVideoInOnCreate) activity.getTestFragment();
-
-        // the pending enter transition flag should be automatically cleared
-        assertEquals(StateMachine.STATUS_INVOKED,
-                detailsFragment.STATE_ENTER_TRANSITION_COMPLETE.getStatus());
-        assertNull(TransitionHelper.getEnterTransition(activity.getWindow()));
-        assertEquals(0, getCoverDrawableAlpha(detailsFragment.mDetailsBackgroundController));
-        assertTrue(detailsFragment.getRowsSupportFragment().getView().hasFocus());
-        //SystemClock.sleep(5000);
-        assertFalse(detailsFragment.isShowingTitle());
-
-        SystemClock.sleep(1000);
-        assertNull(detailsFragment.mVideoSupportFragment);
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(
-                new Runnable() {
-                    @Override
-                    public void run() {
-                        final MediaPlayerGlue glue = new MediaPlayerGlue(activity);
-                        detailsFragment.mDetailsBackgroundController.setupVideoPlayback(glue);
-                        glue.setMode(MediaPlayerGlue.REPEAT_ALL);
-                        glue.setArtist("A Googleer");
-                        glue.setTitle("Diving with Sharks");
-                        glue.setMediaSource(Uri.parse(
-                                "android.resource://android.support.v17.leanback.test/raw/video"));
-                    }
-                }
-        );
-        // once the video fragment is created it would be immediately assigned focus
-        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return detailsFragment.mVideoSupportFragment != null
-                        && detailsFragment.mVideoSupportFragment.getView() != null
-                        && detailsFragment.mVideoSupportFragment.getView().hasFocus();
-            }
-        });
-        // wait auto hide play controls done:
-        PollingCheck.waitFor(8000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return ((PlaybackSupportFragment) detailsFragment.mVideoSupportFragment).mBgAlpha == 0;
-            }
-        });
-
-        // switchToRows does nothing if there is no row
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(
-                new Runnable() {
-                    @Override
-                    public void run() {
-                        detailsFragment.mDetailsBackgroundController.switchToRows();
-                    }
-                }
-        );
-        assertTrue(detailsFragment.mVideoSupportFragment.getView().hasFocus());
-
-        // create item, it should be layout outside screen
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(
-                new Runnable() {
-                    @Override
-                    public void run() {
-                        detailsFragment.setItem(new PhotoItem("Hello world",
-                                "Fake content goes here",
-                                android.support.v17.leanback.test.R.drawable.spiderman));
-                    }
-                }
-        );
-        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return detailsFragment.getVerticalGridView().getChildCount() > 0
-                        && detailsFragment.getVerticalGridView().getChildAt(0).getTop()
-                        >= detailsFragment.getVerticalGridView().getHeight();
-            }
-        });
-
-        // pressing BACK will return to details row
-        sendKeys(KeyEvent.KEYCODE_BACK);
-        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return detailsFragment.getVerticalGridView().getChildAt(0).getTop()
-                        < (detailsFragment.getVerticalGridView().getHeight() * 0.7f);
-            }
-        });
-        assertTrue(detailsFragment.getVerticalGridView().getChildAt(0).hasFocus());
-    }
-
-    @Test
-    public void switchToVideoBackToQuit() {
-        final SingleSupportFragmentTestActivity activity =
-                launchAndWaitActivity(DetailsSupportFragmentSwitchToVideoInOnCreate.class,
-                new Options().uiVisibility(
-                        View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN), 0);
-        final DetailsSupportFragmentSwitchToVideoInOnCreate detailsFragment =
-                (DetailsSupportFragmentSwitchToVideoInOnCreate) activity.getTestFragment();
-
-        // the pending enter transition flag should be automatically cleared
-        assertEquals(StateMachine.STATUS_INVOKED,
-                detailsFragment.STATE_ENTER_TRANSITION_COMPLETE.getStatus());
-        assertNull(TransitionHelper.getEnterTransition(activity.getWindow()));
-        assertEquals(0, getCoverDrawableAlpha(detailsFragment.mDetailsBackgroundController));
-        assertTrue(detailsFragment.getRowsSupportFragment().getView().hasFocus());
-        assertFalse(detailsFragment.isShowingTitle());
-
-        SystemClock.sleep(1000);
-        assertNull(detailsFragment.mVideoSupportFragment);
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(
-                new Runnable() {
-                    @Override
-                    public void run() {
-                        final MediaPlayerGlue glue = new MediaPlayerGlue(activity);
-                        detailsFragment.mDetailsBackgroundController.setupVideoPlayback(glue);
-                        glue.setMode(MediaPlayerGlue.REPEAT_ALL);
-                        glue.setArtist("A Googleer");
-                        glue.setTitle("Diving with Sharks");
-                        glue.setMediaSource(Uri.parse(
-                                "android.resource://android.support.v17.leanback.test/raw/video"));
-                    }
-                }
-        );
-        // once the video fragment is created it would be immediately assigned focus
-        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return detailsFragment.mVideoSupportFragment != null
-                        && detailsFragment.mVideoSupportFragment.getView() != null
-                        && detailsFragment.mVideoSupportFragment.getView().hasFocus();
-            }
-        });
-        // wait auto hide play controls done:
-        PollingCheck.waitFor(8000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return ((PlaybackSupportFragment) detailsFragment.mVideoSupportFragment).mBgAlpha == 0;
-            }
-        });
-
-        // before any details row is presented, pressing BACK will quit the activity
-        sendKeys(KeyEvent.KEYCODE_BACK);
-        PollingCheck.waitFor(4000, new PollingCheck.ActivityDestroy(activity));
-    }
-
-    public static class DetailsSupportFragmentSwitchToVideoAndPrepareEntranceTransition
-            extends DetailsTestSupportFragment {
-
-        final DetailsSupportFragmentBackgroundController mDetailsBackground =
-                new DetailsSupportFragmentBackgroundController(this);
-
-        public DetailsSupportFragmentSwitchToVideoAndPrepareEntranceTransition() {
-            mTimeToLoadOverviewRow = mTimeToLoadRelatedRow = 100;
-        }
-
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            mDetailsBackground.enableParallax();
-            mDetailsBackground.switchToVideo();
-            prepareEntranceTransition();
-        }
-
-        @Override
-        public void onViewCreated(View view, Bundle savedInstanceState) {
-            super.onViewCreated(view, savedInstanceState);
-        }
-
-        @Override
-        public void onStart() {
-            super.onStart();
-            Bitmap bitmap = BitmapFactory.decodeResource(getActivity().getResources(),
-                    android.support.v17.leanback.test.R.drawable.spiderman);
-            mDetailsBackground.setCoverBitmap(bitmap);
-        }
-
-        @Override
-        public void onStop() {
-            mDetailsBackground.setCoverBitmap(null);
-            super.onStop();
-        }
-    }
-
-    @Test
-    public void switchToVideoInOnCreateAndPrepareEntranceTransition() {
-        SingleSupportFragmentTestActivity activity = launchAndWaitActivity(
-                DetailsSupportFragmentSwitchToVideoAndPrepareEntranceTransition.class,
-                new Options().uiVisibility(
-                        View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN), 0);
-        final DetailsSupportFragmentSwitchToVideoAndPrepareEntranceTransition detailsFragment =
-                (DetailsSupportFragmentSwitchToVideoAndPrepareEntranceTransition)
-                        activity.getTestFragment();
-
-        assertEquals(StateMachine.STATUS_INVOKED,
-                detailsFragment.STATE_ENTRANCE_COMPLETE.getStatus());
-    }
-
-    public static class DetailsSupportFragmentEntranceTransition
-            extends DetailsTestSupportFragment {
-
-        final DetailsSupportFragmentBackgroundController mDetailsBackground =
-                new DetailsSupportFragmentBackgroundController(this);
-
-        public DetailsSupportFragmentEntranceTransition() {
-            mTimeToLoadOverviewRow = mTimeToLoadRelatedRow = 100;
-        }
-
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            mDetailsBackground.enableParallax();
-            prepareEntranceTransition();
-        }
-
-        @Override
-        public void onStart() {
-            super.onStart();
-            Bitmap bitmap = BitmapFactory.decodeResource(getActivity().getResources(),
-                    android.support.v17.leanback.test.R.drawable.spiderman);
-            mDetailsBackground.setCoverBitmap(bitmap);
-        }
-
-        @Override
-        public void onStop() {
-            mDetailsBackground.setCoverBitmap(null);
-            super.onStop();
-        }
-    }
-
-    @Test
-    public void entranceTransitionBlocksSwitchToVideo() {
-        SingleSupportFragmentTestActivity activity =
-                launchAndWaitActivity(DetailsSupportFragmentEntranceTransition.class,
-                new Options().uiVisibility(
-                        View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN), 0);
-        final DetailsSupportFragmentEntranceTransition detailsFragment =
-                (DetailsSupportFragmentEntranceTransition)
-                        activity.getTestFragment();
-
-        if (Build.VERSION.SDK_INT < 21) {
-            // when enter transition is not supported, mCanUseHost is immmediately true
-            assertTrue(detailsFragment.mDetailsBackgroundController.mCanUseHost);
-        } else {
-            // calling switchToVideo() between prepareEntranceTransition and entrance transition
-            // finishes will be ignored.
-            InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
-                @Override
-                public void run() {
-                    detailsFragment.mDetailsBackgroundController.switchToVideo();
-                }
-            });
-            assertFalse(detailsFragment.mDetailsBackgroundController.mCanUseHost);
-        }
-        assertEquals(255, getCoverDrawableAlpha(detailsFragment.mDetailsBackgroundController));
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                detailsFragment.setItem(new PhotoItem("Hello world", "Fake content goes here",
-                        android.support.v17.leanback.test.R.drawable.spiderman));
-                detailsFragment.startEntranceTransition();
-            }
-        });
-        // once Entrance transition is finished, mCanUseHost will be true
-        // and we can switchToVideo and fade out the background.
-        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return detailsFragment.mDetailsBackgroundController.mCanUseHost;
-            }
-        });
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                detailsFragment.mDetailsBackgroundController.switchToVideo();
-            }
-        });
-        PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return 0 == getCoverDrawableAlpha(detailsFragment.mDetailsBackgroundController);
-            }
-        });
-    }
-
-    public static class DetailsSupportFragmentEntranceTransitionTimeout extends DetailsTestSupportFragment {
-
-        public DetailsSupportFragmentEntranceTransitionTimeout() {
-        }
-
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            prepareEntranceTransition();
-        }
-
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
-    public void startEntranceTransitionAfterDestroyed() {
-        SingleSupportFragmentTestActivity activity = launchAndWaitActivity(
-                DetailsSupportFragmentEntranceTransition.class, new Options().uiVisibility(
-                        View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN),
-                1000);
-        final DetailsSupportFragmentEntranceTransition detailsFragment =
-                (DetailsSupportFragmentEntranceTransition)
-                        activity.getTestFragment();
-
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                detailsFragment.setItem(new PhotoItem("Hello world", "Fake content goes here",
-                        android.support.v17.leanback.test.R.drawable.spiderman));
-            }
-        });
-        SystemClock.sleep(100);
-        activity.finish();
-        PollingCheck.waitFor(new PollingCheck.ActivityDestroy(activity));
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                detailsFragment.startEntranceTransition();
-            }
-        });
-    }
-}
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/DetailsTestFragment.java b/v17/leanback/tests/java/android/support/v17/leanback/app/DetailsTestFragment.java
deleted file mode 100644
index 354e574..0000000
--- a/v17/leanback/tests/java/android/support/v17/leanback/app/DetailsTestFragment.java
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.support.v17.leanback.app;
-
-import android.content.res.Resources;
-import android.os.Bundle;
-import android.os.Handler;
-import android.support.v17.leanback.test.R;
-import android.support.v17.leanback.widget.AbstractDetailsDescriptionPresenter;
-import android.support.v17.leanback.widget.Action;
-import android.support.v17.leanback.widget.ArrayObjectAdapter;
-import android.support.v17.leanback.widget.ClassPresenterSelector;
-import android.support.v17.leanback.widget.DetailsOverviewRow;
-import android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter;
-import android.support.v17.leanback.widget.HeaderItem;
-import android.support.v17.leanback.widget.ImageCardView;
-import android.support.v17.leanback.widget.ListRow;
-import android.support.v17.leanback.widget.ListRowPresenter;
-import android.support.v17.leanback.widget.Presenter;
-import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
-import android.view.ViewGroup;
-
-/**
- * Base class provides overview row and some related rows.
- */
-public class DetailsTestFragment extends android.support.v17.leanback.app.DetailsFragment {
-    private static final int NUM_ROWS = 3;
-    private ArrayObjectAdapter mRowsAdapter;
-    private PhotoItem mPhotoItem;
-    private final Presenter mCardPresenter = new Presenter() {
-        @Override
-        public ViewHolder onCreateViewHolder(ViewGroup parent) {
-            ImageCardView cardView = new ImageCardView(getActivity());
-            cardView.setFocusable(true);
-            cardView.setFocusableInTouchMode(true);
-            return new ViewHolder(cardView);
-        }
-
-        @Override
-        public void onBindViewHolder(ViewHolder viewHolder, Object item) {
-            ImageCardView imageCardView = (ImageCardView) viewHolder.view;
-            imageCardView.setTitleText("Android Tv");
-            imageCardView.setContentText("Android Tv Production Inc.");
-            imageCardView.setMainImageDimensions(313, 176);
-        }
-
-        @Override
-        public void onUnbindViewHolder(ViewHolder viewHolder) {
-        }
-    };
-
-    private static final int ACTION_RENT = 2;
-    private static final int ACTION_BUY = 3;
-
-    protected long mTimeToLoadOverviewRow = 1000;
-    protected long mTimeToLoadRelatedRow = 2000;
-
-    private Action mActionRent;
-    private Action mActionBuy;
-
-    protected int mMinVerticalOffset = -100;
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setTitle("Leanback Sample App");
-
-        mActionRent = new Action(ACTION_RENT, "Rent", "$3.99",
-                getResources().getDrawable(R.drawable.ic_action_a));
-        mActionBuy = new Action(ACTION_BUY, "Buy $9.99");
-
-        ClassPresenterSelector ps = new ClassPresenterSelector();
-        FullWidthDetailsOverviewRowPresenter dorPresenter =
-                new FullWidthDetailsOverviewRowPresenter(new AbstractDetailsDescriptionPresenter() {
-                    @Override
-                    protected void onBindDescription(
-                            AbstractDetailsDescriptionPresenter.ViewHolder vh, Object item) {
-                        vh.getTitle().setText("Funny Movie");
-                        vh.getSubtitle().setText("Android TV Production Inc.");
-                        vh.getBody().setText("What a great movie!");
-                    }
-                });
-
-        ps.addClassPresenter(DetailsOverviewRow.class, dorPresenter);
-        ps.addClassPresenter(ListRow.class, new ListRowPresenter());
-        mRowsAdapter = new ArrayObjectAdapter(ps);
-    }
-
-    public void setItem(PhotoItem photoItem) {
-        mPhotoItem = photoItem;
-        mRowsAdapter.clear();
-        new Handler().postDelayed(new Runnable() {
-            @Override
-            public void run() {
-                if (getActivity() == null) {
-                    return;
-                }
-                Resources res = getActivity().getResources();
-                DetailsOverviewRow dor = new DetailsOverviewRow(mPhotoItem.getTitle());
-                dor.setImageDrawable(res.getDrawable(mPhotoItem.getImageResourceId()));
-                SparseArrayObjectAdapter adapter = new SparseArrayObjectAdapter();
-                adapter.set(ACTION_RENT, mActionRent);
-                adapter.set(ACTION_BUY, mActionBuy);
-                dor.setActionsAdapter(adapter);
-                mRowsAdapter.add(0, dor);
-                setSelectedPosition(0, true);
-            }
-        }, mTimeToLoadOverviewRow);
-
-
-        new Handler().postDelayed(new Runnable() {
-            @Override
-            public void run() {
-                if (getActivity() == null) {
-                    return;
-                }
-                for (int i = 0; i < NUM_ROWS; ++i) {
-                    ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(mCardPresenter);
-                    listRowAdapter.add(new PhotoItem("Hello world", R.drawable.spiderman));
-                    listRowAdapter.add(new PhotoItem("This is a test", R.drawable.spiderman));
-                    listRowAdapter.add(new PhotoItem("Android TV", R.drawable.spiderman));
-                    listRowAdapter.add(new PhotoItem("Leanback", R.drawable.spiderman));
-                    HeaderItem header = new HeaderItem(i, "Row " + i);
-                    mRowsAdapter.add(new ListRow(header, listRowAdapter));
-                }
-            }
-        }, mTimeToLoadRelatedRow);
-
-        setAdapter(mRowsAdapter);
-    }
-
-}
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/DetailsTestSupportFragment.java b/v17/leanback/tests/java/android/support/v17/leanback/app/DetailsTestSupportFragment.java
deleted file mode 100644
index 7d03a45..0000000
--- a/v17/leanback/tests/java/android/support/v17/leanback/app/DetailsTestSupportFragment.java
+++ /dev/null
@@ -1,148 +0,0 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from DetailsTestFragment.java.  DO NOT MODIFY. */
-
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.support.v17.leanback.app;
-
-import android.content.res.Resources;
-import android.os.Bundle;
-import android.os.Handler;
-import android.support.v17.leanback.test.R;
-import android.support.v17.leanback.widget.AbstractDetailsDescriptionPresenter;
-import android.support.v17.leanback.widget.Action;
-import android.support.v17.leanback.widget.ArrayObjectAdapter;
-import android.support.v17.leanback.widget.ClassPresenterSelector;
-import android.support.v17.leanback.widget.DetailsOverviewRow;
-import android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter;
-import android.support.v17.leanback.widget.HeaderItem;
-import android.support.v17.leanback.widget.ImageCardView;
-import android.support.v17.leanback.widget.ListRow;
-import android.support.v17.leanback.widget.ListRowPresenter;
-import android.support.v17.leanback.widget.Presenter;
-import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
-import android.view.ViewGroup;
-
-/**
- * Base class provides overview row and some related rows.
- */
-public class DetailsTestSupportFragment extends android.support.v17.leanback.app.DetailsSupportFragment {
-    private static final int NUM_ROWS = 3;
-    private ArrayObjectAdapter mRowsAdapter;
-    private PhotoItem mPhotoItem;
-    private final Presenter mCardPresenter = new Presenter() {
-        @Override
-        public ViewHolder onCreateViewHolder(ViewGroup parent) {
-            ImageCardView cardView = new ImageCardView(getActivity());
-            cardView.setFocusable(true);
-            cardView.setFocusableInTouchMode(true);
-            return new ViewHolder(cardView);
-        }
-
-        @Override
-        public void onBindViewHolder(ViewHolder viewHolder, Object item) {
-            ImageCardView imageCardView = (ImageCardView) viewHolder.view;
-            imageCardView.setTitleText("Android Tv");
-            imageCardView.setContentText("Android Tv Production Inc.");
-            imageCardView.setMainImageDimensions(313, 176);
-        }
-
-        @Override
-        public void onUnbindViewHolder(ViewHolder viewHolder) {
-        }
-    };
-
-    private static final int ACTION_RENT = 2;
-    private static final int ACTION_BUY = 3;
-
-    protected long mTimeToLoadOverviewRow = 1000;
-    protected long mTimeToLoadRelatedRow = 2000;
-
-    private Action mActionRent;
-    private Action mActionBuy;
-
-    protected int mMinVerticalOffset = -100;
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setTitle("Leanback Sample App");
-
-        mActionRent = new Action(ACTION_RENT, "Rent", "$3.99",
-                getResources().getDrawable(R.drawable.ic_action_a));
-        mActionBuy = new Action(ACTION_BUY, "Buy $9.99");
-
-        ClassPresenterSelector ps = new ClassPresenterSelector();
-        FullWidthDetailsOverviewRowPresenter dorPresenter =
-                new FullWidthDetailsOverviewRowPresenter(new AbstractDetailsDescriptionPresenter() {
-                    @Override
-                    protected void onBindDescription(
-                            AbstractDetailsDescriptionPresenter.ViewHolder vh, Object item) {
-                        vh.getTitle().setText("Funny Movie");
-                        vh.getSubtitle().setText("Android TV Production Inc.");
-                        vh.getBody().setText("What a great movie!");
-                    }
-                });
-
-        ps.addClassPresenter(DetailsOverviewRow.class, dorPresenter);
-        ps.addClassPresenter(ListRow.class, new ListRowPresenter());
-        mRowsAdapter = new ArrayObjectAdapter(ps);
-    }
-
-    public void setItem(PhotoItem photoItem) {
-        mPhotoItem = photoItem;
-        mRowsAdapter.clear();
-        new Handler().postDelayed(new Runnable() {
-            @Override
-            public void run() {
-                if (getActivity() == null) {
-                    return;
-                }
-                Resources res = getActivity().getResources();
-                DetailsOverviewRow dor = new DetailsOverviewRow(mPhotoItem.getTitle());
-                dor.setImageDrawable(res.getDrawable(mPhotoItem.getImageResourceId()));
-                SparseArrayObjectAdapter adapter = new SparseArrayObjectAdapter();
-                adapter.set(ACTION_RENT, mActionRent);
-                adapter.set(ACTION_BUY, mActionBuy);
-                dor.setActionsAdapter(adapter);
-                mRowsAdapter.add(0, dor);
-                setSelectedPosition(0, true);
-            }
-        }, mTimeToLoadOverviewRow);
-
-
-        new Handler().postDelayed(new Runnable() {
-            @Override
-            public void run() {
-                if (getActivity() == null) {
-                    return;
-                }
-                for (int i = 0; i < NUM_ROWS; ++i) {
-                    ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(mCardPresenter);
-                    listRowAdapter.add(new PhotoItem("Hello world", R.drawable.spiderman));
-                    listRowAdapter.add(new PhotoItem("This is a test", R.drawable.spiderman));
-                    listRowAdapter.add(new PhotoItem("Android TV", R.drawable.spiderman));
-                    listRowAdapter.add(new PhotoItem("Leanback", R.drawable.spiderman));
-                    HeaderItem header = new HeaderItem(i, "Row " + i);
-                    mRowsAdapter.add(new ListRow(header, listRowAdapter));
-                }
-            }
-        }, mTimeToLoadRelatedRow);
-
-        setAdapter(mRowsAdapter);
-    }
-
-}
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepFragmentTest.java b/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepFragmentTest.java
deleted file mode 100644
index fa324bf..0000000
--- a/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepFragmentTest.java
+++ /dev/null
@@ -1,451 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.support.v17.leanback.app;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.nullable;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-import android.os.Bundle;
-import android.support.test.filters.LargeTest;
-import android.support.test.runner.AndroidJUnit4;
-import android.support.v17.leanback.testutils.PollingCheck;
-import android.support.v17.leanback.widget.GuidedAction;
-import android.support.v17.leanback.widget.GuidedActionsStylist;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
-
-import java.util.ArrayList;
-import java.util.List;
-
-@LargeTest
-@RunWith(AndroidJUnit4.class)
-public class GuidedStepFragmentTest extends GuidedStepFragmentTestBase {
-
-    private static final int ON_DESTROY_TIMEOUT = 5000;
-
-    @Test
-    public void nextAndBack() throws Throwable {
-        final String firstFragmentName = generateMethodTestName("first");
-        final String secondFragmentName = generateMethodTestName("second");
-        GuidedStepTestFragment.Provider first = mockProvider(firstFragmentName);
-        doAnswer(new Answer<Void>() {
-            @Override
-            public Void answer(InvocationOnMock invocation) {
-                List actions = (List) invocation.getArguments()[0];
-                actions.add(new GuidedAction.Builder().id(1000).title("OK").build());
-                return null;
-            }
-        }).when(first).onCreateActions(any(List.class), nullable(Bundle.class));
-        doAnswer(new Answer<Void>() {
-            @Override
-            public Void answer(InvocationOnMock invocation) {
-                GuidedAction action = (GuidedAction) invocation.getArguments()[0];
-                GuidedStepTestFragment.Provider obj = (GuidedStepTestFragment.Provider)
-                        invocation.getMock();
-                if (action.getId() == 1000) {
-                    GuidedStepFragment.add(obj.getFragmentManager(),
-                            new GuidedStepTestFragment(secondFragmentName));
-                }
-                return null;
-            }
-        }).when(first).onGuidedActionClicked(any(GuidedAction.class));
-
-        GuidedStepTestFragment.Provider second = mockProvider(secondFragmentName);
-
-        GuidedStepFragmentTestActivity activity = launchTestActivity(firstFragmentName);
-        verify(first, times(1)).onCreate(nullable(Bundle.class));
-        verify(first, times(1)).onCreateGuidance(nullable(Bundle.class));
-        verify(first, times(1)).onCreateActions(any(List.class), nullable(Bundle.class));
-        verify(first, times(1)).onCreateButtonActions(any(List.class), nullable(Bundle.class));
-        verify(first, times(1)).onCreateView(any(LayoutInflater.class), any(ViewGroup.class),
-                nullable(Bundle.class), any(View.class));
-        verify(first, times(1)).onViewStateRestored(nullable(Bundle.class));
-        verify(first, times(1)).onStart();
-        verify(first, times(1)).onResume();
-
-        sendKey(KeyEvent.KEYCODE_DPAD_CENTER);
-        verify(first, times(1)).onGuidedActionClicked(any(GuidedAction.class));
-
-        PollingCheck.waitFor(new EnterTransitionFinish(second));
-        verify(first, times(1)).onPause();
-        verify(first, times(1)).onStop();
-        verify(first, times(1)).onDestroyView();
-        verify(second, times(1)).onCreate(nullable(Bundle.class));
-        verify(second, times(1)).onCreateGuidance(nullable(Bundle.class));
-        verify(second, times(1)).onCreateActions(any(List.class), nullable(Bundle.class));
-        verify(second, times(1)).onCreateButtonActions(any(List.class), nullable(Bundle.class));
-        verify(second, times(1)).onCreateView(any(LayoutInflater.class), nullable(ViewGroup.class),
-                nullable(Bundle.class), any(View.class));
-        verify(second, times(1)).onViewStateRestored(nullable(Bundle.class));
-        verify(second, times(1)).onStart();
-        verify(second, times(1)).onResume();
-
-        sendKey(KeyEvent.KEYCODE_BACK);
-
-        PollingCheck.waitFor(new EnterTransitionFinish(first));
-        verify(second, times(1)).onPause();
-        verify(second, times(1)).onStop();
-        verify(second, times(1)).onDestroyView();
-        verify(second, times(1)).onDestroy();
-        verify(first, times(1)).onCreateActions(any(List.class), nullable(Bundle.class));
-        verify(first, times(2)).onCreateView(any(LayoutInflater.class), any(ViewGroup.class),
-                nullable(Bundle.class), any(View.class));
-        verify(first, times(2)).onViewStateRestored(nullable(Bundle.class));
-        verify(first, times(2)).onStart();
-        verify(first, times(2)).onResume();
-
-        sendKey(KeyEvent.KEYCODE_BACK);
-        PollingCheck.waitFor(new PollingCheck.ActivityDestroy(activity));
-        verify(first, timeout(ON_DESTROY_TIMEOUT).times(1)).onDestroy();
-        assertTrue(activity.isDestroyed());
-    }
-
-    @Test
-    public void restoreFragments() throws Throwable {
-        final String firstFragmentName = generateMethodTestName("first");
-        final String secondFragmentName = generateMethodTestName("second");
-        GuidedStepTestFragment.Provider first = mockProvider(firstFragmentName);
-        doAnswer(new Answer<Void>() {
-            @Override
-            public Void answer(InvocationOnMock invocation) {
-                List actions = (List) invocation.getArguments()[0];
-                actions.add(new GuidedAction.Builder().id(1000).title("OK").build());
-                actions.add(new GuidedAction.Builder().id(1001).editable(true).title("text")
-                        .build());
-                actions.add(new GuidedAction.Builder().id(1002).editable(true).title("text")
-                        .autoSaveRestoreEnabled(false).build());
-                return null;
-            }
-        }).when(first).onCreateActions(any(List.class), nullable(Bundle.class));
-        doAnswer(new Answer<Void>() {
-            @Override
-            public Void answer(InvocationOnMock invocation) {
-                GuidedAction action = (GuidedAction) invocation.getArguments()[0];
-                GuidedStepTestFragment.Provider obj = (GuidedStepTestFragment.Provider)
-                        invocation.getMock();
-                if (action.getId() == 1000) {
-                    GuidedStepFragment.add(obj.getFragmentManager(),
-                            new GuidedStepTestFragment(secondFragmentName));
-                }
-                return null;
-            }
-        }).when(first).onGuidedActionClicked(any(GuidedAction.class));
-
-        GuidedStepTestFragment.Provider second = mockProvider(secondFragmentName);
-
-        final GuidedStepFragmentTestActivity activity = launchTestActivity(firstFragmentName);
-        first.getFragment().findActionById(1001).setTitle("modified text");
-        first.getFragment().findActionById(1002).setTitle("modified text");
-        sendKey(KeyEvent.KEYCODE_DPAD_CENTER);
-        PollingCheck.waitFor(new EnterTransitionFinish(second));
-
-        activityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                activity.recreate();
-            }
-        });
-        PollingCheck.waitFor(new EnterTransitionFinish(second));
-        verify(first, times(1)).onCreateView(any(LayoutInflater.class), any(ViewGroup.class),
-                nullable(Bundle.class), any(View.class));
-        verify(first, times(1)).onDestroy();
-        verify(second, times(2)).onCreate(nullable(Bundle.class));
-        verify(second, times(2)).onCreateView(any(LayoutInflater.class), any(ViewGroup.class),
-                nullable(Bundle.class), any(View.class));
-        verify(second, times(1)).onDestroy();
-
-        sendKey(KeyEvent.KEYCODE_BACK);
-        PollingCheck.waitFor(new EnterTransitionFinish(first));
-        verify(second, times(2)).onPause();
-        verify(second, times(2)).onStop();
-        verify(second, times(2)).onDestroyView();
-        verify(second, times(2)).onDestroy();
-        assertEquals("modified text", first.getFragment().findActionById(1001).getTitle());
-        assertEquals("text", first.getFragment().findActionById(1002).getTitle());
-        verify(first, times(2)).onCreate(nullable(Bundle.class));
-        verify(first, times(2)).onCreateActions(any(List.class), nullable(Bundle.class));
-        verify(first, times(2)).onCreateView(any(LayoutInflater.class), any(ViewGroup.class),
-                nullable(Bundle.class), any(View.class));
-    }
-
-
-    @Test
-    public void finishGuidedStepFragment_finishes_activity() throws Throwable {
-        final String firstFragmentName = generateMethodTestName("first");
-        GuidedStepTestFragment.Provider first = mockProvider(firstFragmentName);
-        doAnswer(new Answer<Void>() {
-            @Override
-            public Void answer(InvocationOnMock invocation) {
-                List actions = (List) invocation.getArguments()[0];
-                actions.add(new GuidedAction.Builder().id(1001).title("Finish activity").build());
-                return null;
-            }
-        }).when(first).onCreateActions(any(List.class), nullable(Bundle.class));
-        doAnswer(new Answer<Void>() {
-            @Override
-            public Void answer(InvocationOnMock invocation) {
-                GuidedAction action = (GuidedAction) invocation.getArguments()[0];
-                GuidedStepTestFragment.Provider obj = (GuidedStepTestFragment.Provider)
-                        invocation.getMock();
-                if (action.getId() == 1001) {
-                    obj.getFragment().finishGuidedStepFragments();
-                }
-                return null;
-            }
-        }).when(first).onGuidedActionClicked(any(GuidedAction.class));
-
-        final GuidedStepFragmentTestActivity activity = launchTestActivity(firstFragmentName);
-
-        View viewFinish = first.getFragment().getActionItemView(0);
-        assertTrue(viewFinish.hasFocus());
-        sendKey(KeyEvent.KEYCODE_DPAD_CENTER);
-        PollingCheck.waitFor(new PollingCheck.ActivityDestroy(activity));
-        verify(first, timeout(ON_DESTROY_TIMEOUT).times(1)).onDestroy();
-    }
-
-    @Test
-    public void finishGuidedStepFragment_finishes_fragments() throws Throwable {
-        final String firstFragmentName = generateMethodTestName("first");
-        GuidedStepTestFragment.Provider first = mockProvider(firstFragmentName);
-        doAnswer(new Answer<Void>() {
-            @Override
-            public Void answer(InvocationOnMock invocation) {
-                List actions = (List) invocation.getArguments()[0];
-                actions.add(new GuidedAction.Builder().id(1001).title("Finish fragments").build());
-                return null;
-            }
-        }).when(first).onCreateActions(any(List.class), nullable(Bundle.class));
-        doAnswer(new Answer<Void>() {
-            @Override
-            public Void answer(InvocationOnMock invocation) {
-                GuidedAction action = (GuidedAction) invocation.getArguments()[0];
-                GuidedStepTestFragment.Provider obj = (GuidedStepTestFragment.Provider)
-                        invocation.getMock();
-                if (action.getId() == 1001) {
-                    obj.getFragment().finishGuidedStepFragments();
-                }
-                return null;
-            }
-        }).when(first).onGuidedActionClicked(any(GuidedAction.class));
-
-        final GuidedStepFragmentTestActivity activity = launchTestActivity(firstFragmentName,
-                false /*asRoot*/);
-
-        View viewFinish = first.getFragment().getActionItemView(0);
-        assertTrue(viewFinish.hasFocus());
-        sendKey(KeyEvent.KEYCODE_DPAD_CENTER);
-
-        // fragment should be destroyed, activity should not destroyed
-        waitOnDestroy(first, 1);
-        assertFalse(activity.isDestroyed());
-    }
-
-    @Test
-    public void subActions() throws Throwable {
-        final String firstFragmentName = generateMethodTestName("first");
-        final String secondFragmentName = generateMethodTestName("second");
-        final boolean[] expandSubActionInOnCreateView = new boolean[] {false};
-        GuidedStepTestFragment.Provider first = mockProvider(firstFragmentName);
-        doAnswer(new Answer<Void>() {
-            @Override
-            public Void answer(InvocationOnMock invocation) {
-                GuidedStepTestFragment.Provider obj = (GuidedStepTestFragment.Provider)
-                        invocation.getMock();
-                if (expandSubActionInOnCreateView[0]) {
-                    obj.getFragment().expandAction(obj.getFragment().findActionById(1000), false);
-                }
-                return null;
-            }
-        }).when(first).onCreateView(any(LayoutInflater.class), any(ViewGroup.class),
-                nullable(Bundle.class), any(View.class));
-        doAnswer(new Answer<Void>() {
-            @Override
-            public Void answer(InvocationOnMock invocation) {
-                List actions = (List) invocation.getArguments()[0];
-                List<GuidedAction> subActions = new ArrayList<GuidedAction>();
-                subActions.add(new GuidedAction.Builder().id(2000).title("item1").build());
-                subActions.add(new GuidedAction.Builder().id(2001).title("item2").build());
-                actions.add(new GuidedAction.Builder().id(1000).subActions(subActions)
-                        .title("list").build());
-                return null;
-            }
-        }).when(first).onCreateActions(any(List.class), nullable(Bundle.class));
-        doAnswer(new Answer<Boolean>() {
-            @Override
-            public Boolean answer(InvocationOnMock invocation) {
-                GuidedStepTestFragment.Provider obj = (GuidedStepTestFragment.Provider)
-                        invocation.getMock();
-                GuidedAction action = (GuidedAction) invocation.getArguments()[0];
-                if (action.getId() == 2000) {
-                    return true;
-                } else if (action.getId() == 2001) {
-                    GuidedStepFragment.add(obj.getFragmentManager(),
-                            new GuidedStepTestFragment(secondFragmentName));
-                    return false;
-                }
-                return false;
-            }
-        }).when(first).onSubGuidedActionClicked(any(GuidedAction.class));
-
-        GuidedStepTestFragment.Provider second = mockProvider(secondFragmentName);
-
-        final GuidedStepFragmentTestActivity activity = launchTestActivity(firstFragmentName);
-
-        // after clicked, it sub actions list should expand
-        View viewForList = first.getFragment().getActionItemView(0);
-        assertTrue(viewForList.hasFocus());
-        sendKey(KeyEvent.KEYCODE_DPAD_CENTER);
-        PollingCheck.waitFor(new ExpandTransitionFinish(first));
-        assertFalse(viewForList.hasFocus());
-
-        sendKey(KeyEvent.KEYCODE_DPAD_CENTER);
-        ArgumentCaptor<GuidedAction> actionCapture = ArgumentCaptor.forClass(GuidedAction.class);
-        verify(first, times(1)).onSubGuidedActionClicked(actionCapture.capture());
-        assertEquals(2000, actionCapture.getValue().getId());
-        // after clicked a sub action, it sub actions list should close
-        PollingCheck.waitFor(new ExpandTransitionFinish(first));
-        assertTrue(viewForList.hasFocus());
-
-        sendKey(KeyEvent.KEYCODE_DPAD_CENTER);
-        PollingCheck.waitFor(new ExpandTransitionFinish(first));
-
-        assertFalse(viewForList.hasFocus());
-        sendKey(KeyEvent.KEYCODE_DPAD_DOWN);
-        sendKey(KeyEvent.KEYCODE_DPAD_CENTER);
-        ArgumentCaptor<GuidedAction> actionCapture2 = ArgumentCaptor.forClass(GuidedAction.class);
-        verify(first, times(2)).onSubGuidedActionClicked(actionCapture2.capture());
-        assertEquals(2001, actionCapture2.getValue().getId());
-
-        PollingCheck.waitFor(new EnterTransitionFinish(second));
-        verify(second, times(1)).onCreateView(any(LayoutInflater.class), any(ViewGroup.class),
-                nullable(Bundle.class), any(View.class));
-
-        // test expand sub action when return to first fragment
-        expandSubActionInOnCreateView[0] = true;
-        sendKey(KeyEvent.KEYCODE_BACK);
-        PollingCheck.waitFor(new EnterTransitionFinish(first));
-        verify(first, times(2)).onCreateView(any(LayoutInflater.class), any(ViewGroup.class),
-                nullable(Bundle.class), any(View.class));
-        assertTrue(first.getFragment().isExpanded());
-
-        sendKey(KeyEvent.KEYCODE_BACK);
-        PollingCheck.waitFor(new ExpandTransitionFinish(first));
-        assertFalse(first.getFragment().isExpanded());
-
-        sendKey(KeyEvent.KEYCODE_BACK);
-        PollingCheck.waitFor(new PollingCheck.ActivityDestroy(activity));
-        verify(first, timeout(ON_DESTROY_TIMEOUT).times(1)).onDestroy();
-    }
-
-    @Test
-    public void setActionsWhenSubActionsExpanded() throws Throwable {
-        final String firstFragmentName = generateMethodTestName("first");
-        GuidedStepTestFragment.Provider first = mockProvider(firstFragmentName);
-        doAnswer(new Answer<Void>() {
-            @Override
-            public Void answer(InvocationOnMock invocation) {
-                List actions = (List) invocation.getArguments()[0];
-                List<GuidedAction> subActions = new ArrayList<GuidedAction>();
-                subActions.add(new GuidedAction.Builder().id(2000).title("item1").build());
-                actions.add(new GuidedAction.Builder().id(1000).subActions(subActions)
-                        .title("list").build());
-                return null;
-            }
-        }).when(first).onCreateActions(any(List.class), nullable(Bundle.class));
-        doAnswer(new Answer<Boolean>() {
-            @Override
-            public Boolean answer(InvocationOnMock invocation) {
-                GuidedStepTestFragment.Provider obj = (GuidedStepTestFragment.Provider)
-                        invocation.getMock();
-                GuidedAction action = (GuidedAction) invocation.getArguments()[0];
-                if (action.getId() == 2000) {
-                    List<GuidedAction> newActions = new ArrayList<GuidedAction>();
-                    newActions.add(new GuidedAction.Builder().id(1001).title("item2").build());
-                    obj.getFragment().setActions(newActions);
-                    return false;
-                }
-                return false;
-            }
-        }).when(first).onSubGuidedActionClicked(any(GuidedAction.class));
-
-        final GuidedStepFragmentTestActivity activity = launchTestActivity(firstFragmentName);
-
-        // after clicked, it sub actions list should expand
-        View firstView = first.getFragment().getActionItemView(0);
-        assertTrue(firstView.hasFocus());
-        sendKey(KeyEvent.KEYCODE_DPAD_CENTER);
-        PollingCheck.waitFor(new ExpandTransitionFinish(first));
-        assertFalse(firstView.hasFocus());
-
-        sendKey(KeyEvent.KEYCODE_DPAD_CENTER);
-        ArgumentCaptor<GuidedAction> actionCapture = ArgumentCaptor.forClass(GuidedAction.class);
-        verify(first, times(1)).onSubGuidedActionClicked(actionCapture.capture());
-        // after clicked a sub action, whole action list is replaced.
-        PollingCheck.waitFor(new ExpandTransitionFinish(first));
-        assertFalse(first.getFragment().isExpanded());
-        View newFirstView  = first.getFragment().getActionItemView(0);
-        assertTrue(newFirstView.hasFocus());
-        assertTrue(newFirstView.getVisibility() == View.VISIBLE);
-        GuidedActionsStylist.ViewHolder vh = (GuidedActionsStylist.ViewHolder) first.getFragment()
-                .getGuidedActionsStylist().getActionsGridView().getChildViewHolder(newFirstView);
-        assertEquals(1001, vh.getAction().getId());
-
-    }
-
-    @Test
-    public void buttonActionsRtl() throws Throwable {
-        final String firstFragmentName = generateMethodTestName("first");
-        GuidedStepTestFragment.Provider first = mockProvider(firstFragmentName);
-        doAnswer(new Answer<Void>() {
-            @Override
-            public Void answer(InvocationOnMock invocation) {
-                List actions = (List) invocation.getArguments()[0];
-                actions.add(new GuidedAction.Builder().id(1000).title("action").build());
-                return null;
-            }
-        }).when(first).onCreateActions(any(List.class), nullable(Bundle.class));
-        doAnswer(new Answer<Void>() {
-            @Override
-            public Void answer(InvocationOnMock invocation) {
-                List actions = (List) invocation.getArguments()[0];
-                actions.add(new GuidedAction.Builder().id(1001).title("button action").build());
-                return null;
-            }
-        }).when(first).onCreateButtonActions(any(List.class), nullable(Bundle.class));
-
-        final GuidedStepFragmentTestActivity activity = launchTestActivity(firstFragmentName,
-                true, View.LAYOUT_DIRECTION_RTL);
-
-        assertEquals(View.LAYOUT_DIRECTION_RTL, first.getFragment().getView().getLayoutDirection());
-        View firstView = first.getFragment().getActionItemView(0);
-        assertTrue(firstView.hasFocus());
-    }
-}
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepFragmentTestActivity.java b/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepFragmentTestActivity.java
deleted file mode 100644
index 4dcf188..0000000
--- a/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepFragmentTestActivity.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package android.support.v17.leanback.app;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.os.Bundle;
-
-/**
- * @hide from javadoc
- */
-public class GuidedStepFragmentTestActivity extends Activity {
-
-    /**
-     * Frst Test that will be included in this Activity
-     */
-    public static final String EXTRA_TEST_NAME = "testName";
-    /**
-     * True(default) to addAsRoot() for first Test, false to use add()
-     */
-    public static final String EXTRA_ADD_AS_ROOT = "addAsRoot";
-
-    /**
-     * Layout direction
-     */
-    public static final String EXTRA_LAYOUT_DIRECTION = "layoutDir";
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        Intent intent = getIntent();
-
-        int layoutDirection = intent.getIntExtra(EXTRA_LAYOUT_DIRECTION, -1);
-        if (layoutDirection != -1) {
-            findViewById(android.R.id.content).setLayoutDirection(layoutDirection);
-        }
-        if (savedInstanceState == null) {
-            String firstTestName = intent.getStringExtra(EXTRA_TEST_NAME);
-            if (firstTestName != null) {
-                GuidedStepTestFragment testFragment = new GuidedStepTestFragment(firstTestName);
-                if (intent.getBooleanExtra(EXTRA_ADD_AS_ROOT, true)) {
-                    GuidedStepTestFragment.addAsRoot(this, testFragment, android.R.id.content);
-                } else {
-                    GuidedStepTestFragment.add(getFragmentManager(), testFragment,
-                            android.R.id.content);
-                }
-            }
-        }
-    }
-}
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepFragmentTestBase.java b/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepFragmentTestBase.java
deleted file mode 100644
index 7059c9a..0000000
--- a/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepFragmentTestBase.java
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.support.v17.leanback.app;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.Intent;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.rule.ActivityTestRule;
-import android.support.v17.leanback.R;
-import android.support.v17.leanback.testutils.PollingCheck;
-import android.view.View;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.rules.TestName;
-
-/**
- * @hide from javadoc
- */
-public class GuidedStepFragmentTestBase {
-
-    private static final long TIMEOUT = 5000;
-
-    @Rule public TestName mUnitTestName = new TestName();
-
-    @Rule
-    public ActivityTestRule<GuidedStepFragmentTestActivity> activityTestRule =
-            new ActivityTestRule<>(GuidedStepFragmentTestActivity.class, false, false);
-
-    @Before
-    public void clearTests() {
-        GuidedStepTestFragment.clearTests();
-    }
-
-    public static class ExpandTransitionFinish extends PollingCheck.PollingCheckCondition {
-        GuidedStepTestFragment.Provider mProvider;
-
-        public ExpandTransitionFinish(GuidedStepTestFragment.Provider provider) {
-            mProvider = provider;
-        }
-
-        @Override
-        public boolean canPreProceed() {
-            return false;
-        }
-
-        @Override
-        public boolean canProceed() {
-            GuidedStepTestFragment fragment = mProvider.getFragment();
-            if (fragment != null && fragment.getView() != null) {
-                if (!fragment.getGuidedActionsStylist().isInExpandTransition()) {
-                    // expand transition finishes
-                    return true;
-                }
-            }
-            return false;
-        }
-    }
-
-    public static void waitOnDestroy(GuidedStepTestFragment.Provider provider,
-            int times) {
-        verify(provider, timeout((int)TIMEOUT).times(times)).onDestroy();
-    }
-
-    public static class EnterTransitionFinish extends PollingCheck.PollingCheckCondition {
-        PollingCheck.ViewScreenPositionDetector mDector =
-                new PollingCheck.ViewScreenPositionDetector();
-
-        GuidedStepTestFragment.Provider mProvider;
-
-        public EnterTransitionFinish(GuidedStepTestFragment.Provider provider) {
-            mProvider = provider;
-        }
-        @Override
-        public boolean canProceed() {
-            GuidedStepTestFragment fragment = mProvider.getFragment();
-            if (fragment != null && fragment.getView() != null) {
-                View view = fragment.getView().findViewById(R.id.guidance_title);
-                if (view != null) {
-                    if (mDector.isViewStableOnScreen(view)) {
-                        return true;
-                    }
-                }
-            }
-            return false;
-        }
-    }
-
-    public static void sendKey(int keyCode) {
-        InstrumentationRegistry.getInstrumentation().sendKeyDownUpSync(keyCode);
-    }
-
-    public String generateMethodTestName(String testName) {
-        return mUnitTestName.getMethodName() + "_" + testName;
-    }
-
-    public GuidedStepFragmentTestActivity launchTestActivity(String firstTestName) {
-        Intent intent = new Intent();
-        intent.putExtra(GuidedStepFragmentTestActivity.EXTRA_TEST_NAME, firstTestName);
-        return activityTestRule.launchActivity(intent);
-    }
-
-    public GuidedStepFragmentTestActivity launchTestActivity(String firstTestName,
-            boolean addAsRoot) {
-        Intent intent = new Intent();
-        intent.putExtra(GuidedStepFragmentTestActivity.EXTRA_TEST_NAME, firstTestName);
-        intent.putExtra(GuidedStepFragmentTestActivity.EXTRA_ADD_AS_ROOT, addAsRoot);
-        return activityTestRule.launchActivity(intent);
-    }
-
-    public GuidedStepFragmentTestActivity launchTestActivity(String firstTestName,
-            boolean addAsRoot, int layoutDirection) {
-        Intent intent = new Intent();
-        intent.putExtra(GuidedStepFragmentTestActivity.EXTRA_TEST_NAME, firstTestName);
-        intent.putExtra(GuidedStepFragmentTestActivity.EXTRA_ADD_AS_ROOT, addAsRoot);
-        intent.putExtra(GuidedStepFragmentTestActivity.EXTRA_LAYOUT_DIRECTION, layoutDirection);
-        return activityTestRule.launchActivity(intent);
-    }
-
-    public GuidedStepTestFragment.Provider mockProvider(String testName) {
-        GuidedStepTestFragment.Provider test = mock(GuidedStepTestFragment.Provider.class);
-        when(test.getActivity()).thenCallRealMethod();
-        when(test.getFragmentManager()).thenCallRealMethod();
-        when(test.getFragment()).thenCallRealMethod();
-        GuidedStepTestFragment.setupTest(testName, test);
-        return test;
-    }
-}
-
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepSupportFragmentTest.java b/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepSupportFragmentTest.java
deleted file mode 100644
index b4d9b59..0000000
--- a/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepSupportFragmentTest.java
+++ /dev/null
@@ -1,454 +0,0 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from GuidedStepFragmentTest.java.  DO NOT MODIFY. */
-
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.support.v17.leanback.app;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.nullable;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-import android.os.Bundle;
-import android.support.test.filters.LargeTest;
-import android.support.test.runner.AndroidJUnit4;
-import android.support.v17.leanback.testutils.PollingCheck;
-import android.support.v17.leanback.widget.GuidedAction;
-import android.support.v17.leanback.widget.GuidedActionsStylist;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
-
-import java.util.ArrayList;
-import java.util.List;
-
-@LargeTest
-@RunWith(AndroidJUnit4.class)
-public class GuidedStepSupportFragmentTest extends GuidedStepSupportFragmentTestBase {
-
-    private static final int ON_DESTROY_TIMEOUT = 5000;
-
-    @Test
-    public void nextAndBack() throws Throwable {
-        final String firstFragmentName = generateMethodTestName("first");
-        final String secondFragmentName = generateMethodTestName("second");
-        GuidedStepTestSupportFragment.Provider first = mockProvider(firstFragmentName);
-        doAnswer(new Answer<Void>() {
-            @Override
-            public Void answer(InvocationOnMock invocation) {
-                List actions = (List) invocation.getArguments()[0];
-                actions.add(new GuidedAction.Builder().id(1000).title("OK").build());
-                return null;
-            }
-        }).when(first).onCreateActions(any(List.class), nullable(Bundle.class));
-        doAnswer(new Answer<Void>() {
-            @Override
-            public Void answer(InvocationOnMock invocation) {
-                GuidedAction action = (GuidedAction) invocation.getArguments()[0];
-                GuidedStepTestSupportFragment.Provider obj = (GuidedStepTestSupportFragment.Provider)
-                        invocation.getMock();
-                if (action.getId() == 1000) {
-                    GuidedStepSupportFragment.add(obj.getFragmentManager(),
-                            new GuidedStepTestSupportFragment(secondFragmentName));
-                }
-                return null;
-            }
-        }).when(first).onGuidedActionClicked(any(GuidedAction.class));
-
-        GuidedStepTestSupportFragment.Provider second = mockProvider(secondFragmentName);
-
-        GuidedStepSupportFragmentTestActivity activity = launchTestActivity(firstFragmentName);
-        verify(first, times(1)).onCreate(nullable(Bundle.class));
-        verify(first, times(1)).onCreateGuidance(nullable(Bundle.class));
-        verify(first, times(1)).onCreateActions(any(List.class), nullable(Bundle.class));
-        verify(first, times(1)).onCreateButtonActions(any(List.class), nullable(Bundle.class));
-        verify(first, times(1)).onCreateView(any(LayoutInflater.class), any(ViewGroup.class),
-                nullable(Bundle.class), any(View.class));
-        verify(first, times(1)).onViewStateRestored(nullable(Bundle.class));
-        verify(first, times(1)).onStart();
-        verify(first, times(1)).onResume();
-
-        sendKey(KeyEvent.KEYCODE_DPAD_CENTER);
-        verify(first, times(1)).onGuidedActionClicked(any(GuidedAction.class));
-
-        PollingCheck.waitFor(new EnterTransitionFinish(second));
-        verify(first, times(1)).onPause();
-        verify(first, times(1)).onStop();
-        verify(first, times(1)).onDestroyView();
-        verify(second, times(1)).onCreate(nullable(Bundle.class));
-        verify(second, times(1)).onCreateGuidance(nullable(Bundle.class));
-        verify(second, times(1)).onCreateActions(any(List.class), nullable(Bundle.class));
-        verify(second, times(1)).onCreateButtonActions(any(List.class), nullable(Bundle.class));
-        verify(second, times(1)).onCreateView(any(LayoutInflater.class), nullable(ViewGroup.class),
-                nullable(Bundle.class), any(View.class));
-        verify(second, times(1)).onViewStateRestored(nullable(Bundle.class));
-        verify(second, times(1)).onStart();
-        verify(second, times(1)).onResume();
-
-        sendKey(KeyEvent.KEYCODE_BACK);
-
-        PollingCheck.waitFor(new EnterTransitionFinish(first));
-        verify(second, times(1)).onPause();
-        verify(second, times(1)).onStop();
-        verify(second, times(1)).onDestroyView();
-        verify(second, times(1)).onDestroy();
-        verify(first, times(1)).onCreateActions(any(List.class), nullable(Bundle.class));
-        verify(first, times(2)).onCreateView(any(LayoutInflater.class), any(ViewGroup.class),
-                nullable(Bundle.class), any(View.class));
-        verify(first, times(2)).onViewStateRestored(nullable(Bundle.class));
-        verify(first, times(2)).onStart();
-        verify(first, times(2)).onResume();
-
-        sendKey(KeyEvent.KEYCODE_BACK);
-        PollingCheck.waitFor(new PollingCheck.ActivityDestroy(activity));
-        verify(first, timeout(ON_DESTROY_TIMEOUT).times(1)).onDestroy();
-        assertTrue(activity.isDestroyed());
-    }
-
-    @Test
-    public void restoreFragments() throws Throwable {
-        final String firstFragmentName = generateMethodTestName("first");
-        final String secondFragmentName = generateMethodTestName("second");
-        GuidedStepTestSupportFragment.Provider first = mockProvider(firstFragmentName);
-        doAnswer(new Answer<Void>() {
-            @Override
-            public Void answer(InvocationOnMock invocation) {
-                List actions = (List) invocation.getArguments()[0];
-                actions.add(new GuidedAction.Builder().id(1000).title("OK").build());
-                actions.add(new GuidedAction.Builder().id(1001).editable(true).title("text")
-                        .build());
-                actions.add(new GuidedAction.Builder().id(1002).editable(true).title("text")
-                        .autoSaveRestoreEnabled(false).build());
-                return null;
-            }
-        }).when(first).onCreateActions(any(List.class), nullable(Bundle.class));
-        doAnswer(new Answer<Void>() {
-            @Override
-            public Void answer(InvocationOnMock invocation) {
-                GuidedAction action = (GuidedAction) invocation.getArguments()[0];
-                GuidedStepTestSupportFragment.Provider obj = (GuidedStepTestSupportFragment.Provider)
-                        invocation.getMock();
-                if (action.getId() == 1000) {
-                    GuidedStepSupportFragment.add(obj.getFragmentManager(),
-                            new GuidedStepTestSupportFragment(secondFragmentName));
-                }
-                return null;
-            }
-        }).when(first).onGuidedActionClicked(any(GuidedAction.class));
-
-        GuidedStepTestSupportFragment.Provider second = mockProvider(secondFragmentName);
-
-        final GuidedStepSupportFragmentTestActivity activity = launchTestActivity(firstFragmentName);
-        first.getFragment().findActionById(1001).setTitle("modified text");
-        first.getFragment().findActionById(1002).setTitle("modified text");
-        sendKey(KeyEvent.KEYCODE_DPAD_CENTER);
-        PollingCheck.waitFor(new EnterTransitionFinish(second));
-
-        activityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                activity.recreate();
-            }
-        });
-        PollingCheck.waitFor(new EnterTransitionFinish(second));
-        verify(first, times(1)).onCreateView(any(LayoutInflater.class), any(ViewGroup.class),
-                nullable(Bundle.class), any(View.class));
-        verify(first, times(1)).onDestroy();
-        verify(second, times(2)).onCreate(nullable(Bundle.class));
-        verify(second, times(2)).onCreateView(any(LayoutInflater.class), any(ViewGroup.class),
-                nullable(Bundle.class), any(View.class));
-        verify(second, times(1)).onDestroy();
-
-        sendKey(KeyEvent.KEYCODE_BACK);
-        PollingCheck.waitFor(new EnterTransitionFinish(first));
-        verify(second, times(2)).onPause();
-        verify(second, times(2)).onStop();
-        verify(second, times(2)).onDestroyView();
-        verify(second, times(2)).onDestroy();
-        assertEquals("modified text", first.getFragment().findActionById(1001).getTitle());
-        assertEquals("text", first.getFragment().findActionById(1002).getTitle());
-        verify(first, times(2)).onCreate(nullable(Bundle.class));
-        verify(first, times(2)).onCreateActions(any(List.class), nullable(Bundle.class));
-        verify(first, times(2)).onCreateView(any(LayoutInflater.class), any(ViewGroup.class),
-                nullable(Bundle.class), any(View.class));
-    }
-
-
-    @Test
-    public void finishGuidedStepSupportFragment_finishes_activity() throws Throwable {
-        final String firstFragmentName = generateMethodTestName("first");
-        GuidedStepTestSupportFragment.Provider first = mockProvider(firstFragmentName);
-        doAnswer(new Answer<Void>() {
-            @Override
-            public Void answer(InvocationOnMock invocation) {
-                List actions = (List) invocation.getArguments()[0];
-                actions.add(new GuidedAction.Builder().id(1001).title("Finish activity").build());
-                return null;
-            }
-        }).when(first).onCreateActions(any(List.class), nullable(Bundle.class));
-        doAnswer(new Answer<Void>() {
-            @Override
-            public Void answer(InvocationOnMock invocation) {
-                GuidedAction action = (GuidedAction) invocation.getArguments()[0];
-                GuidedStepTestSupportFragment.Provider obj = (GuidedStepTestSupportFragment.Provider)
-                        invocation.getMock();
-                if (action.getId() == 1001) {
-                    obj.getFragment().finishGuidedStepSupportFragments();
-                }
-                return null;
-            }
-        }).when(first).onGuidedActionClicked(any(GuidedAction.class));
-
-        final GuidedStepSupportFragmentTestActivity activity = launchTestActivity(firstFragmentName);
-
-        View viewFinish = first.getFragment().getActionItemView(0);
-        assertTrue(viewFinish.hasFocus());
-        sendKey(KeyEvent.KEYCODE_DPAD_CENTER);
-        PollingCheck.waitFor(new PollingCheck.ActivityDestroy(activity));
-        verify(first, timeout(ON_DESTROY_TIMEOUT).times(1)).onDestroy();
-    }
-
-    @Test
-    public void finishGuidedStepSupportFragment_finishes_fragments() throws Throwable {
-        final String firstFragmentName = generateMethodTestName("first");
-        GuidedStepTestSupportFragment.Provider first = mockProvider(firstFragmentName);
-        doAnswer(new Answer<Void>() {
-            @Override
-            public Void answer(InvocationOnMock invocation) {
-                List actions = (List) invocation.getArguments()[0];
-                actions.add(new GuidedAction.Builder().id(1001).title("Finish fragments").build());
-                return null;
-            }
-        }).when(first).onCreateActions(any(List.class), nullable(Bundle.class));
-        doAnswer(new Answer<Void>() {
-            @Override
-            public Void answer(InvocationOnMock invocation) {
-                GuidedAction action = (GuidedAction) invocation.getArguments()[0];
-                GuidedStepTestSupportFragment.Provider obj = (GuidedStepTestSupportFragment.Provider)
-                        invocation.getMock();
-                if (action.getId() == 1001) {
-                    obj.getFragment().finishGuidedStepSupportFragments();
-                }
-                return null;
-            }
-        }).when(first).onGuidedActionClicked(any(GuidedAction.class));
-
-        final GuidedStepSupportFragmentTestActivity activity = launchTestActivity(firstFragmentName,
-                false /*asRoot*/);
-
-        View viewFinish = first.getFragment().getActionItemView(0);
-        assertTrue(viewFinish.hasFocus());
-        sendKey(KeyEvent.KEYCODE_DPAD_CENTER);
-
-        // fragment should be destroyed, activity should not destroyed
-        waitOnDestroy(first, 1);
-        assertFalse(activity.isDestroyed());
-    }
-
-    @Test
-    public void subActions() throws Throwable {
-        final String firstFragmentName = generateMethodTestName("first");
-        final String secondFragmentName = generateMethodTestName("second");
-        final boolean[] expandSubActionInOnCreateView = new boolean[] {false};
-        GuidedStepTestSupportFragment.Provider first = mockProvider(firstFragmentName);
-        doAnswer(new Answer<Void>() {
-            @Override
-            public Void answer(InvocationOnMock invocation) {
-                GuidedStepTestSupportFragment.Provider obj = (GuidedStepTestSupportFragment.Provider)
-                        invocation.getMock();
-                if (expandSubActionInOnCreateView[0]) {
-                    obj.getFragment().expandAction(obj.getFragment().findActionById(1000), false);
-                }
-                return null;
-            }
-        }).when(first).onCreateView(any(LayoutInflater.class), any(ViewGroup.class),
-                nullable(Bundle.class), any(View.class));
-        doAnswer(new Answer<Void>() {
-            @Override
-            public Void answer(InvocationOnMock invocation) {
-                List actions = (List) invocation.getArguments()[0];
-                List<GuidedAction> subActions = new ArrayList<GuidedAction>();
-                subActions.add(new GuidedAction.Builder().id(2000).title("item1").build());
-                subActions.add(new GuidedAction.Builder().id(2001).title("item2").build());
-                actions.add(new GuidedAction.Builder().id(1000).subActions(subActions)
-                        .title("list").build());
-                return null;
-            }
-        }).when(first).onCreateActions(any(List.class), nullable(Bundle.class));
-        doAnswer(new Answer<Boolean>() {
-            @Override
-            public Boolean answer(InvocationOnMock invocation) {
-                GuidedStepTestSupportFragment.Provider obj = (GuidedStepTestSupportFragment.Provider)
-                        invocation.getMock();
-                GuidedAction action = (GuidedAction) invocation.getArguments()[0];
-                if (action.getId() == 2000) {
-                    return true;
-                } else if (action.getId() == 2001) {
-                    GuidedStepSupportFragment.add(obj.getFragmentManager(),
-                            new GuidedStepTestSupportFragment(secondFragmentName));
-                    return false;
-                }
-                return false;
-            }
-        }).when(first).onSubGuidedActionClicked(any(GuidedAction.class));
-
-        GuidedStepTestSupportFragment.Provider second = mockProvider(secondFragmentName);
-
-        final GuidedStepSupportFragmentTestActivity activity = launchTestActivity(firstFragmentName);
-
-        // after clicked, it sub actions list should expand
-        View viewForList = first.getFragment().getActionItemView(0);
-        assertTrue(viewForList.hasFocus());
-        sendKey(KeyEvent.KEYCODE_DPAD_CENTER);
-        PollingCheck.waitFor(new ExpandTransitionFinish(first));
-        assertFalse(viewForList.hasFocus());
-
-        sendKey(KeyEvent.KEYCODE_DPAD_CENTER);
-        ArgumentCaptor<GuidedAction> actionCapture = ArgumentCaptor.forClass(GuidedAction.class);
-        verify(first, times(1)).onSubGuidedActionClicked(actionCapture.capture());
-        assertEquals(2000, actionCapture.getValue().getId());
-        // after clicked a sub action, it sub actions list should close
-        PollingCheck.waitFor(new ExpandTransitionFinish(first));
-        assertTrue(viewForList.hasFocus());
-
-        sendKey(KeyEvent.KEYCODE_DPAD_CENTER);
-        PollingCheck.waitFor(new ExpandTransitionFinish(first));
-
-        assertFalse(viewForList.hasFocus());
-        sendKey(KeyEvent.KEYCODE_DPAD_DOWN);
-        sendKey(KeyEvent.KEYCODE_DPAD_CENTER);
-        ArgumentCaptor<GuidedAction> actionCapture2 = ArgumentCaptor.forClass(GuidedAction.class);
-        verify(first, times(2)).onSubGuidedActionClicked(actionCapture2.capture());
-        assertEquals(2001, actionCapture2.getValue().getId());
-
-        PollingCheck.waitFor(new EnterTransitionFinish(second));
-        verify(second, times(1)).onCreateView(any(LayoutInflater.class), any(ViewGroup.class),
-                nullable(Bundle.class), any(View.class));
-
-        // test expand sub action when return to first fragment
-        expandSubActionInOnCreateView[0] = true;
-        sendKey(KeyEvent.KEYCODE_BACK);
-        PollingCheck.waitFor(new EnterTransitionFinish(first));
-        verify(first, times(2)).onCreateView(any(LayoutInflater.class), any(ViewGroup.class),
-                nullable(Bundle.class), any(View.class));
-        assertTrue(first.getFragment().isExpanded());
-
-        sendKey(KeyEvent.KEYCODE_BACK);
-        PollingCheck.waitFor(new ExpandTransitionFinish(first));
-        assertFalse(first.getFragment().isExpanded());
-
-        sendKey(KeyEvent.KEYCODE_BACK);
-        PollingCheck.waitFor(new PollingCheck.ActivityDestroy(activity));
-        verify(first, timeout(ON_DESTROY_TIMEOUT).times(1)).onDestroy();
-    }
-
-    @Test
-    public void setActionsWhenSubActionsExpanded() throws Throwable {
-        final String firstFragmentName = generateMethodTestName("first");
-        GuidedStepTestSupportFragment.Provider first = mockProvider(firstFragmentName);
-        doAnswer(new Answer<Void>() {
-            @Override
-            public Void answer(InvocationOnMock invocation) {
-                List actions = (List) invocation.getArguments()[0];
-                List<GuidedAction> subActions = new ArrayList<GuidedAction>();
-                subActions.add(new GuidedAction.Builder().id(2000).title("item1").build());
-                actions.add(new GuidedAction.Builder().id(1000).subActions(subActions)
-                        .title("list").build());
-                return null;
-            }
-        }).when(first).onCreateActions(any(List.class), nullable(Bundle.class));
-        doAnswer(new Answer<Boolean>() {
-            @Override
-            public Boolean answer(InvocationOnMock invocation) {
-                GuidedStepTestSupportFragment.Provider obj = (GuidedStepTestSupportFragment.Provider)
-                        invocation.getMock();
-                GuidedAction action = (GuidedAction) invocation.getArguments()[0];
-                if (action.getId() == 2000) {
-                    List<GuidedAction> newActions = new ArrayList<GuidedAction>();
-                    newActions.add(new GuidedAction.Builder().id(1001).title("item2").build());
-                    obj.getFragment().setActions(newActions);
-                    return false;
-                }
-                return false;
-            }
-        }).when(first).onSubGuidedActionClicked(any(GuidedAction.class));
-
-        final GuidedStepSupportFragmentTestActivity activity = launchTestActivity(firstFragmentName);
-
-        // after clicked, it sub actions list should expand
-        View firstView = first.getFragment().getActionItemView(0);
-        assertTrue(firstView.hasFocus());
-        sendKey(KeyEvent.KEYCODE_DPAD_CENTER);
-        PollingCheck.waitFor(new ExpandTransitionFinish(first));
-        assertFalse(firstView.hasFocus());
-
-        sendKey(KeyEvent.KEYCODE_DPAD_CENTER);
-        ArgumentCaptor<GuidedAction> actionCapture = ArgumentCaptor.forClass(GuidedAction.class);
-        verify(first, times(1)).onSubGuidedActionClicked(actionCapture.capture());
-        // after clicked a sub action, whole action list is replaced.
-        PollingCheck.waitFor(new ExpandTransitionFinish(first));
-        assertFalse(first.getFragment().isExpanded());
-        View newFirstView  = first.getFragment().getActionItemView(0);
-        assertTrue(newFirstView.hasFocus());
-        assertTrue(newFirstView.getVisibility() == View.VISIBLE);
-        GuidedActionsStylist.ViewHolder vh = (GuidedActionsStylist.ViewHolder) first.getFragment()
-                .getGuidedActionsStylist().getActionsGridView().getChildViewHolder(newFirstView);
-        assertEquals(1001, vh.getAction().getId());
-
-    }
-
-    @Test
-    public void buttonActionsRtl() throws Throwable {
-        final String firstFragmentName = generateMethodTestName("first");
-        GuidedStepTestSupportFragment.Provider first = mockProvider(firstFragmentName);
-        doAnswer(new Answer<Void>() {
-            @Override
-            public Void answer(InvocationOnMock invocation) {
-                List actions = (List) invocation.getArguments()[0];
-                actions.add(new GuidedAction.Builder().id(1000).title("action").build());
-                return null;
-            }
-        }).when(first).onCreateActions(any(List.class), nullable(Bundle.class));
-        doAnswer(new Answer<Void>() {
-            @Override
-            public Void answer(InvocationOnMock invocation) {
-                List actions = (List) invocation.getArguments()[0];
-                actions.add(new GuidedAction.Builder().id(1001).title("button action").build());
-                return null;
-            }
-        }).when(first).onCreateButtonActions(any(List.class), nullable(Bundle.class));
-
-        final GuidedStepSupportFragmentTestActivity activity = launchTestActivity(firstFragmentName,
-                true, View.LAYOUT_DIRECTION_RTL);
-
-        assertEquals(View.LAYOUT_DIRECTION_RTL, first.getFragment().getView().getLayoutDirection());
-        View firstView = first.getFragment().getActionItemView(0);
-        assertTrue(firstView.hasFocus());
-    }
-}
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepSupportFragmentTestActivity.java b/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepSupportFragmentTestActivity.java
deleted file mode 100644
index fb877ed..0000000
--- a/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepSupportFragmentTestActivity.java
+++ /dev/null
@@ -1,66 +0,0 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from GuidedStepFragmentTestActivity.java.  DO NOT MODIFY. */
-
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package android.support.v17.leanback.app;
-
-import android.support.v4.app.FragmentActivity;
-import android.content.Intent;
-import android.os.Bundle;
-
-/**
- * @hide from javadoc
- */
-public class GuidedStepSupportFragmentTestActivity extends FragmentActivity {
-
-    /**
-     * Frst Test that will be included in this Activity
-     */
-    public static final String EXTRA_TEST_NAME = "testName";
-    /**
-     * True(default) to addAsRoot() for first Test, false to use add()
-     */
-    public static final String EXTRA_ADD_AS_ROOT = "addAsRoot";
-
-    /**
-     * Layout direction
-     */
-    public static final String EXTRA_LAYOUT_DIRECTION = "layoutDir";
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        Intent intent = getIntent();
-
-        int layoutDirection = intent.getIntExtra(EXTRA_LAYOUT_DIRECTION, -1);
-        if (layoutDirection != -1) {
-            findViewById(android.R.id.content).setLayoutDirection(layoutDirection);
-        }
-        if (savedInstanceState == null) {
-            String firstTestName = intent.getStringExtra(EXTRA_TEST_NAME);
-            if (firstTestName != null) {
-                GuidedStepTestSupportFragment testFragment = new GuidedStepTestSupportFragment(firstTestName);
-                if (intent.getBooleanExtra(EXTRA_ADD_AS_ROOT, true)) {
-                    GuidedStepTestSupportFragment.addAsRoot(this, testFragment, android.R.id.content);
-                } else {
-                    GuidedStepTestSupportFragment.add(getSupportFragmentManager(), testFragment,
-                            android.R.id.content);
-                }
-            }
-        }
-    }
-}
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepSupportFragmentTestBase.java b/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepSupportFragmentTestBase.java
deleted file mode 100644
index 17533fa..0000000
--- a/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepSupportFragmentTestBase.java
+++ /dev/null
@@ -1,149 +0,0 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from GuidedStepFrgamentTestBase.java.  DO NOT MODIFY. */
-
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.support.v17.leanback.app;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.Intent;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.rule.ActivityTestRule;
-import android.support.v17.leanback.R;
-import android.support.v17.leanback.testutils.PollingCheck;
-import android.view.View;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.rules.TestName;
-
-/**
- * @hide from javadoc
- */
-public class GuidedStepSupportFragmentTestBase {
-
-    private static final long TIMEOUT = 5000;
-
-    @Rule public TestName mUnitTestName = new TestName();
-
-    @Rule
-    public ActivityTestRule<GuidedStepSupportFragmentTestActivity> activityTestRule =
-            new ActivityTestRule<>(GuidedStepSupportFragmentTestActivity.class, false, false);
-
-    @Before
-    public void clearTests() {
-        GuidedStepTestSupportFragment.clearTests();
-    }
-
-    public static class ExpandTransitionFinish extends PollingCheck.PollingCheckCondition {
-        GuidedStepTestSupportFragment.Provider mProvider;
-
-        public ExpandTransitionFinish(GuidedStepTestSupportFragment.Provider provider) {
-            mProvider = provider;
-        }
-
-        @Override
-        public boolean canPreProceed() {
-            return false;
-        }
-
-        @Override
-        public boolean canProceed() {
-            GuidedStepTestSupportFragment fragment = mProvider.getFragment();
-            if (fragment != null && fragment.getView() != null) {
-                if (!fragment.getGuidedActionsStylist().isInExpandTransition()) {
-                    // expand transition finishes
-                    return true;
-                }
-            }
-            return false;
-        }
-    }
-
-    public static void waitOnDestroy(GuidedStepTestSupportFragment.Provider provider,
-            int times) {
-        verify(provider, timeout((int)TIMEOUT).times(times)).onDestroy();
-    }
-
-    public static class EnterTransitionFinish extends PollingCheck.PollingCheckCondition {
-        PollingCheck.ViewScreenPositionDetector mDector =
-                new PollingCheck.ViewScreenPositionDetector();
-
-        GuidedStepTestSupportFragment.Provider mProvider;
-
-        public EnterTransitionFinish(GuidedStepTestSupportFragment.Provider provider) {
-            mProvider = provider;
-        }
-        @Override
-        public boolean canProceed() {
-            GuidedStepTestSupportFragment fragment = mProvider.getFragment();
-            if (fragment != null && fragment.getView() != null) {
-                View view = fragment.getView().findViewById(R.id.guidance_title);
-                if (view != null) {
-                    if (mDector.isViewStableOnScreen(view)) {
-                        return true;
-                    }
-                }
-            }
-            return false;
-        }
-    }
-
-    public static void sendKey(int keyCode) {
-        InstrumentationRegistry.getInstrumentation().sendKeyDownUpSync(keyCode);
-    }
-
-    public String generateMethodTestName(String testName) {
-        return mUnitTestName.getMethodName() + "_" + testName;
-    }
-
-    public GuidedStepSupportFragmentTestActivity launchTestActivity(String firstTestName) {
-        Intent intent = new Intent();
-        intent.putExtra(GuidedStepSupportFragmentTestActivity.EXTRA_TEST_NAME, firstTestName);
-        return activityTestRule.launchActivity(intent);
-    }
-
-    public GuidedStepSupportFragmentTestActivity launchTestActivity(String firstTestName,
-            boolean addAsRoot) {
-        Intent intent = new Intent();
-        intent.putExtra(GuidedStepSupportFragmentTestActivity.EXTRA_TEST_NAME, firstTestName);
-        intent.putExtra(GuidedStepSupportFragmentTestActivity.EXTRA_ADD_AS_ROOT, addAsRoot);
-        return activityTestRule.launchActivity(intent);
-    }
-
-    public GuidedStepSupportFragmentTestActivity launchTestActivity(String firstTestName,
-            boolean addAsRoot, int layoutDirection) {
-        Intent intent = new Intent();
-        intent.putExtra(GuidedStepSupportFragmentTestActivity.EXTRA_TEST_NAME, firstTestName);
-        intent.putExtra(GuidedStepSupportFragmentTestActivity.EXTRA_ADD_AS_ROOT, addAsRoot);
-        intent.putExtra(GuidedStepSupportFragmentTestActivity.EXTRA_LAYOUT_DIRECTION, layoutDirection);
-        return activityTestRule.launchActivity(intent);
-    }
-
-    public GuidedStepTestSupportFragment.Provider mockProvider(String testName) {
-        GuidedStepTestSupportFragment.Provider test = mock(GuidedStepTestSupportFragment.Provider.class);
-        when(test.getActivity()).thenCallRealMethod();
-        when(test.getFragmentManager()).thenCallRealMethod();
-        when(test.getFragment()).thenCallRealMethod();
-        GuidedStepTestSupportFragment.setupTest(testName, test);
-        return test;
-    }
-}
-
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepTestFragment.java b/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepTestFragment.java
deleted file mode 100644
index c530925..0000000
--- a/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepTestFragment.java
+++ /dev/null
@@ -1,239 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package android.support.v17.leanback.app;
-
-import android.app.Activity;
-import android.app.FragmentManager;
-import android.os.Bundle;
-import android.view.ViewGroup;
-import android.view.View;
-import android.view.LayoutInflater;
-
-
-import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
-import android.support.v17.leanback.widget.GuidedAction;
-
-import java.util.List;
-import java.util.HashMap;
-
-/**
- * @hide from javadoc
- */
-public class GuidedStepTestFragment extends GuidedStepFragment {
-
-    private static final String KEY_TEST_NAME = "key_test_name";
-
-    private static final HashMap<String, Provider> sTestMap = new HashMap<String, Provider>();
-
-    public static class Provider {
-
-        GuidedStepTestFragment mFragment;
-
-        public void onCreate(Bundle savedInstanceState) {
-        }
-
-        public void onSaveInstanceState(Bundle outState) {
-        }
-
-        public Guidance onCreateGuidance(Bundle savedInstanceState) {
-            return new Guidance("", "", "", null);
-        }
-
-        public void onCreateActions(List<GuidedAction> actions, Bundle savedInstanceState) {
-        }
-
-        public void onCreateButtonActions(List<GuidedAction> actions, Bundle savedInstanceState) {
-        }
-
-        public void onGuidedActionClicked(GuidedAction action) {
-        }
-
-        public boolean onSubGuidedActionClicked(GuidedAction action) {
-            return true;
-        }
-
-        public void onCreateView(LayoutInflater inflater, ViewGroup container,
-                Bundle savedInstanceState, View result) {
-        }
-
-        public void onDestroyView() {
-        }
-
-        public void onDestroy() {
-        }
-
-        public void onStart() {
-        }
-
-        public void onStop() {
-        }
-
-        public void onResume() {
-        }
-
-        public void onPause() {
-        }
-
-        public void onViewStateRestored(Bundle bundle) {
-        }
-
-        public void onDetach() {
-        }
-
-        public GuidedStepTestFragment getFragment() {
-            return mFragment;
-        }
-
-        public Activity getActivity() {
-            return mFragment.getActivity();
-        }
-
-        public FragmentManager getFragmentManager() {
-            return mFragment.getFragmentManager();
-        }
-    }
-
-    public static void setupTest(String testName, Provider provider) {
-        sTestMap.put(testName, provider);
-    }
-
-    public static void clearTests() {
-        sTestMap.clear();
-    }
-
-    CharSequence mTestName;
-    Provider mProvider;
-
-    public GuidedStepTestFragment() {
-    }
-
-    public GuidedStepTestFragment(String testName) {
-        setTestName(testName);
-    }
-
-    public void setTestName(CharSequence testName) {
-        mTestName = testName;
-    }
-
-    public CharSequence getTestName() {
-        return mTestName;
-    }
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        if (savedInstanceState != null) {
-            mTestName = savedInstanceState.getCharSequence(KEY_TEST_NAME, null);
-        }
-        mProvider = sTestMap.get(mTestName);
-        if (mProvider == null) {
-            throw new IllegalArgumentException("you must setupTest()");
-        }
-        mProvider.mFragment = this;
-        super.onCreate(savedInstanceState);
-        mProvider.onCreate(savedInstanceState);
-    }
-
-    @Override
-    public void onSaveInstanceState(Bundle outState) {
-        super.onSaveInstanceState(outState);
-        outState.putCharSequence(KEY_TEST_NAME, mTestName);
-        mProvider.onSaveInstanceState(outState);
-    }
-
-    @Override
-    public Guidance onCreateGuidance(Bundle savedInstanceState) {
-        Guidance g = mProvider.onCreateGuidance(savedInstanceState);
-        if (g == null) {
-            g = new Guidance("", "", "", null);
-        }
-        return g;
-    }
-
-    @Override
-    public void onCreateActions(List<GuidedAction> actions, Bundle savedInstanceState) {
-        mProvider.onCreateActions(actions, savedInstanceState);
-    }
-
-    @Override
-    public void onCreateButtonActions(List<GuidedAction> actions, Bundle savedInstanceState) {
-        mProvider.onCreateButtonActions(actions, savedInstanceState);
-    }
-
-    @Override
-    public void onGuidedActionClicked(GuidedAction action) {
-        mProvider.onGuidedActionClicked(action);
-    }
-
-    @Override
-    public boolean onSubGuidedActionClicked(GuidedAction action) {
-        return mProvider.onSubGuidedActionClicked(action);
-    }
-
-    @Override
-    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle state) {
-        View view = super.onCreateView(inflater, container, state);
-        mProvider.onCreateView(inflater, container, state, view);
-        return view;
-    }
-
-    @Override
-    public void onDestroyView() {
-        mProvider.onDestroyView();
-        super.onDestroyView();
-    }
-
-    @Override
-    public void onDestroy() {
-        mProvider.onDestroy();
-        super.onDestroy();
-    }
-
-    @Override
-    public void onPause() {
-        mProvider.onPause();
-        super.onPause();
-    }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-        mProvider.onResume();
-    }
-
-    @Override
-    public void onStart() {
-        super.onStart();
-        mProvider.onStart();
-    }
-
-    @Override
-    public void onStop() {
-        mProvider.onStop();
-        super.onStop();
-    }
-
-    @Override
-    public void onDetach() {
-        mProvider.onDetach();
-        super.onDetach();
-    }
-
-    @Override
-    public void onViewStateRestored(Bundle bundle) {
-        super.onViewStateRestored(bundle);
-        mProvider.onViewStateRestored(bundle);
-    }
-}
-
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepTestSupportFragment.java b/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepTestSupportFragment.java
deleted file mode 100644
index bafc2db..0000000
--- a/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepTestSupportFragment.java
+++ /dev/null
@@ -1,242 +0,0 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from GuidedStepTestFragment.java.  DO NOT MODIFY. */
-
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package android.support.v17.leanback.app;
-
-import android.support.v4.app.FragmentActivity;
-import android.support.v4.app.FragmentManager;
-import android.os.Bundle;
-import android.view.ViewGroup;
-import android.view.View;
-import android.view.LayoutInflater;
-
-
-import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
-import android.support.v17.leanback.widget.GuidedAction;
-
-import java.util.List;
-import java.util.HashMap;
-
-/**
- * @hide from javadoc
- */
-public class GuidedStepTestSupportFragment extends GuidedStepSupportFragment {
-
-    private static final String KEY_TEST_NAME = "key_test_name";
-
-    private static final HashMap<String, Provider> sTestMap = new HashMap<String, Provider>();
-
-    public static class Provider {
-
-        GuidedStepTestSupportFragment mFragment;
-
-        public void onCreate(Bundle savedInstanceState) {
-        }
-
-        public void onSaveInstanceState(Bundle outState) {
-        }
-
-        public Guidance onCreateGuidance(Bundle savedInstanceState) {
-            return new Guidance("", "", "", null);
-        }
-
-        public void onCreateActions(List<GuidedAction> actions, Bundle savedInstanceState) {
-        }
-
-        public void onCreateButtonActions(List<GuidedAction> actions, Bundle savedInstanceState) {
-        }
-
-        public void onGuidedActionClicked(GuidedAction action) {
-        }
-
-        public boolean onSubGuidedActionClicked(GuidedAction action) {
-            return true;
-        }
-
-        public void onCreateView(LayoutInflater inflater, ViewGroup container,
-                Bundle savedInstanceState, View result) {
-        }
-
-        public void onDestroyView() {
-        }
-
-        public void onDestroy() {
-        }
-
-        public void onStart() {
-        }
-
-        public void onStop() {
-        }
-
-        public void onResume() {
-        }
-
-        public void onPause() {
-        }
-
-        public void onViewStateRestored(Bundle bundle) {
-        }
-
-        public void onDetach() {
-        }
-
-        public GuidedStepTestSupportFragment getFragment() {
-            return mFragment;
-        }
-
-        public FragmentActivity getActivity() {
-            return mFragment.getActivity();
-        }
-
-        public FragmentManager getFragmentManager() {
-            return mFragment.getFragmentManager();
-        }
-    }
-
-    public static void setupTest(String testName, Provider provider) {
-        sTestMap.put(testName, provider);
-    }
-
-    public static void clearTests() {
-        sTestMap.clear();
-    }
-
-    CharSequence mTestName;
-    Provider mProvider;
-
-    public GuidedStepTestSupportFragment() {
-    }
-
-    public GuidedStepTestSupportFragment(String testName) {
-        setTestName(testName);
-    }
-
-    public void setTestName(CharSequence testName) {
-        mTestName = testName;
-    }
-
-    public CharSequence getTestName() {
-        return mTestName;
-    }
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        if (savedInstanceState != null) {
-            mTestName = savedInstanceState.getCharSequence(KEY_TEST_NAME, null);
-        }
-        mProvider = sTestMap.get(mTestName);
-        if (mProvider == null) {
-            throw new IllegalArgumentException("you must setupTest()");
-        }
-        mProvider.mFragment = this;
-        super.onCreate(savedInstanceState);
-        mProvider.onCreate(savedInstanceState);
-    }
-
-    @Override
-    public void onSaveInstanceState(Bundle outState) {
-        super.onSaveInstanceState(outState);
-        outState.putCharSequence(KEY_TEST_NAME, mTestName);
-        mProvider.onSaveInstanceState(outState);
-    }
-
-    @Override
-    public Guidance onCreateGuidance(Bundle savedInstanceState) {
-        Guidance g = mProvider.onCreateGuidance(savedInstanceState);
-        if (g == null) {
-            g = new Guidance("", "", "", null);
-        }
-        return g;
-    }
-
-    @Override
-    public void onCreateActions(List<GuidedAction> actions, Bundle savedInstanceState) {
-        mProvider.onCreateActions(actions, savedInstanceState);
-    }
-
-    @Override
-    public void onCreateButtonActions(List<GuidedAction> actions, Bundle savedInstanceState) {
-        mProvider.onCreateButtonActions(actions, savedInstanceState);
-    }
-
-    @Override
-    public void onGuidedActionClicked(GuidedAction action) {
-        mProvider.onGuidedActionClicked(action);
-    }
-
-    @Override
-    public boolean onSubGuidedActionClicked(GuidedAction action) {
-        return mProvider.onSubGuidedActionClicked(action);
-    }
-
-    @Override
-    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle state) {
-        View view = super.onCreateView(inflater, container, state);
-        mProvider.onCreateView(inflater, container, state, view);
-        return view;
-    }
-
-    @Override
-    public void onDestroyView() {
-        mProvider.onDestroyView();
-        super.onDestroyView();
-    }
-
-    @Override
-    public void onDestroy() {
-        mProvider.onDestroy();
-        super.onDestroy();
-    }
-
-    @Override
-    public void onPause() {
-        mProvider.onPause();
-        super.onPause();
-    }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-        mProvider.onResume();
-    }
-
-    @Override
-    public void onStart() {
-        super.onStart();
-        mProvider.onStart();
-    }
-
-    @Override
-    public void onStop() {
-        mProvider.onStop();
-        super.onStop();
-    }
-
-    @Override
-    public void onDetach() {
-        mProvider.onDetach();
-        super.onDetach();
-    }
-
-    @Override
-    public void onViewStateRestored(Bundle bundle) {
-        super.onViewStateRestored(bundle);
-        mProvider.onViewStateRestored(bundle);
-    }
-}
-
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/HeadersFragmentTest.java b/v17/leanback/tests/java/android/support/v17/leanback/app/HeadersFragmentTest.java
deleted file mode 100644
index e05237f..0000000
--- a/v17/leanback/tests/java/android/support/v17/leanback/app/HeadersFragmentTest.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.support.v17.leanback.app;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import android.os.Bundle;
-import android.support.test.filters.MediumTest;
-import android.support.test.runner.AndroidJUnit4;
-import android.support.v17.leanback.widget.ArrayObjectAdapter;
-import android.support.v17.leanback.widget.FocusHighlightHelper;
-import android.support.v17.leanback.widget.HeaderItem;
-import android.support.v17.leanback.widget.ItemBridgeAdapter;
-import android.support.v17.leanback.widget.ListRow;
-import android.support.v17.leanback.widget.ListRowPresenter;
-import android.support.v17.leanback.widget.VerticalGridView;
-import android.view.View;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@MediumTest
-@RunWith(AndroidJUnit4.class)
-public class HeadersFragmentTest extends SingleFragmentTestBase {
-
-    static void loadData(ArrayObjectAdapter adapter, int numRows) {
-        for (int i = 0; i < numRows; ++i) {
-            ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter();
-            HeaderItem header = new HeaderItem(i, "Row " + i);
-            adapter.add(new ListRow(header, listRowAdapter));
-        }
-    }
-
-    public static class F_defaultScale extends HeadersFragment {
-        final ListRowPresenter mPresenter = new ListRowPresenter();
-
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            final ArrayObjectAdapter adapter = new ArrayObjectAdapter(mPresenter);
-            setAdapter(adapter);
-            loadData(adapter, 10);
-        }
-    }
-
-    @Test
-    public void defaultScale() {
-        SingleFragmentTestActivity activity = launchAndWaitActivity(F_defaultScale.class, 1000);
-
-        final VerticalGridView gridView = ((HeadersFragment) activity.getTestFragment())
-                .getVerticalGridView();
-        ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder)
-                gridView.findViewHolderForAdapterPosition(0);
-        assertTrue(vh.itemView.getScaleX() - 1.0f > 0.05f);
-        assertTrue(vh.itemView.getScaleY() - 1.0f > 0.05f);
-    }
-
-    public static class F_disableScale extends HeadersFragment {
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            final ArrayObjectAdapter adapter = new ArrayObjectAdapter(new ListRowPresenter());
-            setAdapter(adapter);
-            loadData(adapter, 10);
-        }
-
-        @Override
-        public void onViewCreated(View view, Bundle savedInstanceState) {
-            super.onViewCreated(view, savedInstanceState);
-            FocusHighlightHelper.setupHeaderItemFocusHighlight(getVerticalGridView(), false);
-        }
-    }
-
-    @Test
-    public void disableScale() {
-        SingleFragmentTestActivity activity = launchAndWaitActivity(F_disableScale.class, 1000);
-
-        final VerticalGridView gridView = ((HeadersFragment) activity.getTestFragment())
-                .getVerticalGridView();
-        ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder)
-                gridView.findViewHolderForAdapterPosition(0);
-        assertEquals(vh.itemView.getScaleX(), 1f, 0.001f);
-        assertEquals(vh.itemView.getScaleY(), 1f, 0.001f);
-    }
-
-    public static class F_disableScaleInConstructor extends HeadersFragment {
-        public F_disableScaleInConstructor() {
-            FocusHighlightHelper.setupHeaderItemFocusHighlight(getBridgeAdapter(), false);
-        }
-
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            final ArrayObjectAdapter adapter = new ArrayObjectAdapter(new ListRowPresenter());
-            setAdapter(adapter);
-            loadData(adapter, 10);
-        }
-    }
-
-    @Test
-    public void disableScaleInConstructor() {
-        SingleFragmentTestActivity activity = launchAndWaitActivity(
-                F_disableScaleInConstructor.class, 1000);
-
-        final VerticalGridView gridView = ((HeadersFragment) activity.getTestFragment())
-                .getVerticalGridView();
-        ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder)
-                gridView.findViewHolderForAdapterPosition(0);
-        assertEquals(vh.itemView.getScaleX(), 1f, 0.001f);
-        assertEquals(vh.itemView.getScaleY(), 1f, 0.001f);
-    }
-}
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/HeadersSupportFragmentTest.java b/v17/leanback/tests/java/android/support/v17/leanback/app/HeadersSupportFragmentTest.java
deleted file mode 100644
index 7ec69b9..0000000
--- a/v17/leanback/tests/java/android/support/v17/leanback/app/HeadersSupportFragmentTest.java
+++ /dev/null
@@ -1,129 +0,0 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from HeadersFragmentTest.java.  DO NOT MODIFY. */
-
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.support.v17.leanback.app;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import android.os.Bundle;
-import android.support.test.filters.MediumTest;
-import android.support.test.runner.AndroidJUnit4;
-import android.support.v17.leanback.widget.ArrayObjectAdapter;
-import android.support.v17.leanback.widget.FocusHighlightHelper;
-import android.support.v17.leanback.widget.HeaderItem;
-import android.support.v17.leanback.widget.ItemBridgeAdapter;
-import android.support.v17.leanback.widget.ListRow;
-import android.support.v17.leanback.widget.ListRowPresenter;
-import android.support.v17.leanback.widget.VerticalGridView;
-import android.view.View;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@MediumTest
-@RunWith(AndroidJUnit4.class)
-public class HeadersSupportFragmentTest extends SingleSupportFragmentTestBase {
-
-    static void loadData(ArrayObjectAdapter adapter, int numRows) {
-        for (int i = 0; i < numRows; ++i) {
-            ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter();
-            HeaderItem header = new HeaderItem(i, "Row " + i);
-            adapter.add(new ListRow(header, listRowAdapter));
-        }
-    }
-
-    public static class F_defaultScale extends HeadersSupportFragment {
-        final ListRowPresenter mPresenter = new ListRowPresenter();
-
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            final ArrayObjectAdapter adapter = new ArrayObjectAdapter(mPresenter);
-            setAdapter(adapter);
-            loadData(adapter, 10);
-        }
-    }
-
-    @Test
-    public void defaultScale() {
-        SingleSupportFragmentTestActivity activity = launchAndWaitActivity(F_defaultScale.class, 1000);
-
-        final VerticalGridView gridView = ((HeadersSupportFragment) activity.getTestFragment())
-                .getVerticalGridView();
-        ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder)
-                gridView.findViewHolderForAdapterPosition(0);
-        assertTrue(vh.itemView.getScaleX() - 1.0f > 0.05f);
-        assertTrue(vh.itemView.getScaleY() - 1.0f > 0.05f);
-    }
-
-    public static class F_disableScale extends HeadersSupportFragment {
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            final ArrayObjectAdapter adapter = new ArrayObjectAdapter(new ListRowPresenter());
-            setAdapter(adapter);
-            loadData(adapter, 10);
-        }
-
-        @Override
-        public void onViewCreated(View view, Bundle savedInstanceState) {
-            super.onViewCreated(view, savedInstanceState);
-            FocusHighlightHelper.setupHeaderItemFocusHighlight(getVerticalGridView(), false);
-        }
-    }
-
-    @Test
-    public void disableScale() {
-        SingleSupportFragmentTestActivity activity = launchAndWaitActivity(F_disableScale.class, 1000);
-
-        final VerticalGridView gridView = ((HeadersSupportFragment) activity.getTestFragment())
-                .getVerticalGridView();
-        ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder)
-                gridView.findViewHolderForAdapterPosition(0);
-        assertEquals(vh.itemView.getScaleX(), 1f, 0.001f);
-        assertEquals(vh.itemView.getScaleY(), 1f, 0.001f);
-    }
-
-    public static class F_disableScaleInConstructor extends HeadersSupportFragment {
-        public F_disableScaleInConstructor() {
-            FocusHighlightHelper.setupHeaderItemFocusHighlight(getBridgeAdapter(), false);
-        }
-
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            final ArrayObjectAdapter adapter = new ArrayObjectAdapter(new ListRowPresenter());
-            setAdapter(adapter);
-            loadData(adapter, 10);
-        }
-    }
-
-    @Test
-    public void disableScaleInConstructor() {
-        SingleSupportFragmentTestActivity activity = launchAndWaitActivity(
-                F_disableScaleInConstructor.class, 1000);
-
-        final VerticalGridView gridView = ((HeadersSupportFragment) activity.getTestFragment())
-                .getVerticalGridView();
-        ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder)
-                gridView.findViewHolderForAdapterPosition(0);
-        assertEquals(vh.itemView.getScaleX(), 1f, 0.001f);
-        assertEquals(vh.itemView.getScaleY(), 1f, 0.001f);
-    }
-}
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackFragmentTest.java b/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackFragmentTest.java
deleted file mode 100644
index 6353ef9..0000000
--- a/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackFragmentTest.java
+++ /dev/null
@@ -1,371 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.support.v17.leanback.app;
-
-import static junit.framework.Assert.assertEquals;
-
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.support.test.filters.FlakyTest;
-import android.support.test.filters.MediumTest;
-import android.support.test.filters.Suppress;
-import android.support.test.runner.AndroidJUnit4;
-import android.support.v17.leanback.media.PlaybackControlGlue;
-import android.support.v17.leanback.media.PlaybackGlue;
-import android.support.v17.leanback.testutils.PollingCheck;
-import android.support.v17.leanback.widget.ControlButtonPresenterSelector;
-import android.support.v17.leanback.widget.ListRow;
-import android.support.v17.leanback.widget.OnItemViewClickedListener;
-import android.support.v17.leanback.widget.OnItemViewSelectedListener;
-import android.support.v17.leanback.widget.PlaybackControlsRow;
-import android.support.v17.leanback.widget.PlaybackControlsRowPresenter;
-import android.support.v17.leanback.widget.Presenter;
-import android.support.v17.leanback.widget.Row;
-import android.support.v17.leanback.widget.RowPresenter;
-import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
-import android.view.KeyEvent;
-import android.view.View;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mockito;
-
-@MediumTest
-@RunWith(AndroidJUnit4.class)
-public class PlaybackFragmentTest extends SingleFragmentTestBase {
-
-    private static final String TAG = "PlaybackFragmentTest";
-    private static final long TRANSITION_LENGTH = 1000;
-
-    @Test
-    public void testDetachCalledWhenDestroyFragment() throws Throwable {
-        final SingleFragmentTestActivity activity =
-                launchAndWaitActivity(PlaybackTestFragment.class, 1000);
-        final PlaybackTestFragment fragment = (PlaybackTestFragment) activity.getTestFragment();
-        PlaybackGlue glue = fragment.getGlue();
-        activityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                activity.finish();
-            }
-        });
-        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return fragment.mDestroyCalled;
-            }
-        });
-        assertNull(glue.getHost());
-    }
-
-    @Test
-    public void testSelectedListener() throws Throwable {
-        SingleFragmentTestActivity activity =
-                launchAndWaitActivity(PlaybackTestFragment.class, 1000);
-        PlaybackTestFragment fragment = (PlaybackTestFragment) activity.getTestFragment();
-
-        assertTrue(fragment.getView().hasFocus());
-
-        OnItemViewSelectedListener selectedListener = Mockito.mock(
-                OnItemViewSelectedListener.class);
-        fragment.setOnItemViewSelectedListener(selectedListener);
-
-
-        PlaybackControlsRow controlsRow = fragment.getGlue().getControlsRow();
-        SparseArrayObjectAdapter primaryActionsAdapter = (SparseArrayObjectAdapter)
-                controlsRow.getPrimaryActionsAdapter();
-
-        PlaybackControlsRow.MultiAction playPause = (PlaybackControlsRow.MultiAction)
-                primaryActionsAdapter.lookup(PlaybackControlGlue.ACTION_PLAY_PAUSE);
-
-        PlaybackControlsRow.MultiAction rewind = (PlaybackControlsRow.MultiAction)
-                primaryActionsAdapter.lookup(PlaybackControlGlue.ACTION_REWIND);
-
-        PlaybackControlsRow.MultiAction thumbsUp = (PlaybackControlsRow.MultiAction)
-                primaryActionsAdapter.lookup(PlaybackControlGlue.ACTION_CUSTOM_LEFT_FIRST);
-
-        ArgumentCaptor<Presenter.ViewHolder> itemVHCaptor =
-                ArgumentCaptor.forClass(Presenter.ViewHolder.class);
-        ArgumentCaptor<Object> itemCaptor = ArgumentCaptor.forClass(Object.class);
-        ArgumentCaptor<RowPresenter.ViewHolder> rowVHCaptor =
-                ArgumentCaptor.forClass(RowPresenter.ViewHolder.class);
-        ArgumentCaptor<Row> rowCaptor = ArgumentCaptor.forClass(Row.class);
-
-
-        // First navigate left within PlaybackControlsRow items.
-        verify(selectedListener, times(0)).onItemSelected(any(Presenter.ViewHolder.class),
-                any(Object.class), any(RowPresenter.ViewHolder.class), any(Row.class));
-        sendKeys(KeyEvent.KEYCODE_DPAD_LEFT);
-        verify(selectedListener, times(1)).onItemSelected(itemVHCaptor.capture(),
-                itemCaptor.capture(), rowVHCaptor.capture(), rowCaptor.capture());
-        assertSame("Same controls row should be passed to the listener", controlsRow,
-                rowCaptor.getValue());
-        assertSame("The selected action should be rewind", rewind, itemCaptor.getValue());
-
-        sendKeys(KeyEvent.KEYCODE_DPAD_LEFT);
-        verify(selectedListener, times(2)).onItemSelected(itemVHCaptor.capture(),
-                itemCaptor.capture(), rowVHCaptor.capture(), rowCaptor.capture());
-        assertSame("Same controls row should be passed to the listener", controlsRow,
-                rowCaptor.getValue());
-        assertSame("The selected action should be thumbsUp", thumbsUp, itemCaptor.getValue());
-
-        // Now navigate down to a ListRow item.
-        ListRow listRow0 = (ListRow) fragment.getAdapter().get(1);
-
-        sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
-        waitForScrollIdle(fragment.getVerticalGridView());
-        verify(selectedListener, times(3)).onItemSelected(itemVHCaptor.capture(),
-                itemCaptor.capture(), rowVHCaptor.capture(), rowCaptor.capture());
-        assertSame("Same list row should be passed to the listener", listRow0,
-                rowCaptor.getValue());
-        // Depending on the focusSearch algorithm, one of the items in the first ListRow must be
-        // selected.
-        boolean listRowItemPassed = (itemCaptor.getValue() == listRow0.getAdapter().get(0)
-                || itemCaptor.getValue() == listRow0.getAdapter().get(1));
-        assertTrue("None of the items in the first ListRow are passed to the selected listener.",
-                listRowItemPassed);
-    }
-
-    @Test
-    public void testClickedListener() throws Throwable {
-        SingleFragmentTestActivity activity =
-                launchAndWaitActivity(PlaybackTestFragment.class, 1000);
-        PlaybackTestFragment fragment = (PlaybackTestFragment) activity.getTestFragment();
-
-        assertTrue(fragment.getView().hasFocus());
-
-        OnItemViewClickedListener clickedListener = Mockito.mock(OnItemViewClickedListener.class);
-        fragment.setOnItemViewClickedListener(clickedListener);
-
-
-        PlaybackControlsRow controlsRow = fragment.getGlue().getControlsRow();
-        SparseArrayObjectAdapter primaryActionsAdapter = (SparseArrayObjectAdapter)
-                controlsRow.getPrimaryActionsAdapter();
-
-        PlaybackControlsRow.MultiAction playPause = (PlaybackControlsRow.MultiAction)
-                primaryActionsAdapter.lookup(PlaybackControlGlue.ACTION_PLAY_PAUSE);
-
-        PlaybackControlsRow.MultiAction rewind = (PlaybackControlsRow.MultiAction)
-                primaryActionsAdapter.lookup(PlaybackControlGlue.ACTION_REWIND);
-
-        PlaybackControlsRow.MultiAction thumbsUp = (PlaybackControlsRow.MultiAction)
-                primaryActionsAdapter.lookup(PlaybackControlGlue.ACTION_CUSTOM_LEFT_FIRST);
-
-        ArgumentCaptor<Presenter.ViewHolder> itemVHCaptor =
-                ArgumentCaptor.forClass(Presenter.ViewHolder.class);
-        ArgumentCaptor<Object> itemCaptor = ArgumentCaptor.forClass(Object.class);
-        ArgumentCaptor<RowPresenter.ViewHolder> rowVHCaptor =
-                ArgumentCaptor.forClass(RowPresenter.ViewHolder.class);
-        ArgumentCaptor<Row> rowCaptor = ArgumentCaptor.forClass(Row.class);
-
-
-        // First navigate left within PlaybackControlsRow items.
-        verify(clickedListener, times(0)).onItemClicked(any(Presenter.ViewHolder.class),
-                any(Object.class), any(RowPresenter.ViewHolder.class), any(Row.class));
-        sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
-        verify(clickedListener, times(1)).onItemClicked(itemVHCaptor.capture(),
-                itemCaptor.capture(), rowVHCaptor.capture(), rowCaptor.capture());
-        assertSame("Same controls row should be passed to the listener", controlsRow,
-                rowCaptor.getValue());
-        assertSame("The clicked action should be playPause", playPause, itemCaptor.getValue());
-
-        sendKeys(KeyEvent.KEYCODE_DPAD_LEFT);
-        verify(clickedListener, times(1)).onItemClicked(any(Presenter.ViewHolder.class),
-                any(Object.class), any(RowPresenter.ViewHolder.class), any(Row.class));
-        sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
-        verify(clickedListener, times(2)).onItemClicked(itemVHCaptor.capture(),
-                itemCaptor.capture(), rowVHCaptor.capture(), rowCaptor.capture());
-        assertSame("Same controls row should be passed to the listener", controlsRow,
-                rowCaptor.getValue());
-        assertSame("The clicked action should be rewind", rewind, itemCaptor.getValue());
-
-        sendKeys(KeyEvent.KEYCODE_DPAD_LEFT);
-        verify(clickedListener, times(2)).onItemClicked(any(Presenter.ViewHolder.class),
-                any(Object.class), any(RowPresenter.ViewHolder.class), any(Row.class));
-        sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
-        verify(clickedListener, times(3)).onItemClicked(itemVHCaptor.capture(),
-                itemCaptor.capture(), rowVHCaptor.capture(), rowCaptor.capture());
-        assertSame("Same controls row should be passed to the listener", controlsRow,
-                rowCaptor.getValue());
-        assertSame("The clicked action should be thumbsUp", thumbsUp, itemCaptor.getValue());
-
-        // Now navigate down to a ListRow item.
-        ListRow listRow0 = (ListRow) fragment.getAdapter().get(1);
-
-        sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
-        waitForScrollIdle(fragment.getVerticalGridView());
-        verify(clickedListener, times(3)).onItemClicked(any(Presenter.ViewHolder.class),
-                any(Object.class), any(RowPresenter.ViewHolder.class), any(Row.class));
-        sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
-        verify(clickedListener, times(4)).onItemClicked(itemVHCaptor.capture(),
-                itemCaptor.capture(), rowVHCaptor.capture(), rowCaptor.capture());
-        assertSame("Same list row should be passed to the listener", listRow0,
-                rowCaptor.getValue());
-        boolean listRowItemPassed = (itemCaptor.getValue() == listRow0.getAdapter().get(0)
-                || itemCaptor.getValue() == listRow0.getAdapter().get(1));
-        assertTrue("None of the items in the first ListRow are passed to the click listener.",
-                listRowItemPassed);
-    }
-
-    @FlakyTest
-    @Suppress
-    @Test
-    public void alignmentRowToBottom() throws Throwable {
-        SingleFragmentTestActivity activity =
-                launchAndWaitActivity(PlaybackTestFragment.class, 1000);
-        final PlaybackTestFragment fragment = (PlaybackTestFragment) activity.getTestFragment();
-
-        assertTrue(fragment.getAdapter().size() > 2);
-
-        View playRow = fragment.getVerticalGridView().getChildAt(0);
-        assertTrue(playRow.hasFocus());
-        assertEquals(playRow.getResources().getDimensionPixelSize(
-                android.support.v17.leanback.test.R.dimen.lb_playback_controls_padding_bottom),
-                fragment.getVerticalGridView().getHeight() - playRow.getBottom());
-
-        activityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                fragment.getVerticalGridView().setSelectedPositionSmooth(
-                        fragment.getAdapter().size() - 1);
-            }
-        });
-        waitForScrollIdle(fragment.getVerticalGridView());
-
-        View lastRow = fragment.getVerticalGridView().getChildAt(
-                fragment.getVerticalGridView().getChildCount() - 1);
-        assertEquals(fragment.getAdapter().size() - 1,
-                fragment.getVerticalGridView().getChildAdapterPosition(lastRow));
-        assertTrue(lastRow.hasFocus());
-        assertEquals(lastRow.getResources().getDimensionPixelSize(
-                android.support.v17.leanback.test.R.dimen.lb_playback_controls_padding_bottom),
-                fragment.getVerticalGridView().getHeight() - lastRow.getBottom());
-    }
-
-    public static class PurePlaybackFragment extends PlaybackFragment {
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            setFadingEnabled(false);
-            PlaybackControlsRow row = new PlaybackControlsRow();
-            SparseArrayObjectAdapter primaryAdapter = new SparseArrayObjectAdapter(
-                    new ControlButtonPresenterSelector());
-            primaryAdapter.set(0, new PlaybackControlsRow.SkipPreviousAction(getActivity()));
-            primaryAdapter.set(1, new PlaybackControlsRow.PlayPauseAction(getActivity()));
-            primaryAdapter.set(2, new PlaybackControlsRow.SkipNextAction(getActivity()));
-            row.setPrimaryActionsAdapter(primaryAdapter);
-            row.setSecondaryActionsAdapter(null);
-            setPlaybackRow(row);
-            setPlaybackRowPresenter(new PlaybackControlsRowPresenter());
-        }
-    }
-
-    @Test
-    public void setupRowAndPresenterWithoutGlue() {
-        SingleFragmentTestActivity activity =
-                launchAndWaitActivity(PurePlaybackFragment.class, 1000);
-        final PurePlaybackFragment fragment = (PurePlaybackFragment)
-                activity.getTestFragment();
-
-        assertTrue(fragment.getAdapter().size() == 1);
-        View playRow = fragment.getVerticalGridView().getChildAt(0);
-        assertTrue(playRow.hasFocus());
-        assertEquals(playRow.getResources().getDimensionPixelSize(
-                android.support.v17.leanback.test.R.dimen.lb_playback_controls_padding_bottom),
-                fragment.getVerticalGridView().getHeight() - playRow.getBottom());
-    }
-
-    public static class ControlGlueFragment extends PlaybackFragment {
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            int[] ffspeeds = new int[] {PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0,
-                    PlaybackControlGlue.PLAYBACK_SPEED_FAST_L1};
-            PlaybackGlue glue = new PlaybackControlGlue(
-                    getActivity(), ffspeeds) {
-                @Override
-                public boolean hasValidMedia() {
-                    return true;
-                }
-
-                @Override
-                public boolean isMediaPlaying() {
-                    return false;
-                }
-
-                @Override
-                public CharSequence getMediaTitle() {
-                    return "Title";
-                }
-
-                @Override
-                public CharSequence getMediaSubtitle() {
-                    return "SubTitle";
-                }
-
-                @Override
-                public int getMediaDuration() {
-                    return 100;
-                }
-
-                @Override
-                public Drawable getMediaArt() {
-                    return null;
-                }
-
-                @Override
-                public long getSupportedActions() {
-                    return PlaybackControlGlue.ACTION_PLAY_PAUSE;
-                }
-
-                @Override
-                public int getCurrentSpeedId() {
-                    return PlaybackControlGlue.PLAYBACK_SPEED_PAUSED;
-                }
-
-                @Override
-                public int getCurrentPosition() {
-                    return 50;
-                }
-            };
-            glue.setHost(new PlaybackFragmentGlueHost(this));
-        }
-    }
-
-    @Test
-    public void setupWithControlGlue() throws Throwable {
-        SingleFragmentTestActivity activity =
-                launchAndWaitActivity(ControlGlueFragment.class, 1000);
-        final ControlGlueFragment fragment = (ControlGlueFragment)
-                activity.getTestFragment();
-
-        assertTrue(fragment.getAdapter().size() == 1);
-
-        View playRow = fragment.getVerticalGridView().getChildAt(0);
-        assertTrue(playRow.hasFocus());
-        assertEquals(playRow.getResources().getDimensionPixelSize(
-                android.support.v17.leanback.test.R.dimen.lb_playback_controls_padding_bottom),
-                fragment.getVerticalGridView().getHeight() - playRow.getBottom());
-    }
-}
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackSupportFragmentTest.java b/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackSupportFragmentTest.java
deleted file mode 100644
index cbc8222..0000000
--- a/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackSupportFragmentTest.java
+++ /dev/null
@@ -1,374 +0,0 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from PlaybackFragmentTest.java.  DO NOT MODIFY. */
-
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.support.v17.leanback.app;
-
-import static junit.framework.Assert.assertEquals;
-
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.support.test.filters.FlakyTest;
-import android.support.test.filters.MediumTest;
-import android.support.test.filters.Suppress;
-import android.support.test.runner.AndroidJUnit4;
-import android.support.v17.leanback.media.PlaybackControlGlue;
-import android.support.v17.leanback.media.PlaybackGlue;
-import android.support.v17.leanback.testutils.PollingCheck;
-import android.support.v17.leanback.widget.ControlButtonPresenterSelector;
-import android.support.v17.leanback.widget.ListRow;
-import android.support.v17.leanback.widget.OnItemViewClickedListener;
-import android.support.v17.leanback.widget.OnItemViewSelectedListener;
-import android.support.v17.leanback.widget.PlaybackControlsRow;
-import android.support.v17.leanback.widget.PlaybackControlsRowPresenter;
-import android.support.v17.leanback.widget.Presenter;
-import android.support.v17.leanback.widget.Row;
-import android.support.v17.leanback.widget.RowPresenter;
-import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
-import android.view.KeyEvent;
-import android.view.View;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mockito;
-
-@MediumTest
-@RunWith(AndroidJUnit4.class)
-public class PlaybackSupportFragmentTest extends SingleSupportFragmentTestBase {
-
-    private static final String TAG = "PlaybackSupportFragmentTest";
-    private static final long TRANSITION_LENGTH = 1000;
-
-    @Test
-    public void testDetachCalledWhenDestroyFragment() throws Throwable {
-        final SingleSupportFragmentTestActivity activity =
-                launchAndWaitActivity(PlaybackTestSupportFragment.class, 1000);
-        final PlaybackTestSupportFragment fragment = (PlaybackTestSupportFragment) activity.getTestFragment();
-        PlaybackGlue glue = fragment.getGlue();
-        activityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                activity.finish();
-            }
-        });
-        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return fragment.mDestroyCalled;
-            }
-        });
-        assertNull(glue.getHost());
-    }
-
-    @Test
-    public void testSelectedListener() throws Throwable {
-        SingleSupportFragmentTestActivity activity =
-                launchAndWaitActivity(PlaybackTestSupportFragment.class, 1000);
-        PlaybackTestSupportFragment fragment = (PlaybackTestSupportFragment) activity.getTestFragment();
-
-        assertTrue(fragment.getView().hasFocus());
-
-        OnItemViewSelectedListener selectedListener = Mockito.mock(
-                OnItemViewSelectedListener.class);
-        fragment.setOnItemViewSelectedListener(selectedListener);
-
-
-        PlaybackControlsRow controlsRow = fragment.getGlue().getControlsRow();
-        SparseArrayObjectAdapter primaryActionsAdapter = (SparseArrayObjectAdapter)
-                controlsRow.getPrimaryActionsAdapter();
-
-        PlaybackControlsRow.MultiAction playPause = (PlaybackControlsRow.MultiAction)
-                primaryActionsAdapter.lookup(PlaybackControlGlue.ACTION_PLAY_PAUSE);
-
-        PlaybackControlsRow.MultiAction rewind = (PlaybackControlsRow.MultiAction)
-                primaryActionsAdapter.lookup(PlaybackControlGlue.ACTION_REWIND);
-
-        PlaybackControlsRow.MultiAction thumbsUp = (PlaybackControlsRow.MultiAction)
-                primaryActionsAdapter.lookup(PlaybackControlGlue.ACTION_CUSTOM_LEFT_FIRST);
-
-        ArgumentCaptor<Presenter.ViewHolder> itemVHCaptor =
-                ArgumentCaptor.forClass(Presenter.ViewHolder.class);
-        ArgumentCaptor<Object> itemCaptor = ArgumentCaptor.forClass(Object.class);
-        ArgumentCaptor<RowPresenter.ViewHolder> rowVHCaptor =
-                ArgumentCaptor.forClass(RowPresenter.ViewHolder.class);
-        ArgumentCaptor<Row> rowCaptor = ArgumentCaptor.forClass(Row.class);
-
-
-        // First navigate left within PlaybackControlsRow items.
-        verify(selectedListener, times(0)).onItemSelected(any(Presenter.ViewHolder.class),
-                any(Object.class), any(RowPresenter.ViewHolder.class), any(Row.class));
-        sendKeys(KeyEvent.KEYCODE_DPAD_LEFT);
-        verify(selectedListener, times(1)).onItemSelected(itemVHCaptor.capture(),
-                itemCaptor.capture(), rowVHCaptor.capture(), rowCaptor.capture());
-        assertSame("Same controls row should be passed to the listener", controlsRow,
-                rowCaptor.getValue());
-        assertSame("The selected action should be rewind", rewind, itemCaptor.getValue());
-
-        sendKeys(KeyEvent.KEYCODE_DPAD_LEFT);
-        verify(selectedListener, times(2)).onItemSelected(itemVHCaptor.capture(),
-                itemCaptor.capture(), rowVHCaptor.capture(), rowCaptor.capture());
-        assertSame("Same controls row should be passed to the listener", controlsRow,
-                rowCaptor.getValue());
-        assertSame("The selected action should be thumbsUp", thumbsUp, itemCaptor.getValue());
-
-        // Now navigate down to a ListRow item.
-        ListRow listRow0 = (ListRow) fragment.getAdapter().get(1);
-
-        sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
-        waitForScrollIdle(fragment.getVerticalGridView());
-        verify(selectedListener, times(3)).onItemSelected(itemVHCaptor.capture(),
-                itemCaptor.capture(), rowVHCaptor.capture(), rowCaptor.capture());
-        assertSame("Same list row should be passed to the listener", listRow0,
-                rowCaptor.getValue());
-        // Depending on the focusSearch algorithm, one of the items in the first ListRow must be
-        // selected.
-        boolean listRowItemPassed = (itemCaptor.getValue() == listRow0.getAdapter().get(0)
-                || itemCaptor.getValue() == listRow0.getAdapter().get(1));
-        assertTrue("None of the items in the first ListRow are passed to the selected listener.",
-                listRowItemPassed);
-    }
-
-    @Test
-    public void testClickedListener() throws Throwable {
-        SingleSupportFragmentTestActivity activity =
-                launchAndWaitActivity(PlaybackTestSupportFragment.class, 1000);
-        PlaybackTestSupportFragment fragment = (PlaybackTestSupportFragment) activity.getTestFragment();
-
-        assertTrue(fragment.getView().hasFocus());
-
-        OnItemViewClickedListener clickedListener = Mockito.mock(OnItemViewClickedListener.class);
-        fragment.setOnItemViewClickedListener(clickedListener);
-
-
-        PlaybackControlsRow controlsRow = fragment.getGlue().getControlsRow();
-        SparseArrayObjectAdapter primaryActionsAdapter = (SparseArrayObjectAdapter)
-                controlsRow.getPrimaryActionsAdapter();
-
-        PlaybackControlsRow.MultiAction playPause = (PlaybackControlsRow.MultiAction)
-                primaryActionsAdapter.lookup(PlaybackControlGlue.ACTION_PLAY_PAUSE);
-
-        PlaybackControlsRow.MultiAction rewind = (PlaybackControlsRow.MultiAction)
-                primaryActionsAdapter.lookup(PlaybackControlGlue.ACTION_REWIND);
-
-        PlaybackControlsRow.MultiAction thumbsUp = (PlaybackControlsRow.MultiAction)
-                primaryActionsAdapter.lookup(PlaybackControlGlue.ACTION_CUSTOM_LEFT_FIRST);
-
-        ArgumentCaptor<Presenter.ViewHolder> itemVHCaptor =
-                ArgumentCaptor.forClass(Presenter.ViewHolder.class);
-        ArgumentCaptor<Object> itemCaptor = ArgumentCaptor.forClass(Object.class);
-        ArgumentCaptor<RowPresenter.ViewHolder> rowVHCaptor =
-                ArgumentCaptor.forClass(RowPresenter.ViewHolder.class);
-        ArgumentCaptor<Row> rowCaptor = ArgumentCaptor.forClass(Row.class);
-
-
-        // First navigate left within PlaybackControlsRow items.
-        verify(clickedListener, times(0)).onItemClicked(any(Presenter.ViewHolder.class),
-                any(Object.class), any(RowPresenter.ViewHolder.class), any(Row.class));
-        sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
-        verify(clickedListener, times(1)).onItemClicked(itemVHCaptor.capture(),
-                itemCaptor.capture(), rowVHCaptor.capture(), rowCaptor.capture());
-        assertSame("Same controls row should be passed to the listener", controlsRow,
-                rowCaptor.getValue());
-        assertSame("The clicked action should be playPause", playPause, itemCaptor.getValue());
-
-        sendKeys(KeyEvent.KEYCODE_DPAD_LEFT);
-        verify(clickedListener, times(1)).onItemClicked(any(Presenter.ViewHolder.class),
-                any(Object.class), any(RowPresenter.ViewHolder.class), any(Row.class));
-        sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
-        verify(clickedListener, times(2)).onItemClicked(itemVHCaptor.capture(),
-                itemCaptor.capture(), rowVHCaptor.capture(), rowCaptor.capture());
-        assertSame("Same controls row should be passed to the listener", controlsRow,
-                rowCaptor.getValue());
-        assertSame("The clicked action should be rewind", rewind, itemCaptor.getValue());
-
-        sendKeys(KeyEvent.KEYCODE_DPAD_LEFT);
-        verify(clickedListener, times(2)).onItemClicked(any(Presenter.ViewHolder.class),
-                any(Object.class), any(RowPresenter.ViewHolder.class), any(Row.class));
-        sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
-        verify(clickedListener, times(3)).onItemClicked(itemVHCaptor.capture(),
-                itemCaptor.capture(), rowVHCaptor.capture(), rowCaptor.capture());
-        assertSame("Same controls row should be passed to the listener", controlsRow,
-                rowCaptor.getValue());
-        assertSame("The clicked action should be thumbsUp", thumbsUp, itemCaptor.getValue());
-
-        // Now navigate down to a ListRow item.
-        ListRow listRow0 = (ListRow) fragment.getAdapter().get(1);
-
-        sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
-        waitForScrollIdle(fragment.getVerticalGridView());
-        verify(clickedListener, times(3)).onItemClicked(any(Presenter.ViewHolder.class),
-                any(Object.class), any(RowPresenter.ViewHolder.class), any(Row.class));
-        sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
-        verify(clickedListener, times(4)).onItemClicked(itemVHCaptor.capture(),
-                itemCaptor.capture(), rowVHCaptor.capture(), rowCaptor.capture());
-        assertSame("Same list row should be passed to the listener", listRow0,
-                rowCaptor.getValue());
-        boolean listRowItemPassed = (itemCaptor.getValue() == listRow0.getAdapter().get(0)
-                || itemCaptor.getValue() == listRow0.getAdapter().get(1));
-        assertTrue("None of the items in the first ListRow are passed to the click listener.",
-                listRowItemPassed);
-    }
-
-    @FlakyTest
-    @Suppress
-    @Test
-    public void alignmentRowToBottom() throws Throwable {
-        SingleSupportFragmentTestActivity activity =
-                launchAndWaitActivity(PlaybackTestSupportFragment.class, 1000);
-        final PlaybackTestSupportFragment fragment = (PlaybackTestSupportFragment) activity.getTestFragment();
-
-        assertTrue(fragment.getAdapter().size() > 2);
-
-        View playRow = fragment.getVerticalGridView().getChildAt(0);
-        assertTrue(playRow.hasFocus());
-        assertEquals(playRow.getResources().getDimensionPixelSize(
-                android.support.v17.leanback.test.R.dimen.lb_playback_controls_padding_bottom),
-                fragment.getVerticalGridView().getHeight() - playRow.getBottom());
-
-        activityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                fragment.getVerticalGridView().setSelectedPositionSmooth(
-                        fragment.getAdapter().size() - 1);
-            }
-        });
-        waitForScrollIdle(fragment.getVerticalGridView());
-
-        View lastRow = fragment.getVerticalGridView().getChildAt(
-                fragment.getVerticalGridView().getChildCount() - 1);
-        assertEquals(fragment.getAdapter().size() - 1,
-                fragment.getVerticalGridView().getChildAdapterPosition(lastRow));
-        assertTrue(lastRow.hasFocus());
-        assertEquals(lastRow.getResources().getDimensionPixelSize(
-                android.support.v17.leanback.test.R.dimen.lb_playback_controls_padding_bottom),
-                fragment.getVerticalGridView().getHeight() - lastRow.getBottom());
-    }
-
-    public static class PurePlaybackSupportFragment extends PlaybackSupportFragment {
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            setFadingEnabled(false);
-            PlaybackControlsRow row = new PlaybackControlsRow();
-            SparseArrayObjectAdapter primaryAdapter = new SparseArrayObjectAdapter(
-                    new ControlButtonPresenterSelector());
-            primaryAdapter.set(0, new PlaybackControlsRow.SkipPreviousAction(getActivity()));
-            primaryAdapter.set(1, new PlaybackControlsRow.PlayPauseAction(getActivity()));
-            primaryAdapter.set(2, new PlaybackControlsRow.SkipNextAction(getActivity()));
-            row.setPrimaryActionsAdapter(primaryAdapter);
-            row.setSecondaryActionsAdapter(null);
-            setPlaybackRow(row);
-            setPlaybackRowPresenter(new PlaybackControlsRowPresenter());
-        }
-    }
-
-    @Test
-    public void setupRowAndPresenterWithoutGlue() {
-        SingleSupportFragmentTestActivity activity =
-                launchAndWaitActivity(PurePlaybackSupportFragment.class, 1000);
-        final PurePlaybackSupportFragment fragment = (PurePlaybackSupportFragment)
-                activity.getTestFragment();
-
-        assertTrue(fragment.getAdapter().size() == 1);
-        View playRow = fragment.getVerticalGridView().getChildAt(0);
-        assertTrue(playRow.hasFocus());
-        assertEquals(playRow.getResources().getDimensionPixelSize(
-                android.support.v17.leanback.test.R.dimen.lb_playback_controls_padding_bottom),
-                fragment.getVerticalGridView().getHeight() - playRow.getBottom());
-    }
-
-    public static class ControlGlueFragment extends PlaybackSupportFragment {
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            int[] ffspeeds = new int[] {PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0,
-                    PlaybackControlGlue.PLAYBACK_SPEED_FAST_L1};
-            PlaybackGlue glue = new PlaybackControlGlue(
-                    getActivity(), ffspeeds) {
-                @Override
-                public boolean hasValidMedia() {
-                    return true;
-                }
-
-                @Override
-                public boolean isMediaPlaying() {
-                    return false;
-                }
-
-                @Override
-                public CharSequence getMediaTitle() {
-                    return "Title";
-                }
-
-                @Override
-                public CharSequence getMediaSubtitle() {
-                    return "SubTitle";
-                }
-
-                @Override
-                public int getMediaDuration() {
-                    return 100;
-                }
-
-                @Override
-                public Drawable getMediaArt() {
-                    return null;
-                }
-
-                @Override
-                public long getSupportedActions() {
-                    return PlaybackControlGlue.ACTION_PLAY_PAUSE;
-                }
-
-                @Override
-                public int getCurrentSpeedId() {
-                    return PlaybackControlGlue.PLAYBACK_SPEED_PAUSED;
-                }
-
-                @Override
-                public int getCurrentPosition() {
-                    return 50;
-                }
-            };
-            glue.setHost(new PlaybackSupportFragmentGlueHost(this));
-        }
-    }
-
-    @Test
-    public void setupWithControlGlue() throws Throwable {
-        SingleSupportFragmentTestActivity activity =
-                launchAndWaitActivity(ControlGlueFragment.class, 1000);
-        final ControlGlueFragment fragment = (ControlGlueFragment)
-                activity.getTestFragment();
-
-        assertTrue(fragment.getAdapter().size() == 1);
-
-        View playRow = fragment.getVerticalGridView().getChildAt(0);
-        assertTrue(playRow.hasFocus());
-        assertEquals(playRow.getResources().getDimensionPixelSize(
-                android.support.v17.leanback.test.R.dimen.lb_playback_controls_padding_bottom),
-                fragment.getVerticalGridView().getHeight() - playRow.getBottom());
-    }
-}
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackTestFragment.java b/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackTestFragment.java
deleted file mode 100644
index 027ea02..0000000
--- a/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackTestFragment.java
+++ /dev/null
@@ -1,368 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.support.v17.leanback.app;
-
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.os.Handler;
-import android.support.v17.leanback.media.PlaybackControlGlue;
-import android.support.v17.leanback.test.R;
-import android.support.v17.leanback.widget.Action;
-import android.support.v17.leanback.widget.ArrayObjectAdapter;
-import android.support.v17.leanback.widget.ClassPresenterSelector;
-import android.support.v17.leanback.widget.HeaderItem;
-import android.support.v17.leanback.widget.ListRow;
-import android.support.v17.leanback.widget.ListRowPresenter;
-import android.support.v17.leanback.widget.OnItemViewClickedListener;
-import android.support.v17.leanback.widget.PlaybackControlsRow;
-import android.support.v17.leanback.widget.Presenter;
-import android.support.v17.leanback.widget.PresenterSelector;
-import android.support.v17.leanback.widget.Row;
-import android.support.v17.leanback.widget.RowPresenter;
-import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.View;
-import android.widget.Toast;
-
-public class PlaybackTestFragment extends PlaybackFragment {
-    private static final String TAG = "PlaybackTestFragment";
-
-    /**
-     * Change this to choose a different overlay background.
-     */
-    private static final int BACKGROUND_TYPE = PlaybackFragment.BG_LIGHT;
-
-    /**
-     * Change this to select hidden
-     */
-    private static final boolean SECONDARY_HIDDEN = false;
-
-    /**
-     * Change the number of related content rows.
-     */
-    private static final int RELATED_CONTENT_ROWS = 3;
-
-    private android.support.v17.leanback.media.PlaybackControlGlue mGlue;
-    boolean mDestroyCalled;
-
-    @Override
-    public SparseArrayObjectAdapter getAdapter() {
-        return (SparseArrayObjectAdapter) super.getAdapter();
-    }
-
-    private OnItemViewClickedListener mOnItemViewClickedListener = new OnItemViewClickedListener() {
-        @Override
-        public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
-                                  RowPresenter.ViewHolder rowViewHolder, Row row) {
-            Log.d(TAG, "onItemClicked: " + item + " row " + row);
-        }
-    };
-
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-        mDestroyCalled = true;
-    }
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        Log.i(TAG, "onCreate");
-        super.onCreate(savedInstanceState);
-
-        setBackgroundType(BACKGROUND_TYPE);
-
-        createComponents(getActivity());
-        setOnItemViewClickedListener(mOnItemViewClickedListener);
-    }
-
-    private void createComponents(Context context) {
-        mGlue = new PlaybackControlHelper(context) {
-            @Override
-            public int getUpdatePeriod() {
-                long totalTime = getControlsRow().getDuration();
-                if (getView() == null || getView().getWidth() == 0 || totalTime <= 0) {
-                    return 1000;
-                }
-                return 16;
-            }
-
-            @Override
-            public void onActionClicked(Action action) {
-                if (action.getId() == R.id.lb_control_picture_in_picture) {
-                    getActivity().enterPictureInPictureMode();
-                    return;
-                }
-                super.onActionClicked(action);
-            }
-
-            @Override
-            protected void onCreateControlsRowAndPresenter() {
-                super.onCreateControlsRowAndPresenter();
-                getControlsRowPresenter().setSecondaryActionsHidden(SECONDARY_HIDDEN);
-            }
-        };
-
-        mGlue.setHost(new PlaybackFragmentGlueHost(this));
-        ClassPresenterSelector selector = new ClassPresenterSelector();
-        selector.addClassPresenter(ListRow.class, new ListRowPresenter());
-
-        setAdapter(new SparseArrayObjectAdapter(selector));
-
-        // Add related content rows
-        for (int i = 0; i < RELATED_CONTENT_ROWS; ++i) {
-            ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(new StringPresenter());
-            listRowAdapter.add("Some related content");
-            listRowAdapter.add("Other related content");
-            HeaderItem header = new HeaderItem(i, "Row " + i);
-            getAdapter().set(1 + i, new ListRow(header, listRowAdapter));
-        }
-    }
-
-    public PlaybackControlGlue getGlue() {
-        return mGlue;
-    }
-
-    abstract static class PlaybackControlHelper extends PlaybackControlGlue {
-        /**
-         * Change the location of the thumbs up/down controls
-         */
-        private static final boolean THUMBS_PRIMARY = true;
-
-        private static final String FAUX_TITLE = "A short song of silence";
-        private static final String FAUX_SUBTITLE = "2014";
-        private static final int FAUX_DURATION = 33 * 1000;
-
-        // These should match the playback service FF behavior
-        private static int[] sFastForwardSpeeds = { 2, 3, 4, 5 };
-
-        private boolean mIsPlaying;
-        private int mSpeed = PLAYBACK_SPEED_PAUSED;
-        private long mStartTime;
-        private long mStartPosition = 0;
-
-        private PlaybackControlsRow.RepeatAction mRepeatAction;
-        private PlaybackControlsRow.ThumbsUpAction mThumbsUpAction;
-        private PlaybackControlsRow.ThumbsDownAction mThumbsDownAction;
-        private PlaybackControlsRow.PictureInPictureAction mPipAction;
-        private static Handler sProgressHandler = new Handler();
-
-        private final Runnable mUpdateProgressRunnable = new Runnable() {
-            @Override
-            public void run() {
-                updateProgress();
-                sProgressHandler.postDelayed(this, getUpdatePeriod());
-            }
-        };
-
-        PlaybackControlHelper(Context context) {
-            super(context, sFastForwardSpeeds);
-            mThumbsUpAction = new PlaybackControlsRow.ThumbsUpAction(context);
-            mThumbsUpAction.setIndex(PlaybackControlsRow.ThumbsUpAction.INDEX_OUTLINE);
-            mThumbsDownAction = new PlaybackControlsRow.ThumbsDownAction(context);
-            mThumbsDownAction.setIndex(PlaybackControlsRow.ThumbsDownAction.INDEX_OUTLINE);
-            mRepeatAction = new PlaybackControlsRow.RepeatAction(context);
-            mPipAction = new PlaybackControlsRow.PictureInPictureAction(context);
-        }
-
-        @Override
-        protected SparseArrayObjectAdapter createPrimaryActionsAdapter(
-                PresenterSelector presenterSelector) {
-            SparseArrayObjectAdapter adapter = new SparseArrayObjectAdapter(presenterSelector);
-            if (THUMBS_PRIMARY) {
-                adapter.set(PlaybackControlGlue.ACTION_CUSTOM_LEFT_FIRST, mThumbsUpAction);
-                adapter.set(PlaybackControlGlue.ACTION_CUSTOM_RIGHT_FIRST, mThumbsDownAction);
-            }
-            return adapter;
-        }
-
-        @Override
-        public void onActionClicked(Action action) {
-            if (shouldDispatchAction(action)) {
-                dispatchAction(action);
-                return;
-            }
-            super.onActionClicked(action);
-        }
-
-        @Override
-        public boolean onKey(View view, int keyCode, KeyEvent keyEvent) {
-            if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
-                Action action = getControlsRow().getActionForKeyCode(keyEvent.getKeyCode());
-                if (shouldDispatchAction(action)) {
-                    dispatchAction(action);
-                    return true;
-                }
-            }
-            return super.onKey(view, keyCode, keyEvent);
-        }
-
-        private boolean shouldDispatchAction(Action action) {
-            return action == mRepeatAction || action == mThumbsUpAction
-                    || action == mThumbsDownAction;
-        }
-
-        private void dispatchAction(Action action) {
-            Toast.makeText(getContext(), action.toString(), Toast.LENGTH_SHORT).show();
-            PlaybackControlsRow.MultiAction multiAction = (PlaybackControlsRow.MultiAction) action;
-            multiAction.nextIndex();
-            notifyActionChanged(multiAction);
-        }
-
-        private void notifyActionChanged(PlaybackControlsRow.MultiAction action) {
-            int index;
-            index = getPrimaryActionsAdapter().indexOf(action);
-            if (index >= 0) {
-                getPrimaryActionsAdapter().notifyArrayItemRangeChanged(index, 1);
-            } else {
-                index = getSecondaryActionsAdapter().indexOf(action);
-                if (index >= 0) {
-                    getSecondaryActionsAdapter().notifyArrayItemRangeChanged(index, 1);
-                }
-            }
-        }
-
-        private SparseArrayObjectAdapter getPrimaryActionsAdapter() {
-            return (SparseArrayObjectAdapter) getControlsRow().getPrimaryActionsAdapter();
-        }
-
-        private ArrayObjectAdapter getSecondaryActionsAdapter() {
-            return (ArrayObjectAdapter) getControlsRow().getSecondaryActionsAdapter();
-        }
-
-        @Override
-        public boolean hasValidMedia() {
-            return true;
-        }
-
-        @Override
-        public boolean isMediaPlaying() {
-            return mIsPlaying;
-        }
-
-        @Override
-        public CharSequence getMediaTitle() {
-            return FAUX_TITLE;
-        }
-
-        @Override
-        public CharSequence getMediaSubtitle() {
-            return FAUX_SUBTITLE;
-        }
-
-        @Override
-        public int getMediaDuration() {
-            return FAUX_DURATION;
-        }
-
-        @Override
-        public Drawable getMediaArt() {
-            return null;
-        }
-
-        @Override
-        public long getSupportedActions() {
-            return ACTION_PLAY_PAUSE | ACTION_FAST_FORWARD | ACTION_REWIND;
-        }
-
-        @Override
-        public int getCurrentSpeedId() {
-            return mSpeed;
-        }
-
-        @Override
-        public int getCurrentPosition() {
-            int speed;
-            if (mSpeed == PlaybackControlGlue.PLAYBACK_SPEED_PAUSED) {
-                speed = 0;
-            } else if (mSpeed == PlaybackControlGlue.PLAYBACK_SPEED_NORMAL) {
-                speed = 1;
-            } else if (mSpeed >= PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0) {
-                int index = mSpeed - PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0;
-                speed = getFastForwardSpeeds()[index];
-            } else if (mSpeed <= -PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0) {
-                int index = -mSpeed - PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0;
-                speed = -getRewindSpeeds()[index];
-            } else {
-                return -1;
-            }
-            long position = mStartPosition + (System.currentTimeMillis() - mStartTime) * speed;
-            if (position > getMediaDuration()) {
-                position = getMediaDuration();
-                onPlaybackComplete(true);
-            } else if (position < 0) {
-                position = 0;
-                onPlaybackComplete(false);
-            }
-            return (int) position;
-        }
-
-        void onPlaybackComplete(final boolean ended) {
-            sProgressHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    if (mRepeatAction.getIndex() == PlaybackControlsRow.RepeatAction.INDEX_NONE) {
-                        pause();
-                    } else {
-                        play(PlaybackControlGlue.PLAYBACK_SPEED_NORMAL);
-                    }
-                    mStartPosition = 0;
-                    onStateChanged();
-                }
-            });
-        }
-
-        @Override
-        public void play(int speed) {
-            if (speed == mSpeed) {
-                return;
-            }
-            mStartPosition = getCurrentPosition();
-            mSpeed = speed;
-            mIsPlaying = true;
-            mStartTime = System.currentTimeMillis();
-        }
-
-        @Override
-        public void pause() {
-            if (mSpeed == PLAYBACK_SPEED_PAUSED) {
-                return;
-            }
-            mStartPosition = getCurrentPosition();
-            mSpeed = PLAYBACK_SPEED_PAUSED;
-            mIsPlaying = false;
-        }
-
-        @Override
-        public void next() {
-            // Not supported
-        }
-
-        @Override
-        public void previous() {
-            // Not supported
-        }
-
-        @Override
-        public void enableProgressUpdating(boolean enable) {
-            sProgressHandler.removeCallbacks(mUpdateProgressRunnable);
-            if (enable) {
-                mUpdateProgressRunnable.run();
-            }
-        }
-    }
-}
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackTestSupportFragment.java b/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackTestSupportFragment.java
deleted file mode 100644
index 273df26..0000000
--- a/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackTestSupportFragment.java
+++ /dev/null
@@ -1,371 +0,0 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from PlaybackTestFragment.java.  DO NOT MODIFY. */
-
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.support.v17.leanback.app;
-
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.os.Handler;
-import android.support.v17.leanback.media.PlaybackControlGlue;
-import android.support.v17.leanback.test.R;
-import android.support.v17.leanback.widget.Action;
-import android.support.v17.leanback.widget.ArrayObjectAdapter;
-import android.support.v17.leanback.widget.ClassPresenterSelector;
-import android.support.v17.leanback.widget.HeaderItem;
-import android.support.v17.leanback.widget.ListRow;
-import android.support.v17.leanback.widget.ListRowPresenter;
-import android.support.v17.leanback.widget.OnItemViewClickedListener;
-import android.support.v17.leanback.widget.PlaybackControlsRow;
-import android.support.v17.leanback.widget.Presenter;
-import android.support.v17.leanback.widget.PresenterSelector;
-import android.support.v17.leanback.widget.Row;
-import android.support.v17.leanback.widget.RowPresenter;
-import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.View;
-import android.widget.Toast;
-
-public class PlaybackTestSupportFragment extends PlaybackSupportFragment {
-    private static final String TAG = "PlaybackTestSupportFragment";
-
-    /**
-     * Change this to choose a different overlay background.
-     */
-    private static final int BACKGROUND_TYPE = PlaybackSupportFragment.BG_LIGHT;
-
-    /**
-     * Change this to select hidden
-     */
-    private static final boolean SECONDARY_HIDDEN = false;
-
-    /**
-     * Change the number of related content rows.
-     */
-    private static final int RELATED_CONTENT_ROWS = 3;
-
-    private android.support.v17.leanback.media.PlaybackControlGlue mGlue;
-    boolean mDestroyCalled;
-
-    @Override
-    public SparseArrayObjectAdapter getAdapter() {
-        return (SparseArrayObjectAdapter) super.getAdapter();
-    }
-
-    private OnItemViewClickedListener mOnItemViewClickedListener = new OnItemViewClickedListener() {
-        @Override
-        public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
-                                  RowPresenter.ViewHolder rowViewHolder, Row row) {
-            Log.d(TAG, "onItemClicked: " + item + " row " + row);
-        }
-    };
-
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-        mDestroyCalled = true;
-    }
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        Log.i(TAG, "onCreate");
-        super.onCreate(savedInstanceState);
-
-        setBackgroundType(BACKGROUND_TYPE);
-
-        createComponents(getActivity());
-        setOnItemViewClickedListener(mOnItemViewClickedListener);
-    }
-
-    private void createComponents(Context context) {
-        mGlue = new PlaybackControlHelper(context) {
-            @Override
-            public int getUpdatePeriod() {
-                long totalTime = getControlsRow().getDuration();
-                if (getView() == null || getView().getWidth() == 0 || totalTime <= 0) {
-                    return 1000;
-                }
-                return 16;
-            }
-
-            @Override
-            public void onActionClicked(Action action) {
-                if (action.getId() == R.id.lb_control_picture_in_picture) {
-                    getActivity().enterPictureInPictureMode();
-                    return;
-                }
-                super.onActionClicked(action);
-            }
-
-            @Override
-            protected void onCreateControlsRowAndPresenter() {
-                super.onCreateControlsRowAndPresenter();
-                getControlsRowPresenter().setSecondaryActionsHidden(SECONDARY_HIDDEN);
-            }
-        };
-
-        mGlue.setHost(new PlaybackSupportFragmentGlueHost(this));
-        ClassPresenterSelector selector = new ClassPresenterSelector();
-        selector.addClassPresenter(ListRow.class, new ListRowPresenter());
-
-        setAdapter(new SparseArrayObjectAdapter(selector));
-
-        // Add related content rows
-        for (int i = 0; i < RELATED_CONTENT_ROWS; ++i) {
-            ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(new StringPresenter());
-            listRowAdapter.add("Some related content");
-            listRowAdapter.add("Other related content");
-            HeaderItem header = new HeaderItem(i, "Row " + i);
-            getAdapter().set(1 + i, new ListRow(header, listRowAdapter));
-        }
-    }
-
-    public PlaybackControlGlue getGlue() {
-        return mGlue;
-    }
-
-    abstract static class PlaybackControlHelper extends PlaybackControlGlue {
-        /**
-         * Change the location of the thumbs up/down controls
-         */
-        private static final boolean THUMBS_PRIMARY = true;
-
-        private static final String FAUX_TITLE = "A short song of silence";
-        private static final String FAUX_SUBTITLE = "2014";
-        private static final int FAUX_DURATION = 33 * 1000;
-
-        // These should match the playback service FF behavior
-        private static int[] sFastForwardSpeeds = { 2, 3, 4, 5 };
-
-        private boolean mIsPlaying;
-        private int mSpeed = PLAYBACK_SPEED_PAUSED;
-        private long mStartTime;
-        private long mStartPosition = 0;
-
-        private PlaybackControlsRow.RepeatAction mRepeatAction;
-        private PlaybackControlsRow.ThumbsUpAction mThumbsUpAction;
-        private PlaybackControlsRow.ThumbsDownAction mThumbsDownAction;
-        private PlaybackControlsRow.PictureInPictureAction mPipAction;
-        private static Handler sProgressHandler = new Handler();
-
-        private final Runnable mUpdateProgressRunnable = new Runnable() {
-            @Override
-            public void run() {
-                updateProgress();
-                sProgressHandler.postDelayed(this, getUpdatePeriod());
-            }
-        };
-
-        PlaybackControlHelper(Context context) {
-            super(context, sFastForwardSpeeds);
-            mThumbsUpAction = new PlaybackControlsRow.ThumbsUpAction(context);
-            mThumbsUpAction.setIndex(PlaybackControlsRow.ThumbsUpAction.INDEX_OUTLINE);
-            mThumbsDownAction = new PlaybackControlsRow.ThumbsDownAction(context);
-            mThumbsDownAction.setIndex(PlaybackControlsRow.ThumbsDownAction.INDEX_OUTLINE);
-            mRepeatAction = new PlaybackControlsRow.RepeatAction(context);
-            mPipAction = new PlaybackControlsRow.PictureInPictureAction(context);
-        }
-
-        @Override
-        protected SparseArrayObjectAdapter createPrimaryActionsAdapter(
-                PresenterSelector presenterSelector) {
-            SparseArrayObjectAdapter adapter = new SparseArrayObjectAdapter(presenterSelector);
-            if (THUMBS_PRIMARY) {
-                adapter.set(PlaybackControlGlue.ACTION_CUSTOM_LEFT_FIRST, mThumbsUpAction);
-                adapter.set(PlaybackControlGlue.ACTION_CUSTOM_RIGHT_FIRST, mThumbsDownAction);
-            }
-            return adapter;
-        }
-
-        @Override
-        public void onActionClicked(Action action) {
-            if (shouldDispatchAction(action)) {
-                dispatchAction(action);
-                return;
-            }
-            super.onActionClicked(action);
-        }
-
-        @Override
-        public boolean onKey(View view, int keyCode, KeyEvent keyEvent) {
-            if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
-                Action action = getControlsRow().getActionForKeyCode(keyEvent.getKeyCode());
-                if (shouldDispatchAction(action)) {
-                    dispatchAction(action);
-                    return true;
-                }
-            }
-            return super.onKey(view, keyCode, keyEvent);
-        }
-
-        private boolean shouldDispatchAction(Action action) {
-            return action == mRepeatAction || action == mThumbsUpAction
-                    || action == mThumbsDownAction;
-        }
-
-        private void dispatchAction(Action action) {
-            Toast.makeText(getContext(), action.toString(), Toast.LENGTH_SHORT).show();
-            PlaybackControlsRow.MultiAction multiAction = (PlaybackControlsRow.MultiAction) action;
-            multiAction.nextIndex();
-            notifyActionChanged(multiAction);
-        }
-
-        private void notifyActionChanged(PlaybackControlsRow.MultiAction action) {
-            int index;
-            index = getPrimaryActionsAdapter().indexOf(action);
-            if (index >= 0) {
-                getPrimaryActionsAdapter().notifyArrayItemRangeChanged(index, 1);
-            } else {
-                index = getSecondaryActionsAdapter().indexOf(action);
-                if (index >= 0) {
-                    getSecondaryActionsAdapter().notifyArrayItemRangeChanged(index, 1);
-                }
-            }
-        }
-
-        private SparseArrayObjectAdapter getPrimaryActionsAdapter() {
-            return (SparseArrayObjectAdapter) getControlsRow().getPrimaryActionsAdapter();
-        }
-
-        private ArrayObjectAdapter getSecondaryActionsAdapter() {
-            return (ArrayObjectAdapter) getControlsRow().getSecondaryActionsAdapter();
-        }
-
-        @Override
-        public boolean hasValidMedia() {
-            return true;
-        }
-
-        @Override
-        public boolean isMediaPlaying() {
-            return mIsPlaying;
-        }
-
-        @Override
-        public CharSequence getMediaTitle() {
-            return FAUX_TITLE;
-        }
-
-        @Override
-        public CharSequence getMediaSubtitle() {
-            return FAUX_SUBTITLE;
-        }
-
-        @Override
-        public int getMediaDuration() {
-            return FAUX_DURATION;
-        }
-
-        @Override
-        public Drawable getMediaArt() {
-            return null;
-        }
-
-        @Override
-        public long getSupportedActions() {
-            return ACTION_PLAY_PAUSE | ACTION_FAST_FORWARD | ACTION_REWIND;
-        }
-
-        @Override
-        public int getCurrentSpeedId() {
-            return mSpeed;
-        }
-
-        @Override
-        public int getCurrentPosition() {
-            int speed;
-            if (mSpeed == PlaybackControlGlue.PLAYBACK_SPEED_PAUSED) {
-                speed = 0;
-            } else if (mSpeed == PlaybackControlGlue.PLAYBACK_SPEED_NORMAL) {
-                speed = 1;
-            } else if (mSpeed >= PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0) {
-                int index = mSpeed - PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0;
-                speed = getFastForwardSpeeds()[index];
-            } else if (mSpeed <= -PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0) {
-                int index = -mSpeed - PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0;
-                speed = -getRewindSpeeds()[index];
-            } else {
-                return -1;
-            }
-            long position = mStartPosition + (System.currentTimeMillis() - mStartTime) * speed;
-            if (position > getMediaDuration()) {
-                position = getMediaDuration();
-                onPlaybackComplete(true);
-            } else if (position < 0) {
-                position = 0;
-                onPlaybackComplete(false);
-            }
-            return (int) position;
-        }
-
-        void onPlaybackComplete(final boolean ended) {
-            sProgressHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    if (mRepeatAction.getIndex() == PlaybackControlsRow.RepeatAction.INDEX_NONE) {
-                        pause();
-                    } else {
-                        play(PlaybackControlGlue.PLAYBACK_SPEED_NORMAL);
-                    }
-                    mStartPosition = 0;
-                    onStateChanged();
-                }
-            });
-        }
-
-        @Override
-        public void play(int speed) {
-            if (speed == mSpeed) {
-                return;
-            }
-            mStartPosition = getCurrentPosition();
-            mSpeed = speed;
-            mIsPlaying = true;
-            mStartTime = System.currentTimeMillis();
-        }
-
-        @Override
-        public void pause() {
-            if (mSpeed == PLAYBACK_SPEED_PAUSED) {
-                return;
-            }
-            mStartPosition = getCurrentPosition();
-            mSpeed = PLAYBACK_SPEED_PAUSED;
-            mIsPlaying = false;
-        }
-
-        @Override
-        public void next() {
-            // Not supported
-        }
-
-        @Override
-        public void previous() {
-            // Not supported
-        }
-
-        @Override
-        public void enableProgressUpdating(boolean enable) {
-            sProgressHandler.removeCallbacks(mUpdateProgressRunnable);
-            if (enable) {
-                mUpdateProgressRunnable.run();
-            }
-        }
-    }
-}
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
deleted file mode 100644
index 16e37cf..0000000
--- a/v17/leanback/tests/java/android/support/v17/leanback/app/RowsFragmentTest.java
+++ /dev/null
@@ -1,464 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.support.v17.leanback.app;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-
-import android.graphics.Rect;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.SystemClock;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.LargeTest;
-import android.support.test.filters.SdkSuppress;
-import android.support.test.runner.AndroidJUnit4;
-import android.support.v17.leanback.R;
-import android.support.v17.leanback.testutils.PollingCheck;
-import android.support.v17.leanback.widget.ArrayObjectAdapter;
-import android.support.v17.leanback.widget.HeaderItem;
-import android.support.v17.leanback.widget.HorizontalGridView;
-import android.support.v17.leanback.widget.ItemBridgeAdapter;
-import android.support.v17.leanback.widget.ListRow;
-import android.support.v17.leanback.widget.ListRowPresenter;
-import android.support.v17.leanback.widget.ObjectAdapter;
-import android.support.v17.leanback.widget.OnItemViewClickedListener;
-import android.support.v17.leanback.widget.Presenter;
-import android.support.v17.leanback.widget.Row;
-import android.support.v17.leanback.widget.RowPresenter;
-import android.support.v17.leanback.widget.SinglePresenterSelector;
-import android.support.v17.leanback.widget.VerticalGridView;
-import android.view.KeyEvent;
-import android.view.View;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-
-@LargeTest
-@RunWith(AndroidJUnit4.class)
-public class RowsFragmentTest extends SingleFragmentTestBase {
-
-    static final StringPresenter sCardPresenter = new StringPresenter();
-
-    static void loadData(ArrayObjectAdapter adapter, int numRows, int repeatPerRow) {
-        for (int i = 0; i < numRows; ++i) {
-            ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(sCardPresenter);
-            int index = 0;
-            for (int j = 0; j < repeatPerRow; ++j) {
-                listRowAdapter.add("Hello world-" + (index++));
-                listRowAdapter.add("This is a test-" + (index++));
-                listRowAdapter.add("Android TV-" + (index++));
-                listRowAdapter.add("Leanback-" + (index++));
-                listRowAdapter.add("Hello world-" + (index++));
-                listRowAdapter.add("Android TV-" + (index++));
-                listRowAdapter.add("Leanback-" + (index++));
-                listRowAdapter.add("GuidedStepFragment-" + (index++));
-            }
-            HeaderItem header = new HeaderItem(i, "Row " + i);
-            adapter.add(new ListRow(header, listRowAdapter));
-        }
-    }
-
-    public static class F_defaultAlignment extends RowsFragment {
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            ListRowPresenter lrp = new ListRowPresenter();
-            ArrayObjectAdapter adapter = new ArrayObjectAdapter(lrp);
-            setAdapter(adapter);
-            loadData(adapter, 10, 1);
-        }
-    }
-
-    @Test
-    public void defaultAlignment() throws Throwable {
-        SingleFragmentTestActivity activity = launchAndWaitActivity(F_defaultAlignment.class, 1000);
-
-        final Rect rect = new Rect();
-
-        final VerticalGridView gridView = ((RowsFragment) activity.getTestFragment())
-                .getVerticalGridView();
-        View row0 = gridView.findViewHolderForAdapterPosition(0).itemView;
-        rect.set(0, 0, row0.getWidth(), row0.getHeight());
-        gridView.offsetDescendantRectToMyCoords(row0, rect);
-        assertEquals("First row is initially aligned to top of screen", 0, rect.top);
-
-        sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
-        waitForScrollIdle(gridView);
-        View row1 = gridView.findViewHolderForAdapterPosition(1).itemView;
-        PollingCheck.waitFor(new PollingCheck.ViewStableOnScreen(row1));
-
-        rect.set(0, 0, row1.getWidth(), row1.getHeight());
-        gridView.offsetDescendantRectToMyCoords(row1, rect);
-        assertTrue("Second row should not be aligned to top of screen", rect.top > 0);
-    }
-
-    public static class F_selectBeforeSetAdapter extends RowsFragment {
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            setSelectedPosition(7, false);
-            new Handler().postDelayed(new Runnable() {
-                @Override
-                public void run() {
-                    getVerticalGridView().requestLayout();
-                }
-            }, 100);
-            new Handler().postDelayed(new Runnable() {
-                @Override
-                public void run() {
-                    ListRowPresenter lrp = new ListRowPresenter();
-                    ArrayObjectAdapter adapter = new ArrayObjectAdapter(lrp);
-                    setAdapter(adapter);
-                    loadData(adapter, 10, 1);
-                }
-            }, 1000);
-        }
-    }
-
-    @Test
-    public void selectBeforeSetAdapter() throws InterruptedException {
-        SingleFragmentTestActivity activity =
-                launchAndWaitActivity(F_selectBeforeSetAdapter.class, 2000);
-
-        final VerticalGridView gridView = ((RowsFragment) activity.getTestFragment())
-                .getVerticalGridView();
-        assertEquals(7, gridView.getSelectedPosition());
-        assertNotNull(gridView.findViewHolderForAdapterPosition(7));
-    }
-
-    public static class F_selectBeforeAddData extends RowsFragment {
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            ListRowPresenter lrp = new ListRowPresenter();
-            final ArrayObjectAdapter adapter = new ArrayObjectAdapter(lrp);
-            setAdapter(adapter);
-            setSelectedPosition(7, false);
-            new Handler().postDelayed(new Runnable() {
-                @Override
-                public void run() {
-                    getVerticalGridView().requestLayout();
-                }
-            }, 100);
-            new Handler().postDelayed(new Runnable() {
-                @Override
-                public void run() {
-                    loadData(adapter, 10, 1);
-                }
-            }, 1000);
-        }
-    }
-
-    @Test
-    public void selectBeforeAddData() throws InterruptedException {
-        SingleFragmentTestActivity activity =
-                launchAndWaitActivity(F_selectBeforeAddData.class, 2000);
-
-        final VerticalGridView gridView = ((RowsFragment) activity.getTestFragment())
-                .getVerticalGridView();
-        assertEquals(7, gridView.getSelectedPosition());
-        assertNotNull(gridView.findViewHolderForAdapterPosition(7));
-    }
-
-    public static class F_selectAfterAddData extends RowsFragment {
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            ListRowPresenter lrp = new ListRowPresenter();
-            final ArrayObjectAdapter adapter = new ArrayObjectAdapter(lrp);
-            setAdapter(adapter);
-            loadData(adapter, 10, 1);
-            new Handler().postDelayed(new Runnable() {
-                @Override
-                public void run() {
-                    setSelectedPosition(7, false);
-                }
-            }, 1000);
-        }
-    }
-
-    @Test
-    public void selectAfterAddData() throws InterruptedException {
-        SingleFragmentTestActivity activity =
-                launchAndWaitActivity(F_selectAfterAddData.class, 2000);
-
-        final VerticalGridView gridView = ((RowsFragment) activity.getTestFragment())
-                .getVerticalGridView();
-        assertEquals(7, gridView.getSelectedPosition());
-        assertNotNull(gridView.findViewHolderForAdapterPosition(7));
-    }
-
-    static WeakReference<F_restoreSelection> sLastF_restoreSelection;
-
-    public static class F_restoreSelection extends RowsFragment {
-        public F_restoreSelection() {
-            sLastF_restoreSelection = new WeakReference<F_restoreSelection>(this);
-        }
-
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            ListRowPresenter lrp = new ListRowPresenter();
-            final ArrayObjectAdapter adapter = new ArrayObjectAdapter(lrp);
-            setAdapter(adapter);
-            loadData(adapter, 10, 1);
-            if (savedInstanceState == null) {
-                setSelectedPosition(7, false);
-            }
-        }
-    }
-
-    @Test
-    public void restoreSelection() {
-        final SingleFragmentTestActivity activity =
-                launchAndWaitActivity(F_restoreSelection.class, 1000);
-
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(
-                new Runnable() {
-                    @Override
-                    public void run() {
-                        activity.recreate();
-                    }
-                }
-        );
-        SystemClock.sleep(1000);
-
-        // mActivity is invalid after recreate(), a new Activity instance is created
-        // but we could get Fragment from static variable.
-        RowsFragment fragment = sLastF_restoreSelection.get();
-        final VerticalGridView gridView = fragment.getVerticalGridView();
-        assertEquals(7, gridView.getSelectedPosition());
-        assertNotNull(gridView.findViewHolderForAdapterPosition(7));
-
-    }
-
-    public static class F_ListRowWithOnClick extends RowsFragment {
-        Presenter.ViewHolder mLastClickedItemViewHolder;
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            setOnItemViewClickedListener(new OnItemViewClickedListener() {
-                @Override
-                public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
-                        RowPresenter.ViewHolder rowViewHolder, Row row) {
-                    mLastClickedItemViewHolder = itemViewHolder;
-                }
-            });
-            ListRowPresenter lrp = new ListRowPresenter();
-            ArrayObjectAdapter adapter = new ArrayObjectAdapter(lrp);
-            setAdapter(adapter);
-            loadData(adapter, 10, 1);
-        }
-    }
-
-    @Test
-    public void prefetchChildItemsBeforeAttach() throws Throwable {
-        SingleFragmentTestActivity activity =
-                launchAndWaitActivity(F_ListRowWithOnClick.class, 1000);
-
-        F_ListRowWithOnClick fragment = (F_ListRowWithOnClick) activity.getTestFragment();
-        final VerticalGridView gridView = fragment.getVerticalGridView();
-        View lastRow = gridView.getChildAt(gridView.getChildCount() - 1);
-        final int lastRowPos = gridView.getChildAdapterPosition(lastRow);
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(
-                new Runnable() {
-                    public void run() {
-                        gridView.setSelectedPositionSmooth(lastRowPos);
-                    }
-                }
-        );
-        waitForScrollIdle(gridView);
-        ItemBridgeAdapter.ViewHolder prefetchedBridgeVh = (ItemBridgeAdapter.ViewHolder)
-                gridView.findViewHolderForAdapterPosition(lastRowPos + 1);
-        RowPresenter prefetchedRowPresenter = (RowPresenter) prefetchedBridgeVh.getPresenter();
-        final ListRowPresenter.ViewHolder prefetchedListRowVh = (ListRowPresenter.ViewHolder)
-                prefetchedRowPresenter.getRowViewHolder(prefetchedBridgeVh.getViewHolder());
-
-        fragment.mLastClickedItemViewHolder = null;
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(
-                new Runnable() {
-                    public void run() {
-                        prefetchedListRowVh.getItemViewHolder(0).view.performClick();
-                    }
-                }
-        );
-        assertSame(prefetchedListRowVh.getItemViewHolder(0), fragment.mLastClickedItemViewHolder);
-    }
-
-    @Test
-    public void changeHasStableIdToTrueAfterViewCreated() throws InterruptedException {
-        SingleFragmentTestActivity activity =
-                launchAndWaitActivity(RowsFragment.class, 2000);
-        final RowsFragment fragment = (RowsFragment) activity.getTestFragment();
-
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(
-                new Runnable() {
-                    public void run() {
-                        ObjectAdapter adapter = new ObjectAdapter() {
-                            @Override
-                            public int size() {
-                                return 0;
-                            }
-
-                            @Override
-                            public Object get(int position) {
-                                return null;
-                            }
-
-                            @Override
-                            public long getId(int position) {
-                                return 1;
-                            }
-                        };
-                        adapter.setHasStableIds(true);
-                        fragment.setAdapter(adapter);
-                    }
-                }
-        );
-    }
-
-    static class StableIdAdapter extends ObjectAdapter {
-        ArrayList<Integer> mList = new ArrayList();
-
-        @Override
-        public long getId(int position) {
-            return mList.get(position).longValue();
-        }
-
-        @Override
-        public Object get(int position) {
-            return mList.get(position);
-        }
-
-        @Override
-        public int size() {
-            return mList.size();
-        }
-    }
-
-    public static class F_rowNotifyItemRangeChange extends BrowseFragment {
-
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            ListRowPresenter lrp = new ListRowPresenter();
-            final ArrayObjectAdapter adapter = new ArrayObjectAdapter(lrp);
-            for (int i = 0; i < 2; i++) {
-                StableIdAdapter listRowAdapter = new StableIdAdapter();
-                listRowAdapter.setHasStableIds(true);
-                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));
-            }
-            setAdapter(adapter);
-            new Handler().postDelayed(new Runnable() {
-                @Override
-                public void run() {
-                    StableIdAdapter rowAdapter = (StableIdAdapter)
-                            ((ListRow) adapter.get(1)).getAdapter();
-                    rowAdapter.notifyItemRangeChanged(0, 3);
-                }
-            }, 500);
-        }
-    }
-
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
-    @Test
-    public void rowNotifyItemRangeChange() throws InterruptedException {
-        SingleFragmentTestActivity activity = launchAndWaitActivity(
-                RowsFragmentTest.F_rowNotifyItemRangeChange.class, 2000);
-
-        VerticalGridView verticalGridView = ((BrowseFragment) activity.getTestFragment())
-                .getRowsFragment().getVerticalGridView();
-        for (int i = 0; i < verticalGridView.getChildCount(); i++) {
-            HorizontalGridView horizontalGridView = verticalGridView.getChildAt(i)
-                    .findViewById(R.id.row_content);
-            for (int j = 0; j < horizontalGridView.getChildCount(); j++) {
-                assertEquals(horizontalGridView.getPaddingTop(),
-                        horizontalGridView.getChildAt(j).getTop());
-            }
-        }
-    }
-
-    public static class F_rowNotifyItemRangeChangeWithTransition extends BrowseFragment {
-
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            ListRowPresenter lrp = new ListRowPresenter();
-            prepareEntranceTransition();
-            final ArrayObjectAdapter adapter = new ArrayObjectAdapter(lrp);
-            for (int i = 0; i < 2; i++) {
-                StableIdAdapter listRowAdapter = new StableIdAdapter();
-                listRowAdapter.setHasStableIds(true);
-                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));
-            }
-            setAdapter(adapter);
-            new Handler().postDelayed(new Runnable() {
-                @Override
-                public void run() {
-                    StableIdAdapter rowAdapter = (StableIdAdapter)
-                            ((ListRow) adapter.get(1)).getAdapter();
-                    rowAdapter.notifyItemRangeChanged(0, 3);
-                }
-            }, 500);
-            new Handler().postDelayed(new Runnable() {
-                @Override
-                public void run() {
-                    startEntranceTransition();
-                }
-            }, 520);
-        }
-    }
-
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
-    @Test
-    public void rowNotifyItemRangeChangeWithTransition() throws InterruptedException {
-        SingleFragmentTestActivity activity = launchAndWaitActivity(
-                        RowsFragmentTest.F_rowNotifyItemRangeChangeWithTransition.class, 3000);
-
-        VerticalGridView verticalGridView = ((BrowseFragment) activity.getTestFragment())
-                .getRowsFragment().getVerticalGridView();
-        for (int i = 0; i < verticalGridView.getChildCount(); i++) {
-            HorizontalGridView horizontalGridView = verticalGridView.getChildAt(i)
-                    .findViewById(R.id.row_content);
-            for (int j = 0; j < horizontalGridView.getChildCount(); j++) {
-                assertEquals(horizontalGridView.getPaddingTop(),
-                        horizontalGridView.getChildAt(j).getTop());
-                assertEquals(0, horizontalGridView.getChildAt(j).getTranslationY(), 0.1f);
-            }
-        }
-    }
-}
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
deleted file mode 100644
index c461f45..0000000
--- a/v17/leanback/tests/java/android/support/v17/leanback/app/RowsSupportFragmentTest.java
+++ /dev/null
@@ -1,467 +0,0 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from RowsFragmentTest.java.  DO NOT MODIFY. */
-
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.support.v17.leanback.app;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-
-import android.graphics.Rect;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.SystemClock;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.LargeTest;
-import android.support.test.filters.SdkSuppress;
-import android.support.test.runner.AndroidJUnit4;
-import android.support.v17.leanback.R;
-import android.support.v17.leanback.testutils.PollingCheck;
-import android.support.v17.leanback.widget.ArrayObjectAdapter;
-import android.support.v17.leanback.widget.HeaderItem;
-import android.support.v17.leanback.widget.HorizontalGridView;
-import android.support.v17.leanback.widget.ItemBridgeAdapter;
-import android.support.v17.leanback.widget.ListRow;
-import android.support.v17.leanback.widget.ListRowPresenter;
-import android.support.v17.leanback.widget.ObjectAdapter;
-import android.support.v17.leanback.widget.OnItemViewClickedListener;
-import android.support.v17.leanback.widget.Presenter;
-import android.support.v17.leanback.widget.Row;
-import android.support.v17.leanback.widget.RowPresenter;
-import android.support.v17.leanback.widget.SinglePresenterSelector;
-import android.support.v17.leanback.widget.VerticalGridView;
-import android.view.KeyEvent;
-import android.view.View;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-
-@LargeTest
-@RunWith(AndroidJUnit4.class)
-public class RowsSupportFragmentTest extends SingleSupportFragmentTestBase {
-
-    static final StringPresenter sCardPresenter = new StringPresenter();
-
-    static void loadData(ArrayObjectAdapter adapter, int numRows, int repeatPerRow) {
-        for (int i = 0; i < numRows; ++i) {
-            ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(sCardPresenter);
-            int index = 0;
-            for (int j = 0; j < repeatPerRow; ++j) {
-                listRowAdapter.add("Hello world-" + (index++));
-                listRowAdapter.add("This is a test-" + (index++));
-                listRowAdapter.add("Android TV-" + (index++));
-                listRowAdapter.add("Leanback-" + (index++));
-                listRowAdapter.add("Hello world-" + (index++));
-                listRowAdapter.add("Android TV-" + (index++));
-                listRowAdapter.add("Leanback-" + (index++));
-                listRowAdapter.add("GuidedStepSupportFragment-" + (index++));
-            }
-            HeaderItem header = new HeaderItem(i, "Row " + i);
-            adapter.add(new ListRow(header, listRowAdapter));
-        }
-    }
-
-    public static class F_defaultAlignment extends RowsSupportFragment {
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            ListRowPresenter lrp = new ListRowPresenter();
-            ArrayObjectAdapter adapter = new ArrayObjectAdapter(lrp);
-            setAdapter(adapter);
-            loadData(adapter, 10, 1);
-        }
-    }
-
-    @Test
-    public void defaultAlignment() throws Throwable {
-        SingleSupportFragmentTestActivity activity = launchAndWaitActivity(F_defaultAlignment.class, 1000);
-
-        final Rect rect = new Rect();
-
-        final VerticalGridView gridView = ((RowsSupportFragment) activity.getTestFragment())
-                .getVerticalGridView();
-        View row0 = gridView.findViewHolderForAdapterPosition(0).itemView;
-        rect.set(0, 0, row0.getWidth(), row0.getHeight());
-        gridView.offsetDescendantRectToMyCoords(row0, rect);
-        assertEquals("First row is initially aligned to top of screen", 0, rect.top);
-
-        sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
-        waitForScrollIdle(gridView);
-        View row1 = gridView.findViewHolderForAdapterPosition(1).itemView;
-        PollingCheck.waitFor(new PollingCheck.ViewStableOnScreen(row1));
-
-        rect.set(0, 0, row1.getWidth(), row1.getHeight());
-        gridView.offsetDescendantRectToMyCoords(row1, rect);
-        assertTrue("Second row should not be aligned to top of screen", rect.top > 0);
-    }
-
-    public static class F_selectBeforeSetAdapter extends RowsSupportFragment {
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            setSelectedPosition(7, false);
-            new Handler().postDelayed(new Runnable() {
-                @Override
-                public void run() {
-                    getVerticalGridView().requestLayout();
-                }
-            }, 100);
-            new Handler().postDelayed(new Runnable() {
-                @Override
-                public void run() {
-                    ListRowPresenter lrp = new ListRowPresenter();
-                    ArrayObjectAdapter adapter = new ArrayObjectAdapter(lrp);
-                    setAdapter(adapter);
-                    loadData(adapter, 10, 1);
-                }
-            }, 1000);
-        }
-    }
-
-    @Test
-    public void selectBeforeSetAdapter() throws InterruptedException {
-        SingleSupportFragmentTestActivity activity =
-                launchAndWaitActivity(F_selectBeforeSetAdapter.class, 2000);
-
-        final VerticalGridView gridView = ((RowsSupportFragment) activity.getTestFragment())
-                .getVerticalGridView();
-        assertEquals(7, gridView.getSelectedPosition());
-        assertNotNull(gridView.findViewHolderForAdapterPosition(7));
-    }
-
-    public static class F_selectBeforeAddData extends RowsSupportFragment {
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            ListRowPresenter lrp = new ListRowPresenter();
-            final ArrayObjectAdapter adapter = new ArrayObjectAdapter(lrp);
-            setAdapter(adapter);
-            setSelectedPosition(7, false);
-            new Handler().postDelayed(new Runnable() {
-                @Override
-                public void run() {
-                    getVerticalGridView().requestLayout();
-                }
-            }, 100);
-            new Handler().postDelayed(new Runnable() {
-                @Override
-                public void run() {
-                    loadData(adapter, 10, 1);
-                }
-            }, 1000);
-        }
-    }
-
-    @Test
-    public void selectBeforeAddData() throws InterruptedException {
-        SingleSupportFragmentTestActivity activity =
-                launchAndWaitActivity(F_selectBeforeAddData.class, 2000);
-
-        final VerticalGridView gridView = ((RowsSupportFragment) activity.getTestFragment())
-                .getVerticalGridView();
-        assertEquals(7, gridView.getSelectedPosition());
-        assertNotNull(gridView.findViewHolderForAdapterPosition(7));
-    }
-
-    public static class F_selectAfterAddData extends RowsSupportFragment {
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            ListRowPresenter lrp = new ListRowPresenter();
-            final ArrayObjectAdapter adapter = new ArrayObjectAdapter(lrp);
-            setAdapter(adapter);
-            loadData(adapter, 10, 1);
-            new Handler().postDelayed(new Runnable() {
-                @Override
-                public void run() {
-                    setSelectedPosition(7, false);
-                }
-            }, 1000);
-        }
-    }
-
-    @Test
-    public void selectAfterAddData() throws InterruptedException {
-        SingleSupportFragmentTestActivity activity =
-                launchAndWaitActivity(F_selectAfterAddData.class, 2000);
-
-        final VerticalGridView gridView = ((RowsSupportFragment) activity.getTestFragment())
-                .getVerticalGridView();
-        assertEquals(7, gridView.getSelectedPosition());
-        assertNotNull(gridView.findViewHolderForAdapterPosition(7));
-    }
-
-    static WeakReference<F_restoreSelection> sLastF_restoreSelection;
-
-    public static class F_restoreSelection extends RowsSupportFragment {
-        public F_restoreSelection() {
-            sLastF_restoreSelection = new WeakReference<F_restoreSelection>(this);
-        }
-
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            ListRowPresenter lrp = new ListRowPresenter();
-            final ArrayObjectAdapter adapter = new ArrayObjectAdapter(lrp);
-            setAdapter(adapter);
-            loadData(adapter, 10, 1);
-            if (savedInstanceState == null) {
-                setSelectedPosition(7, false);
-            }
-        }
-    }
-
-    @Test
-    public void restoreSelection() {
-        final SingleSupportFragmentTestActivity activity =
-                launchAndWaitActivity(F_restoreSelection.class, 1000);
-
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(
-                new Runnable() {
-                    @Override
-                    public void run() {
-                        activity.recreate();
-                    }
-                }
-        );
-        SystemClock.sleep(1000);
-
-        // mActivity is invalid after recreate(), a new Activity instance is created
-        // but we could get Fragment from static variable.
-        RowsSupportFragment fragment = sLastF_restoreSelection.get();
-        final VerticalGridView gridView = fragment.getVerticalGridView();
-        assertEquals(7, gridView.getSelectedPosition());
-        assertNotNull(gridView.findViewHolderForAdapterPosition(7));
-
-    }
-
-    public static class F_ListRowWithOnClick extends RowsSupportFragment {
-        Presenter.ViewHolder mLastClickedItemViewHolder;
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            setOnItemViewClickedListener(new OnItemViewClickedListener() {
-                @Override
-                public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
-                        RowPresenter.ViewHolder rowViewHolder, Row row) {
-                    mLastClickedItemViewHolder = itemViewHolder;
-                }
-            });
-            ListRowPresenter lrp = new ListRowPresenter();
-            ArrayObjectAdapter adapter = new ArrayObjectAdapter(lrp);
-            setAdapter(adapter);
-            loadData(adapter, 10, 1);
-        }
-    }
-
-    @Test
-    public void prefetchChildItemsBeforeAttach() throws Throwable {
-        SingleSupportFragmentTestActivity activity =
-                launchAndWaitActivity(F_ListRowWithOnClick.class, 1000);
-
-        F_ListRowWithOnClick fragment = (F_ListRowWithOnClick) activity.getTestFragment();
-        final VerticalGridView gridView = fragment.getVerticalGridView();
-        View lastRow = gridView.getChildAt(gridView.getChildCount() - 1);
-        final int lastRowPos = gridView.getChildAdapterPosition(lastRow);
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(
-                new Runnable() {
-                    public void run() {
-                        gridView.setSelectedPositionSmooth(lastRowPos);
-                    }
-                }
-        );
-        waitForScrollIdle(gridView);
-        ItemBridgeAdapter.ViewHolder prefetchedBridgeVh = (ItemBridgeAdapter.ViewHolder)
-                gridView.findViewHolderForAdapterPosition(lastRowPos + 1);
-        RowPresenter prefetchedRowPresenter = (RowPresenter) prefetchedBridgeVh.getPresenter();
-        final ListRowPresenter.ViewHolder prefetchedListRowVh = (ListRowPresenter.ViewHolder)
-                prefetchedRowPresenter.getRowViewHolder(prefetchedBridgeVh.getViewHolder());
-
-        fragment.mLastClickedItemViewHolder = null;
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(
-                new Runnable() {
-                    public void run() {
-                        prefetchedListRowVh.getItemViewHolder(0).view.performClick();
-                    }
-                }
-        );
-        assertSame(prefetchedListRowVh.getItemViewHolder(0), fragment.mLastClickedItemViewHolder);
-    }
-
-    @Test
-    public void changeHasStableIdToTrueAfterViewCreated() throws InterruptedException {
-        SingleSupportFragmentTestActivity activity =
-                launchAndWaitActivity(RowsSupportFragment.class, 2000);
-        final RowsSupportFragment fragment = (RowsSupportFragment) activity.getTestFragment();
-
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(
-                new Runnable() {
-                    public void run() {
-                        ObjectAdapter adapter = new ObjectAdapter() {
-                            @Override
-                            public int size() {
-                                return 0;
-                            }
-
-                            @Override
-                            public Object get(int position) {
-                                return null;
-                            }
-
-                            @Override
-                            public long getId(int position) {
-                                return 1;
-                            }
-                        };
-                        adapter.setHasStableIds(true);
-                        fragment.setAdapter(adapter);
-                    }
-                }
-        );
-    }
-
-    static class StableIdAdapter extends ObjectAdapter {
-        ArrayList<Integer> mList = new ArrayList();
-
-        @Override
-        public long getId(int position) {
-            return mList.get(position).longValue();
-        }
-
-        @Override
-        public Object get(int position) {
-            return mList.get(position);
-        }
-
-        @Override
-        public int size() {
-            return mList.size();
-        }
-    }
-
-    public static class F_rowNotifyItemRangeChange extends BrowseSupportFragment {
-
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            ListRowPresenter lrp = new ListRowPresenter();
-            final ArrayObjectAdapter adapter = new ArrayObjectAdapter(lrp);
-            for (int i = 0; i < 2; i++) {
-                StableIdAdapter listRowAdapter = new StableIdAdapter();
-                listRowAdapter.setHasStableIds(true);
-                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));
-            }
-            setAdapter(adapter);
-            new Handler().postDelayed(new Runnable() {
-                @Override
-                public void run() {
-                    StableIdAdapter rowAdapter = (StableIdAdapter)
-                            ((ListRow) adapter.get(1)).getAdapter();
-                    rowAdapter.notifyItemRangeChanged(0, 3);
-                }
-            }, 500);
-        }
-    }
-
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
-    @Test
-    public void rowNotifyItemRangeChange() throws InterruptedException {
-        SingleSupportFragmentTestActivity activity = launchAndWaitActivity(
-                RowsSupportFragmentTest.F_rowNotifyItemRangeChange.class, 2000);
-
-        VerticalGridView verticalGridView = ((BrowseSupportFragment) activity.getTestFragment())
-                .getRowsSupportFragment().getVerticalGridView();
-        for (int i = 0; i < verticalGridView.getChildCount(); i++) {
-            HorizontalGridView horizontalGridView = verticalGridView.getChildAt(i)
-                    .findViewById(R.id.row_content);
-            for (int j = 0; j < horizontalGridView.getChildCount(); j++) {
-                assertEquals(horizontalGridView.getPaddingTop(),
-                        horizontalGridView.getChildAt(j).getTop());
-            }
-        }
-    }
-
-    public static class F_rowNotifyItemRangeChangeWithTransition extends BrowseSupportFragment {
-
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            ListRowPresenter lrp = new ListRowPresenter();
-            prepareEntranceTransition();
-            final ArrayObjectAdapter adapter = new ArrayObjectAdapter(lrp);
-            for (int i = 0; i < 2; i++) {
-                StableIdAdapter listRowAdapter = new StableIdAdapter();
-                listRowAdapter.setHasStableIds(true);
-                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));
-            }
-            setAdapter(adapter);
-            new Handler().postDelayed(new Runnable() {
-                @Override
-                public void run() {
-                    StableIdAdapter rowAdapter = (StableIdAdapter)
-                            ((ListRow) adapter.get(1)).getAdapter();
-                    rowAdapter.notifyItemRangeChanged(0, 3);
-                }
-            }, 500);
-            new Handler().postDelayed(new Runnable() {
-                @Override
-                public void run() {
-                    startEntranceTransition();
-                }
-            }, 520);
-        }
-    }
-
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
-    @Test
-    public void rowNotifyItemRangeChangeWithTransition() throws InterruptedException {
-        SingleSupportFragmentTestActivity activity = launchAndWaitActivity(
-                        RowsSupportFragmentTest.F_rowNotifyItemRangeChangeWithTransition.class, 3000);
-
-        VerticalGridView verticalGridView = ((BrowseSupportFragment) activity.getTestFragment())
-                .getRowsSupportFragment().getVerticalGridView();
-        for (int i = 0; i < verticalGridView.getChildCount(); i++) {
-            HorizontalGridView horizontalGridView = verticalGridView.getChildAt(i)
-                    .findViewById(R.id.row_content);
-            for (int j = 0; j < horizontalGridView.getChildCount(); j++) {
-                assertEquals(horizontalGridView.getPaddingTop(),
-                        horizontalGridView.getChildAt(j).getTop());
-                assertEquals(0, horizontalGridView.getChildAt(j).getTranslationY(), 0.1f);
-            }
-        }
-    }
-}
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/SingleFragmentTestActivity.java b/v17/leanback/tests/java/android/support/v17/leanback/app/SingleFragmentTestActivity.java
deleted file mode 100644
index 6047a1e..0000000
--- a/v17/leanback/tests/java/android/support/v17/leanback/app/SingleFragmentTestActivity.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.support.v17.leanback.app;
-
-import android.app.Activity;
-import android.app.Fragment;
-import android.app.FragmentTransaction;
-import android.content.Intent;
-import android.os.Bundle;
-import android.support.v17.leanback.test.R;
-import android.util.Log;
-
-public class SingleFragmentTestActivity extends Activity {
-
-    /**
-     * Fragment that will be added to activity
-     */
-    public static final String EXTRA_FRAGMENT_NAME = "fragmentName";
-
-    public static final String EXTRA_ACTIVITY_LAYOUT = "activityLayout";
-
-    public static final String EXTRA_UI_VISIBILITY = "uiVisibility";
-    private static final String TAG = "TestActivity";
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        Log.d(TAG, "onCreate " + this);
-        Intent intent = getIntent();
-
-        final int uiOptions = intent.getIntExtra(EXTRA_UI_VISIBILITY, 0);
-        if (uiOptions != 0) {
-            getWindow().getDecorView().setSystemUiVisibility(uiOptions);
-        }
-
-        setContentView(intent.getIntExtra(EXTRA_ACTIVITY_LAYOUT, R.layout.single_fragment));
-        if (savedInstanceState == null && findViewById(R.id.main_frame) != null) {
-            try {
-                Fragment fragment = (Fragment) Class.forName(
-                        intent.getStringExtra(EXTRA_FRAGMENT_NAME)).newInstance();
-                FragmentTransaction ft = getFragmentManager().beginTransaction();
-                ft.replace(R.id.main_frame, fragment);
-                ft.commit();
-            } catch (Exception ex) {
-                ex.printStackTrace();
-                finish();
-            }
-        }
-    }
-
-    public Fragment getTestFragment() {
-        return getFragmentManager().findFragmentById(R.id.main_frame);
-    }
-}
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/SingleFragmentTestBase.java b/v17/leanback/tests/java/android/support/v17/leanback/app/SingleFragmentTestBase.java
deleted file mode 100644
index b26d92d..0000000
--- a/v17/leanback/tests/java/android/support/v17/leanback/app/SingleFragmentTestBase.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.support.v17.leanback.app;
-
-import android.content.Intent;
-import android.os.SystemClock;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.rule.ActivityTestRule;
-import android.support.v7.widget.RecyclerView;
-
-import org.junit.Rule;
-import org.junit.rules.TestName;
-
-public class SingleFragmentTestBase {
-
-    private static final long WAIT_FOR_SCROLL_IDLE_TIMEOUT_MS = 60000;
-
-    @Rule
-    public TestName mUnitTestName = new TestName();
-
-    @Rule
-    public ActivityTestRule<SingleFragmentTestActivity> activityTestRule =
-            new ActivityTestRule<>(SingleFragmentTestActivity.class, false, false);
-
-    public void sendKeys(int ...keys) {
-        for (int i = 0; i < keys.length; i++) {
-            InstrumentationRegistry.getInstrumentation().sendKeyDownUpSync(keys[i]);
-        }
-    }
-
-    /**
-     * Options that will be passed throught Intent to SingleFragmentTestActivity
-     */
-    public static class Options {
-        int mActivityLayoutId;
-        int mUiVisibility;
-
-        public Options() {
-        }
-
-        public Options activityLayoutId(int activityLayoutId) {
-            mActivityLayoutId = activityLayoutId;
-            return this;
-        }
-
-        public Options uiVisibility(int uiVisibility) {
-            mUiVisibility = uiVisibility;
-            return this;
-        }
-
-        public void collect(Intent intent) {
-            if (mActivityLayoutId != 0) {
-                intent.putExtra(SingleFragmentTestActivity.EXTRA_ACTIVITY_LAYOUT,
-                        mActivityLayoutId);
-            }
-            if (mUiVisibility != 0) {
-                intent.putExtra(SingleFragmentTestActivity.EXTRA_UI_VISIBILITY, mUiVisibility);
-            }
-        }
-    }
-
-    public SingleFragmentTestActivity launchAndWaitActivity(Class fragmentClass, long waitTimeMs) {
-        return launchAndWaitActivity(fragmentClass.getName(), null, waitTimeMs);
-    }
-
-    public SingleFragmentTestActivity launchAndWaitActivity(Class fragmentClass, Options options,
-            long waitTimeMs) {
-        return launchAndWaitActivity(fragmentClass.getName(), options, waitTimeMs);
-    }
-
-    public SingleFragmentTestActivity launchAndWaitActivity(String firstFragmentName,
-            Options options, long waitTimeMs) {
-        Intent intent = new Intent();
-        intent.putExtra(SingleFragmentTestActivity.EXTRA_FRAGMENT_NAME, firstFragmentName);
-        if (options != null) {
-            options.collect(intent);
-        }
-        SingleFragmentTestActivity activity = activityTestRule.launchActivity(intent);
-        SystemClock.sleep(waitTimeMs);
-        return activity;
-    }
-
-    protected void waitForScrollIdle(RecyclerView recyclerView) throws Throwable {
-        waitForScrollIdle(recyclerView, null);
-    }
-
-    protected void waitForScrollIdle(RecyclerView recyclerView, Runnable verify) throws Throwable {
-        Thread.sleep(100);
-        int total = 0;
-        while (recyclerView.getLayoutManager().isSmoothScrolling()
-                || recyclerView.getScrollState() != recyclerView.SCROLL_STATE_IDLE) {
-            if ((total += 100) >= WAIT_FOR_SCROLL_IDLE_TIMEOUT_MS) {
-                throw new RuntimeException("waitForScrollIdle Timeout");
-            }
-            try {
-                Thread.sleep(100);
-            } catch (InterruptedException ex) {
-                break;
-            }
-            if (verify != null) {
-                activityTestRule.runOnUiThread(verify);
-            }
-        }
-    }
-
-}
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/SingleSupportFragmentTestActivity.java b/v17/leanback/tests/java/android/support/v17/leanback/app/SingleSupportFragmentTestActivity.java
deleted file mode 100644
index 0fc3183..0000000
--- a/v17/leanback/tests/java/android/support/v17/leanback/app/SingleSupportFragmentTestActivity.java
+++ /dev/null
@@ -1,70 +0,0 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from SingleFragmentTestActivity.java.  DO NOT MODIFY. */
-
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.support.v17.leanback.app;
-
-import android.support.v4.app.FragmentActivity;
-import android.support.v4.app.Fragment;
-import android.support.v4.app.FragmentTransaction;
-import android.content.Intent;
-import android.os.Bundle;
-import android.support.v17.leanback.test.R;
-import android.util.Log;
-
-public class SingleSupportFragmentTestActivity extends FragmentActivity {
-
-    /**
-     * Fragment that will be added to activity
-     */
-    public static final String EXTRA_FRAGMENT_NAME = "fragmentName";
-
-    public static final String EXTRA_ACTIVITY_LAYOUT = "activityLayout";
-
-    public static final String EXTRA_UI_VISIBILITY = "uiVisibility";
-    private static final String TAG = "TestActivity";
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        Log.d(TAG, "onCreate " + this);
-        Intent intent = getIntent();
-
-        final int uiOptions = intent.getIntExtra(EXTRA_UI_VISIBILITY, 0);
-        if (uiOptions != 0) {
-            getWindow().getDecorView().setSystemUiVisibility(uiOptions);
-        }
-
-        setContentView(intent.getIntExtra(EXTRA_ACTIVITY_LAYOUT, R.layout.single_fragment));
-        if (savedInstanceState == null && findViewById(R.id.main_frame) != null) {
-            try {
-                Fragment fragment = (Fragment) Class.forName(
-                        intent.getStringExtra(EXTRA_FRAGMENT_NAME)).newInstance();
-                FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
-                ft.replace(R.id.main_frame, fragment);
-                ft.commit();
-            } catch (Exception ex) {
-                ex.printStackTrace();
-                finish();
-            }
-        }
-    }
-
-    public Fragment getTestFragment() {
-        return getSupportFragmentManager().findFragmentById(R.id.main_frame);
-    }
-}
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/SingleSupportFragmentTestBase.java b/v17/leanback/tests/java/android/support/v17/leanback/app/SingleSupportFragmentTestBase.java
deleted file mode 100644
index 6c00923..0000000
--- a/v17/leanback/tests/java/android/support/v17/leanback/app/SingleSupportFragmentTestBase.java
+++ /dev/null
@@ -1,122 +0,0 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from SingleFrgamentTestBase.java.  DO NOT MODIFY. */
-
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.support.v17.leanback.app;
-
-import android.content.Intent;
-import android.os.SystemClock;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.rule.ActivityTestRule;
-import android.support.v7.widget.RecyclerView;
-
-import org.junit.Rule;
-import org.junit.rules.TestName;
-
-public class SingleSupportFragmentTestBase {
-
-    private static final long WAIT_FOR_SCROLL_IDLE_TIMEOUT_MS = 60000;
-
-    @Rule
-    public TestName mUnitTestName = new TestName();
-
-    @Rule
-    public ActivityTestRule<SingleSupportFragmentTestActivity> activityTestRule =
-            new ActivityTestRule<>(SingleSupportFragmentTestActivity.class, false, false);
-
-    public void sendKeys(int ...keys) {
-        for (int i = 0; i < keys.length; i++) {
-            InstrumentationRegistry.getInstrumentation().sendKeyDownUpSync(keys[i]);
-        }
-    }
-
-    /**
-     * Options that will be passed throught Intent to SingleSupportFragmentTestActivity
-     */
-    public static class Options {
-        int mActivityLayoutId;
-        int mUiVisibility;
-
-        public Options() {
-        }
-
-        public Options activityLayoutId(int activityLayoutId) {
-            mActivityLayoutId = activityLayoutId;
-            return this;
-        }
-
-        public Options uiVisibility(int uiVisibility) {
-            mUiVisibility = uiVisibility;
-            return this;
-        }
-
-        public void collect(Intent intent) {
-            if (mActivityLayoutId != 0) {
-                intent.putExtra(SingleSupportFragmentTestActivity.EXTRA_ACTIVITY_LAYOUT,
-                        mActivityLayoutId);
-            }
-            if (mUiVisibility != 0) {
-                intent.putExtra(SingleSupportFragmentTestActivity.EXTRA_UI_VISIBILITY, mUiVisibility);
-            }
-        }
-    }
-
-    public SingleSupportFragmentTestActivity launchAndWaitActivity(Class fragmentClass, long waitTimeMs) {
-        return launchAndWaitActivity(fragmentClass.getName(), null, waitTimeMs);
-    }
-
-    public SingleSupportFragmentTestActivity launchAndWaitActivity(Class fragmentClass, Options options,
-            long waitTimeMs) {
-        return launchAndWaitActivity(fragmentClass.getName(), options, waitTimeMs);
-    }
-
-    public SingleSupportFragmentTestActivity launchAndWaitActivity(String firstFragmentName,
-            Options options, long waitTimeMs) {
-        Intent intent = new Intent();
-        intent.putExtra(SingleSupportFragmentTestActivity.EXTRA_FRAGMENT_NAME, firstFragmentName);
-        if (options != null) {
-            options.collect(intent);
-        }
-        SingleSupportFragmentTestActivity activity = activityTestRule.launchActivity(intent);
-        SystemClock.sleep(waitTimeMs);
-        return activity;
-    }
-
-    protected void waitForScrollIdle(RecyclerView recyclerView) throws Throwable {
-        waitForScrollIdle(recyclerView, null);
-    }
-
-    protected void waitForScrollIdle(RecyclerView recyclerView, Runnable verify) throws Throwable {
-        Thread.sleep(100);
-        int total = 0;
-        while (recyclerView.getLayoutManager().isSmoothScrolling()
-                || recyclerView.getScrollState() != recyclerView.SCROLL_STATE_IDLE) {
-            if ((total += 100) >= WAIT_FOR_SCROLL_IDLE_TIMEOUT_MS) {
-                throw new RuntimeException("waitForScrollIdle Timeout");
-            }
-            try {
-                Thread.sleep(100);
-            } catch (InterruptedException ex) {
-                break;
-            }
-            if (verify != null) {
-                activityTestRule.runOnUiThread(verify);
-            }
-        }
-    }
-
-}
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/VerticalGridFragmentTest.java b/v17/leanback/tests/java/android/support/v17/leanback/app/VerticalGridFragmentTest.java
deleted file mode 100644
index 2c36cda..0000000
--- a/v17/leanback/tests/java/android/support/v17/leanback/app/VerticalGridFragmentTest.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.v17.leanback.app;
-
-import android.app.Fragment;
-import android.os.Bundle;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.runner.AndroidJUnit4;
-import android.support.v17.leanback.widget.ArrayObjectAdapter;
-import android.support.v17.leanback.widget.VerticalGridPresenter;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@MediumTest
-@RunWith(AndroidJUnit4.class)
-public class VerticalGridFragmentTest extends SingleFragmentTestBase {
-
-    public static class GridFragment extends VerticalGridFragment {
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            if (savedInstanceState == null) {
-                prepareEntranceTransition();
-            }
-            VerticalGridPresenter gridPresenter = new VerticalGridPresenter();
-            gridPresenter.setNumberOfColumns(3);
-            setGridPresenter(gridPresenter);
-            setAdapter(new ArrayObjectAdapter());
-        }
-    }
-
-    @Test
-    public void immediateRemoveFragment() throws Throwable {
-        final SingleFragmentTestActivity activity = launchAndWaitActivity(GridFragment.class, 500);
-
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                GridFragment f = new GridFragment();
-                activity.getFragmentManager().beginTransaction()
-                        .replace(android.R.id.content, f, null).commit();
-                f.startEntranceTransition();
-                activity.getFragmentManager().beginTransaction()
-                        .replace(android.R.id.content, new Fragment(), null).commit();
-            }
-        });
-
-        Thread.sleep(1000);
-        activity.finish();
-    }
-
-}
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/VerticalGridSupportFragmentTest.java b/v17/leanback/tests/java/android/support/v17/leanback/app/VerticalGridSupportFragmentTest.java
deleted file mode 100644
index 9ca930a..0000000
--- a/v17/leanback/tests/java/android/support/v17/leanback/app/VerticalGridSupportFragmentTest.java
+++ /dev/null
@@ -1,71 +0,0 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from VerticalGridFragmentTest.java.  DO NOT MODIFY. */
-
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.v17.leanback.app;
-
-import android.support.v4.app.Fragment;
-import android.os.Bundle;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
-import android.support.test.runner.AndroidJUnit4;
-import android.support.v17.leanback.widget.ArrayObjectAdapter;
-import android.support.v17.leanback.widget.VerticalGridPresenter;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@MediumTest
-@RunWith(AndroidJUnit4.class)
-public class VerticalGridSupportFragmentTest extends SingleSupportFragmentTestBase {
-
-    public static class GridFragment extends VerticalGridSupportFragment {
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            if (savedInstanceState == null) {
-                prepareEntranceTransition();
-            }
-            VerticalGridPresenter gridPresenter = new VerticalGridPresenter();
-            gridPresenter.setNumberOfColumns(3);
-            setGridPresenter(gridPresenter);
-            setAdapter(new ArrayObjectAdapter());
-        }
-    }
-
-    @Test
-    public void immediateRemoveFragment() throws Throwable {
-        final SingleSupportFragmentTestActivity activity = launchAndWaitActivity(GridFragment.class, 500);
-
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                GridFragment f = new GridFragment();
-                activity.getSupportFragmentManager().beginTransaction()
-                        .replace(android.R.id.content, f, null).commit();
-                f.startEntranceTransition();
-                activity.getSupportFragmentManager().beginTransaction()
-                        .replace(android.R.id.content, new Fragment(), null).commit();
-            }
-        });
-
-        Thread.sleep(1000);
-        activity.finish();
-    }
-
-}
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/VideoFragmentTest.java b/v17/leanback/tests/java/android/support/v17/leanback/app/VideoFragmentTest.java
deleted file mode 100644
index 7fe3902..0000000
--- a/v17/leanback/tests/java/android/support/v17/leanback/app/VideoFragmentTest.java
+++ /dev/null
@@ -1,253 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.support.v17.leanback.app;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNotSame;
-import static org.junit.Assert.assertTrue;
-
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.SystemClock;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.LargeTest;
-import android.support.test.runner.AndroidJUnit4;
-import android.support.v17.leanback.media.MediaPlayerGlue;
-import android.support.v17.leanback.media.PlaybackGlue;
-import android.support.v17.leanback.media.PlaybackGlueHost;
-import android.support.v17.leanback.test.R;
-import android.support.v17.leanback.testutils.PollingCheck;
-import android.view.LayoutInflater;
-import android.view.SurfaceHolder;
-import android.view.View;
-import android.view.ViewGroup;
-
-import junit.framework.Assert;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@LargeTest
-@RunWith(AndroidJUnit4.class)
-public class VideoFragmentTest extends SingleFragmentTestBase {
-
-    public static class Fragment_setSurfaceViewCallbackBeforeCreate extends VideoFragment {
-        boolean mSurfaceCreated;
-        @Override
-        public View onCreateView(
-                LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
-
-            setSurfaceHolderCallback(new SurfaceHolder.Callback() {
-                @Override
-                public void surfaceCreated(SurfaceHolder holder) {
-                    mSurfaceCreated = true;
-                }
-
-                @Override
-                public void surfaceChanged(SurfaceHolder holder, int format, int width,
-                                           int height) {
-                }
-
-                @Override
-                public void surfaceDestroyed(SurfaceHolder holder) {
-                    mSurfaceCreated = false;
-                }
-            });
-
-            return super.onCreateView(inflater, container, savedInstanceState);
-        }
-    }
-
-    @Test
-    public void setSurfaceViewCallbackBeforeCreate() {
-        final SingleFragmentTestActivity activity =
-                launchAndWaitActivity(Fragment_setSurfaceViewCallbackBeforeCreate.class, 1000);
-        Fragment_setSurfaceViewCallbackBeforeCreate fragment1 =
-                (Fragment_setSurfaceViewCallbackBeforeCreate) activity.getTestFragment();
-        assertNotNull(fragment1);
-        assertTrue(fragment1.mSurfaceCreated);
-
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                activity.getFragmentManager().beginTransaction()
-                        .replace(R.id.main_frame, new Fragment_setSurfaceViewCallbackBeforeCreate())
-                        .commitAllowingStateLoss();
-            }
-        });
-        SystemClock.sleep(500);
-
-        assertFalse(fragment1.mSurfaceCreated);
-
-        Fragment_setSurfaceViewCallbackBeforeCreate fragment2 =
-                (Fragment_setSurfaceViewCallbackBeforeCreate) activity.getTestFragment();
-        assertNotNull(fragment2);
-        assertTrue(fragment2.mSurfaceCreated);
-        assertNotSame(fragment1, fragment2);
-    }
-
-    @Test
-    public void setSurfaceViewCallbackAfterCreate() {
-        SingleFragmentTestActivity activity = launchAndWaitActivity(VideoFragment.class, 1000);
-        VideoFragment fragment = (VideoFragment) activity.getTestFragment();
-
-        assertNotNull(fragment);
-
-        final boolean[] surfaceCreated = new boolean[1];
-        fragment.setSurfaceHolderCallback(new SurfaceHolder.Callback() {
-            @Override
-            public void surfaceCreated(SurfaceHolder holder) {
-                surfaceCreated[0] = true;
-            }
-
-            @Override
-            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
-            }
-
-            @Override
-            public void surfaceDestroyed(SurfaceHolder holder) {
-                surfaceCreated[0] = false;
-            }
-        });
-        assertTrue(surfaceCreated[0]);
-    }
-
-    public static class Fragment_withVideoPlayer extends VideoFragment {
-        MediaPlayerGlue mGlue;
-        int mOnCreateCalled;
-        int mOnCreateViewCalled;
-        int mOnDestroyViewCalled;
-        int mOnDestroyCalled;
-        int mGlueAttachedToHost;
-        int mGlueDetachedFromHost;
-        int mGlueOnReadyForPlaybackCalled;
-
-        public Fragment_withVideoPlayer() {
-            setRetainInstance(true);
-        }
-
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            mOnCreateCalled++;
-            super.onCreate(savedInstanceState);
-            mGlue = new MediaPlayerGlue(getActivity()) {
-                @Override
-                protected void onDetachedFromHost() {
-                    mGlueDetachedFromHost++;
-                    super.onDetachedFromHost();
-                }
-
-                @Override
-                protected void onAttachedToHost(PlaybackGlueHost host) {
-                    super.onAttachedToHost(host);
-                    mGlueAttachedToHost++;
-                }
-            };
-            mGlue.setMode(MediaPlayerGlue.REPEAT_ALL);
-            mGlue.setArtist("Leanback");
-            mGlue.setTitle("Leanback team at work");
-            mGlue.setMediaSource(
-                    Uri.parse("android.resource://android.support.v17.leanback.test/raw/video"));
-            mGlue.addPlayerCallback(new PlaybackGlue.PlayerCallback() {
-                @Override
-                public void onPreparedStateChanged(PlaybackGlue glue) {
-                    if (glue.isPrepared()) {
-                        mGlueOnReadyForPlaybackCalled++;
-                        mGlue.play();
-                    }
-                }
-            });
-            mGlue.setHost(new VideoFragmentGlueHost(this));
-        }
-
-        @Override
-        public View onCreateView(LayoutInflater inflater, ViewGroup container,
-                                 Bundle savedInstanceState) {
-            mOnCreateViewCalled++;
-            return super.onCreateView(inflater, container, savedInstanceState);
-        }
-
-        @Override
-        public void onDestroyView() {
-            mOnDestroyViewCalled++;
-            super.onDestroyView();
-        }
-
-        @Override
-        public void onDestroy() {
-            mOnDestroyCalled++;
-            super.onDestroy();
-        }
-    }
-
-    @Test
-    public void mediaPlayerGlueInVideoFragment() {
-        final SingleFragmentTestActivity activity =
-                launchAndWaitActivity(Fragment_withVideoPlayer.class, 1000);
-        final Fragment_withVideoPlayer fragment = (Fragment_withVideoPlayer)
-                activity.getTestFragment();
-
-        PollingCheck.waitFor(5000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return fragment.mGlue.isMediaPlaying();
-            }
-        });
-
-        assertEquals(1, fragment.mOnCreateCalled);
-        assertEquals(1, fragment.mOnCreateViewCalled);
-        assertEquals(0, fragment.mOnDestroyViewCalled);
-        assertEquals(1, fragment.mGlueOnReadyForPlaybackCalled);
-        View fragmentViewBeforeRecreate = fragment.getView();
-
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                activity.recreate();
-            }
-        });
-
-        PollingCheck.waitFor(5000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return fragment.mOnCreateViewCalled == 2 && fragment.mGlue.isMediaPlaying();
-            }
-        });
-        View fragmentViewAfterRecreate = fragment.getView();
-
-        Assert.assertNotSame(fragmentViewBeforeRecreate, fragmentViewAfterRecreate);
-        assertEquals(1, fragment.mOnCreateCalled);
-        assertEquals(2, fragment.mOnCreateViewCalled);
-        assertEquals(1, fragment.mOnDestroyViewCalled);
-
-        assertEquals(1, fragment.mGlueAttachedToHost);
-        assertEquals(0, fragment.mGlueDetachedFromHost);
-        assertEquals(1, fragment.mGlueOnReadyForPlaybackCalled);
-
-        activity.finish();
-        PollingCheck.waitFor(5000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return fragment.mGlueDetachedFromHost == 1;
-            }
-        });
-        assertEquals(2, fragment.mOnDestroyViewCalled);
-        assertEquals(1, fragment.mOnDestroyCalled);
-    }
-
-}
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/VideoSupportFragmentTest.java b/v17/leanback/tests/java/android/support/v17/leanback/app/VideoSupportFragmentTest.java
deleted file mode 100644
index d96dc4d..0000000
--- a/v17/leanback/tests/java/android/support/v17/leanback/app/VideoSupportFragmentTest.java
+++ /dev/null
@@ -1,256 +0,0 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from VideoFragmentTest.java.  DO NOT MODIFY. */
-
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.support.v17.leanback.app;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNotSame;
-import static org.junit.Assert.assertTrue;
-
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.SystemClock;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.LargeTest;
-import android.support.test.runner.AndroidJUnit4;
-import android.support.v17.leanback.media.MediaPlayerGlue;
-import android.support.v17.leanback.media.PlaybackGlue;
-import android.support.v17.leanback.media.PlaybackGlueHost;
-import android.support.v17.leanback.test.R;
-import android.support.v17.leanback.testutils.PollingCheck;
-import android.view.LayoutInflater;
-import android.view.SurfaceHolder;
-import android.view.View;
-import android.view.ViewGroup;
-
-import junit.framework.Assert;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@LargeTest
-@RunWith(AndroidJUnit4.class)
-public class VideoSupportFragmentTest extends SingleSupportFragmentTestBase {
-
-    public static class Fragment_setSurfaceViewCallbackBeforeCreate extends VideoSupportFragment {
-        boolean mSurfaceCreated;
-        @Override
-        public View onCreateView(
-                LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
-
-            setSurfaceHolderCallback(new SurfaceHolder.Callback() {
-                @Override
-                public void surfaceCreated(SurfaceHolder holder) {
-                    mSurfaceCreated = true;
-                }
-
-                @Override
-                public void surfaceChanged(SurfaceHolder holder, int format, int width,
-                                           int height) {
-                }
-
-                @Override
-                public void surfaceDestroyed(SurfaceHolder holder) {
-                    mSurfaceCreated = false;
-                }
-            });
-
-            return super.onCreateView(inflater, container, savedInstanceState);
-        }
-    }
-
-    @Test
-    public void setSurfaceViewCallbackBeforeCreate() {
-        final SingleSupportFragmentTestActivity activity =
-                launchAndWaitActivity(Fragment_setSurfaceViewCallbackBeforeCreate.class, 1000);
-        Fragment_setSurfaceViewCallbackBeforeCreate fragment1 =
-                (Fragment_setSurfaceViewCallbackBeforeCreate) activity.getTestFragment();
-        assertNotNull(fragment1);
-        assertTrue(fragment1.mSurfaceCreated);
-
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                activity.getSupportFragmentManager().beginTransaction()
-                        .replace(R.id.main_frame, new Fragment_setSurfaceViewCallbackBeforeCreate())
-                        .commitAllowingStateLoss();
-            }
-        });
-        SystemClock.sleep(500);
-
-        assertFalse(fragment1.mSurfaceCreated);
-
-        Fragment_setSurfaceViewCallbackBeforeCreate fragment2 =
-                (Fragment_setSurfaceViewCallbackBeforeCreate) activity.getTestFragment();
-        assertNotNull(fragment2);
-        assertTrue(fragment2.mSurfaceCreated);
-        assertNotSame(fragment1, fragment2);
-    }
-
-    @Test
-    public void setSurfaceViewCallbackAfterCreate() {
-        SingleSupportFragmentTestActivity activity = launchAndWaitActivity(VideoSupportFragment.class, 1000);
-        VideoSupportFragment fragment = (VideoSupportFragment) activity.getTestFragment();
-
-        assertNotNull(fragment);
-
-        final boolean[] surfaceCreated = new boolean[1];
-        fragment.setSurfaceHolderCallback(new SurfaceHolder.Callback() {
-            @Override
-            public void surfaceCreated(SurfaceHolder holder) {
-                surfaceCreated[0] = true;
-            }
-
-            @Override
-            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
-            }
-
-            @Override
-            public void surfaceDestroyed(SurfaceHolder holder) {
-                surfaceCreated[0] = false;
-            }
-        });
-        assertTrue(surfaceCreated[0]);
-    }
-
-    public static class Fragment_withVideoPlayer extends VideoSupportFragment {
-        MediaPlayerGlue mGlue;
-        int mOnCreateCalled;
-        int mOnCreateViewCalled;
-        int mOnDestroyViewCalled;
-        int mOnDestroyCalled;
-        int mGlueAttachedToHost;
-        int mGlueDetachedFromHost;
-        int mGlueOnReadyForPlaybackCalled;
-
-        public Fragment_withVideoPlayer() {
-            setRetainInstance(true);
-        }
-
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            mOnCreateCalled++;
-            super.onCreate(savedInstanceState);
-            mGlue = new MediaPlayerGlue(getActivity()) {
-                @Override
-                protected void onDetachedFromHost() {
-                    mGlueDetachedFromHost++;
-                    super.onDetachedFromHost();
-                }
-
-                @Override
-                protected void onAttachedToHost(PlaybackGlueHost host) {
-                    super.onAttachedToHost(host);
-                    mGlueAttachedToHost++;
-                }
-            };
-            mGlue.setMode(MediaPlayerGlue.REPEAT_ALL);
-            mGlue.setArtist("Leanback");
-            mGlue.setTitle("Leanback team at work");
-            mGlue.setMediaSource(
-                    Uri.parse("android.resource://android.support.v17.leanback.test/raw/video"));
-            mGlue.addPlayerCallback(new PlaybackGlue.PlayerCallback() {
-                @Override
-                public void onPreparedStateChanged(PlaybackGlue glue) {
-                    if (glue.isPrepared()) {
-                        mGlueOnReadyForPlaybackCalled++;
-                        mGlue.play();
-                    }
-                }
-            });
-            mGlue.setHost(new VideoSupportFragmentGlueHost(this));
-        }
-
-        @Override
-        public View onCreateView(LayoutInflater inflater, ViewGroup container,
-                                 Bundle savedInstanceState) {
-            mOnCreateViewCalled++;
-            return super.onCreateView(inflater, container, savedInstanceState);
-        }
-
-        @Override
-        public void onDestroyView() {
-            mOnDestroyViewCalled++;
-            super.onDestroyView();
-        }
-
-        @Override
-        public void onDestroy() {
-            mOnDestroyCalled++;
-            super.onDestroy();
-        }
-    }
-
-    @Test
-    public void mediaPlayerGlueInVideoSupportFragment() {
-        final SingleSupportFragmentTestActivity activity =
-                launchAndWaitActivity(Fragment_withVideoPlayer.class, 1000);
-        final Fragment_withVideoPlayer fragment = (Fragment_withVideoPlayer)
-                activity.getTestFragment();
-
-        PollingCheck.waitFor(5000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return fragment.mGlue.isMediaPlaying();
-            }
-        });
-
-        assertEquals(1, fragment.mOnCreateCalled);
-        assertEquals(1, fragment.mOnCreateViewCalled);
-        assertEquals(0, fragment.mOnDestroyViewCalled);
-        assertEquals(1, fragment.mGlueOnReadyForPlaybackCalled);
-        View fragmentViewBeforeRecreate = fragment.getView();
-
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                activity.recreate();
-            }
-        });
-
-        PollingCheck.waitFor(5000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return fragment.mOnCreateViewCalled == 2 && fragment.mGlue.isMediaPlaying();
-            }
-        });
-        View fragmentViewAfterRecreate = fragment.getView();
-
-        Assert.assertNotSame(fragmentViewBeforeRecreate, fragmentViewAfterRecreate);
-        assertEquals(1, fragment.mOnCreateCalled);
-        assertEquals(2, fragment.mOnCreateViewCalled);
-        assertEquals(1, fragment.mOnDestroyViewCalled);
-
-        assertEquals(1, fragment.mGlueAttachedToHost);
-        assertEquals(0, fragment.mGlueDetachedFromHost);
-        assertEquals(1, fragment.mGlueOnReadyForPlaybackCalled);
-
-        activity.finish();
-        PollingCheck.waitFor(5000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return fragment.mGlueDetachedFromHost == 1;
-            }
-        });
-        assertEquals(2, fragment.mOnDestroyViewCalled);
-        assertEquals(1, fragment.mOnDestroyCalled);
-    }
-
-}
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/widget/GridWidgetTest.java b/v17/leanback/tests/java/android/support/v17/leanback/widget/GridWidgetTest.java
deleted file mode 100644
index 5de0aa7..0000000
--- a/v17/leanback/tests/java/android/support/v17/leanback/widget/GridWidgetTest.java
+++ /dev/null
@@ -1,5631 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.support.v17.leanback.widget;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNotSame;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.verify;
-
-import android.content.Intent;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Rect;
-import android.graphics.drawable.ColorDrawable;
-import android.os.Build;
-import android.os.Parcelable;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.LargeTest;
-import android.support.test.filters.SdkSuppress;
-import android.support.test.rule.ActivityTestRule;
-import android.support.test.runner.AndroidJUnit4;
-import android.support.v17.leanback.test.R;
-import android.support.v17.leanback.testutils.PollingCheck;
-import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
-import android.support.v7.widget.DefaultItemAnimator;
-import android.support.v7.widget.RecyclerView;
-import android.support.v7.widget.RecyclerViewAccessibilityDelegate;
-import android.text.Selection;
-import android.text.Spannable;
-import android.util.DisplayMetrics;
-import android.util.SparseArray;
-import android.util.SparseIntArray;
-import android.util.TypedValue;
-import android.view.KeyEvent;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-
-import org.junit.After;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TestName;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.HashSet;
-
-@LargeTest
-@RunWith(AndroidJUnit4.class)
-public class GridWidgetTest {
-
-    private static final float DELTA = 1f;
-    private static final boolean HUMAN_DELAY = false;
-    private static final long WAIT_FOR_SCROLL_IDLE_TIMEOUT_MS = 60000;
-    private static final int WAIT_FOR_LAYOUT_PASS_TIMEOUT_MS = 2000;
-    private static final int WAIT_FOR_ITEM_ANIMATION_FINISH_TIMEOUT_MS = 6000;
-
-    protected ActivityTestRule<GridActivity> mActivityTestRule;
-    protected GridActivity mActivity;
-    protected BaseGridView mGridView;
-    protected GridLayoutManager mLayoutManager;
-    private GridLayoutManager.OnLayoutCompleteListener mWaitLayoutListener;
-    protected int mOrientation;
-    protected int mNumRows;
-    protected int[] mRemovedItems;
-
-    private final Comparator<View> mRowSortComparator = new Comparator<View>() {
-        @Override
-        public int compare(View lhs, View rhs) {
-            if (mOrientation == BaseGridView.HORIZONTAL) {
-                return lhs.getLeft() - rhs.getLeft();
-            } else {
-                return lhs.getTop() - rhs.getTop();
-            }
-        };
-    };
-
-    /**
-     * Verify margins between items on same row are same.
-     */
-    private final Runnable mVerifyLayout = new Runnable() {
-        @Override
-        public void run() {
-            verifyMargin();
-        }
-    };
-
-    @Rule public TestName testName = new TestName();
-
-    public static void sendKey(int keyCode) {
-        InstrumentationRegistry.getInstrumentation().sendKeyDownUpSync(keyCode);
-    }
-
-    public static void sendRepeatedKeys(int repeats, int keyCode) {
-        for (int i = 0; i < repeats; i++) {
-            InstrumentationRegistry.getInstrumentation().sendKeyDownUpSync(keyCode);
-        }
-    }
-
-    private void humanDelay(int delay) throws InterruptedException {
-        if (HUMAN_DELAY) Thread.sleep(delay);
-    }
-    /**
-     * Change size of the Adapter and notifyDataSetChanged.
-     */
-    private void changeArraySize(final int size) throws Throwable {
-        performAndWaitForAnimation(new Runnable() {
-            @Override
-            public void run() {
-                mActivity.changeArraySize(size);
-            }
-        });
-    }
-
-    static String dumpGridView(BaseGridView gridView) {
-        return "findFocus:" + gridView.getRootView().findFocus()
-                + " isLayoutRequested:" + gridView.isLayoutRequested()
-                + " selectedPosition:" + gridView.getSelectedPosition()
-                + " adapter.itemCount:" + gridView.getAdapter().getItemCount()
-                + " itemAnimator.isRunning:" + gridView.getItemAnimator().isRunning()
-                + " scrollState:" + gridView.getScrollState();
-    }
-
-    /**
-     * Change selected position.
-     */
-    private void setSelectedPosition(final int position, final int scrollExtra) throws Throwable {
-        startWaitLayout();
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.setSelectedPosition(position, scrollExtra);
-            }
-        });
-        waitForLayout(false);
-    }
-
-    private void setSelectedPosition(final int position) throws Throwable {
-        setSelectedPosition(position, 0);
-    }
-
-    private void setSelectedPositionSmooth(final int position) throws Throwable {
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.setSelectedPositionSmooth(position);
-            }
-        });
-    }
-    /**
-     * Scrolls using given key.
-     */
-    protected void scroll(int key, Runnable verify) throws Throwable {
-        do {
-            if (verify != null) {
-                mActivityTestRule.runOnUiThread(verify);
-            }
-            sendRepeatedKeys(10, key);
-            try {
-                Thread.sleep(300);
-            } catch (InterruptedException ex) {
-                break;
-            }
-        } while (mGridView.getLayoutManager().isSmoothScrolling()
-                || mGridView.getScrollState() != BaseGridView.SCROLL_STATE_IDLE);
-    }
-
-    protected void scrollToBegin(Runnable verify) throws Throwable {
-        int key;
-        // first move to first column/row
-        if (mOrientation == BaseGridView.HORIZONTAL) {
-            key = KeyEvent.KEYCODE_DPAD_UP;
-        } else {
-            if (mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL) {
-                key = KeyEvent.KEYCODE_DPAD_RIGHT;
-            } else {
-                key = KeyEvent.KEYCODE_DPAD_LEFT;
-            }
-        }
-        scroll(key, null);
-        if (mOrientation == BaseGridView.HORIZONTAL) {
-            if (mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL) {
-                key = KeyEvent.KEYCODE_DPAD_RIGHT;
-            } else {
-                key = KeyEvent.KEYCODE_DPAD_LEFT;
-            }
-        } else {
-            key = KeyEvent.KEYCODE_DPAD_UP;
-        }
-        scroll(key, verify);
-    }
-
-    protected void scrollToEnd(Runnable verify) throws Throwable {
-        int key;
-        // first move to first column/row
-        if (mOrientation == BaseGridView.HORIZONTAL) {
-            key = KeyEvent.KEYCODE_DPAD_UP;
-        } else {
-            if (mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL) {
-                key = KeyEvent.KEYCODE_DPAD_RIGHT;
-            } else {
-                key = KeyEvent.KEYCODE_DPAD_LEFT;
-            }
-        }
-        scroll(key, null);
-        if (mOrientation == BaseGridView.HORIZONTAL) {
-            if (mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL) {
-                key = KeyEvent.KEYCODE_DPAD_LEFT;
-            } else {
-                key = KeyEvent.KEYCODE_DPAD_RIGHT;
-            }
-        } else {
-            key = KeyEvent.KEYCODE_DPAD_DOWN;
-        }
-        scroll(key, verify);
-    }
-
-    /**
-     * Group and sort children by their position on each row (HORIZONTAL) or column(VERTICAL).
-     */
-    protected View[][] sortByRows() {
-        final HashMap<Integer, ArrayList<View>> rows = new HashMap<Integer, ArrayList<View>>();
-        ArrayList<Integer> rowLocations = new ArrayList<>();
-        for (int i = 0; i < mGridView.getChildCount(); i++) {
-            View v = mGridView.getChildAt(i);
-            int rowLocation;
-            if (mOrientation == BaseGridView.HORIZONTAL) {
-                rowLocation = v.getTop();
-            } else {
-                rowLocation = mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL
-                        ? v.getRight() : v.getLeft();
-            }
-            ArrayList<View> views = rows.get(rowLocation);
-            if (views == null) {
-                views = new ArrayList<View>();
-                rows.put(rowLocation, views);
-                rowLocations.add(rowLocation);
-            }
-            views.add(v);
-        }
-        Object[] sortedLocations = rowLocations.toArray();
-        Arrays.sort(sortedLocations);
-        if (mNumRows != rows.size()) {
-            assertEquals("Dump Views by rows "+rows, mNumRows, rows.size());
-        }
-        View[][] sorted = new View[rows.size()][];
-        for (int i = 0; i < rowLocations.size(); i++) {
-            Integer rowLocation = rowLocations.get(i);
-            ArrayList<View> arr = rows.get(rowLocation);
-            View[] views = arr.toArray(new View[arr.size()]);
-            Arrays.sort(views, mRowSortComparator);
-            sorted[i] = views;
-        }
-        return sorted;
-    }
-
-    protected void verifyMargin() {
-        View[][] sorted = sortByRows();
-        for (int row = 0; row < sorted.length; row++) {
-            View[] views = sorted[row];
-            int margin = -1;
-            for (int i = 1; i < views.length; i++) {
-                if (mOrientation == BaseGridView.HORIZONTAL) {
-                    assertEquals(mGridView.getHorizontalMargin(),
-                            views[i].getLeft() - views[i - 1].getRight());
-                } else {
-                    assertEquals(mGridView.getVerticalMargin(),
-                            views[i].getTop() - views[i - 1].getBottom());
-                }
-            }
-        }
-    }
-
-    protected void verifyBeginAligned() {
-        View[][] sorted = sortByRows();
-        int alignedLocation = 0;
-        if (mOrientation == BaseGridView.HORIZONTAL) {
-            if (mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL) {
-                for (int i = 0; i < sorted.length; i++) {
-                    if (i == 0) {
-                        alignedLocation = sorted[i][sorted[i].length - 1].getRight();
-                    } else {
-                        assertEquals(alignedLocation, sorted[i][sorted[i].length - 1].getRight());
-                    }
-                }
-            } else {
-                for (int i = 0; i < sorted.length; i++) {
-                    if (i == 0) {
-                        alignedLocation = sorted[i][0].getLeft();
-                    } else {
-                        assertEquals(alignedLocation, sorted[i][0].getLeft());
-                    }
-                }
-            }
-        } else {
-            for (int i = 0; i < sorted.length; i++) {
-                if (i == 0) {
-                    alignedLocation = sorted[i][0].getTop();
-                } else {
-                    assertEquals(alignedLocation, sorted[i][0].getTop());
-                }
-            }
-        }
-    }
-
-    protected int[] getEndEdges() {
-        View[][] sorted = sortByRows();
-        int[] edges = new int[sorted.length];
-        if (mOrientation == BaseGridView.HORIZONTAL) {
-            if (mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL) {
-                for (int i = 0; i < sorted.length; i++) {
-                    edges[i] = sorted[i][0].getLeft();
-                }
-            } else {
-                for (int i = 0; i < sorted.length; i++) {
-                    edges[i] = sorted[i][sorted[i].length - 1].getRight();
-                }
-            }
-        } else {
-            for (int i = 0; i < sorted.length; i++) {
-                edges[i] = sorted[i][sorted[i].length - 1].getBottom();
-            }
-        }
-        return edges;
-    }
-
-    protected void verifyEdgesSame(int[] edges, int[] edges2) {
-        assertEquals(edges.length, edges2.length);
-        for (int i = 0; i < edges.length; i++) {
-            assertEquals(edges[i], edges2[i]);
-        }
-    }
-
-    protected void verifyBoundCount(int count) {
-        if (mActivity.getBoundCount() != count) {
-            StringBuffer b = new StringBuffer();
-            b.append("ItemsLength: ");
-            for (int i = 0; i < mActivity.mItemLengths.length; i++) {
-                b.append(mActivity.mItemLengths[i]).append(",");
-            }
-            assertEquals("Bound count does not match, ItemsLengths: "+ b,
-                    count, mActivity.getBoundCount());
-        }
-    }
-
-    private static int getCenterY(View v) {
-        return (v.getTop() + v.getBottom())/2;
-    }
-
-    private static int getCenterX(View v) {
-        return (v.getLeft() + v.getRight())/2;
-    }
-
-    private void initActivity(Intent intent) throws Throwable {
-        mActivityTestRule = new ActivityTestRule<GridActivity>(GridActivity.class, false, false);
-        mActivity = mActivityTestRule.launchActivity(intent);
-        mActivityTestRule.runOnUiThread(new Runnable() {
-                @Override
-                public void run() {
-                    mActivity.setTitle(testName.getMethodName());
-                }
-            });
-        Thread.sleep(1000);
-        mGridView = mActivity.mGridView;
-        mLayoutManager = (GridLayoutManager) mGridView.getLayoutManager();
-    }
-
-    @After
-    public void clearTest() {
-        mWaitLayoutListener = null;
-        mLayoutManager = null;
-        mGridView = null;
-        mActivity = null;
-        mActivityTestRule = null;
-    }
-
-    /**
-     * Must be called before waitForLayout() to prepare layout listener.
-     */
-    protected void startWaitLayout() {
-        if (mWaitLayoutListener != null) {
-            throw new IllegalStateException("startWaitLayout() already called");
-        }
-        if (mLayoutManager.mLayoutCompleteListener != null) {
-            throw new IllegalStateException("Cannot startWaitLayout()");
-        }
-        mWaitLayoutListener = mLayoutManager.mLayoutCompleteListener =
-                mock(GridLayoutManager.OnLayoutCompleteListener.class);
-    }
-
-    /**
-     * wait layout to be called and remove the listener.
-     */
-    protected void waitForLayout() {
-        waitForLayout(true);
-    }
-
-    /**
-     * wait layout to be called and remove the listener.
-     * @param force True if always wait regardless if layout requested
-     */
-    protected void waitForLayout(boolean force) {
-        if (mWaitLayoutListener == null) {
-            throw new IllegalStateException("startWaitLayout() not called");
-        }
-        if (mWaitLayoutListener != mLayoutManager.mLayoutCompleteListener) {
-            throw new IllegalStateException("layout listener inconistent");
-        }
-        try {
-            if (force || mGridView.isLayoutRequested()) {
-                verify(mWaitLayoutListener, timeout(WAIT_FOR_LAYOUT_PASS_TIMEOUT_MS).atLeastOnce())
-                        .onLayoutCompleted(any(RecyclerView.State.class));
-            }
-        } finally {
-            mWaitLayoutListener = null;
-            mLayoutManager.mLayoutCompleteListener = null;
-        }
-    }
-
-    /**
-     * If currently running animator, wait for it to finish, otherwise return immediately.
-     * To wait the ItemAnimator start, you can use waitForLayout() to make sure layout pass has
-     * processed adapter change.
-     */
-    protected void waitForItemAnimation(int timeoutMs) throws Throwable {
-        final RecyclerView.ItemAnimator.ItemAnimatorFinishedListener listener = mock(
-                RecyclerView.ItemAnimator.ItemAnimatorFinishedListener.class);
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.getItemAnimator().isRunning(listener);
-            }
-        });
-        verify(listener, timeout(timeoutMs).atLeastOnce()).onAnimationsFinished();
-    }
-
-    protected void waitForItemAnimation() throws Throwable {
-        waitForItemAnimation(WAIT_FOR_ITEM_ANIMATION_FINISH_TIMEOUT_MS);
-    }
-
-    /**
-     * wait animation start
-     */
-    protected void waitForItemAnimationStart() throws Throwable {
-        long totalWait = 0;
-        while (!mGridView.getItemAnimator().isRunning()) {
-            Thread.sleep(10);
-            if ((totalWait += 10) > WAIT_FOR_ITEM_ANIMATION_FINISH_TIMEOUT_MS) {
-                throw new RuntimeException("waitForItemAnimationStart Timeout");
-            }
-        }
-    }
-
-    /**
-     * Run task in UI thread and wait for layout and ItemAnimator finishes.
-     */
-    protected void performAndWaitForAnimation(Runnable task) throws Throwable {
-        startWaitLayout();
-        mActivityTestRule.runOnUiThread(task);
-        waitForLayout();
-        waitForItemAnimation();
-    }
-
-    protected void waitForScrollIdle() throws Throwable {
-        waitForScrollIdle(null);
-    }
-
-    /**
-     * Wait for grid view stop scroll and optionally verify state of grid view.
-     */
-    protected void waitForScrollIdle(Runnable verify) throws Throwable {
-        Thread.sleep(100);
-        int total = 0;
-        while (mGridView.getLayoutManager().isSmoothScrolling()
-                || mGridView.getScrollState() != BaseGridView.SCROLL_STATE_IDLE) {
-            if ((total += 100) >= WAIT_FOR_SCROLL_IDLE_TIMEOUT_MS) {
-                throw new RuntimeException("waitForScrollIdle Timeout");
-            }
-            try {
-                Thread.sleep(100);
-            } catch (InterruptedException ex) {
-                break;
-            }
-            if (verify != null) {
-                mActivityTestRule.runOnUiThread(verify);
-            }
-        }
-    }
-
-    @Test
-    public void testThreeRowHorizontalBasic() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_grid);
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 100);
-        initActivity(intent);
-        mOrientation = BaseGridView.HORIZONTAL;
-        mNumRows = 3;
-
-        scrollToEnd(mVerifyLayout);
-
-        scrollToBegin(mVerifyLayout);
-
-        verifyBeginAligned();
-    }
-
-    static class DividerDecoration extends RecyclerView.ItemDecoration {
-
-        private ColorDrawable mTopDivider;
-        private ColorDrawable mBottomDivider;
-        private int mLeftOffset;
-        private int mRightOffset;
-        private int mTopOffset;
-        private int mBottomOffset;
-
-        DividerDecoration(int leftOffset, int topOffset, int rightOffset, int bottomOffset) {
-            mLeftOffset = leftOffset;
-            mTopOffset = topOffset;
-            mRightOffset = rightOffset;
-            mBottomOffset = bottomOffset;
-        }
-
-        @Override
-        public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
-            if (mTopDivider == null) {
-                mTopDivider = new ColorDrawable(Color.RED);
-            }
-            if (mBottomDivider == null) {
-                mBottomDivider = new ColorDrawable(Color.BLUE);
-            }
-            final int childCount = parent.getChildCount();
-            final int width = parent.getWidth();
-            for (int childViewIndex = 0; childViewIndex < childCount; childViewIndex++) {
-                final View view = parent.getChildAt(childViewIndex);
-                mTopDivider.setBounds(0, (int) view.getY() - mTopOffset, width, (int) view.getY());
-                mTopDivider.draw(c);
-                mBottomDivider.setBounds(0, (int) view.getY() + view.getHeight(), width,
-                        (int) view.getY() + view.getHeight() + mBottomOffset);
-                mBottomDivider.draw(c);
-            }
-        }
-
-        @Override
-        public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
-                                   RecyclerView.State state) {
-            outRect.left = mLeftOffset;
-            outRect.top = mTopOffset;
-            outRect.right = mRightOffset;
-            outRect.bottom = mBottomOffset;
-        }
-    }
-
-    @Test
-    public void testItemDecorationAndMargins() throws Throwable {
-
-        final int leftMargin = 3;
-        final int topMargin = 4;
-        final int rightMargin = 7;
-        final int bottomMargin = 8;
-        final int itemHeight = 100;
-
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
-        intent.putExtra(GridActivity.EXTRA_ITEMS, new int[]{itemHeight, itemHeight, itemHeight});
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_MARGINS,
-                new int[]{leftMargin, topMargin, rightMargin, bottomMargin});
-        initActivity(intent);
-        mOrientation = BaseGridView.VERTICAL;
-        mNumRows = 1;
-
-        final int paddingLeft = mGridView.getPaddingLeft();
-        final int paddingTop = mGridView.getPaddingTop();
-        final int verticalSpace = mGridView.getVerticalMargin();
-        final int decorationLeft = 17;
-        final int decorationTop = 1;
-        final int decorationRight = 19;
-        final int decorationBottom = 2;
-
-        performAndWaitForAnimation(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.addItemDecoration(new DividerDecoration(decorationLeft, decorationTop,
-                        decorationRight, decorationBottom));
-            }
-        });
-
-        View child0 = mGridView.getChildAt(0);
-        View child1 = mGridView.getChildAt(1);
-        View child2 = mGridView.getChildAt(2);
-
-        assertEquals(itemHeight, child0.getBottom() - child0.getTop());
-
-        // verify left margins
-        assertEquals(paddingLeft + leftMargin + decorationLeft, child0.getLeft());
-        assertEquals(paddingLeft + leftMargin + decorationLeft, child1.getLeft());
-        assertEquals(paddingLeft + leftMargin + decorationLeft, child2.getLeft());
-        // verify top bottom margins and decoration offset
-        assertEquals(paddingTop + topMargin + decorationTop, child0.getTop());
-        assertEquals(bottomMargin + decorationBottom + verticalSpace + decorationTop + topMargin,
-                child1.getTop() - child0.getBottom());
-        assertEquals(bottomMargin + decorationBottom + verticalSpace + decorationTop + topMargin,
-                child2.getTop() - child1.getBottom());
-
-    }
-
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
-    @Test
-    public void testItemDecorationAndMarginsAndOpticalBounds() throws Throwable {
-        final int leftMargin = 3;
-        final int topMargin = 4;
-        final int rightMargin = 7;
-        final int bottomMargin = 8;
-        final int itemHeight = 100;
-        final int ninePatchDrawableResourceId = R.drawable.lb_card_shadow_focused;
-
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
-        intent.putExtra(GridActivity.EXTRA_ITEMS, new int[]{itemHeight, itemHeight, itemHeight});
-        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.relative_layout);
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_MARGINS,
-                new int[]{leftMargin, topMargin, rightMargin, bottomMargin});
-        intent.putExtra(GridActivity.EXTRA_NINEPATCH_SHADOW, ninePatchDrawableResourceId);
-        initActivity(intent);
-        mOrientation = BaseGridView.VERTICAL;
-        mNumRows = 1;
-
-        final int paddingLeft = mGridView.getPaddingLeft();
-        final int paddingTop = mGridView.getPaddingTop();
-        final int verticalSpace = mGridView.getVerticalMargin();
-        final int decorationLeft = 17;
-        final int decorationTop = 1;
-        final int decorationRight = 19;
-        final int decorationBottom = 2;
-
-        final Rect opticalPaddings = new Rect();
-        mGridView.getResources().getDrawable(ninePatchDrawableResourceId)
-                .getPadding(opticalPaddings);
-        final int opticalInsetsLeft = opticalPaddings.left;
-        final int opticalInsetsTop = opticalPaddings.top;
-        final int opticalInsetsRight = opticalPaddings.right;
-        final int opticalInsetsBottom = opticalPaddings.bottom;
-        assertTrue(opticalInsetsLeft > 0);
-        assertTrue(opticalInsetsTop > 0);
-        assertTrue(opticalInsetsRight > 0);
-        assertTrue(opticalInsetsBottom > 0);
-
-        performAndWaitForAnimation(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.addItemDecoration(new DividerDecoration(decorationLeft, decorationTop,
-                        decorationRight, decorationBottom));
-            }
-        });
-
-        View child0 = mGridView.getChildAt(0);
-        View child1 = mGridView.getChildAt(1);
-        View child2 = mGridView.getChildAt(2);
-
-        assertEquals(itemHeight + opticalInsetsTop + opticalInsetsBottom,
-                child0.getBottom() - child0.getTop());
-
-        // verify left margins decoration and optical insets
-        assertEquals(paddingLeft + leftMargin + decorationLeft - opticalInsetsLeft,
-                child0.getLeft());
-        assertEquals(paddingLeft + leftMargin + decorationLeft - opticalInsetsLeft,
-                child1.getLeft());
-        assertEquals(paddingLeft + leftMargin + decorationLeft - opticalInsetsLeft,
-                child2.getLeft());
-        // verify top bottom margins decoration offset and optical insets
-        assertEquals(paddingTop + topMargin + decorationTop, child0.getTop() + opticalInsetsTop);
-        assertEquals(bottomMargin + decorationBottom + verticalSpace + decorationTop + topMargin,
-                (child1.getTop() + opticalInsetsTop) - (child0.getBottom() - opticalInsetsBottom));
-        assertEquals(bottomMargin + decorationBottom + verticalSpace + decorationTop + topMargin,
-                (child2.getTop() + opticalInsetsTop) - (child1.getBottom() - opticalInsetsBottom));
-
-    }
-
-    @Test
-    public void testThreeColumnVerticalBasic() throws Throwable {
-
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_grid);
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 200);
-        initActivity(intent);
-        mOrientation = BaseGridView.VERTICAL;
-        mNumRows = 3;
-
-        scrollToEnd(mVerifyLayout);
-
-        scrollToBegin(mVerifyLayout);
-
-        verifyBeginAligned();
-    }
-
-    @Test
-    public void testRedundantAppendRemove() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.vertical_grid_testredundantappendremove);
-        intent.putExtra(GridActivity.EXTRA_ITEMS, new int[]{
-                149,177,128,234,227,187,163,223,146,210,228,148,227,193,182,197,177,142,225,207,
-                157,171,209,204,187,184,123,221,197,153,202,179,193,214,226,173,225,143,188,159,
-                139,193,233,143,227,203,222,124,228,223,164,131,228,126,211,160,165,152,235,184,
-                155,224,149,181,171,229,200,234,177,130,164,172,188,139,132,203,179,220,147,131,
-                226,127,230,239,183,203,206,227,123,170,239,234,200,149,237,204,160,133,202,234,
-                173,122,139,149,151,153,216,231,121,145,227,153,186,174,223,180,123,215,206,216,
-                239,222,219,207,193,218,140,133,171,153,183,132,233,138,159,174,189,171,143,128,
-                152,222,141,202,224,190,134,120,181,231,230,136,132,224,136,210,207,150,128,183,
-                221,194,179,220,126,221,137,205,223,193,172,132,226,209,133,191,227,127,159,171,
-                180,149,237,177,194,207,170,202,161,144,147,199,205,186,164,140,193,203,224,129});
-        initActivity(intent);
-        mOrientation = BaseGridView.VERTICAL;
-        mNumRows = 3;
-
-        scrollToEnd(mVerifyLayout);
-
-        scrollToBegin(mVerifyLayout);
-
-        verifyBeginAligned();
-    }
-
-    @Test
-    public void testRedundantAppendRemove2() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.horizontal_grid_testredundantappendremove2);
-        intent.putExtra(GridActivity.EXTRA_ITEMS, new int[]{
-                318,333,199,224,246,273,269,289,340,313,265,306,349,269,185,282,257,354,316,252,
-                237,290,283,343,196,313,290,343,191,262,342,228,343,349,251,203,226,305,265,213,
-                216,333,295,188,187,281,288,311,244,232,224,332,290,181,267,276,226,261,335,355,
-                225,217,219,183,234,285,257,304,182,250,244,223,257,219,342,185,347,205,302,315,
-                299,309,292,237,192,309,228,250,347,227,337,298,299,185,185,331,223,284,265,351});
-        initActivity(intent);
-        mOrientation = BaseGridView.HORIZONTAL;
-        mNumRows = 3;
-        mLayoutManager = (GridLayoutManager) mGridView.getLayoutManager();
-
-        // test append without staggered result cache
-        scrollToEnd(mVerifyLayout);
-
-        int[] endEdges = getEndEdges();
-
-        scrollToBegin(mVerifyLayout);
-
-        verifyBeginAligned();
-
-        // now test append with staggered result cache
-        changeArraySize(3);
-        assertEquals("Staggerd cache should be kept as is when no item size change",
-                100, ((StaggeredGrid) mLayoutManager.mGrid).mLocations.size());
-
-        changeArraySize(100);
-
-        scrollToEnd(mVerifyLayout);
-
-        // we should get same aligned end edges
-        int[] endEdges2 = getEndEdges();
-        verifyEdgesSame(endEdges, endEdges2);
-    }
-
-
-    @Test
-    public void testLayoutWhenAViewIsInvalidated() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 1000);
-        intent.putExtra(GridActivity.EXTRA_HAS_STABLE_IDS, true);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        mNumRows = 1;
-        initActivity(intent);
-        mOrientation = BaseGridView.VERTICAL;
-        waitOneUiCycle();
-
-        // push views to cache.
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mActivity.mItemLengths[0] = mActivity.mItemLengths[0] * 3;
-                mActivity.mGridView.getAdapter().notifyItemChanged(0);
-            }
-        });
-        waitForItemAnimation();
-
-        // notifyDataSetChange will mark the cached views FLAG_INVALID
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mActivity.mGridView.getAdapter().notifyDataSetChanged();
-            }
-        });
-        waitForItemAnimation();
-
-        // Cached views will be added in prelayout with FLAG_INVALID, in post layout we should
-        // handle it properly
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mActivity.mItemLengths[0] = mActivity.mItemLengths[0] / 3;
-                mActivity.mGridView.getAdapter().notifyItemChanged(0);
-            }
-        });
-
-        waitForItemAnimation();
-    }
-
-    @Test
-    public void testWrongInsertViewIndexInFastRelayout() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 2);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        mNumRows = 1;
-        initActivity(intent);
-        mOrientation = BaseGridView.VERTICAL;
-
-        // removing two children, they will be hidden views as first 2 children of RV.
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.getItemAnimator().setRemoveDuration(2000);
-                mActivity.removeItems(0, 2);
-            }
-        });
-        waitForItemAnimationStart();
-
-        // add three views and notify change of the first item.
-        startWaitLayout();
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mActivity.addItems(0, new int[]{161, 161, 161});
-            }
-        });
-        waitForLayout();
-        startWaitLayout();
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.getAdapter().notifyItemChanged(0);
-            }
-        });
-        waitForLayout();
-        // after layout, the viewholder should still be the first child of LayoutManager.
-        assertEquals(0, mGridView.getChildAdapterPosition(
-                mGridView.getLayoutManager().getChildAt(0)));
-    }
-
-    @Test
-    public void testMoveIntoPrelayoutItems() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 1000);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        mNumRows = 1;
-        initActivity(intent);
-        mOrientation = BaseGridView.VERTICAL;
-
-        final int lastItemPos = mGridView.getChildCount() - 1;
-        assertTrue(mGridView.getChildCount() >= 4);
-        // notify change of 3 items, so prelayout will layout extra 3 items, then move an item
-        // into the extra layout range. Post layout's fastRelayout() should handle this properly.
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.getAdapter().notifyItemChanged(lastItemPos - 3);
-                mGridView.getAdapter().notifyItemChanged(lastItemPos - 2);
-                mGridView.getAdapter().notifyItemChanged(lastItemPos - 1);
-                mActivity.moveItem(900, lastItemPos + 2, true);
-            }
-        });
-        waitForItemAnimation();
-    }
-
-    @Test
-    public void testMoveIntoPrelayoutItems2() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 1000);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        mNumRows = 1;
-        initActivity(intent);
-        mOrientation = BaseGridView.VERTICAL;
-
-        setSelectedPosition(999);
-        final int firstItemPos = mGridView.getChildAdapterPosition(mGridView.getChildAt(0));
-        assertTrue(mGridView.getChildCount() >= 4);
-        // notify change of 3 items, so prelayout will layout extra 3 items, then move an item
-        // into the extra layout range. Post layout's fastRelayout() should handle this properly.
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.getAdapter().notifyItemChanged(firstItemPos + 1);
-                mGridView.getAdapter().notifyItemChanged(firstItemPos + 2);
-                mGridView.getAdapter().notifyItemChanged(firstItemPos + 3);
-                mActivity.moveItem(0, firstItemPos - 2, true);
-            }
-        });
-        waitForItemAnimation();
-    }
-
-    void preparePredictiveLayout() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_linear);
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 100);
-        initActivity(intent);
-        mOrientation = BaseGridView.HORIZONTAL;
-        mNumRows = 1;
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.getItemAnimator().setAddDuration(1000);
-                mGridView.getItemAnimator().setRemoveDuration(1000);
-                mGridView.getItemAnimator().setMoveDuration(1000);
-                mGridView.getItemAnimator().setChangeDuration(1000);
-                mGridView.setSelectedPositionSmooth(50);
-            }
-        });
-        waitForScrollIdle(mVerifyLayout);
-    }
-
-    @Test
-    public void testPredictiveLayoutAdd1() throws Throwable {
-        preparePredictiveLayout();
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mActivity.addItems(51, new int[]{300, 300, 300, 300});
-            }
-        });
-        waitForItemAnimationStart();
-        waitForItemAnimation();
-        assertEquals(50, mGridView.getSelectedPosition());
-        assertEquals(RecyclerView.SCROLL_STATE_IDLE, mGridView.getScrollState());
-    }
-
-    @Test
-    public void testPredictiveLayoutAdd2() throws Throwable {
-        preparePredictiveLayout();
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mActivity.addItems(50, new int[]{300, 300, 300, 300});
-            }
-        });
-        waitForItemAnimationStart();
-        waitForItemAnimation();
-        assertEquals(54, mGridView.getSelectedPosition());
-        assertEquals(RecyclerView.SCROLL_STATE_IDLE, mGridView.getScrollState());
-    }
-
-    @Test
-    public void testPredictiveLayoutRemove1() throws Throwable {
-        preparePredictiveLayout();
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mActivity.removeItems(51, 3);
-            }
-        });
-        waitForItemAnimationStart();
-        waitForItemAnimation();
-        assertEquals(50, mGridView.getSelectedPosition());
-        assertEquals(RecyclerView.SCROLL_STATE_IDLE, mGridView.getScrollState());
-    }
-
-    @Test
-    public void testPredictiveLayoutRemove2() throws Throwable {
-        preparePredictiveLayout();
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mActivity.removeItems(47, 3);
-            }
-        });
-        waitForItemAnimationStart();
-        waitForItemAnimation();
-        assertEquals(47, mGridView.getSelectedPosition());
-        assertEquals(RecyclerView.SCROLL_STATE_IDLE, mGridView.getScrollState());
-    }
-
-    @Test
-    public void testPredictiveLayoutRemove3() throws Throwable {
-        preparePredictiveLayout();
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mActivity.removeItems(0, 51);
-            }
-        });
-        waitForItemAnimationStart();
-        waitForItemAnimation();
-        assertEquals(0, mGridView.getSelectedPosition());
-        assertEquals(RecyclerView.SCROLL_STATE_IDLE, mGridView.getScrollState());
-    }
-
-    @Test
-    public void testPredictiveOnMeasureWrapContent() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.horizontal_linear_wrap_content);
-        int count = 50;
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, count);
-        initActivity(intent);
-        mOrientation = BaseGridView.HORIZONTAL;
-        mNumRows = 1;
-
-        waitForScrollIdle(mVerifyLayout);
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.setHasFixedSize(false);
-            }
-        });
-
-        for (int i = 0; i < 30; i++) {
-            final int oldCount = count;
-            final int newCount = i;
-            mActivityTestRule.runOnUiThread(new Runnable() {
-                @Override
-                public void run() {
-                    if (oldCount > 0) {
-                        mActivity.removeItems(0, oldCount);
-                    }
-                    if (newCount > 0) {
-                        int[] newItems = new int[newCount];
-                        for (int i = 0; i < newCount; i++) {
-                            newItems[i] = 400;
-                        }
-                        mActivity.addItems(0, newItems);
-                    }
-                }
-            });
-            waitForItemAnimationStart();
-            waitForItemAnimation();
-            count = newCount;
-        }
-
-    }
-
-    @Test
-    public void testPredictiveLayoutRemove4() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.horizontal_grid);
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 200);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        initActivity(intent);
-        mOrientation = BaseGridView.HORIZONTAL;
-        mNumRows = 3;
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.setSelectedPositionSmooth(50);
-            }
-        });
-        waitForScrollIdle();
-        performAndWaitForAnimation(new Runnable() {
-            @Override
-            public void run() {
-                mActivity.removeItems(0, 49);
-            }
-        });
-        assertEquals(1, mGridView.getSelectedPosition());
-    }
-
-    @Test
-    public void testPredictiveLayoutRemove5() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.horizontal_grid);
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 200);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, true);
-        initActivity(intent);
-        mOrientation = BaseGridView.HORIZONTAL;
-        mNumRows = 3;
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.setSelectedPositionSmooth(50);
-            }
-        });
-        waitForScrollIdle();
-        performAndWaitForAnimation(new Runnable() {
-            @Override
-            public void run() {
-                mActivity.removeItems(50, 40);
-            }
-        });
-        assertEquals(50, mGridView.getSelectedPosition());
-        scrollToBegin(mVerifyLayout);
-        verifyBeginAligned();
-    }
-
-    void waitOneUiCycle() throws Throwable {
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-            }
-        });
-    }
-
-    @Test
-    public void testDontPruneMovingItem() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_linear);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 2000);
-        initActivity(intent);
-        mOrientation = BaseGridView.HORIZONTAL;
-        mNumRows = 1;
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.getItemAnimator().setMoveDuration(2000);
-                mGridView.setSelectedPosition(50);
-            }
-        });
-        waitForScrollIdle();
-        final ArrayList<RecyclerView.ViewHolder> moveViewHolders = new ArrayList();
-        for (int i = 51;; i++) {
-            RecyclerView.ViewHolder vh = mGridView.findViewHolderForAdapterPosition(i);
-            if (vh == null) {
-                break;
-            }
-            moveViewHolders.add(vh);
-        }
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                // add a lot of items, so we will push everything to right of 51 out side window
-                int[] lots_items = new int[1000];
-                for (int i = 0; i < lots_items.length; i++) {
-                    lots_items[i] = 300;
-                }
-                mActivity.addItems(51, lots_items);
-            }
-        });
-        waitOneUiCycle();
-        // run a scroll pass, the scroll pass should not remove the animating views even they are
-        // outside visible areas.
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.scrollBy(-3, 0);
-            }
-        });
-        waitOneUiCycle();
-        for (int i = 0; i < moveViewHolders.size(); i++) {
-            assertSame(mGridView, moveViewHolders.get(i).itemView.getParent());
-        }
-    }
-
-    @Test
-    public void testMoveItemToTheRight() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_linear);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 2000);
-        initActivity(intent);
-        mOrientation = BaseGridView.HORIZONTAL;
-        mNumRows = 1;
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.getItemAnimator().setAddDuration(2000);
-                mGridView.getItemAnimator().setMoveDuration(2000);
-                mGridView.setSelectedPosition(50);
-            }
-        });
-        waitForScrollIdle();
-        RecyclerView.ViewHolder moveViewHolder = mGridView.findViewHolderForAdapterPosition(51);
-
-        int lastPos = mGridView.getChildAdapterPosition(mGridView.getChildAt(
-                mGridView.getChildCount() - 1));
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mActivity.moveItem(51, 1000, true);
-            }
-        });
-        final ArrayList<View> moveInViewHolders = new ArrayList();
-        waitForItemAnimationStart();
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                for (int i = 0; i < mGridView.getLayoutManager().getChildCount(); i++) {
-                    View v = mGridView.getLayoutManager().getChildAt(i);
-                    if (mGridView.getChildAdapterPosition(v) >= 51) {
-                        moveInViewHolders.add(v);
-                    }
-                }
-            }
-        });
-        waitOneUiCycle();
-        assertTrue("prelayout should layout extra items to slide in",
-                moveInViewHolders.size() > lastPos - 51);
-        // run a scroll pass, the scroll pass should not remove the animating views even they are
-        // outside visible areas.
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.scrollBy(-3, 0);
-            }
-        });
-        waitOneUiCycle();
-        for (int i = 0; i < moveInViewHolders.size(); i++) {
-            assertSame(mGridView, moveInViewHolders.get(i).getParent());
-        }
-        assertSame(mGridView, moveViewHolder.itemView.getParent());
-        assertFalse(moveViewHolder.isRecyclable());
-        waitForItemAnimation();
-        assertNull(moveViewHolder.itemView.getParent());
-        assertTrue(moveViewHolder.isRecyclable());
-    }
-
-    @Test
-    public void testMoveItemToTheLeft() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_linear);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 2000);
-        initActivity(intent);
-        mOrientation = BaseGridView.HORIZONTAL;
-        mNumRows = 1;
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.getItemAnimator().setAddDuration(2000);
-                mGridView.getItemAnimator().setMoveDuration(2000);
-                mGridView.setSelectedPosition(1500);
-            }
-        });
-        waitForScrollIdle();
-        RecyclerView.ViewHolder moveViewHolder = mGridView.findViewHolderForAdapterPosition(1499);
-
-        int firstPos = mGridView.getChildAdapterPosition(mGridView.getChildAt(0));
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mActivity.moveItem(1499, 1, true);
-            }
-        });
-        final ArrayList<View> moveInViewHolders = new ArrayList();
-        waitForItemAnimationStart();
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                for (int i = 0; i < mGridView.getLayoutManager().getChildCount(); i++) {
-                    View v = mGridView.getLayoutManager().getChildAt(i);
-                    if (mGridView.getChildAdapterPosition(v) <= 1499) {
-                        moveInViewHolders.add(v);
-                    }
-                }
-            }
-        });
-        waitOneUiCycle();
-        assertTrue("prelayout should layout extra items to slide in ",
-                moveInViewHolders.size() > 1499 - firstPos);
-        // run a scroll pass, the scroll pass should not remove the animating views even they are
-        // outside visible areas.
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.scrollBy(3, 0);
-            }
-        });
-        waitOneUiCycle();
-        for (int i = 0; i < moveInViewHolders.size(); i++) {
-            assertSame(mGridView, moveInViewHolders.get(i).getParent());
-        }
-        assertSame(mGridView, moveViewHolder.itemView.getParent());
-        assertFalse(moveViewHolder.isRecyclable());
-        waitForItemAnimation();
-        assertNull(moveViewHolder.itemView.getParent());
-        assertTrue(moveViewHolder.isRecyclable());
-    }
-
-    @Test
-    public void testContinuousSwapForward() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.horizontal_linear);
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 200);
-        initActivity(intent);
-        mOrientation = BaseGridView.HORIZONTAL;
-        mNumRows = 1;
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.setSelectedPositionSmooth(150);
-            }
-        });
-        waitForScrollIdle(mVerifyLayout);
-        for (int i = 150; i < 199; i++) {
-            final int swapIndex = i;
-            mActivityTestRule.runOnUiThread(new Runnable() {
-                @Override
-                public void run() {
-                    mActivity.swap(swapIndex, swapIndex + 1);
-                }
-            });
-            Thread.sleep(10);
-        }
-        waitForItemAnimation();
-        assertEquals(199, mGridView.getSelectedPosition());
-        // check if ItemAnimation finishes at aligned positions:
-        int leftEdge = mGridView.getLayoutManager().findViewByPosition(199).getLeft();
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.requestLayout();
-            }
-        });
-        waitForScrollIdle();
-        assertEquals(leftEdge, mGridView.getLayoutManager().findViewByPosition(199).getLeft());
-    }
-
-    @Test
-    public void testContinuousSwapBackward() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.horizontal_linear);
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 200);
-        initActivity(intent);
-        mOrientation = BaseGridView.HORIZONTAL;
-        mNumRows = 1;
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.setSelectedPositionSmooth(50);
-            }
-        });
-        waitForScrollIdle(mVerifyLayout);
-        for (int i = 50; i > 0; i--) {
-            final int swapIndex = i;
-            mActivityTestRule.runOnUiThread(new Runnable() {
-                @Override
-                public void run() {
-                    mActivity.swap(swapIndex, swapIndex - 1);
-                }
-            });
-            Thread.sleep(10);
-        }
-        waitForItemAnimation();
-        assertEquals(0, mGridView.getSelectedPosition());
-        // check if ItemAnimation finishes at aligned positions:
-        int leftEdge = mGridView.getLayoutManager().findViewByPosition(0).getLeft();
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.requestLayout();
-            }
-        });
-        waitForScrollIdle();
-        assertEquals(leftEdge, mGridView.getLayoutManager().findViewByPosition(0).getLeft());
-    }
-
-    @Test
-    public void testScrollAndStuck() throws Throwable {
-        // see b/67370222 fastRelayout() may be stuck.
-        final int numItems = 19;
-        final int[] itemsLength = new int[numItems];
-        for (int i = 0; i < numItems; i++) {
-            itemsLength[i] = 288;
-        }
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.horizontal_linear);
-        intent.putExtra(GridActivity.EXTRA_ITEMS, itemsLength);
-        initActivity(intent);
-        mOrientation = BaseGridView.HORIZONTAL;
-        mNumRows = 1;
-
-        // set left right padding to 112, space between items to be 16.
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                ViewGroup.LayoutParams lp = mGridView.getLayoutParams();
-                lp.width = 1920;
-                mGridView.setLayoutParams(lp);
-                mGridView.setPadding(112, mGridView.getPaddingTop(), 112,
-                        mGridView.getPaddingBottom());
-                mGridView.setItemSpacing(16);
-            }
-        });
-        waitOneUiCycle();
-
-        int scrollPos = 0;
-        while (true) {
-            final View view = mGridView.getChildAt(mGridView.getChildCount() - 1);
-            final int pos = mGridView.getChildViewHolder(view).getAdapterPosition();
-            if (scrollPos != pos) {
-                scrollPos = pos;
-                mActivityTestRule.runOnUiThread(new Runnable() {
-                    @Override
-                    public void run() {
-                        mGridView.smoothScrollToPosition(pos);
-                    }
-                });
-            }
-            // wait until we see 2nd from last:
-            if (pos >= 17) {
-                if (pos == 17) {
-                    // great we can test fastRelayout() bug.
-                    Thread.sleep(50);
-                    mActivityTestRule.runOnUiThread(new Runnable() {
-                        @Override
-                        public void run() {
-                            view.requestLayout();
-                        }
-                    });
-                }
-                break;
-            }
-            Thread.sleep(16);
-        }
-        waitForScrollIdle();
-    }
-
-    @Test
-    public void testSwapAfterScroll() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.horizontal_linear);
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 200);
-        initActivity(intent);
-        mOrientation = BaseGridView.HORIZONTAL;
-        mNumRows = 1;
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.getItemAnimator().setMoveDuration(1000);
-                mGridView.setSelectedPositionSmooth(150);
-            }
-        });
-        waitForScrollIdle();
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.setSelectedPositionSmooth(151);
-            }
-        });
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                // we want to swap and select new target which is at 150 before swap
-                mGridView.setSelectedPositionSmooth(150);
-                mActivity.swap(150, 151);
-            }
-        });
-        waitForItemAnimation();
-        waitForScrollIdle();
-        assertEquals(151, mGridView.getSelectedPosition());
-        // check if ItemAnimation finishes at aligned positions:
-        int leftEdge = mGridView.getLayoutManager().findViewByPosition(151).getLeft();
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.requestLayout();
-            }
-        });
-        waitForScrollIdle();
-        assertEquals(leftEdge, mGridView.getLayoutManager().findViewByPosition(151).getLeft());
-    }
-
-    @Test
-    public void testItemMovedHorizontal() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.horizontal_grid);
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 200);
-        initActivity(intent);
-        mOrientation = BaseGridView.HORIZONTAL;
-        mNumRows = 3;
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.setSelectedPositionSmooth(150);
-            }
-        });
-        waitForScrollIdle(mVerifyLayout);
-        performAndWaitForAnimation(new Runnable() {
-            @Override
-            public void run() {
-                mActivity.swap(150, 152);
-            }
-        });
-        mActivityTestRule.runOnUiThread(mVerifyLayout);
-
-        scrollToBegin(mVerifyLayout);
-
-        verifyBeginAligned();
-    }
-
-    @Test
-    public void testItemMovedHorizontalRtl() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.horizontal_linear_rtl);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        intent.putExtra(GridActivity.EXTRA_ITEMS, new int[] {40, 40, 40});
-        initActivity(intent);
-        mOrientation = BaseGridView.HORIZONTAL;
-        mNumRows = 1;
-
-        performAndWaitForAnimation(new Runnable() {
-            @Override
-            public void run() {
-                mActivity.moveItem(0, 1, true);
-            }
-        });
-        assertEquals(mGridView.getWidth() - mGridView.getPaddingRight(),
-                mGridView.findViewHolderForAdapterPosition(0).itemView.getRight());
-    }
-
-    @Test
-    public void testScrollSecondaryCannotScroll() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.horizontal_grid);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 2000);
-        initActivity(intent);
-        mOrientation = BaseGridView.HORIZONTAL;
-        mNumRows = 3;
-        final int topPadding = 2;
-        final int bottomPadding = 2;
-        final int height = mGridView.getHeight();
-        final int spacing = 2;
-        final int rowHeight = (height - topPadding - bottomPadding) / 4 - spacing;
-        final HorizontalGridView horizontalGridView = (HorizontalGridView) mGridView;
-
-        startWaitLayout();
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                horizontalGridView.setPadding(0, topPadding, 0, bottomPadding);
-                horizontalGridView.setItemSpacing(spacing);
-                horizontalGridView.setNumRows(mNumRows);
-                horizontalGridView.setRowHeight(rowHeight);
-            }
-        });
-        waitForLayout();
-        // navigate vertically in first column, first row should always be aligned to top padding
-        for (int i = 0; i < 3; i++) {
-            setSelectedPosition(i);
-            assertEquals(topPadding, mGridView.findViewHolderForAdapterPosition(0).itemView
-                    .getTop());
-        }
-        // navigate vertically in 100th column, first row should always be aligned to top padding
-        for (int i = 300; i < 301; i++) {
-            setSelectedPosition(i);
-            assertEquals(topPadding, mGridView.findViewHolderForAdapterPosition(300).itemView
-                    .getTop());
-        }
-    }
-
-    @Test
-    public void testScrollSecondaryNeedScroll() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.horizontal_grid);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 2000);
-        initActivity(intent);
-        mOrientation = BaseGridView.HORIZONTAL;
-        // test a lot of rows so we have to scroll vertically to reach
-        mNumRows = 9;
-        final int topPadding = 2;
-        final int bottomPadding = 2;
-        final int height = mGridView.getHeight();
-        final int spacing = 2;
-        final int rowHeight = (height - topPadding - bottomPadding) / 4 - spacing;
-        final HorizontalGridView horizontalGridView = (HorizontalGridView) mGridView;
-
-        startWaitLayout();
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                horizontalGridView.setPadding(0, topPadding, 0, bottomPadding);
-                horizontalGridView.setItemSpacing(spacing);
-                horizontalGridView.setNumRows(mNumRows);
-                horizontalGridView.setRowHeight(rowHeight);
-            }
-        });
-        waitForLayout();
-        View view;
-        // first row should be aligned to top padding
-        setSelectedPosition(0);
-        assertEquals(topPadding, mGridView.findViewHolderForAdapterPosition(0).itemView.getTop());
-        // middle row should be aligned to keyline (1/2 of screen height)
-        setSelectedPosition(4);
-        view = mGridView.findViewHolderForAdapterPosition(4).itemView;
-        assertEquals(height / 2, (view.getTop() + view.getBottom()) / 2);
-        // last row should be aligned to bottom padding.
-        setSelectedPosition(8);
-        view = mGridView.findViewHolderForAdapterPosition(8).itemView;
-        assertEquals(height, view.getTop() + rowHeight + bottomPadding);
-        setSelectedPositionSmooth(4);
-        waitForScrollIdle();
-        // middle row should be aligned to keyline (1/2 of screen height)
-        setSelectedPosition(4);
-        view = mGridView.findViewHolderForAdapterPosition(4).itemView;
-        assertEquals(height / 2, (view.getTop() + view.getBottom()) / 2);
-        // first row should be aligned to top padding
-        setSelectedPositionSmooth(0);
-        waitForScrollIdle();
-        assertEquals(topPadding, mGridView.findViewHolderForAdapterPosition(0).itemView.getTop());
-    }
-
-    @Test
-    public void testItemMovedVertical() throws Throwable {
-
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.vertical_grid);
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 200);
-        initActivity(intent);
-        mOrientation = BaseGridView.VERTICAL;
-        mNumRows = 3;
-
-        mGridView.setSelectedPositionSmooth(150);
-        waitForScrollIdle(mVerifyLayout);
-        performAndWaitForAnimation(new Runnable() {
-            @Override
-            public void run() {
-                mActivity.swap(150, 152);
-            }
-        });
-        mActivityTestRule.runOnUiThread(mVerifyLayout);
-
-        scrollToEnd(mVerifyLayout);
-        scrollToBegin(mVerifyLayout);
-
-        verifyBeginAligned();
-    }
-
-    @Test
-    public void testAddLastItemHorizontal() throws Throwable {
-
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.horizontal_linear);
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 50);
-        initActivity(intent);
-        mOrientation = BaseGridView.HORIZONTAL;
-        mNumRows = 1;
-
-        mActivityTestRule.runOnUiThread(
-                new Runnable() {
-                    @Override
-                    public void run() {
-                        mGridView.setSelectedPositionSmooth(49);
-                    }
-                }
-        );
-        waitForScrollIdle(mVerifyLayout);
-        performAndWaitForAnimation(new Runnable() {
-            @Override
-            public void run() {
-                mActivity.addItems(50, new int[]{150});
-            }
-        });
-
-        // assert new added item aligned to right edge
-        assertEquals(mGridView.getWidth() - mGridView.getPaddingRight(),
-                mGridView.getLayoutManager().findViewByPosition(50).getRight());
-    }
-
-    @Test
-    public void testAddMultipleLastItemsHorizontal() throws Throwable {
-
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.horizontal_linear);
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 50);
-        initActivity(intent);
-        mOrientation = BaseGridView.HORIZONTAL;
-        mNumRows = 1;
-
-        mActivityTestRule.runOnUiThread(
-                new Runnable() {
-                    @Override
-                    public void run() {
-                        mGridView.setWindowAlignment(BaseGridView.WINDOW_ALIGN_BOTH_EDGE);
-                        mGridView.setWindowAlignmentOffsetPercent(50);
-                        mGridView.setSelectedPositionSmooth(49);
-                    }
-                }
-        );
-        waitForScrollIdle(mVerifyLayout);
-        performAndWaitForAnimation(new Runnable() {
-            @Override
-            public void run() {
-                mActivity.addItems(50, new int[]{150, 150, 150, 150, 150, 150, 150, 150, 150,
-                        150, 150, 150, 150, 150});
-            }
-        });
-
-        // The focused item will be at center of window
-        View view = mGridView.getLayoutManager().findViewByPosition(49);
-        assertEquals(mGridView.getWidth() / 2, (view.getLeft() + view.getRight()) / 2);
-    }
-
-    @Test
-    public void testItemAddRemoveHorizontal() throws Throwable {
-
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.horizontal_grid);
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 200);
-        initActivity(intent);
-        mOrientation = BaseGridView.HORIZONTAL;
-        mNumRows = 3;
-
-        scrollToEnd(mVerifyLayout);
-        int[] endEdges = getEndEdges();
-
-        mGridView.setSelectedPositionSmooth(150);
-        waitForScrollIdle(mVerifyLayout);
-        performAndWaitForAnimation(new Runnable() {
-            @Override
-            public void run() {
-                mRemovedItems = mActivity.removeItems(151, 4);
-            }
-        });
-
-        scrollToEnd(mVerifyLayout);
-        mGridView.setSelectedPositionSmooth(150);
-        waitForScrollIdle(mVerifyLayout);
-
-        performAndWaitForAnimation(new Runnable() {
-            @Override
-            public void run() {
-                mActivity.addItems(151, mRemovedItems);
-            }
-        });
-        scrollToEnd(mVerifyLayout);
-
-        // we should get same aligned end edges
-        int[] endEdges2 = getEndEdges();
-        verifyEdgesSame(endEdges, endEdges2);
-
-        scrollToBegin(mVerifyLayout);
-        verifyBeginAligned();
-    }
-
-    @Test
-    public void testSetSelectedPositionDetached() throws Throwable {
-
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.horizontal_linear);
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 50);
-        initActivity(intent);
-        mOrientation = BaseGridView.HORIZONTAL;
-        mNumRows = 1;
-
-        final int focusToIndex = 49;
-        final ViewGroup parent = (ViewGroup) mGridView.getParent();
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                parent.removeView(mGridView);
-            }
-        });
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.setSelectedPositionSmooth(focusToIndex);
-            }
-        });
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                parent.addView(mGridView);
-                mGridView.requestFocus();
-            }
-        });
-        waitForScrollIdle();
-        assertEquals(mGridView.getSelectedPosition(), focusToIndex);
-        assertTrue(mGridView.getLayoutManager().findViewByPosition(focusToIndex).hasFocus());
-
-        final int focusToIndex2 = 0;
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                parent.removeView(mGridView);
-            }
-        });
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.setSelectedPosition(focusToIndex2);
-            }
-        });
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                parent.addView(mGridView);
-                mGridView.requestFocus();
-            }
-        });
-        assertEquals(mGridView.getSelectedPosition(), focusToIndex2);
-        waitForScrollIdle();
-        assertTrue(mGridView.getLayoutManager().findViewByPosition(focusToIndex2).hasFocus());
-    }
-
-    @Test
-    public void testBug22209986() throws Throwable {
-
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.horizontal_linear);
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 50);
-        initActivity(intent);
-        mOrientation = BaseGridView.HORIZONTAL;
-        mNumRows = 1;
-
-        final int focusToIndex = mGridView.getChildCount() - 1;
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.setSelectedPositionSmooth(focusToIndex);
-            }
-        });
-
-        waitForScrollIdle();
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.setSelectedPositionSmooth(focusToIndex + 1);
-            }
-        });
-        // let the scroll running for a while and requestLayout during scroll
-        Thread.sleep(80);
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                assertEquals(mGridView.getScrollState(), BaseGridView.SCROLL_STATE_SETTLING);
-                mGridView.requestLayout();
-            }
-        });
-        waitForScrollIdle();
-
-        int leftEdge = mGridView.getLayoutManager().findViewByPosition(focusToIndex).getLeft();
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.requestLayout();
-            }
-        });
-        waitForScrollIdle();
-        assertEquals(leftEdge,
-                mGridView.getLayoutManager().findViewByPosition(focusToIndex).getLeft());
-    }
-
-    void testScrollAndRemove(int[] itemsLength, int numItems) throws Throwable {
-
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.horizontal_linear);
-        if (itemsLength != null) {
-            intent.putExtra(GridActivity.EXTRA_ITEMS, itemsLength);
-        } else {
-            intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems);
-        }
-        initActivity(intent);
-        mOrientation = BaseGridView.HORIZONTAL;
-        mNumRows = 1;
-
-        final int focusToIndex = mGridView.getChildCount() - 1;
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.setSelectedPositionSmooth(focusToIndex);
-            }
-        });
-
-        performAndWaitForAnimation(new Runnable() {
-            @Override
-            public void run() {
-                mActivity.removeItems(focusToIndex, 1);
-            }
-        });
-
-        waitOneUiCycle();
-        waitForScrollIdle();
-        int leftEdge = mGridView.getLayoutManager().findViewByPosition(focusToIndex).getLeft();
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.requestLayout();
-            }
-        });
-        waitForScrollIdle();
-        assertEquals(leftEdge,
-                mGridView.getLayoutManager().findViewByPosition(focusToIndex).getLeft(), DELTA);
-    }
-
-    @Test
-    public void testScrollAndRemove() throws Throwable {
-        // test random lengths for 50 items
-        testScrollAndRemove(null, 50);
-    }
-
-    /**
-     * This test verifies if scroll limits are ignored when onLayoutChildren compensate remaining
-     * scroll distance. b/64931938
-     * In the test, second child is long, other children are short.
-     * Test scrolls to the long child, and when scrolling, remove the long child. We made it long
-     * to have enough remaining scroll distance when the layout pass kicks in.
-     * The onLayoutChildren() would compensate the remaining scroll distance, moving all items
-     * toward right, which will make the first item's left edge bigger than left padding,
-     * which would violate the "scroll limit of left" in a regular scroll case, but
-     * in layout pass, we still honor that scroll request, ignoring the scroll limit.
-     */
-    @Test
-    public void testScrollAndRemoveSample1() throws Throwable {
-        DisplayMetrics dm = InstrumentationRegistry.getInstrumentation().getTargetContext()
-                .getResources().getDisplayMetrics();
-        // screen width for long item and 4DP for other items
-        int longItemLength = dm.widthPixels;
-        int shortItemLength = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4, dm);
-        int[] items = new int[1000];
-        for (int i = 0; i < items.length; i++) {
-            items[i] = shortItemLength;
-        }
-        items[1] = longItemLength;
-        testScrollAndRemove(items, 0);
-    }
-
-    @Test
-    public void testScrollAndInsert() throws Throwable {
-
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.vertical_grid);
-        int[] items = new int[1000];
-        for (int i = 0; i < items.length; i++) {
-            items[i] = 300 + (int)(Math.random() * 100);
-        }
-        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, true);
-        mOrientation = BaseGridView.VERTICAL;
-        mNumRows = 3;
-
-        initActivity(intent);
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.setSelectedPositionSmooth(150);
-            }
-        });
-        waitForScrollIdle(mVerifyLayout);
-
-        View view =  mGridView.getChildAt(mGridView.getChildCount() - 1);
-        final int focusToIndex = mGridView.getChildAdapterPosition(view);
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.setSelectedPositionSmooth(focusToIndex);
-            }
-        });
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                int[] newItems = new int[]{300, 300, 300};
-                mActivity.addItems(0, newItems);
-            }
-        });
-        waitForScrollIdle();
-        int topEdge = mGridView.getLayoutManager().findViewByPosition(focusToIndex).getTop();
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.requestLayout();
-            }
-        });
-        waitForScrollIdle();
-        assertEquals(topEdge,
-                mGridView.getLayoutManager().findViewByPosition(focusToIndex).getTop());
-    }
-
-    @Test
-    public void testScrollAndInsertBeforeVisibleItem() throws Throwable {
-
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.vertical_grid);
-        int[] items = new int[1000];
-        for (int i = 0; i < items.length; i++) {
-            items[i] = 300 + (int)(Math.random() * 100);
-        }
-        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, true);
-        mOrientation = BaseGridView.VERTICAL;
-        mNumRows = 3;
-
-        initActivity(intent);
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.setSelectedPositionSmooth(150);
-            }
-        });
-        waitForScrollIdle(mVerifyLayout);
-
-        View view =  mGridView.getChildAt(mGridView.getChildCount() - 1);
-        final int focusToIndex = mGridView.getChildAdapterPosition(view);
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.setSelectedPositionSmooth(focusToIndex);
-            }
-        });
-
-        performAndWaitForAnimation(new Runnable() {
-            @Override
-            public void run() {
-                int[] newItems = new int[]{300, 300, 300};
-                mActivity.addItems(focusToIndex, newItems);
-            }
-        });
-    }
-
-    @Test
-    public void testSmoothScrollAndRemove() throws Throwable {
-
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.horizontal_linear);
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 300);
-        initActivity(intent);
-        mOrientation = BaseGridView.HORIZONTAL;
-        mNumRows = 1;
-
-        final int focusToIndex = 200;
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.setSelectedPositionSmooth(focusToIndex);
-            }
-        });
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mActivity.removeItems(focusToIndex, 1);
-            }
-        });
-
-        assertTrue("removing the index of not attached child should not affect smooth scroller",
-                mGridView.getLayoutManager().isSmoothScrolling());
-        waitForScrollIdle();
-        int leftEdge = mGridView.getLayoutManager().findViewByPosition(focusToIndex).getLeft();
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.requestLayout();
-            }
-        });
-        waitForScrollIdle();
-        assertEquals(leftEdge,
-                mGridView.getLayoutManager().findViewByPosition(focusToIndex).getLeft());
-    }
-
-    @Test
-    public void testSmoothScrollAndRemove2() throws Throwable {
-
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.horizontal_linear);
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 300);
-        initActivity(intent);
-        mOrientation = BaseGridView.HORIZONTAL;
-        mNumRows = 1;
-
-        final int focusToIndex = 200;
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.setSelectedPositionSmooth(focusToIndex);
-            }
-        });
-
-        startWaitLayout();
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                final int removeIndex = mGridView.getChildViewHolder(
-                        mGridView.getChildAt(mGridView.getChildCount() - 1)).getAdapterPosition();
-                mActivity.removeItems(removeIndex, 1);
-            }
-        });
-        waitForLayout();
-
-        assertTrue("removing the index of attached child should not kill smooth scroller",
-                mGridView.getLayoutManager().isSmoothScrolling());
-        waitForItemAnimation();
-        waitForScrollIdle();
-        int leftEdge = mGridView.getLayoutManager().findViewByPosition(focusToIndex).getLeft();
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.requestLayout();
-            }
-        });
-        waitForScrollIdle();
-        assertEquals(leftEdge,
-                mGridView.getLayoutManager().findViewByPosition(focusToIndex).getLeft());
-    }
-
-    @Test
-    public void testPendingSmoothScrollAndRemove() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.vertical_linear);
-        intent.putExtra(GridActivity.EXTRA_REQUEST_FOCUS_ONLAYOUT, true);
-        int[] items = new int[100];
-        for (int i = 0; i < items.length; i++) {
-            items[i] = 630 + (int)(Math.random() * 100);
-        }
-        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, true);
-        mOrientation = BaseGridView.VERTICAL;
-        mNumRows = 1;
-
-        initActivity(intent);
-
-        mGridView.setSelectedPositionSmooth(0);
-        waitForScrollIdle(mVerifyLayout);
-        assertTrue(mGridView.getChildAt(0).hasFocus());
-
-        // Pressing lots of key to make sure smooth scroller is running
-        mGridView.mLayoutManager.mMaxPendingMoves = 100;
-        for (int i = 0; i < 100; i++) {
-            sendKey(KeyEvent.KEYCODE_DPAD_DOWN);
-        }
-
-        assertTrue(mGridView.getLayoutManager().isSmoothScrolling());
-        startWaitLayout();
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                final int removeIndex = mGridView.getChildViewHolder(
-                        mGridView.getChildAt(mGridView.getChildCount() - 1)).getAdapterPosition();
-                mActivity.removeItems(removeIndex, 1);
-            }
-        });
-        waitForLayout();
-
-        assertTrue("removing the index of attached child should not kill smooth scroller",
-                mGridView.getLayoutManager().isSmoothScrolling());
-
-        waitForItemAnimation();
-        waitForScrollIdle();
-        int focusIndex = mGridView.getSelectedPosition();
-        int topEdge = mGridView.getLayoutManager().findViewByPosition(focusIndex).getTop();
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.requestLayout();
-            }
-        });
-        waitForScrollIdle();
-        assertEquals(topEdge,
-                mGridView.getLayoutManager().findViewByPosition(focusIndex).getTop());
-    }
-
-    @Test
-    public void testFocusToFirstItem() throws Throwable {
-
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.horizontal_grid);
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 200);
-        initActivity(intent);
-        mOrientation = BaseGridView.HORIZONTAL;
-        mNumRows = 3;
-
-        performAndWaitForAnimation(new Runnable() {
-            @Override
-            public void run() {
-                mRemovedItems = mActivity.removeItems(0, 200);
-            }
-        });
-
-        humanDelay(500);
-        performAndWaitForAnimation(new Runnable() {
-            @Override
-            public void run() {
-                mActivity.addItems(0, mRemovedItems);
-            }
-        });
-
-        humanDelay(500);
-        assertTrue(mGridView.getLayoutManager().findViewByPosition(0).hasFocus());
-
-        changeArraySize(0);
-
-        changeArraySize(200);
-        assertTrue(mGridView.getLayoutManager().findViewByPosition(0).hasFocus());
-    }
-
-    @Test
-    public void testNonFocusableHorizontal() throws Throwable {
-        final int numItems = 200;
-        final int startPos = 45;
-        final int skips = 20;
-        final int numColumns = 3;
-        final int endPos = startPos + numColumns * (skips + 1);
-
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.horizontal_grid);
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        mOrientation = BaseGridView.HORIZONTAL;
-        mNumRows = numColumns;
-        boolean[] focusable = new boolean[numItems];
-        for (int i = 0; i < focusable.length; i++) {
-            focusable[i] = true;
-        }
-        for (int i = startPos + mNumRows, j = 0; j < skips; i += mNumRows, j++) {
-            focusable[i] = false;
-        }
-        intent.putExtra(GridActivity.EXTRA_ITEMS_FOCUSABLE, focusable);
-        initActivity(intent);
-
-        mGridView.setSelectedPositionSmooth(startPos);
-        waitForScrollIdle(mVerifyLayout);
-
-        if (mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL) {
-            sendKey(KeyEvent.KEYCODE_DPAD_LEFT);
-        } else {
-            sendKey(KeyEvent.KEYCODE_DPAD_RIGHT);
-        }
-        waitForScrollIdle(mVerifyLayout);
-        assertEquals(endPos, mGridView.getSelectedPosition());
-
-        if (mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL) {
-            sendKey(KeyEvent.KEYCODE_DPAD_RIGHT);
-        } else {
-            sendKey(KeyEvent.KEYCODE_DPAD_LEFT);
-        }
-        waitForScrollIdle(mVerifyLayout);
-        assertEquals(startPos, mGridView.getSelectedPosition());
-
-    }
-
-    @Test
-    public void testNoInitialFocusable() throws Throwable {
-
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.horizontal_linear);
-        final int numItems = 100;
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        mOrientation = BaseGridView.HORIZONTAL;
-        mNumRows = 1;
-        boolean[] focusable = new boolean[numItems];
-        final int firstFocusableIndex = 10;
-        for (int i = 0; i < firstFocusableIndex; i++) {
-            focusable[i] = false;
-        }
-        for (int i = firstFocusableIndex; i < focusable.length; i++) {
-            focusable[i] = true;
-        }
-        intent.putExtra(GridActivity.EXTRA_ITEMS_FOCUSABLE, focusable);
-        initActivity(intent);
-        assertTrue(mGridView.isFocused());
-
-        if (mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL) {
-            sendKey(KeyEvent.KEYCODE_DPAD_LEFT);
-        } else {
-            sendKey(KeyEvent.KEYCODE_DPAD_RIGHT);
-        }
-        waitForScrollIdle(mVerifyLayout);
-        assertEquals(firstFocusableIndex, mGridView.getSelectedPosition());
-        assertTrue(mGridView.getLayoutManager().findViewByPosition(firstFocusableIndex).hasFocus());
-    }
-
-    @Test
-    public void testFocusOutOfEmptyListView() throws Throwable {
-
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.horizontal_linear);
-        final int numItems = 100;
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        mOrientation = BaseGridView.HORIZONTAL;
-        mNumRows = 1;
-        initActivity(intent);
-
-        final View horizontalGridView = new HorizontalGridViewEx(mGridView.getContext());
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                horizontalGridView.setFocusable(true);
-                horizontalGridView.setFocusableInTouchMode(true);
-                horizontalGridView.setLayoutParams(new ViewGroup.LayoutParams(100, 100));
-                ((ViewGroup) mGridView.getParent()).addView(horizontalGridView, 0);
-                horizontalGridView.requestFocus();
-            }
-        });
-
-        assertTrue(horizontalGridView.isFocused());
-
-        sendKey(KeyEvent.KEYCODE_DPAD_DOWN);
-
-        assertTrue(mGridView.hasFocus());
-    }
-
-    @Test
-    public void testTransferFocusToChildWhenGainFocus() throws Throwable {
-
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.horizontal_linear);
-        final int numItems = 100;
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        mOrientation = BaseGridView.HORIZONTAL;
-        mNumRows = 1;
-        boolean[] focusable = new boolean[numItems];
-        final int firstFocusableIndex = 1;
-        for (int i = 0; i < firstFocusableIndex; i++) {
-            focusable[i] = false;
-        }
-        for (int i = firstFocusableIndex; i < focusable.length; i++) {
-            focusable[i] = true;
-        }
-        intent.putExtra(GridActivity.EXTRA_ITEMS_FOCUSABLE, focusable);
-        initActivity(intent);
-
-        assertEquals(firstFocusableIndex, mGridView.getSelectedPosition());
-        assertTrue(mGridView.getLayoutManager().findViewByPosition(firstFocusableIndex).hasFocus());
-    }
-
-    @Test
-    public void testFocusFromSecondChild() throws Throwable {
-
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.horizontal_linear);
-        final int numItems = 100;
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        mOrientation = BaseGridView.HORIZONTAL;
-        mNumRows = 1;
-        boolean[] focusable = new boolean[numItems];
-        for (int i = 0; i < focusable.length; i++) {
-            focusable[i] = false;
-        }
-        intent.putExtra(GridActivity.EXTRA_ITEMS_FOCUSABLE, focusable);
-        initActivity(intent);
-
-        // switching Adapter to cause a full rebind,  test if it will focus to second item.
-        performAndWaitForAnimation(new Runnable() {
-            @Override
-            public void run() {
-                mActivity.mNumItems = numItems;
-                mActivity.mItemFocusables[1] = true;
-                mActivity.rebindToNewAdapter();
-            }
-        });
-        assertTrue(mGridView.findViewHolderForAdapterPosition(1).itemView.hasFocus());
-    }
-
-    @Test
-    public void removeFocusableItemAndFocusableRecyclerViewGetsFocus() throws Throwable {
-        final int numItems = 100;
-        final int numColumns = 3;
-        final int focusableIndex = 2;
-
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.vertical_grid);
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        mOrientation = BaseGridView.VERTICAL;
-        mNumRows = numColumns;
-        boolean[] focusable = new boolean[numItems];
-        for (int i = 0; i < focusable.length; i++) {
-            focusable[i] = false;
-        }
-        focusable[focusableIndex] = true;
-        intent.putExtra(GridActivity.EXTRA_ITEMS_FOCUSABLE, focusable);
-        initActivity(intent);
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.setSelectedPositionSmooth(focusableIndex);
-            }
-        });
-        waitForScrollIdle(mVerifyLayout);
-        assertEquals(focusableIndex, mGridView.getSelectedPosition());
-
-        performAndWaitForAnimation(new Runnable() {
-            @Override
-            public void run() {
-                mActivity.removeItems(focusableIndex, 1);
-            }
-        });
-        assertTrue(dumpGridView(mGridView), mGridView.isFocused());
-    }
-
-    @Test
-    public void removeFocusableItemAndUnFocusableRecyclerViewLosesFocus() throws Throwable {
-        final int numItems = 100;
-        final int numColumns = 3;
-        final int focusableIndex = 2;
-
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.vertical_grid);
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        mOrientation = BaseGridView.VERTICAL;
-        mNumRows = numColumns;
-        boolean[] focusable = new boolean[numItems];
-        for (int i = 0; i < focusable.length; i++) {
-            focusable[i] = false;
-        }
-        focusable[focusableIndex] = true;
-        intent.putExtra(GridActivity.EXTRA_ITEMS_FOCUSABLE, focusable);
-        initActivity(intent);
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.setFocusableInTouchMode(false);
-                mGridView.setFocusable(false);
-                mGridView.setSelectedPositionSmooth(focusableIndex);
-            }
-        });
-        waitForScrollIdle(mVerifyLayout);
-        assertEquals(focusableIndex, mGridView.getSelectedPosition());
-
-        performAndWaitForAnimation(new Runnable() {
-            @Override
-            public void run() {
-                mActivity.removeItems(focusableIndex, 1);
-            }
-        });
-        assertFalse(dumpGridView(mGridView), mGridView.hasFocus());
-    }
-
-    @Test
-    public void testNonFocusableVertical() throws Throwable {
-        final int numItems = 200;
-        final int startPos = 44;
-        final int skips = 20;
-        final int numColumns = 3;
-        final int endPos = startPos + numColumns * (skips + 1);
-
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.vertical_grid);
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        mOrientation = BaseGridView.VERTICAL;
-        mNumRows = numColumns;
-        boolean[] focusable = new boolean[numItems];
-        for (int i = 0; i < focusable.length; i++) {
-            focusable[i] = true;
-        }
-        for (int i = startPos + mNumRows, j = 0; j < skips; i += mNumRows, j++) {
-            focusable[i] = false;
-        }
-        intent.putExtra(GridActivity.EXTRA_ITEMS_FOCUSABLE, focusable);
-        initActivity(intent);
-
-        mGridView.setSelectedPositionSmooth(startPos);
-        waitForScrollIdle(mVerifyLayout);
-
-        sendKey(KeyEvent.KEYCODE_DPAD_DOWN);
-        waitForScrollIdle(mVerifyLayout);
-        assertEquals(endPos, mGridView.getSelectedPosition());
-
-        sendKey(KeyEvent.KEYCODE_DPAD_UP);
-        waitForScrollIdle(mVerifyLayout);
-        assertEquals(startPos, mGridView.getSelectedPosition());
-
-    }
-
-    @Test
-    public void testLtrFocusOutStartDisabled() throws Throwable {
-        final int numItems = 200;
-
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_grid_ltr);
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        mOrientation = BaseGridView.VERTICAL;
-        mNumRows = 1;
-        initActivity(intent);
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.requestFocus();
-                mGridView.setSelectedPositionSmooth(0);
-            }
-        });
-        waitForScrollIdle(mVerifyLayout);
-
-        sendKey(KeyEvent.KEYCODE_DPAD_LEFT);
-        waitForScrollIdle(mVerifyLayout);
-        assertTrue(mGridView.hasFocus());
-    }
-
-    @Test
-    public void testRtlFocusOutStartDisabled() throws Throwable {
-        final int numItems = 200;
-
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_grid_rtl);
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        mOrientation = BaseGridView.VERTICAL;
-        mNumRows = 1;
-        initActivity(intent);
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.requestFocus();
-                mGridView.setSelectedPositionSmooth(0);
-            }
-        });
-        waitForScrollIdle(mVerifyLayout);
-
-        sendKey(KeyEvent.KEYCODE_DPAD_RIGHT);
-        waitForScrollIdle(mVerifyLayout);
-        assertTrue(mGridView.hasFocus());
-    }
-
-    @Test
-    public void testTransferFocusable() throws Throwable {
-        final int numItems = 200;
-        final int numColumns = 3;
-        final int startPos = 1;
-
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.horizontal_grid);
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        mOrientation = BaseGridView.HORIZONTAL;
-        mNumRows = numColumns;
-        boolean[] focusable = new boolean[numItems];
-        for (int i = 0; i < focusable.length; i++) {
-            focusable[i] = true;
-        }
-        for (int i = 0; i < startPos; i++) {
-            focusable[i] = false;
-        }
-        intent.putExtra(GridActivity.EXTRA_ITEMS_FOCUSABLE, focusable);
-        initActivity(intent);
-
-        changeArraySize(0);
-        assertTrue(mGridView.isFocused());
-
-        changeArraySize(numItems);
-        assertTrue(mGridView.getLayoutManager().findViewByPosition(startPos).hasFocus());
-    }
-
-    @Test
-    public void testTransferFocusable2() throws Throwable {
-        final int numItems = 200;
-        final int numColumns = 3;
-        final int startPos = 3; // make sure view at startPos is in visible area.
-
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.horizontal_grid);
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, true);
-        mOrientation = BaseGridView.HORIZONTAL;
-        mNumRows = numColumns;
-        boolean[] focusable = new boolean[numItems];
-        for (int i = 0; i < focusable.length; i++) {
-            focusable[i] = true;
-        }
-        for (int i = 0; i < startPos; i++) {
-            focusable[i] = false;
-        }
-        intent.putExtra(GridActivity.EXTRA_ITEMS_FOCUSABLE, focusable);
-        initActivity(intent);
-
-        assertTrue(mGridView.getLayoutManager().findViewByPosition(startPos).hasFocus());
-
-        changeArraySize(0);
-        assertTrue(mGridView.isFocused());
-
-        changeArraySize(numItems);
-        assertTrue(mGridView.getLayoutManager().findViewByPosition(startPos).hasFocus());
-    }
-
-    @Test
-    public void testNonFocusableLoseInFastLayout() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.vertical_linear);
-        int[] items = new int[300];
-        for (int i = 0; i < items.length; i++) {
-            items[i] = 480;
-        }
-        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        intent.putExtra(GridActivity.EXTRA_REQUEST_LAYOUT_ONFOCUS, true);
-        mOrientation = BaseGridView.VERTICAL;
-        mNumRows = 1;
-        int pressDown = 15;
-
-        initActivity(intent);
-
-        mGridView.setSelectedPositionSmooth(0);
-        waitForScrollIdle(mVerifyLayout);
-
-        for (int i = 0; i < pressDown; i++) {
-            sendKey(KeyEvent.KEYCODE_DPAD_DOWN);
-        }
-        waitForScrollIdle(mVerifyLayout);
-        assertFalse(mGridView.isFocused());
-
-    }
-
-    @Test
-    public void testFocusableViewAvailable() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.vertical_linear);
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 0);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        intent.putExtra(GridActivity.EXTRA_ITEMS_FOCUSABLE,
-                new boolean[]{false, false, true, false, false});
-        mOrientation = BaseGridView.VERTICAL;
-        mNumRows = 1;
-
-        initActivity(intent);
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                // RecyclerView does not respect focusable and focusableInTouchMode flag, so
-                // set flags in code.
-                mGridView.setFocusableInTouchMode(false);
-                mGridView.setFocusable(false);
-            }
-        });
-
-        assertFalse(mGridView.isFocused());
-
-        final boolean[] scrolled = new boolean[]{false};
-        mGridView.addOnScrollListener(new RecyclerView.OnScrollListener() {
-            @Override
-            public void onScrolled(RecyclerView recyclerView, int dx, int dy){
-                if (dy > 0) {
-                    scrolled[0] = true;
-                }
-            }
-        });
-        performAndWaitForAnimation(new Runnable() {
-            @Override
-            public void run() {
-                mActivity.addItems(0, new int[]{200, 300, 500, 500, 200});
-            }
-        });
-        waitForScrollIdle(mVerifyLayout);
-
-        assertFalse("GridView should not be scrolled", scrolled[0]);
-        assertTrue(mGridView.getLayoutManager().findViewByPosition(2).hasFocus());
-
-    }
-
-    @Test
-    public void testSetSelectionWithDelta() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.vertical_linear);
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 300);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        mOrientation = BaseGridView.VERTICAL;
-        mNumRows = 1;
-
-        initActivity(intent);
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.setSelectedPositionSmooth(3);
-            }
-        });
-        waitForScrollIdle(mVerifyLayout);
-        int top1 = mGridView.getLayoutManager().findViewByPosition(3).getTop();
-
-        humanDelay(1000);
-
-        // scroll to position with delta
-        setSelectedPosition(3, 100);
-        int top2 = mGridView.getLayoutManager().findViewByPosition(3).getTop();
-        assertEquals(top1 - 100, top2);
-
-        // scroll to same position without delta, it will be reset
-        setSelectedPosition(3, 0);
-        int top3 = mGridView.getLayoutManager().findViewByPosition(3).getTop();
-        assertEquals(top1, top3);
-
-        // scroll invisible item after last visible item
-        final int lastVisiblePos = ((GridLayoutManager)mGridView.getLayoutManager())
-                .mGrid.getLastVisibleIndex();
-        setSelectedPosition(lastVisiblePos + 1, 100);
-        int top4 = mGridView.getLayoutManager().findViewByPosition(lastVisiblePos + 1).getTop();
-        assertEquals(top1 - 100, top4);
-
-        // scroll invisible item before first visible item
-        final int firstVisiblePos = ((GridLayoutManager)mGridView.getLayoutManager())
-                .mGrid.getFirstVisibleIndex();
-        setSelectedPosition(firstVisiblePos - 1, 100);
-        int top5 = mGridView.getLayoutManager().findViewByPosition(firstVisiblePos - 1).getTop();
-        assertEquals(top1 - 100, top5);
-
-        // scroll to invisible item that is far away.
-        setSelectedPosition(50, 100);
-        int top6 = mGridView.getLayoutManager().findViewByPosition(50).getTop();
-        assertEquals(top1 - 100, top6);
-
-        // scroll to invisible item that is far away.
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.setSelectedPositionSmooth(100);
-            }
-        });
-        waitForScrollIdle(mVerifyLayout);
-        int top7 = mGridView.getLayoutManager().findViewByPosition(100).getTop();
-        assertEquals(top1, top7);
-
-        // scroll to invisible item that is far away.
-        setSelectedPosition(10, 50);
-        int top8 = mGridView.getLayoutManager().findViewByPosition(10).getTop();
-        assertEquals(top1 - 50, top8);
-    }
-
-    @Test
-    public void testSetSelectionWithDeltaInGrid() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.vertical_grid);
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 500);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, true);
-        mOrientation = BaseGridView.VERTICAL;
-        mNumRows = 3;
-
-        initActivity(intent);
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.setSelectedPositionSmooth(10);
-            }
-        });
-        waitForScrollIdle(mVerifyLayout);
-        int top1 = getCenterY(mGridView.getLayoutManager().findViewByPosition(10));
-
-        humanDelay(500);
-
-        // scroll to position with delta
-        setSelectedPosition(20, 100);
-        int top2 = getCenterY(mGridView.getLayoutManager().findViewByPosition(20));
-        assertEquals(top1 - 100, top2);
-
-        // scroll to same position without delta, it will be reset
-        setSelectedPosition(20, 0);
-        int top3 = getCenterY(mGridView.getLayoutManager().findViewByPosition(20));
-        assertEquals(top1, top3);
-
-        // scroll invisible item after last visible item
-        final int lastVisiblePos = ((GridLayoutManager)mGridView.getLayoutManager())
-                .mGrid.getLastVisibleIndex();
-        setSelectedPosition(lastVisiblePos + 1, 100);
-        int top4 = getCenterY(mGridView.getLayoutManager().findViewByPosition(lastVisiblePos + 1));
-        verifyMargin();
-        assertEquals(top1 - 100, top4);
-
-        // scroll invisible item before first visible item
-        final int firstVisiblePos = ((GridLayoutManager)mGridView.getLayoutManager())
-                .mGrid.getFirstVisibleIndex();
-        setSelectedPosition(firstVisiblePos - 1, 100);
-        int top5 = getCenterY(mGridView.getLayoutManager().findViewByPosition(firstVisiblePos - 1));
-        assertEquals(top1 - 100, top5);
-
-        // scroll to invisible item that is far away.
-        setSelectedPosition(100, 100);
-        int top6 = getCenterY(mGridView.getLayoutManager().findViewByPosition(100));
-        assertEquals(top1 - 100, top6);
-
-        // scroll to invisible item that is far away.
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.setSelectedPositionSmooth(200);
-            }
-        });
-        waitForScrollIdle(mVerifyLayout);
-        Thread.sleep(500);
-        int top7 = getCenterY(mGridView.getLayoutManager().findViewByPosition(200));
-        assertEquals(top1, top7);
-
-        // scroll to invisible item that is far away.
-        setSelectedPosition(10, 50);
-        int top8 = getCenterY(mGridView.getLayoutManager().findViewByPosition(10));
-        assertEquals(top1 - 50, top8);
-    }
-
-
-    @Test
-    public void testSetSelectionWithDeltaInGrid1() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.vertical_grid);
-        intent.putExtra(GridActivity.EXTRA_ITEMS, new int[]{
-                193,176,153,141,203,184,232,139,177,206,222,136,132,237,172,137,
-                188,172,163,213,158,219,209,147,133,229,170,197,138,215,188,205,
-                223,192,225,170,195,127,229,229,210,195,134,142,160,139,130,222,
-                150,163,180,176,157,137,234,169,159,167,182,150,224,231,202,236,
-                123,140,181,223,120,185,183,221,123,210,134,158,166,208,149,128,
-                192,214,212,198,133,140,158,133,229,173,226,141,180,128,127,218,
-                192,235,183,213,216,150,143,193,125,141,219,210,195,195,192,191,
-                212,236,157,189,160,220,147,158,220,199,233,231,201,180,168,141,
-                156,204,191,183,190,153,123,210,238,151,139,221,223,200,175,191,
-                132,184,197,204,236,157,230,151,195,219,212,143,172,149,219,184,
-                164,211,132,187,172,142,174,146,127,147,206,238,188,129,199,226,
-                132,220,210,159,235,153,208,182,196,123,180,159,131,135,175,226,
-                127,134,237,211,133,225,132,124,160,226,224,200,173,137,217,169,
-                182,183,176,185,122,168,195,159,172,129,126,129,166,136,149,220,
-                178,191,192,238,180,208,234,154,222,206,239,228,129,140,203,125,
-                214,175,125,169,196,132,234,138,192,142,234,190,215,232,239,122,
-                188,158,128,221,159,237,207,157,232,138,132,214,122,199,121,191,
-                199,209,126,164,175,187,173,186,194,224,191,196,146,208,213,210,
-                164,176,202,213,123,157,179,138,217,129,186,166,237,211,157,130,
-                137,132,171,232,216,239,180,151,137,132,190,133,218,155,171,227,
-                193,147,197,164,120,218,193,154,170,196,138,222,161,235,143,154,
-                192,178,228,195,178,133,203,178,173,206,178,212,136,157,169,124,
-                172,121,128,223,238,125,217,187,184,156,169,215,231,124,210,174,
-                146,226,185,134,223,228,183,182,136,133,199,146,180,233,226,225,
-                174,233,145,235,216,170,192,171,132,132,134,223,233,148,154,162,
-                192,179,197,203,139,197,174,187,135,132,180,136,192,195,124,221,
-                120,189,233,233,146,225,234,163,215,143,132,198,156,205,151,190,
-                204,239,221,229,123,138,134,217,219,136,218,215,167,139,195,125,
-                202,225,178,226,145,208,130,194,228,197,157,215,124,147,174,123,
-                237,140,172,181,161,151,229,216,199,199,179,213,146,122,222,162,
-                139,173,165,150,160,217,207,137,165,175,129,158,134,133,178,199,
-                215,213,122,197
-        });
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, true);
-        mOrientation = BaseGridView.VERTICAL;
-        mNumRows = 3;
-
-        initActivity(intent);
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.setSelectedPositionSmooth(10);
-            }
-        });
-        waitForScrollIdle(mVerifyLayout);
-        int top1 = getCenterY(mGridView.getLayoutManager().findViewByPosition(10));
-
-        humanDelay(500);
-
-        // scroll to position with delta
-        setSelectedPosition(20, 100);
-        int top2 = getCenterY(mGridView.getLayoutManager().findViewByPosition(20));
-        assertEquals(top1 - 100, top2);
-
-        // scroll to same position without delta, it will be reset
-        setSelectedPosition(20, 0);
-        int top3 = getCenterY(mGridView.getLayoutManager().findViewByPosition(20));
-        assertEquals(top1, top3);
-
-        // scroll invisible item after last visible item
-        final int lastVisiblePos = ((GridLayoutManager)mGridView.getLayoutManager())
-                .mGrid.getLastVisibleIndex();
-        setSelectedPosition(lastVisiblePos + 1, 100);
-        int top4 = getCenterY(mGridView.getLayoutManager().findViewByPosition(lastVisiblePos + 1));
-        verifyMargin();
-        assertEquals(top1 - 100, top4);
-
-        // scroll invisible item before first visible item
-        final int firstVisiblePos = ((GridLayoutManager)mGridView.getLayoutManager())
-                .mGrid.getFirstVisibleIndex();
-        setSelectedPosition(firstVisiblePos - 1, 100);
-        int top5 = getCenterY(mGridView.getLayoutManager().findViewByPosition(firstVisiblePos - 1));
-        assertEquals(top1 - 100, top5);
-
-        // scroll to invisible item that is far away.
-        setSelectedPosition(100, 100);
-        int top6 = getCenterY(mGridView.getLayoutManager().findViewByPosition(100));
-        assertEquals(top1 - 100, top6);
-
-        // scroll to invisible item that is far away.
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.setSelectedPositionSmooth(200);
-            }
-        });
-        waitForScrollIdle(mVerifyLayout);
-        Thread.sleep(500);
-        int top7 = getCenterY(mGridView.getLayoutManager().findViewByPosition(200));
-        assertEquals(top1, top7);
-
-        // scroll to invisible item that is far away.
-        setSelectedPosition(10, 50);
-        int top8 = getCenterY(mGridView.getLayoutManager().findViewByPosition(10));
-        assertEquals(top1 - 50, top8);
-    }
-
-    @Test
-    public void testSmoothScrollSelectionEvents() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.vertical_grid);
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 500);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        mOrientation = BaseGridView.VERTICAL;
-        mNumRows = 3;
-        initActivity(intent);
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.setSelectedPositionSmooth(30);
-            }
-        });
-        waitForScrollIdle(mVerifyLayout);
-        humanDelay(500);
-
-        final ArrayList<Integer> selectedPositions = new ArrayList<Integer>();
-        mGridView.setOnChildSelectedListener(new OnChildSelectedListener() {
-            @Override
-            public void onChildSelected(ViewGroup parent, View view, int position, long id) {
-                selectedPositions.add(position);
-            }
-        });
-
-        sendRepeatedKeys(10, KeyEvent.KEYCODE_DPAD_UP);
-        humanDelay(500);
-        waitForScrollIdle(mVerifyLayout);
-        // should only get childselected event for item 0 once
-        assertTrue(selectedPositions.size() > 0);
-        assertEquals(0, selectedPositions.get(selectedPositions.size() - 1).intValue());
-        for (int i = selectedPositions.size() - 2; i >= 0; i--) {
-            assertFalse(0 == selectedPositions.get(i).intValue());
-        }
-
-    }
-
-    @Test
-    public void testSmoothScrollSelectionEventsLinear() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.vertical_linear);
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 500);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        mOrientation = BaseGridView.VERTICAL;
-        mNumRows = 1;
-        initActivity(intent);
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.setSelectedPositionSmooth(10);
-            }
-        });
-        waitForScrollIdle(mVerifyLayout);
-        humanDelay(500);
-
-        final ArrayList<Integer> selectedPositions = new ArrayList<Integer>();
-        mGridView.setOnChildSelectedListener(new OnChildSelectedListener() {
-            @Override
-            public void onChildSelected(ViewGroup parent, View view, int position, long id) {
-                selectedPositions.add(position);
-            }
-        });
-
-        sendRepeatedKeys(10, KeyEvent.KEYCODE_DPAD_UP);
-        humanDelay(500);
-        waitForScrollIdle(mVerifyLayout);
-        // should only get childselected event for item 0 once
-        assertTrue(selectedPositions.size() > 0);
-        assertEquals(0, selectedPositions.get(selectedPositions.size() - 1).intValue());
-        for (int i = selectedPositions.size() - 2; i >= 0; i--) {
-            assertFalse(0 == selectedPositions.get(i).intValue());
-        }
-
-    }
-
-    @Test
-    public void testScrollToNoneExisting() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.vertical_grid);
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 100);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        mOrientation = BaseGridView.VERTICAL;
-        mNumRows = 3;
-        initActivity(intent);
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.setSelectedPositionSmooth(99);
-            }
-        });
-        waitForScrollIdle(mVerifyLayout);
-        humanDelay(500);
-
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.setSelectedPositionSmooth(50);
-            }
-        });
-        Thread.sleep(100);
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.requestLayout();
-                mGridView.setSelectedPositionSmooth(0);
-            }
-        });
-        waitForScrollIdle(mVerifyLayout);
-        humanDelay(500);
-
-    }
-
-    @Test
-    public void testSmoothscrollerInterrupted() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.vertical_linear);
-        intent.putExtra(GridActivity.EXTRA_REQUEST_FOCUS_ONLAYOUT, true);
-        int[] items = new int[100];
-        for (int i = 0; i < items.length; i++) {
-            items[i] = 680;
-        }
-        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        mOrientation = BaseGridView.VERTICAL;
-        mNumRows = 1;
-
-        initActivity(intent);
-
-        mGridView.setSelectedPositionSmooth(0);
-        waitForScrollIdle(mVerifyLayout);
-        assertTrue(mGridView.getChildAt(0).hasFocus());
-
-        // Pressing lots of key to make sure smooth scroller is running
-        for (int i = 0; i < 20; i++) {
-            sendKey(KeyEvent.KEYCODE_DPAD_DOWN);
-        }
-        while (mGridView.getLayoutManager().isSmoothScrolling()
-                || mGridView.getScrollState() != BaseGridView.SCROLL_STATE_IDLE) {
-            // Repeatedly pressing to make sure pending keys does not drop to zero.
-            sendKey(KeyEvent.KEYCODE_DPAD_DOWN);
-        }
-    }
-
-    @Test
-    public void testSmoothscrollerCancelled() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.vertical_linear);
-        intent.putExtra(GridActivity.EXTRA_REQUEST_FOCUS_ONLAYOUT, true);
-        int[] items = new int[100];
-        for (int i = 0; i < items.length; i++) {
-            items[i] = 680;
-        }
-        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        mOrientation = BaseGridView.VERTICAL;
-        mNumRows = 1;
-
-        initActivity(intent);
-
-        mGridView.setSelectedPositionSmooth(0);
-        waitForScrollIdle(mVerifyLayout);
-        assertTrue(mGridView.getChildAt(0).hasFocus());
-
-        int targetPosition = items.length - 1;
-        mGridView.setSelectedPositionSmooth(targetPosition);
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.stopScroll();
-            }
-        });
-        waitForScrollIdle();
-        waitForItemAnimation();
-        assertEquals(mGridView.getSelectedPosition(), targetPosition);
-        assertSame(mGridView.getLayoutManager().findViewByPosition(targetPosition),
-                mGridView.findFocus());
-    }
-
-    @Test
-    public void testSetNumRowsAndAddItem() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.vertical_linear);
-        intent.putExtra(GridActivity.EXTRA_REQUEST_FOCUS_ONLAYOUT, true);
-        int[] items = new int[2];
-        for (int i = 0; i < items.length; i++) {
-            items[i] = 300;
-        }
-        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        mOrientation = BaseGridView.VERTICAL;
-        mNumRows = 1;
-
-        initActivity(intent);
-
-        mGridView.setSelectedPositionSmooth(0);
-        waitForScrollIdle(mVerifyLayout);
-
-        mActivity.addItems(items.length, new int[]{300});
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                ((VerticalGridView) mGridView).setNumColumns(2);
-            }
-        });
-        Thread.sleep(1000);
-        assertTrue(mGridView.getChildAt(2).getLeft() != mGridView.getChildAt(1).getLeft());
-    }
-
-
-    @Test
-    public void testRequestLayoutBugInLayout() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.vertical_linear);
-        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.relative_layout);
-        intent.putExtra(GridActivity.EXTRA_REQUEST_FOCUS_ONLAYOUT, true);
-        int[] items = new int[100];
-        for (int i = 0; i < items.length; i++) {
-            items[i] = 300;
-        }
-        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        mOrientation = BaseGridView.VERTICAL;
-        mNumRows = 1;
-
-        initActivity(intent);
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.setSelectedPositionSmooth(1);
-            }
-        });
-        waitForScrollIdle(mVerifyLayout);
-
-        sendKey(KeyEvent.KEYCODE_DPAD_UP);
-        waitForScrollIdle(mVerifyLayout);
-
-        assertEquals("Line 2", ((TextView) mGridView.findFocus()).getText().toString());
-    }
-
-
-    @Test
-    public void testChangeLayoutInChild() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.vertical_linear_wrap_content);
-        intent.putExtra(GridActivity.EXTRA_REQUEST_LAYOUT_ONFOCUS, true);
-        int[] items = new int[2];
-        for (int i = 0; i < items.length; i++) {
-            items[i] = 300;
-        }
-        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        mOrientation = BaseGridView.VERTICAL;
-        mNumRows = 1;
-
-        initActivity(intent);
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.setSelectedPositionSmooth(0);
-            }
-        });
-        waitForScrollIdle(mVerifyLayout);
-        verifyMargin();
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.setSelectedPositionSmooth(1);
-            }
-        });
-        waitForScrollIdle(mVerifyLayout);
-        verifyMargin();
-    }
-
-    @Test
-    public void testWrapContent() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.horizontal_grid_wrap);
-        int[] items = new int[200];
-        for (int i = 0; i < items.length; i++) {
-            items[i] = 300;
-        }
-        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
-        mOrientation = BaseGridView.HORIZONTAL;
-        mNumRows = 1;
-
-        initActivity(intent);
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mActivity.attachToNewAdapter(new int[0]);
-            }
-        });
-
-    }
-
-    @Test
-    public void testZeroFixedSecondarySize() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.vertical_linear_measured_with_zero);
-        intent.putExtra(GridActivity.EXTRA_SECONDARY_SIZE_ZERO, true);
-        int[] items = new int[2];
-        for (int i = 0; i < items.length; i++) {
-            items[i] = 0;
-        }
-        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        mOrientation = BaseGridView.VERTICAL;
-        mNumRows = 1;
-
-        initActivity(intent);
-
-    }
-
-    @Test
-    public void testChildStates() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
-        int[] items = new int[100];
-        for (int i = 0; i < items.length; i++) {
-            items[i] = 200;
-        }
-        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        intent.putExtra(GridActivity.EXTRA_REQUEST_LAYOUT_ONFOCUS, true);
-        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.selectable_text_view);
-        mOrientation = BaseGridView.VERTICAL;
-        mNumRows = 1;
-
-        initActivity(intent);
-        mGridView.setSaveChildrenPolicy(VerticalGridView.SAVE_ALL_CHILD);
-
-        final SparseArray<Parcelable> container = new SparseArray<Parcelable>();
-
-        // 1 Save view states
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                Selection.setSelection((Spannable)(((TextView) mGridView.getChildAt(0))
-                        .getText()), 0, 1);
-                Selection.setSelection((Spannable)(((TextView) mGridView.getChildAt(1))
-                        .getText()), 0, 1);
-                mGridView.saveHierarchyState(container);
-            }
-        });
-
-        // 2 Change view states
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                Selection.setSelection((Spannable)(((TextView) mGridView.getChildAt(0))
-                        .getText()), 1, 2);
-                Selection.setSelection((Spannable)(((TextView) mGridView.getChildAt(1))
-                        .getText()), 1, 2);
-            }
-        });
-
-        // 3 Detached and re-attached,  should still maintain state of (2)
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.setSelectedPositionSmooth(1);
-            }
-        });
-        waitForScrollIdle(mVerifyLayout);
-        assertEquals(((TextView) mGridView.getChildAt(0)).getSelectionStart(), 1);
-        assertEquals(((TextView) mGridView.getChildAt(0)).getSelectionEnd(), 2);
-        assertEquals(((TextView) mGridView.getChildAt(1)).getSelectionStart(), 1);
-        assertEquals(((TextView) mGridView.getChildAt(1)).getSelectionEnd(), 2);
-
-        // 4 Recycled and rebound, should load state from (2)
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.setSelectedPositionSmooth(20);
-            }
-        });
-        waitForScrollIdle(mVerifyLayout);
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.setSelectedPositionSmooth(0);
-            }
-        });
-        waitForScrollIdle(mVerifyLayout);
-        assertEquals(((TextView) mGridView.getChildAt(0)).getSelectionStart(), 1);
-        assertEquals(((TextView) mGridView.getChildAt(0)).getSelectionEnd(), 2);
-        assertEquals(((TextView) mGridView.getChildAt(1)).getSelectionStart(), 1);
-        assertEquals(((TextView) mGridView.getChildAt(1)).getSelectionEnd(), 2);
-    }
-
-
-    @Test
-    public void testNoDispatchSaveChildState() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
-        int[] items = new int[100];
-        for (int i = 0; i < items.length; i++) {
-            items[i] = 200;
-        }
-        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.selectable_text_view);
-        mOrientation = BaseGridView.VERTICAL;
-        mNumRows = 1;
-
-        initActivity(intent);
-        mGridView.setSaveChildrenPolicy(VerticalGridView.SAVE_NO_CHILD);
-
-        final SparseArray<Parcelable> container = new SparseArray<Parcelable>();
-
-        // 1. Set text selection, save view states should do nothing on child
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                for (int i = 0; i < mGridView.getChildCount(); i++) {
-                    Selection.setSelection((Spannable)(((TextView) mGridView.getChildAt(i))
-                            .getText()), 0, 1);
-                }
-                mGridView.saveHierarchyState(container);
-            }
-        });
-
-        // 2. clear the text selection
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                for (int i = 0; i < mGridView.getChildCount(); i++) {
-                    Selection.removeSelection((Spannable)(((TextView) mGridView.getChildAt(i))
-                            .getText()));
-                }
-            }
-        });
-
-        // 3. Restore view states should be a no-op for child
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.restoreHierarchyState(container);
-                for (int i = 0; i < mGridView.getChildCount(); i++) {
-                    assertEquals(-1, ((TextView) mGridView.getChildAt(i)).getSelectionStart());
-                    assertEquals(-1, ((TextView) mGridView.getChildAt(i)).getSelectionEnd());
-                }
-            }
-        });
-    }
-
-
-    static interface ViewTypeProvider {
-        public int getViewType(int position);
-    }
-
-    static interface ItemAlignmentFacetProvider {
-        public ItemAlignmentFacet getItemAlignmentFacet(int viewType);
-    }
-
-    static class TwoViewTypesProvider implements ViewTypeProvider {
-        static int VIEW_TYPE_FIRST = 1;
-        static int VIEW_TYPE_DEFAULT = 0;
-        @Override
-        public int getViewType(int position) {
-            if (position == 0) {
-                return VIEW_TYPE_FIRST;
-            } else {
-                return VIEW_TYPE_DEFAULT;
-            }
-        }
-    }
-
-    static class ChangeableViewTypesProvider implements ViewTypeProvider {
-        static SparseIntArray sViewTypes = new SparseIntArray();
-        @Override
-        public int getViewType(int position) {
-            return sViewTypes.get(position);
-        }
-        public static void clear() {
-            sViewTypes.clear();
-        }
-        public static void setViewType(int position, int type) {
-            sViewTypes.put(position, type);
-        }
-    }
-
-    static class PositionItemAlignmentFacetProviderForRelativeLayout1
-            implements ItemAlignmentFacetProvider {
-        ItemAlignmentFacet mMultipleFacet;
-
-        PositionItemAlignmentFacetProviderForRelativeLayout1() {
-            mMultipleFacet = new ItemAlignmentFacet();
-            ItemAlignmentFacet.ItemAlignmentDef[] defs =
-                    new ItemAlignmentFacet.ItemAlignmentDef[2];
-            defs[0] = new ItemAlignmentFacet.ItemAlignmentDef();
-            defs[0].setItemAlignmentViewId(R.id.t1);
-            defs[1] = new ItemAlignmentFacet.ItemAlignmentDef();
-            defs[1].setItemAlignmentViewId(R.id.t2);
-            defs[1].setItemAlignmentOffsetPercent(100);
-            defs[1].setItemAlignmentOffset(-10);
-            mMultipleFacet.setAlignmentDefs(defs);
-        }
-
-        @Override
-        public ItemAlignmentFacet getItemAlignmentFacet(int position) {
-            if (position == 0) {
-                return mMultipleFacet;
-            } else {
-                return null;
-            }
-        }
-    }
-
-    @Test
-    public void testMultipleScrollPosition1() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.vertical_linear);
-        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.relative_layout);
-        intent.putExtra(GridActivity.EXTRA_REQUEST_FOCUS_ONLAYOUT, true);
-        int[] items = new int[100];
-        for (int i = 0; i < items.length; i++) {
-            items[i] = 300;
-        }
-        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        intent.putExtra(GridActivity.EXTRA_VIEWTYPEPROVIDER_CLASS,
-                TwoViewTypesProvider.class.getName());
-        // Set ItemAlignment for each ViewHolder and view type,  ViewHolder should
-        // override the view type settings.
-        intent.putExtra(GridActivity.EXTRA_ITEMALIGNMENTPROVIDER_CLASS,
-                PositionItemAlignmentFacetProviderForRelativeLayout1.class.getName());
-        intent.putExtra(GridActivity.EXTRA_ITEMALIGNMENTPROVIDER_VIEWTYPE_CLASS,
-                ViewTypePositionItemAlignmentFacetProviderForRelativeLayout2.class.getName());
-        mOrientation = BaseGridView.VERTICAL;
-        mNumRows = 1;
-
-        initActivity(intent);
-
-        assertEquals("First view is aligned with padding top",
-                mGridView.getPaddingTop(), mGridView.getChildAt(0).getTop());
-
-        sendKey(KeyEvent.KEYCODE_DPAD_DOWN);
-        waitForScrollIdle(mVerifyLayout);
-
-        final View v = mGridView.getChildAt(0);
-        View t1 = v.findViewById(R.id.t1);
-        int t1align = (t1.getTop() + t1.getBottom()) / 2;
-        View t2 = v.findViewById(R.id.t2);
-        int t2align = t2.getBottom() - 10;
-        assertEquals("Expected alignment for 2nd textview",
-                mGridView.getPaddingTop() - (t2align - t1align),
-                v.getTop());
-    }
-
-    static class PositionItemAlignmentFacetProviderForRelativeLayout2 implements
-            ItemAlignmentFacetProvider {
-        ItemAlignmentFacet mMultipleFacet;
-
-        PositionItemAlignmentFacetProviderForRelativeLayout2() {
-            mMultipleFacet = new ItemAlignmentFacet();
-            ItemAlignmentFacet.ItemAlignmentDef[] defs = new ItemAlignmentFacet.ItemAlignmentDef[2];
-            defs[0] = new ItemAlignmentFacet.ItemAlignmentDef();
-            defs[0].setItemAlignmentViewId(R.id.t1);
-            defs[0].setItemAlignmentOffsetPercent(0);
-            defs[1] = new ItemAlignmentFacet.ItemAlignmentDef();
-            defs[1].setItemAlignmentViewId(R.id.t2);
-            defs[1].setItemAlignmentOffsetPercent(ItemAlignmentFacet.ITEM_ALIGN_OFFSET_PERCENT_DISABLED);
-            defs[1].setItemAlignmentOffset(-10);
-            mMultipleFacet.setAlignmentDefs(defs);
-        }
-
-        @Override
-        public ItemAlignmentFacet getItemAlignmentFacet(int position) {
-            if (position == 0) {
-                return mMultipleFacet;
-            } else {
-                return null;
-            }
-        }
-    }
-
-    @Test
-    public void testMultipleScrollPosition2() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
-        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.relative_layout);
-        intent.putExtra(GridActivity.EXTRA_REQUEST_FOCUS_ONLAYOUT, true);
-        int[] items = new int[100];
-        for (int i = 0; i < items.length; i++) {
-            items[i] = 300;
-        }
-        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        intent.putExtra(GridActivity.EXTRA_VIEWTYPEPROVIDER_CLASS,
-                TwoViewTypesProvider.class.getName());
-        intent.putExtra(GridActivity.EXTRA_ITEMALIGNMENTPROVIDER_CLASS,
-                PositionItemAlignmentFacetProviderForRelativeLayout2.class.getName());
-        mOrientation = BaseGridView.VERTICAL;
-        mNumRows = 1;
-
-        initActivity(intent);
-
-        assertEquals("First view is aligned with padding top", mGridView.getPaddingTop(),
-                mGridView.getChildAt(0).getTop());
-
-        sendKey(KeyEvent.KEYCODE_DPAD_DOWN);
-        waitForScrollIdle(mVerifyLayout);
-
-        final View v = mGridView.getChildAt(0);
-        View t1 = v.findViewById(R.id.t1);
-        int t1align = t1.getTop();
-        View t2 = v.findViewById(R.id.t2);
-        int t2align = t2.getTop() - 10;
-        assertEquals("Expected alignment for 2nd textview",
-                mGridView.getPaddingTop() - (t2align - t1align), v.getTop());
-    }
-
-    static class ViewTypePositionItemAlignmentFacetProviderForRelativeLayout2 implements
-            ItemAlignmentFacetProvider {
-        ItemAlignmentFacet mMultipleFacet;
-
-        ViewTypePositionItemAlignmentFacetProviderForRelativeLayout2() {
-            mMultipleFacet = new ItemAlignmentFacet();
-            ItemAlignmentFacet.ItemAlignmentDef[] defs = new ItemAlignmentFacet.ItemAlignmentDef[2];
-            defs[0] = new ItemAlignmentFacet.ItemAlignmentDef();
-            defs[0].setItemAlignmentViewId(R.id.t1);
-            defs[0].setItemAlignmentOffsetPercent(0);
-            defs[1] = new ItemAlignmentFacet.ItemAlignmentDef();
-            defs[1].setItemAlignmentViewId(R.id.t2);
-            defs[1].setItemAlignmentOffsetPercent(100);
-            defs[1].setItemAlignmentOffset(-10);
-            mMultipleFacet.setAlignmentDefs(defs);
-        }
-
-        @Override
-        public ItemAlignmentFacet getItemAlignmentFacet(int viewType) {
-            if (viewType == TwoViewTypesProvider.VIEW_TYPE_FIRST) {
-                return mMultipleFacet;
-            } else {
-                return null;
-            }
-        }
-    }
-
-    @Test
-    public void testMultipleScrollPosition3() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
-        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.relative_layout);
-        intent.putExtra(GridActivity.EXTRA_REQUEST_FOCUS_ONLAYOUT, true);
-        int[] items = new int[100];
-        for (int i = 0; i < items.length; i++) {
-            items[i] = 300;
-        }
-        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        intent.putExtra(GridActivity.EXTRA_VIEWTYPEPROVIDER_CLASS,
-                TwoViewTypesProvider.class.getName());
-        intent.putExtra(GridActivity.EXTRA_ITEMALIGNMENTPROVIDER_VIEWTYPE_CLASS,
-                ViewTypePositionItemAlignmentFacetProviderForRelativeLayout2.class.getName());
-        mOrientation = BaseGridView.VERTICAL;
-        mNumRows = 1;
-
-        initActivity(intent);
-
-        assertEquals("First view is aligned with padding top", mGridView.getPaddingTop(),
-                mGridView.getChildAt(0).getTop());
-
-        sendKey(KeyEvent.KEYCODE_DPAD_DOWN);
-        waitForScrollIdle(mVerifyLayout);
-
-        final View v = mGridView.getChildAt(0);
-        View t1 = v.findViewById(R.id.t1);
-        int t1align = t1.getTop();
-        View t2 = v.findViewById(R.id.t2);
-        int t2align = t2.getBottom() - 10;
-        assertEquals("Expected alignment for 2nd textview",
-                mGridView.getPaddingTop() - (t2align - t1align), v.getTop());
-    }
-
-    @Test
-    public void testSelectionAndAddItemInOneCycle() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.vertical_linear);
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 0);
-        initActivity(intent);
-        mOrientation = BaseGridView.HORIZONTAL;
-        mNumRows = 1;
-
-        performAndWaitForAnimation(new Runnable() {
-            @Override
-            public void run() {
-                mActivity.addItems(0, new int[]{300, 300});
-                mGridView.setSelectedPosition(0);
-            }
-        });
-        assertEquals(0, mGridView.getSelectedPosition());
-    }
-
-    @Test
-    public void testSelectViewTaskSmoothWithAdapterChange() throws Throwable {
-        testSelectViewTaskWithAdapterChange(true /*smooth*/);
-    }
-
-    @Test
-    public void testSelectViewTaskWithAdapterChange() throws Throwable {
-        testSelectViewTaskWithAdapterChange(false /*smooth*/);
-    }
-
-    private void testSelectViewTaskWithAdapterChange(final boolean smooth) throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.vertical_linear);
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 2);
-        initActivity(intent);
-        mOrientation = BaseGridView.HORIZONTAL;
-        mNumRows = 1;
-
-        final View firstView = mGridView.getLayoutManager().findViewByPosition(0);
-        final View[] selectedViewByTask = new View[1];
-        final ViewHolderTask task = new ViewHolderTask() {
-            @Override
-            public void run(RecyclerView.ViewHolder viewHolder) {
-                selectedViewByTask[0] = viewHolder.itemView;
-            }
-        };
-        performAndWaitForAnimation(new Runnable() {
-            @Override
-            public void run() {
-                mActivity.removeItems(0, 1);
-                if (smooth) {
-                    mGridView.setSelectedPositionSmooth(0, task);
-                } else {
-                    mGridView.setSelectedPosition(0, task);
-                }
-            }
-        });
-        assertEquals(0, mGridView.getSelectedPosition());
-        assertNotNull(selectedViewByTask[0]);
-        assertNotSame(firstView, selectedViewByTask[0]);
-        assertSame(mGridView.getLayoutManager().findViewByPosition(0), selectedViewByTask[0]);
-    }
-
-    @Test
-    public void testNotifyItemTypeChangedSelectionEvent() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.vertical_linear);
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 10);
-        intent.putExtra(GridActivity.EXTRA_VIEWTYPEPROVIDER_CLASS,
-                ChangeableViewTypesProvider.class.getName());
-        ChangeableViewTypesProvider.clear();
-        initActivity(intent);
-        mOrientation = BaseGridView.HORIZONTAL;
-        mNumRows = 1;
-
-        final ArrayList<Integer> selectedLog = new ArrayList<Integer>();
-        mGridView.setOnChildSelectedListener(new OnChildSelectedListener() {
-            @Override
-            public void onChildSelected(ViewGroup parent, View view, int position, long id) {
-                selectedLog.add(position);
-            }
-        });
-
-        performAndWaitForAnimation(new Runnable() {
-            @Override
-            public void run() {
-                ChangeableViewTypesProvider.setViewType(0, 1);
-                mGridView.getAdapter().notifyItemChanged(0, 1);
-            }
-        });
-        assertEquals(0, mGridView.getSelectedPosition());
-        assertEquals(selectedLog.size(), 1);
-        assertEquals((int) selectedLog.get(0), 0);
-    }
-
-    @Test
-    public void testSelectionSmoothAndAddItemInOneCycle() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.vertical_linear);
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 0);
-        initActivity(intent);
-        mOrientation = BaseGridView.HORIZONTAL;
-        mNumRows = 1;
-
-        performAndWaitForAnimation(new Runnable() {
-            @Override
-            public void run() {
-                mActivity.addItems(0, new int[]{300, 300});
-                mGridView.setSelectedPositionSmooth(0);
-            }
-        });
-        assertEquals(0, mGridView.getSelectedPosition());
-    }
-
-    @Test
-    public void testExtraLayoutSpace() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.vertical_linear);
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 1000);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        initActivity(intent);
-
-        final int windowSize = mGridView.getHeight();
-        final int extraLayoutSize = windowSize;
-        mOrientation = BaseGridView.VERTICAL;
-        mNumRows = 1;
-
-        // add extra layout space
-        startWaitLayout();
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.setExtraLayoutSpace(extraLayoutSize);
-            }
-        });
-        waitForLayout();
-        View v;
-        v = mGridView.getChildAt(mGridView.getChildCount() - 1);
-        assertTrue(v.getTop() < windowSize + extraLayoutSize);
-        assertTrue(v.getBottom() >= windowSize + extraLayoutSize - mGridView.getVerticalMargin());
-
-        mGridView.setSelectedPositionSmooth(150);
-        waitForScrollIdle(mVerifyLayout);
-        v = mGridView.getChildAt(0);
-        assertTrue(v.getBottom() > - extraLayoutSize);
-        assertTrue(v.getTop() <= -extraLayoutSize + mGridView.getVerticalMargin());
-
-        // clear extra layout space
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.setExtraLayoutSpace(0);
-                verifyMargin();
-            }
-        });
-        Thread.sleep(50);
-        v = mGridView.getChildAt(mGridView.getChildCount() - 1);
-        assertTrue(v.getTop() < windowSize);
-        assertTrue(v.getBottom() >= windowSize - mGridView.getVerticalMargin());
-    }
-
-    @Test
-    public void testFocusFinder() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.vertical_linear_with_button);
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 3);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        initActivity(intent);
-        mOrientation = BaseGridView.VERTICAL;
-        mNumRows = 1;
-
-        // test focus from button to vertical grid view
-        final View button = mActivity.findViewById(R.id.button);
-        assertTrue(button.isFocused());
-        sendKey(KeyEvent.KEYCODE_DPAD_DOWN);
-        assertFalse(mGridView.isFocused());
-        assertTrue(mGridView.hasFocus());
-
-        // FocusFinder should find last focused(2nd) item on DPAD_DOWN
-        final View secondChild = mGridView.getChildAt(1);
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                secondChild.requestFocus();
-                button.requestFocus();
-            }
-        });
-        assertTrue(button.isFocused());
-        sendKey(KeyEvent.KEYCODE_DPAD_DOWN);
-        assertTrue(secondChild.isFocused());
-
-        // Bug 26918143 Even VerticalGridView is not focusable, FocusFinder should find last focused
-        // (2nd) item on DPAD_DOWN.
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                button.requestFocus();
-            }
-        });
-        mGridView.setFocusable(false);
-        mGridView.setFocusableInTouchMode(false);
-        assertTrue(button.isFocused());
-        sendKey(KeyEvent.KEYCODE_DPAD_DOWN);
-        assertTrue(secondChild.isFocused());
-    }
-
-    @Test
-    public void testRestoreIndexAndAddItems() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.vertical_linear);
-        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.horizontal_item);
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 4);
-        initActivity(intent);
-        mOrientation = BaseGridView.VERTICAL;
-        mNumRows = 1;
-
-        assertEquals(mGridView.getSelectedPosition(), 0);
-        final SparseArray<Parcelable> states = new SparseArray<>();
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.saveHierarchyState(states);
-                mGridView.setAdapter(null);
-            }
-
-        });
-        performAndWaitForAnimation(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.restoreHierarchyState(states);
-                mActivity.attachToNewAdapter(new int[0]);
-                mActivity.addItems(0, new int[]{100, 100, 100, 100});
-            }
-
-        });
-        assertEquals(mGridView.getSelectedPosition(), 0);
-    }
-
-    @Test
-    public void test27766012() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.vertical_linear_with_button_onleft);
-        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.horizontal_item);
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 2);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        intent.putExtra(GridActivity.EXTRA_UPDATE_SIZE, false);
-        initActivity(intent);
-        mOrientation = BaseGridView.VERTICAL;
-        mNumRows = 1;
-
-        // set remove animator two seconds
-        mGridView.getItemAnimator().setRemoveDuration(2000);
-        final View view = mGridView.getChildAt(1);
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                view.requestFocus();
-            }
-        });
-        assertTrue(view.hasFocus());
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mActivity.removeItems(0, 2);
-            }
-
-        });
-        // wait one second, removing second view is still attached to parent
-        Thread.sleep(1000);
-        assertSame(view.getParent(), mGridView);
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                // refocus to the removed item and do a focus search.
-                view.requestFocus();
-                view.focusSearch(View.FOCUS_UP);
-            }
-
-        });
-    }
-
-    @Test
-    public void testBug27258366() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.vertical_linear_with_button_onleft);
-        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.horizontal_item);
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 10);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        intent.putExtra(GridActivity.EXTRA_UPDATE_SIZE, false);
-        initActivity(intent);
-        mOrientation = BaseGridView.VERTICAL;
-        mNumRows = 1;
-
-        // move item1 500 pixels right, when focus is on item1, default focus finder will pick
-        // item0 and item2 for the best match of focusSearch(FOCUS_LEFT).  The grid widget
-        // must override default addFocusables(), not to add item0 or item2.
-        mActivity.mAdapterListener = new GridActivity.AdapterListener() {
-            @Override
-            public void onBind(RecyclerView.ViewHolder vh, int position) {
-                if (position == 1) {
-                    vh.itemView.setPaddingRelative(500, 0, 0, 0);
-                } else {
-                    vh.itemView.setPaddingRelative(0, 0, 0, 0);
-                }
-            }
-        };
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.getAdapter().notifyDataSetChanged();
-            }
-        });
-        Thread.sleep(100);
-
-        final ViewGroup secondChild = (ViewGroup) mGridView.getChildAt(1);
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                secondChild.requestFocus();
-            }
-        });
-        sendKey(KeyEvent.KEYCODE_DPAD_LEFT);
-        Thread.sleep(100);
-        final View button = mActivity.findViewById(R.id.button);
-        assertTrue(button.isFocused());
-    }
-
-    @Test
-    public void testUpdateHeightScrollHorizontal() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.horizontal_linear);
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 30);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        intent.putExtra(GridActivity.EXTRA_REQUEST_LAYOUT_ONFOCUS, true);
-        intent.putExtra(GridActivity.EXTRA_UPDATE_SIZE, false);
-        intent.putExtra(GridActivity.EXTRA_UPDATE_SIZE_SECONDARY, true);
-        initActivity(intent);
-        mOrientation = BaseGridView.HORIZONTAL;
-        mNumRows = 1;
-
-        final int childTop = mGridView.getChildAt(0).getTop();
-        // scroll to end, all children's top should not change.
-        scrollToEnd(new Runnable() {
-            @Override
-            public void run() {
-                for (int i = 0; i < mGridView.getChildCount(); i++) {
-                    assertEquals(childTop, mGridView.getChildAt(i).getTop());
-                }
-            }
-        });
-        // sanity check last child has focus with a larger height.
-        assertTrue(mGridView.getChildAt(0).getHeight()
-                < mGridView.getChildAt(mGridView.getChildCount() - 1).getHeight());
-    }
-
-    @Test
-    public void testUpdateWidthScrollHorizontal() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.horizontal_linear);
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 30);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        intent.putExtra(GridActivity.EXTRA_REQUEST_LAYOUT_ONFOCUS, true);
-        intent.putExtra(GridActivity.EXTRA_UPDATE_SIZE, true);
-        intent.putExtra(GridActivity.EXTRA_UPDATE_SIZE_SECONDARY, false);
-        initActivity(intent);
-        mOrientation = BaseGridView.HORIZONTAL;
-        mNumRows = 1;
-
-        final int childTop = mGridView.getChildAt(0).getTop();
-        // scroll to end, all children's top should not change.
-        scrollToEnd(new Runnable() {
-            @Override
-            public void run() {
-                for (int i = 0; i < mGridView.getChildCount(); i++) {
-                    assertEquals(childTop, mGridView.getChildAt(i).getTop());
-                }
-            }
-        });
-        // sanity check last child has focus with a larger width.
-        assertTrue(mGridView.getChildAt(0).getWidth()
-                < mGridView.getChildAt(mGridView.getChildCount() - 1).getWidth());
-        if (mGridView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
-            assertEquals(mGridView.getPaddingLeft(),
-                    mGridView.getChildAt(mGridView.getChildCount() - 1).getLeft());
-        } else {
-            assertEquals(mGridView.getWidth() - mGridView.getPaddingRight(),
-                    mGridView.getChildAt(mGridView.getChildCount() - 1).getRight());
-        }
-    }
-
-    @Test
-    public void testUpdateWidthScrollHorizontalRtl() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.horizontal_linear_rtl);
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 30);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        intent.putExtra(GridActivity.EXTRA_REQUEST_LAYOUT_ONFOCUS, true);
-        intent.putExtra(GridActivity.EXTRA_UPDATE_SIZE, true);
-        intent.putExtra(GridActivity.EXTRA_UPDATE_SIZE_SECONDARY, false);
-        initActivity(intent);
-        mOrientation = BaseGridView.HORIZONTAL;
-        mNumRows = 1;
-
-        final int childTop = mGridView.getChildAt(0).getTop();
-        // scroll to end, all children's top should not change.
-        scrollToEnd(new Runnable() {
-            @Override
-            public void run() {
-                for (int i = 0; i < mGridView.getChildCount(); i++) {
-                    assertEquals(childTop, mGridView.getChildAt(i).getTop());
-                }
-            }
-        });
-        // sanity check last child has focus with a larger width.
-        assertTrue(mGridView.getChildAt(0).getWidth()
-                < mGridView.getChildAt(mGridView.getChildCount() - 1).getWidth());
-        assertEquals(mGridView.getPaddingLeft(),
-                mGridView.getChildAt(mGridView.getChildCount() - 1).getLeft());
-    }
-
-    @Test
-    public void testAccessibility() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.vertical_linear);
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 1000);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        initActivity(intent);
-        mOrientation = BaseGridView.VERTICAL;
-        mNumRows = 1;
-
-        assertTrue(0 == mGridView.getSelectedPosition());
-
-        final RecyclerViewAccessibilityDelegate delegateCompat = mGridView
-                .getCompatAccessibilityDelegate();
-        final AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain();
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                delegateCompat.onInitializeAccessibilityNodeInfo(mGridView, info);
-            }
-        });
-        assertTrue("test sanity", info.isScrollable());
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                delegateCompat.performAccessibilityAction(mGridView,
-                        AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD, null);
-            }
-        });
-        waitForScrollIdle(mVerifyLayout);
-        int selectedPosition1 = mGridView.getSelectedPosition();
-        assertTrue(0 < selectedPosition1);
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                delegateCompat.onInitializeAccessibilityNodeInfo(mGridView, info);
-            }
-        });
-        assertTrue("test sanity", info.isScrollable());
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                delegateCompat.performAccessibilityAction(mGridView,
-                        AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD, null);
-            }
-        });
-        waitForScrollIdle(mVerifyLayout);
-        int selectedPosition2 = mGridView.getSelectedPosition();
-        assertTrue(selectedPosition2 < selectedPosition1);
-    }
-
-    @Test
-    public void testAccessibilityScrollForwardHalfVisible() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
-        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.item_button_at_bottom);
-        intent.putExtra(GridActivity.EXTRA_ITEMS,  new int[]{});
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        initActivity(intent);
-        mOrientation = BaseGridView.VERTICAL;
-        mNumRows = 1;
-
-        int height = mGridView.getHeight() - mGridView.getPaddingTop()
-                - mGridView.getPaddingBottom();
-        final int childHeight = height - mGridView.getVerticalSpacing() - 100;
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.setWindowAlignment(BaseGridView.WINDOW_ALIGN_NO_EDGE);
-                mGridView.setWindowAlignmentOffset(100);
-                mGridView.setWindowAlignmentOffsetPercent(BaseGridView
-                        .WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
-                mGridView.setItemAlignmentOffset(0);
-                mGridView.setItemAlignmentOffsetPercent(BaseGridView
-                        .ITEM_ALIGN_OFFSET_PERCENT_DISABLED);
-            }
-        });
-        mActivity.addItems(0, new int[]{childHeight, childHeight});
-        waitForItemAnimation();
-        setSelectedPosition(0);
-
-        final RecyclerViewAccessibilityDelegate delegateCompat = mGridView
-                .getCompatAccessibilityDelegate();
-        final AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain();
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                delegateCompat.onInitializeAccessibilityNodeInfo(mGridView, info);
-            }
-        });
-        assertTrue("test sanity", info.isScrollable());
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                delegateCompat.performAccessibilityAction(mGridView,
-                        AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD, null);
-            }
-        });
-        waitForScrollIdle(mVerifyLayout);
-        assertEquals(1, mGridView.getSelectedPosition());
-    }
-
-    @Test
-    public void testAccessibilityScrollBackwardHalfVisible() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
-        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.item_button_at_top);
-        intent.putExtra(GridActivity.EXTRA_ITEMS,  new int[]{});
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        initActivity(intent);
-        mOrientation = BaseGridView.VERTICAL;
-        mNumRows = 1;
-
-        int height = mGridView.getHeight() - mGridView.getPaddingTop()
-                - mGridView.getPaddingBottom();
-        final int childHeight = height - mGridView.getVerticalSpacing() - 100;
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.setWindowAlignment(BaseGridView.WINDOW_ALIGN_NO_EDGE);
-                mGridView.setWindowAlignmentOffset(100);
-                mGridView.setWindowAlignmentOffsetPercent(BaseGridView
-                        .WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
-                mGridView.setItemAlignmentOffset(0);
-                mGridView.setItemAlignmentOffsetPercent(BaseGridView
-                        .ITEM_ALIGN_OFFSET_PERCENT_DISABLED);
-            }
-        });
-        mActivity.addItems(0, new int[]{childHeight, childHeight});
-        waitForItemAnimation();
-        setSelectedPosition(1);
-
-        final RecyclerViewAccessibilityDelegate delegateCompat = mGridView
-                .getCompatAccessibilityDelegate();
-        final AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain();
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                delegateCompat.onInitializeAccessibilityNodeInfo(mGridView, info);
-            }
-        });
-        assertTrue("test sanity", info.isScrollable());
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                delegateCompat.performAccessibilityAction(mGridView,
-                        AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD, null);
-            }
-        });
-        waitForScrollIdle(mVerifyLayout);
-        assertEquals(0, mGridView.getSelectedPosition());
-    }
-
-    void slideInAndWaitIdle() throws Throwable {
-        slideInAndWaitIdle(5000);
-    }
-
-    void slideInAndWaitIdle(long timeout) throws Throwable {
-        // animateIn() would reset position
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.animateIn();
-            }
-        });
-        PollingCheck.waitFor(timeout, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return !mGridView.getLayoutManager().isSmoothScrolling()
-                        && mGridView.getScrollState() == RecyclerView.SCROLL_STATE_IDLE;
-            }
-        });
-    }
-
-    @Test
-    public void testAnimateOutBlockScrollTo() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.vertical_linear_with_button_onleft);
-        int[] items = new int[100];
-        for (int i = 0; i < items.length; i++) {
-            items[i] = 300;
-        }
-        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        mOrientation = BaseGridView.VERTICAL;
-        mNumRows = 1;
-
-        initActivity(intent);
-
-        assertEquals("First view is aligned with padding top", mGridView.getPaddingTop(),
-                mGridView.getChildAt(0).getTop());
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.animateOut();
-            }
-        });
-        // wait until sliding out.
-        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return mGridView.getChildAt(0).getTop() > mGridView.getPaddingTop();
-            }
-        });
-        // scrollToPosition() should not affect slideOut status
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.scrollToPosition(0);
-            }
-        });
-        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return mGridView.getScrollState() == RecyclerView.SCROLL_STATE_IDLE;
-            }
-        });
-        assertTrue("First view slided Out", mGridView.getChildAt(0).getTop()
-                >= mGridView.getHeight());
-
-        slideInAndWaitIdle();
-        assertEquals("First view is aligned with padding top", mGridView.getPaddingTop(),
-                mGridView.getChildAt(0).getTop());
-    }
-
-    @Test
-    public void testAnimateOutBlockSmoothScrolling() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.vertical_linear_with_button_onleft);
-        int[] items = new int[30];
-        for (int i = 0; i < items.length; i++) {
-            items[i] = 300;
-        }
-        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        mOrientation = BaseGridView.VERTICAL;
-        mNumRows = 1;
-
-        initActivity(intent);
-
-        assertEquals("First view is aligned with padding top", mGridView.getPaddingTop(),
-                mGridView.getChildAt(0).getTop());
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.animateOut();
-            }
-        });
-        // wait until sliding out.
-        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return mGridView.getChildAt(0).getTop() > mGridView.getPaddingTop();
-            }
-        });
-        // smoothScrollToPosition() should not affect slideOut status
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.smoothScrollToPosition(29);
-            }
-        });
-        PollingCheck.waitFor(10000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return mGridView.getScrollState() == RecyclerView.SCROLL_STATE_IDLE;
-            }
-        });
-        assertTrue("First view slided Out", mGridView.getChildAt(0).getTop()
-                >= mGridView.getHeight());
-
-        slideInAndWaitIdle();
-        View lastChild = mGridView.getChildAt(mGridView.getChildCount() - 1);
-        assertSame("Scrolled to last child",
-                mGridView.findViewHolderForAdapterPosition(29).itemView, lastChild);
-    }
-
-    @Test
-    public void testAnimateOutBlockLongScrollTo() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.vertical_linear_with_button_onleft);
-        int[] items = new int[30];
-        for (int i = 0; i < items.length; i++) {
-            items[i] = 300;
-        }
-        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        mOrientation = BaseGridView.VERTICAL;
-        mNumRows = 1;
-
-        initActivity(intent);
-
-        assertEquals("First view is aligned with padding top", mGridView.getPaddingTop(),
-                mGridView.getChildAt(0).getTop());
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.animateOut();
-            }
-        });
-        // wait until sliding out.
-        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return mGridView.getChildAt(0).getTop() > mGridView.getPaddingTop();
-            }
-        });
-        // smoothScrollToPosition() should not affect slideOut status
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.scrollToPosition(29);
-            }
-        });
-        PollingCheck.waitFor(10000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return mGridView.getScrollState() == RecyclerView.SCROLL_STATE_IDLE;
-            }
-        });
-        assertTrue("First view slided Out", mGridView.getChildAt(0).getTop()
-                >= mGridView.getHeight());
-
-        slideInAndWaitIdle();
-        View lastChild = mGridView.getChildAt(mGridView.getChildCount() - 1);
-        assertSame("Scrolled to last child",
-                mGridView.findViewHolderForAdapterPosition(29).itemView, lastChild);
-    }
-
-    @Test
-    public void testAnimateOutBlockLayout() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.vertical_linear_with_button_onleft);
-        int[] items = new int[100];
-        for (int i = 0; i < items.length; i++) {
-            items[i] = 300;
-        }
-        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        mOrientation = BaseGridView.VERTICAL;
-        mNumRows = 1;
-
-        initActivity(intent);
-
-        assertEquals("First view is aligned with padding top", mGridView.getPaddingTop(),
-                mGridView.getChildAt(0).getTop());
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.animateOut();
-            }
-        });
-        // wait until sliding out.
-        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return mGridView.getChildAt(0).getTop() > mGridView.getPaddingTop();
-            }
-        });
-        // change adapter should not affect slideOut status
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mActivity.changeItem(0, 200);
-            }
-        });
-        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return mGridView.getScrollState() == RecyclerView.SCROLL_STATE_IDLE;
-            }
-        });
-        assertTrue("First view slided Out", mGridView.getChildAt(0).getTop()
-                >= mGridView.getHeight());
-        assertEquals("onLayout suppressed during slide out", 300,
-                mGridView.getChildAt(0).getHeight());
-
-        slideInAndWaitIdle();
-        assertEquals("First view is aligned with padding top", mGridView.getPaddingTop(),
-                mGridView.getChildAt(0).getTop());
-        // size of item should be updated immediately after slide in animation finishes:
-        PollingCheck.waitFor(1000, new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return 200 == mGridView.getChildAt(0).getHeight();
-            }
-        });
-    }
-
-    @Test
-    public void testAnimateOutBlockFocusChange() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.vertical_linear_with_button_onleft);
-        int[] items = new int[100];
-        for (int i = 0; i < items.length; i++) {
-            items[i] = 300;
-        }
-        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        mOrientation = BaseGridView.VERTICAL;
-        mNumRows = 1;
-
-        initActivity(intent);
-
-        assertEquals("First view is aligned with padding top", mGridView.getPaddingTop(),
-                mGridView.getChildAt(0).getTop());
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.animateOut();
-                mActivity.findViewById(R.id.button).requestFocus();
-            }
-        });
-        assertTrue(mActivity.findViewById(R.id.button).hasFocus());
-        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return mGridView.getChildAt(0).getTop() > mGridView.getPaddingTop();
-            }
-        });
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.requestFocus();
-            }
-        });
-        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return mGridView.getScrollState() == RecyclerView.SCROLL_STATE_IDLE;
-            }
-        });
-        assertTrue("First view slided Out", mGridView.getChildAt(0).getTop()
-                >= mGridView.getHeight());
-
-        slideInAndWaitIdle();
-        assertEquals("First view is aligned with padding top", mGridView.getPaddingTop(),
-                mGridView.getChildAt(0).getTop());
-    }
-
-    @Test
-    public void testHorizontalAnimateOutBlockScrollTo() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.horizontal_linear);
-        int[] items = new int[100];
-        for (int i = 0; i < items.length; i++) {
-            items[i] = 300;
-        }
-        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        mOrientation = BaseGridView.HORIZONTAL;
-        mNumRows = 1;
-
-        initActivity(intent);
-
-        assertEquals("First view is aligned with padding left", mGridView.getPaddingLeft(),
-                mGridView.getChildAt(0).getLeft());
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.animateOut();
-            }
-        });
-        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return mGridView.getChildAt(0).getLeft() > mGridView.getPaddingLeft();
-            }
-        });
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.scrollToPosition(0);
-            }
-        });
-        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return mGridView.getScrollState() == RecyclerView.SCROLL_STATE_IDLE;
-            }
-        });
-
-        assertTrue("First view is slided out", mGridView.getChildAt(0).getLeft()
-                > mGridView.getWidth());
-
-        slideInAndWaitIdle();
-        assertEquals("First view is aligned with padding left", mGridView.getPaddingLeft(),
-                mGridView.getChildAt(0).getLeft());
-
-    }
-
-    @Test
-    public void testHorizontalAnimateOutRtl() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.horizontal_linear_rtl);
-        int[] items = new int[100];
-        for (int i = 0; i < items.length; i++) {
-            items[i] = 300;
-        }
-        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        mOrientation = BaseGridView.HORIZONTAL;
-        mNumRows = 1;
-
-        initActivity(intent);
-
-        assertEquals("First view is aligned with padding right",
-                mGridView.getWidth() - mGridView.getPaddingRight(),
-                mGridView.getChildAt(0).getRight());
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.animateOut();
-            }
-        });
-        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return mGridView.getChildAt(0).getRight()
-                        < mGridView.getWidth() - mGridView.getPaddingRight();
-            }
-        });
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.smoothScrollToPosition(0);
-            }
-        });
-        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return mGridView.getScrollState() == RecyclerView.SCROLL_STATE_IDLE;
-            }
-        });
-
-        assertTrue("First view is slided out", mGridView.getChildAt(0).getRight() < 0);
-
-        slideInAndWaitIdle();
-        assertEquals("First view is aligned with padding right",
-                mGridView.getWidth() - mGridView.getPaddingRight(),
-                mGridView.getChildAt(0).getRight());
-    }
-
-    @Test
-    public void testSmoothScrollerOutRange() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.vertical_linear_with_button_onleft);
-        intent.putExtra(GridActivity.EXTRA_REQUEST_FOCUS_ONLAYOUT, true);
-        int[] items = new int[30];
-        for (int i = 0; i < items.length; i++) {
-            items[i] = 680;
-        }
-        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        mOrientation = BaseGridView.VERTICAL;
-        mNumRows = 1;
-
-        initActivity(intent);
-
-        final View button = mActivity.findViewById(R.id.button);
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            public void run() {
-                button.requestFocus();
-            }
-        });
-
-        mGridView.setSelectedPositionSmooth(0);
-        waitForScrollIdle(mVerifyLayout);
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            public void run() {
-                mGridView.setSelectedPositionSmooth(120);
-            }
-        });
-        waitForScrollIdle(mVerifyLayout);
-        assertTrue(button.hasFocus());
-        int key;
-        if (mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL) {
-            key = KeyEvent.KEYCODE_DPAD_LEFT;
-        } else {
-            key = KeyEvent.KEYCODE_DPAD_RIGHT;
-        }
-        sendKey(key);
-        // the GridView should has focus in its children
-        assertTrue(mGridView.hasFocus());
-        assertFalse(mGridView.isFocused());
-        assertEquals(29, mGridView.getSelectedPosition());
-    }
-
-    @Test
-    public void testRemoveLastItemWithStableId() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
-        intent.putExtra(GridActivity.EXTRA_HAS_STABLE_IDS, true);
-        int[] items = new int[1];
-        for (int i = 0; i < items.length; i++) {
-            items[i] = 680;
-        }
-        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        mOrientation = BaseGridView.VERTICAL;
-        mNumRows = 1;
-
-        initActivity(intent);
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.getItemAnimator().setRemoveDuration(2000);
-                mActivity.removeItems(0, 1, false);
-                mGridView.getAdapter().notifyDataSetChanged();
-            }
-        });
-        Thread.sleep(500);
-        assertEquals(-1, mGridView.getSelectedPosition());
-    }
-
-    @Test
-    public void testUpdateAndSelect1() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
-        intent.putExtra(GridActivity.EXTRA_HAS_STABLE_IDS, false);
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 10);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        mOrientation = BaseGridView.VERTICAL;
-        mNumRows = 1;
-
-        initActivity(intent);
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.getAdapter().notifyDataSetChanged();
-                mGridView.setSelectedPosition(1);
-            }
-        });
-        waitOneUiCycle();
-        assertEquals(1, mGridView.getSelectedPosition());
-    }
-
-    @Test
-    public void testUpdateAndSelect2() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
-        intent.putExtra(GridActivity.EXTRA_HAS_STABLE_IDS, false);
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 100);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        mOrientation = BaseGridView.VERTICAL;
-        mNumRows = 1;
-
-        initActivity(intent);
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.getAdapter().notifyDataSetChanged();
-                mGridView.setSelectedPosition(50);
-            }
-        });
-        waitOneUiCycle();
-        assertEquals(50, mGridView.getSelectedPosition());
-    }
-
-    @Test
-    public void testUpdateAndSelect3() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
-        intent.putExtra(GridActivity.EXTRA_HAS_STABLE_IDS, false);
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 10);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        mOrientation = BaseGridView.VERTICAL;
-        mNumRows = 1;
-
-        initActivity(intent);
-
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                int[] newItems = new int[100];
-                for (int i = 0; i < newItems.length; i++) {
-                    newItems[i] = mActivity.mItemLengths[0];
-                }
-                mActivity.addItems(0, newItems, false);
-                mGridView.getAdapter().notifyDataSetChanged();
-                mGridView.setSelectedPosition(50);
-            }
-        });
-        waitOneUiCycle();
-        assertEquals(50, mGridView.getSelectedPosition());
-    }
-
-    @Test
-    public void testFocusedPositonAfterRemoved1() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
-        final int[] items = new int[2];
-        for (int i = 0; i < items.length; i++) {
-            items[i] = 300;
-        }
-        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        mOrientation = BaseGridView.VERTICAL;
-        mNumRows = 1;
-
-        initActivity(intent);
-        setSelectedPosition(1);
-        assertEquals(1, mGridView.getSelectedPosition());
-
-        final int[] newItems = new int[3];
-        for (int i = 0; i < newItems.length; i++) {
-            newItems[i] = 300;
-        }
-        performAndWaitForAnimation(new Runnable() {
-            @Override
-            public void run() {
-                mActivity.removeItems(0, 2, true);
-                mActivity.addItems(0, newItems, true);
-            }
-        });
-        assertEquals(0, mGridView.getSelectedPosition());
-    }
-
-    @Test
-    public void testFocusedPositonAfterRemoved2() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
-        final int[] items = new int[2];
-        for (int i = 0; i < items.length; i++) {
-            items[i] = 300;
-        }
-        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        mOrientation = BaseGridView.VERTICAL;
-        mNumRows = 1;
-
-        initActivity(intent);
-        setSelectedPosition(1);
-        assertEquals(1, mGridView.getSelectedPosition());
-
-        final int[] newItems = new int[3];
-        for (int i = 0; i < newItems.length; i++) {
-            newItems[i] = 300;
-        }
-        performAndWaitForAnimation(new Runnable() {
-            @Override
-            public void run() {
-                mActivity.removeItems(1, 1, true);
-                mActivity.addItems(1, newItems, true);
-            }
-        });
-        assertEquals(1, mGridView.getSelectedPosition());
-    }
-
-    static void assertNoCollectionItemInfo(AccessibilityNodeInfoCompat info) {
-        AccessibilityNodeInfoCompat.CollectionItemInfoCompat nodeInfoCompat =
-                info.getCollectionItemInfo();
-        if (nodeInfoCompat == null) {
-            return;
-        }
-        assertTrue(nodeInfoCompat.getRowIndex() < 0);
-        assertTrue(nodeInfoCompat.getColumnIndex() < 0);
-    }
-
-    /**
-     * This test would need talkback on.
-     */
-    @Test
-    public void testAccessibilityOfItemsBeingPushedOut() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_grid);
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 100);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        mOrientation = BaseGridView.HORIZONTAL;
-        mNumRows = 3;
-
-        initActivity(intent);
-
-        final int lastPos = mGridView.getChildAdapterPosition(
-                mGridView.getChildAt(mGridView.getChildCount() - 1));
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.getLayoutManager().setItemPrefetchEnabled(false);
-            }
-        });
-        final int numItemsToPushOut = mNumRows;
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                // set longer enough so that accessibility service will initialize node
-                // within setImportantForAccessibility().
-                mGridView.getItemAnimator().setRemoveDuration(2000);
-                mGridView.getItemAnimator().setAddDuration(2000);
-                final int[] newItems = new int[numItemsToPushOut];
-                final int newItemValue = mActivity.mItemLengths[0];
-                for (int i = 0; i < newItems.length; i++) {
-                    newItems[i] = newItemValue;
-                }
-                mActivity.addItems(lastPos - numItemsToPushOut + 1, newItems);
-            }
-        });
-        waitForItemAnimation();
-    }
-
-    /**
-     * This test simulates talkback by calling setImportanceForAccessibility at end of animation
-     */
-    @Test
-    public void simulatesAccessibilityOfItemsBeingPushedOut() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_grid);
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 100);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        mOrientation = BaseGridView.HORIZONTAL;
-        mNumRows = 3;
-
-        initActivity(intent);
-
-        final HashSet<View> moveAnimationViews = new HashSet();
-        mActivity.mImportantForAccessibilityListener =
-                new GridActivity.ImportantForAccessibilityListener() {
-            RecyclerView.LayoutManager mLM = mGridView.getLayoutManager();
-            @Override
-            public void onImportantForAccessibilityChanged(View view, int newValue) {
-                // simulates talkack, having setImportantForAccessibility to call
-                // onInitializeAccessibilityNodeInfoForItem() for the DISAPPEARING items.
-                if (moveAnimationViews.contains(view)) {
-                    AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain();
-                    mLM.onInitializeAccessibilityNodeInfoForItem(
-                            null, null, view, info);
-                }
-            }
-        };
-        final int lastPos = mGridView.getChildAdapterPosition(
-                mGridView.getChildAt(mGridView.getChildCount() - 1));
-        final int numItemsToPushOut = mNumRows;
-        for (int i = 0; i < numItemsToPushOut; i++) {
-            moveAnimationViews.add(
-                    mGridView.getChildAt(mGridView.getChildCount() - 1 - i));
-        }
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.setItemAnimator(new DefaultItemAnimator() {
-                    @Override
-                    public void onMoveFinished(RecyclerView.ViewHolder item) {
-                        moveAnimationViews.remove(item.itemView);
-                    }
-                });
-                mGridView.getLayoutManager().setItemPrefetchEnabled(false);
-            }
-        });
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                final int[] newItems = new int[numItemsToPushOut];
-                final int newItemValue = mActivity.mItemLengths[0] + 1;
-                for (int i = 0; i < newItems.length; i++) {
-                    newItems[i] = newItemValue;
-                }
-                mActivity.addItems(lastPos - numItemsToPushOut + 1, newItems);
-            }
-        });
-        while (moveAnimationViews.size() != 0) {
-            Thread.sleep(100);
-        }
-    }
-
-    @Test
-    public void testAccessibilityNodeInfoOnRemovedFirstItem() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_grid);
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 6);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        mOrientation = BaseGridView.HORIZONTAL;
-        mNumRows = 3;
-
-        initActivity(intent);
-
-        final View lastView = mGridView.findViewHolderForAdapterPosition(0).itemView;
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.getItemAnimator().setRemoveDuration(20000);
-                mActivity.removeItems(0, 1);
-            }
-        });
-        waitForItemAnimationStart();
-        AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain(lastView);
-        mGridView.getLayoutManager().onInitializeAccessibilityNodeInfoForItem(null, null,
-                lastView, info);
-        assertNoCollectionItemInfo(info);
-    }
-
-    @Test
-    public void testAccessibilityNodeInfoOnRemovedLastItem() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_grid);
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 6);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        mOrientation = BaseGridView.HORIZONTAL;
-        mNumRows = 3;
-
-        initActivity(intent);
-
-        final View lastView = mGridView.findViewHolderForAdapterPosition(5).itemView;
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.getItemAnimator().setRemoveDuration(20000);
-                mActivity.removeItems(5, 1);
-            }
-        });
-        waitForItemAnimationStart();
-        AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain(lastView);
-        mGridView.getLayoutManager().onInitializeAccessibilityNodeInfoForItem(null, null,
-                lastView, info);
-        assertNoCollectionItemInfo(info);
-    }
-
-    static class FiveViewTypesProvider implements ViewTypeProvider {
-
-        @Override
-        public int getViewType(int position) {
-            switch (position) {
-                case 0:
-                    return 0;
-                case 1:
-                    return 1;
-                case 2:
-                    return 2;
-                case 3:
-                    return 3;
-                case 4:
-                    return 4;
-            }
-            return 199;
-        }
-    }
-
-    // Used by testItemAlignmentVertical() testItemAlignmentHorizontal()
-    static class ItemAlignmentWithPaddingFacetProvider implements
-            ItemAlignmentFacetProvider {
-        final ItemAlignmentFacet mFacet0;
-        final ItemAlignmentFacet mFacet1;
-        final ItemAlignmentFacet mFacet2;
-        final ItemAlignmentFacet mFacet3;
-        final ItemAlignmentFacet mFacet4;
-
-        ItemAlignmentWithPaddingFacetProvider() {
-            ItemAlignmentFacet.ItemAlignmentDef[] defs;
-            mFacet0 = new ItemAlignmentFacet();
-            defs = new ItemAlignmentFacet.ItemAlignmentDef[1];
-            defs[0] = new ItemAlignmentFacet.ItemAlignmentDef();
-            defs[0].setItemAlignmentViewId(R.id.t1);
-            defs[0].setItemAlignmentOffsetPercent(0);
-            defs[0].setItemAlignmentOffsetWithPadding(false);
-            mFacet0.setAlignmentDefs(defs);
-            mFacet1 = new ItemAlignmentFacet();
-            defs = new ItemAlignmentFacet.ItemAlignmentDef[1];
-            defs[0] = new ItemAlignmentFacet.ItemAlignmentDef();
-            defs[0].setItemAlignmentViewId(R.id.t1);
-            defs[0].setItemAlignmentOffsetPercent(0);
-            defs[0].setItemAlignmentOffsetWithPadding(true);
-            mFacet1.setAlignmentDefs(defs);
-            mFacet2 = new ItemAlignmentFacet();
-            defs = new ItemAlignmentFacet.ItemAlignmentDef[1];
-            defs[0] = new ItemAlignmentFacet.ItemAlignmentDef();
-            defs[0].setItemAlignmentViewId(R.id.t2);
-            defs[0].setItemAlignmentOffsetPercent(100);
-            defs[0].setItemAlignmentOffsetWithPadding(true);
-            mFacet2.setAlignmentDefs(defs);
-            mFacet3 = new ItemAlignmentFacet();
-            defs = new ItemAlignmentFacet.ItemAlignmentDef[1];
-            defs[0] = new ItemAlignmentFacet.ItemAlignmentDef();
-            defs[0].setItemAlignmentViewId(R.id.t2);
-            defs[0].setItemAlignmentOffsetPercent(50);
-            defs[0].setItemAlignmentOffsetWithPadding(true);
-            mFacet3.setAlignmentDefs(defs);
-            mFacet4 = new ItemAlignmentFacet();
-            defs = new ItemAlignmentFacet.ItemAlignmentDef[1];
-            defs[0] = new ItemAlignmentFacet.ItemAlignmentDef();
-            defs[0].setItemAlignmentViewId(R.id.t2);
-            defs[0].setItemAlignmentOffsetPercent(50);
-            defs[0].setItemAlignmentOffsetWithPadding(false);
-            mFacet4.setAlignmentDefs(defs);
-        }
-
-        @Override
-        public ItemAlignmentFacet getItemAlignmentFacet(int viewType) {
-            switch (viewType) {
-                case 0:
-                    return mFacet0;
-                case 1:
-                    return mFacet1;
-                case 2:
-                    return mFacet2;
-                case 3:
-                    return mFacet3;
-                case 4:
-                    return mFacet4;
-            }
-            return null;
-        }
-    }
-
-    @Test
-    public void testItemAlignmentVertical() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
-        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.relative_layout2);
-        int[] items = new int[5];
-        for (int i = 0; i < items.length; i++) {
-            items[i] = 300;
-        }
-        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        intent.putExtra(GridActivity.EXTRA_VIEWTYPEPROVIDER_CLASS,
-                FiveViewTypesProvider.class.getName());
-        intent.putExtra(GridActivity.EXTRA_ITEMALIGNMENTPROVIDER_CLASS,
-                ItemAlignmentWithPaddingFacetProvider.class.getName());
-        mOrientation = BaseGridView.VERTICAL;
-        mNumRows = 1;
-
-        initActivity(intent);
-        startWaitLayout();
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.setWindowAlignment(BaseGridView.WINDOW_ALIGN_NO_EDGE);
-                mGridView.setWindowAlignmentOffsetPercent(50);
-                mGridView.setWindowAlignmentOffset(0);
-            }
-        });
-        waitForLayout();
-
-        final float windowAlignCenter = mGridView.getHeight() / 2f;
-        Rect rect = new Rect();
-        View textView;
-
-        // test 1: does not include padding
-        textView = mGridView.findViewHolderForAdapterPosition(0).itemView.findViewById(R.id.t1);
-        rect.set(0, 0, textView.getWidth(), textView.getHeight());
-        mGridView.offsetDescendantRectToMyCoords(textView, rect);
-        assertEquals(windowAlignCenter, rect.top, DELTA);
-
-        // test 2: including low padding
-        setSelectedPosition(1);
-        textView = mGridView.findViewHolderForAdapterPosition(1).itemView.findViewById(R.id.t1);
-        assertTrue(textView.getPaddingTop() > 0);
-        rect.set(0, textView.getPaddingTop(), textView.getWidth(), textView.getHeight());
-        mGridView.offsetDescendantRectToMyCoords(textView, rect);
-        assertEquals(windowAlignCenter, rect.top, DELTA);
-
-        // test 3: including high padding
-        setSelectedPosition(2);
-        textView = mGridView.findViewHolderForAdapterPosition(2).itemView.findViewById(R.id.t2);
-        assertTrue(textView.getPaddingBottom() > 0);
-        rect.set(0, 0, textView.getWidth(),
-                textView.getHeight() - textView.getPaddingBottom());
-        mGridView.offsetDescendantRectToMyCoords(textView, rect);
-        assertEquals(windowAlignCenter, rect.bottom, DELTA);
-
-        // test 4: including padding will be ignored if offsetPercent is not 0 or 100
-        setSelectedPosition(3);
-        textView = mGridView.findViewHolderForAdapterPosition(3).itemView.findViewById(R.id.t2);
-        assertTrue(textView.getPaddingTop() != textView.getPaddingBottom());
-        rect.set(0, 0, textView.getWidth(), textView.getHeight() / 2);
-        mGridView.offsetDescendantRectToMyCoords(textView, rect);
-        assertEquals(windowAlignCenter, rect.bottom, DELTA);
-
-        // test 5: does not include padding
-        setSelectedPosition(4);
-        textView = mGridView.findViewHolderForAdapterPosition(4).itemView.findViewById(R.id.t2);
-        assertTrue(textView.getPaddingTop() != textView.getPaddingBottom());
-        rect.set(0, 0, textView.getWidth(), textView.getHeight() / 2);
-        mGridView.offsetDescendantRectToMyCoords(textView, rect);
-        assertEquals(windowAlignCenter, rect.bottom, DELTA);
-    }
-
-    @Test
-    public void testItemAlignmentHorizontal() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_linear);
-        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.relative_layout3);
-        int[] items = new int[5];
-        for (int i = 0; i < items.length; i++) {
-            items[i] = 300;
-        }
-        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        intent.putExtra(GridActivity.EXTRA_VIEWTYPEPROVIDER_CLASS,
-                FiveViewTypesProvider.class.getName());
-        intent.putExtra(GridActivity.EXTRA_ITEMALIGNMENTPROVIDER_CLASS,
-                ItemAlignmentWithPaddingFacetProvider.class.getName());
-        mOrientation = BaseGridView.VERTICAL;
-        mNumRows = 1;
-
-        initActivity(intent);
-        startWaitLayout();
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.setWindowAlignment(BaseGridView.WINDOW_ALIGN_NO_EDGE);
-                mGridView.setWindowAlignmentOffsetPercent(50);
-                mGridView.setWindowAlignmentOffset(0);
-            }
-        });
-        waitForLayout();
-
-        final float windowAlignCenter = mGridView.getWidth() / 2f;
-        Rect rect = new Rect();
-        View textView;
-
-        // test 1: does not include padding
-        textView = mGridView.findViewHolderForAdapterPosition(0).itemView.findViewById(R.id.t1);
-        rect.set(0, 0, textView.getWidth(), textView.getHeight());
-        mGridView.offsetDescendantRectToMyCoords(textView, rect);
-        assertEquals(windowAlignCenter, rect.left, DELTA);
-
-        // test 2: including low padding
-        setSelectedPosition(1);
-        textView = mGridView.findViewHolderForAdapterPosition(1).itemView.findViewById(R.id.t1);
-        assertTrue(textView.getPaddingLeft() > 0);
-        rect.set(textView.getPaddingLeft(), 0, textView.getWidth(), textView.getHeight());
-        mGridView.offsetDescendantRectToMyCoords(textView, rect);
-        assertEquals(windowAlignCenter, rect.left, DELTA);
-
-        // test 3: including high padding
-        setSelectedPosition(2);
-        textView = mGridView.findViewHolderForAdapterPosition(2).itemView.findViewById(R.id.t2);
-        assertTrue(textView.getPaddingRight() > 0);
-        rect.set(0, 0, textView.getWidth() - textView.getPaddingRight(),
-                textView.getHeight());
-        mGridView.offsetDescendantRectToMyCoords(textView, rect);
-        assertEquals(windowAlignCenter, rect.right, DELTA);
-
-        // test 4: including padding will be ignored if offsetPercent is not 0 or 100
-        setSelectedPosition(3);
-        textView = mGridView.findViewHolderForAdapterPosition(3).itemView.findViewById(R.id.t2);
-        assertTrue(textView.getPaddingLeft() != textView.getPaddingRight());
-        rect.set(0, 0, textView.getWidth() / 2, textView.getHeight());
-        mGridView.offsetDescendantRectToMyCoords(textView, rect);
-        assertEquals(windowAlignCenter, rect.right, DELTA);
-
-        // test 5: does not include padding
-        setSelectedPosition(4);
-        textView = mGridView.findViewHolderForAdapterPosition(4).itemView.findViewById(R.id.t2);
-        assertTrue(textView.getPaddingLeft() != textView.getPaddingRight());
-        rect.set(0, 0, textView.getWidth() / 2, textView.getHeight());
-        mGridView.offsetDescendantRectToMyCoords(textView, rect);
-        assertEquals(windowAlignCenter, rect.right, DELTA);
-    }
-
-    @Test
-    public void testItemAlignmentHorizontalRtl() throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_linear);
-        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.relative_layout3);
-        int[] items = new int[5];
-        for (int i = 0; i < items.length; i++) {
-            items[i] = 300;
-        }
-        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        intent.putExtra(GridActivity.EXTRA_VIEWTYPEPROVIDER_CLASS,
-                FiveViewTypesProvider.class.getName());
-        intent.putExtra(GridActivity.EXTRA_ITEMALIGNMENTPROVIDER_CLASS,
-                ItemAlignmentWithPaddingFacetProvider.class.getName());
-        mOrientation = BaseGridView.VERTICAL;
-        mNumRows = 1;
-
-        initActivity(intent);
-        startWaitLayout();
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mGridView.setLayoutDirection(View.LAYOUT_DIRECTION_RTL);
-                mGridView.setWindowAlignment(BaseGridView.WINDOW_ALIGN_NO_EDGE);
-                mGridView.setWindowAlignmentOffsetPercent(50);
-                mGridView.setWindowAlignmentOffset(0);
-            }
-        });
-        waitForLayout();
-
-        final float windowAlignCenter = mGridView.getWidth() / 2f;
-        Rect rect = new Rect();
-        View textView;
-
-        // test 1: does not include padding
-        textView = mGridView.findViewHolderForAdapterPosition(0).itemView.findViewById(R.id.t1);
-        rect.set(0, 0, textView.getWidth(), textView.getHeight());
-        mGridView.offsetDescendantRectToMyCoords(textView, rect);
-        assertEquals(windowAlignCenter, rect.right, DELTA);
-
-        // test 2: including low padding
-        setSelectedPosition(1);
-        textView = mGridView.findViewHolderForAdapterPosition(1).itemView.findViewById(R.id.t1);
-        assertTrue(textView.getPaddingRight() > 0);
-        rect.set(0, 0, textView.getWidth() - textView.getPaddingRight(),
-                textView.getHeight());
-        mGridView.offsetDescendantRectToMyCoords(textView, rect);
-        assertEquals(windowAlignCenter, rect.right, DELTA);
-
-        // test 3: including high padding
-        setSelectedPosition(2);
-        textView = mGridView.findViewHolderForAdapterPosition(2).itemView.findViewById(R.id.t2);
-        assertTrue(textView.getPaddingLeft() > 0);
-        rect.set(textView.getPaddingLeft(), 0, textView.getWidth(),
-                textView.getHeight());
-        mGridView.offsetDescendantRectToMyCoords(textView, rect);
-        assertEquals(windowAlignCenter, rect.left, DELTA);
-
-        // test 4: including padding will be ignored if offsetPercent is not 0 or 100
-        setSelectedPosition(3);
-        textView = mGridView.findViewHolderForAdapterPosition(3).itemView.findViewById(R.id.t2);
-        assertTrue(textView.getPaddingLeft() != textView.getPaddingRight());
-        rect.set(0, 0, textView.getWidth() / 2, textView.getHeight());
-        mGridView.offsetDescendantRectToMyCoords(textView, rect);
-        assertEquals(windowAlignCenter, rect.right, DELTA);
-
-        // test 5: does not include padding
-        setSelectedPosition(4);
-        textView = mGridView.findViewHolderForAdapterPosition(4).itemView.findViewById(R.id.t2);
-        assertTrue(textView.getPaddingLeft() != textView.getPaddingRight());
-        rect.set(0, 0, textView.getWidth() / 2, textView.getHeight());
-        mGridView.offsetDescendantRectToMyCoords(textView, rect);
-        assertEquals(windowAlignCenter, rect.right, DELTA);
-    }
-
-    enum ItemLocation {
-        ITEM_AT_LOW,
-        ITEM_AT_KEY_LINE,
-        ITEM_AT_HIGH
-    };
-
-    static class ItemAt {
-        final int mScrollPosition;
-        final int mPosition;
-        final ItemLocation mLocation;
-
-        ItemAt(int scrollPosition, int position, ItemLocation loc) {
-            mScrollPosition = scrollPosition;
-            mPosition = position;
-            mLocation = loc;
-        }
-
-        ItemAt(int position, ItemLocation loc) {
-            mScrollPosition = position;
-            mPosition = position;
-            mLocation = loc;
-        }
-    }
-
-    /**
-     * When scroll to position, item at position is expected at given location.
-     */
-    static ItemAt itemAt(int position, ItemLocation location) {
-        return new ItemAt(position, location);
-    }
-
-    /**
-     * When scroll to scrollPosition, item at position is expected at given location.
-     */
-    static ItemAt itemAt(int scrollPosition, int position, ItemLocation location) {
-        return new ItemAt(scrollPosition, position, location);
-    }
-
-    void prepareKeyLineTest(int numItems) throws Throwable {
-        Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_linear);
-        int[] items = new int[numItems];
-        for (int i = 0; i < items.length; i++) {
-            items[i] = 32;
-        }
-        intent.putExtra(GridActivity.EXTRA_ITEMS, items);
-        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        mOrientation = BaseGridView.HORIZONTAL;
-        mNumRows = 1;
-
-        initActivity(intent);
-    }
-
-    public void testPreferKeyLine(final int windowAlignment,
-            final boolean preferKeyLineOverLow,
-            final boolean preferKeyLineOverHigh,
-            ItemLocation assertFirstItemLocation,
-            ItemLocation assertLastItemLocation) throws Throwable {
-        testPreferKeyLine(windowAlignment, preferKeyLineOverLow, preferKeyLineOverHigh,
-                itemAt(0, assertFirstItemLocation),
-                itemAt(mActivity.mNumItems - 1, assertLastItemLocation));
-    }
-
-    public void testPreferKeyLine(final int windowAlignment,
-            final boolean preferKeyLineOverLow,
-            final boolean preferKeyLineOverHigh,
-            ItemLocation assertFirstItemLocation,
-            ItemAt assertLastItemLocation) throws Throwable {
-        testPreferKeyLine(windowAlignment, preferKeyLineOverLow, preferKeyLineOverHigh,
-                itemAt(0, assertFirstItemLocation),
-                assertLastItemLocation);
-    }
-
-    public void testPreferKeyLine(final int windowAlignment,
-            final boolean preferKeyLineOverLow,
-            final boolean preferKeyLineOverHigh,
-            ItemAt assertFirstItemLocation,
-            ItemLocation assertLastItemLocation) throws Throwable {
-        testPreferKeyLine(windowAlignment, preferKeyLineOverLow, preferKeyLineOverHigh,
-                assertFirstItemLocation,
-                itemAt(mActivity.mNumItems - 1, assertLastItemLocation));
-    }
-
-    public void testPreferKeyLine(final int windowAlignment,
-            final boolean preferKeyLineOverLow,
-            final boolean preferKeyLineOverHigh,
-            ItemAt assertFirstItemLocation,
-            ItemAt assertLastItemLocation) throws Throwable {
-        TestPreferKeyLineOptions options = new TestPreferKeyLineOptions();
-        options.mAssertItemLocations = new ItemAt[] {assertFirstItemLocation,
-                assertLastItemLocation};
-        options.mPreferKeyLineOverLow = preferKeyLineOverLow;
-        options.mPreferKeyLineOverHigh = preferKeyLineOverHigh;
-        options.mWindowAlignment = windowAlignment;
-
-        options.mRtl = false;
-        testPreferKeyLine(options);
-
-        options.mRtl = true;
-        testPreferKeyLine(options);
-    }
-
-    static class TestPreferKeyLineOptions {
-        int mWindowAlignment;
-        boolean mPreferKeyLineOverLow;
-        boolean mPreferKeyLineOverHigh;
-        ItemAt[] mAssertItemLocations;
-        boolean mRtl;
-    }
-
-    public void testPreferKeyLine(final TestPreferKeyLineOptions options) throws Throwable {
-        startWaitLayout();
-        mActivityTestRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                if (options.mRtl) {
-                    mGridView.setLayoutDirection(View.LAYOUT_DIRECTION_RTL);
-                } else {
-                    mGridView.setLayoutDirection(View.LAYOUT_DIRECTION_LTR);
-                }
-                mGridView.setWindowAlignment(options.mWindowAlignment);
-                mGridView.setWindowAlignmentOffsetPercent(50);
-                mGridView.setWindowAlignmentOffset(0);
-                mGridView.setWindowAlignmentPreferKeyLineOverLowEdge(options.mPreferKeyLineOverLow);
-                mGridView.setWindowAlignmentPreferKeyLineOverHighEdge(
-                        options.mPreferKeyLineOverHigh);
-            }
-        });
-        waitForLayout();
-
-        final int paddingStart = mGridView.getPaddingStart();
-        final int paddingEnd = mGridView.getPaddingEnd();
-        final int windowAlignCenter = mGridView.getWidth() / 2;
-
-        for (int i = 0; i < options.mAssertItemLocations.length; i++) {
-            ItemAt assertItemLocation = options.mAssertItemLocations[i];
-            setSelectedPosition(assertItemLocation.mScrollPosition);
-            View view = mGridView.findViewHolderForAdapterPosition(assertItemLocation.mPosition)
-                    .itemView;
-            switch (assertItemLocation.mLocation) {
-                case ITEM_AT_LOW:
-                    if (options.mRtl) {
-                        assertEquals(mGridView.getWidth() - paddingStart, view.getRight());
-                    } else {
-                        assertEquals(paddingStart, view.getLeft());
-                    }
-                    break;
-                case ITEM_AT_HIGH:
-                    if (options.mRtl) {
-                        assertEquals(paddingEnd, view.getLeft());
-                    } else {
-                        assertEquals(mGridView.getWidth() - paddingEnd, view.getRight());
-                    }
-                    break;
-                case ITEM_AT_KEY_LINE:
-                    assertEquals(windowAlignCenter, (view.getLeft() + view.getRight()) / 2, DELTA);
-                    break;
-            }
-        }
-    }
-
-    @Test
-    public void testPreferKeyLine1() throws Throwable {
-        prepareKeyLineTest(1);
-        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_NO_EDGE, false, false,
-                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE);
-        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_NO_EDGE, false, true,
-                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE);
-        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_NO_EDGE, true, false,
-                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE);
-        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_NO_EDGE, true, true,
-                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE);
-
-        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_LOW_EDGE, false, false,
-                ItemLocation.ITEM_AT_LOW, ItemLocation.ITEM_AT_LOW);
-        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_LOW_EDGE, false, true,
-                ItemLocation.ITEM_AT_LOW, ItemLocation.ITEM_AT_LOW);
-        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_LOW_EDGE, true, false,
-                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE);
-        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_LOW_EDGE, true, true,
-                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE);
-
-        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE, false, false,
-                ItemLocation.ITEM_AT_HIGH, ItemLocation.ITEM_AT_HIGH);
-        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE, false, true,
-                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE);
-        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE, true, false,
-                ItemLocation.ITEM_AT_HIGH, ItemLocation.ITEM_AT_HIGH);
-        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE, true, true,
-                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE);
-
-        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_BOTH_EDGE, false, false,
-                ItemLocation.ITEM_AT_LOW, ItemLocation.ITEM_AT_LOW);
-        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_BOTH_EDGE, false, true,
-                ItemLocation.ITEM_AT_LOW, ItemLocation.ITEM_AT_LOW);
-        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_BOTH_EDGE, true, false,
-                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE);
-        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_BOTH_EDGE, true, true,
-                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE);
-    }
-
-    @Test
-    public void testPreferKeyLine2() throws Throwable {
-        prepareKeyLineTest(2);
-        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_NO_EDGE, false, false,
-                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE);
-        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_NO_EDGE, false, true,
-                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE);
-        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_NO_EDGE, true, false,
-                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE);
-        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_NO_EDGE, true, true,
-                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE);
-
-        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_LOW_EDGE, false, false,
-                ItemLocation.ITEM_AT_LOW, itemAt(1, 0, ItemLocation.ITEM_AT_LOW));
-        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_LOW_EDGE, false, true,
-                ItemLocation.ITEM_AT_LOW, itemAt(1, 0, ItemLocation.ITEM_AT_LOW));
-        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_LOW_EDGE, true, false,
-                itemAt(0, 1, ItemLocation.ITEM_AT_KEY_LINE),
-                itemAt(1, 1, ItemLocation.ITEM_AT_KEY_LINE));
-        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_LOW_EDGE, true, true,
-                itemAt(0, 1, ItemLocation.ITEM_AT_KEY_LINE),
-                itemAt(1, 1, ItemLocation.ITEM_AT_KEY_LINE));
-
-        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE, false, false,
-                itemAt(0, 1, ItemLocation.ITEM_AT_HIGH),
-                itemAt(1, 1, ItemLocation.ITEM_AT_HIGH));
-        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE, false, true,
-                itemAt(0, 0, ItemLocation.ITEM_AT_KEY_LINE),
-                itemAt(1, 0, ItemLocation.ITEM_AT_KEY_LINE));
-        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE, true, false,
-                itemAt(0, 1, ItemLocation.ITEM_AT_HIGH),
-                itemAt(1, 1, ItemLocation.ITEM_AT_HIGH));
-        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE, true, true,
-                itemAt(0, 0, ItemLocation.ITEM_AT_KEY_LINE),
-                itemAt(1, 0, ItemLocation.ITEM_AT_KEY_LINE));
-
-        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_BOTH_EDGE, false, false,
-                ItemLocation.ITEM_AT_LOW, itemAt(1, 0, ItemLocation.ITEM_AT_LOW));
-        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_BOTH_EDGE, false, true,
-                ItemLocation.ITEM_AT_LOW, itemAt(1, 0, ItemLocation.ITEM_AT_LOW));
-        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_BOTH_EDGE, true, false,
-                itemAt(0, 1, ItemLocation.ITEM_AT_KEY_LINE),
-                itemAt(1, 1, ItemLocation.ITEM_AT_KEY_LINE));
-        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_BOTH_EDGE, true, true,
-                itemAt(0, 1, ItemLocation.ITEM_AT_KEY_LINE),
-                itemAt(1, 1, ItemLocation.ITEM_AT_KEY_LINE));
-    }
-
-    @Test
-    public void testPreferKeyLine10000() throws Throwable {
-        prepareKeyLineTest(10000);
-        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_NO_EDGE, false, false,
-                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE);
-        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_NO_EDGE, false, true,
-                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE);
-        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_NO_EDGE, true, false,
-                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE);
-        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_NO_EDGE, true, true,
-                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_KEY_LINE);
-
-        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_LOW_EDGE, false, false,
-                ItemLocation.ITEM_AT_LOW, ItemLocation.ITEM_AT_KEY_LINE);
-        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_LOW_EDGE, false, true,
-                ItemLocation.ITEM_AT_LOW, ItemLocation.ITEM_AT_KEY_LINE);
-        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_LOW_EDGE, true, false,
-                ItemLocation.ITEM_AT_LOW, ItemLocation.ITEM_AT_KEY_LINE);
-        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_LOW_EDGE, true, true,
-                ItemLocation.ITEM_AT_LOW, ItemLocation.ITEM_AT_KEY_LINE);
-
-        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE, false, false,
-                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_HIGH);
-        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE, false, true,
-                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_HIGH);
-        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE, true, false,
-                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_HIGH);
-        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE, true, true,
-                ItemLocation.ITEM_AT_KEY_LINE, ItemLocation.ITEM_AT_HIGH);
-
-        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_BOTH_EDGE, false, false,
-                ItemLocation.ITEM_AT_LOW, ItemLocation.ITEM_AT_HIGH);
-        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_BOTH_EDGE, false, true,
-                ItemLocation.ITEM_AT_LOW, ItemLocation.ITEM_AT_HIGH);
-        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_BOTH_EDGE, true, false,
-                ItemLocation.ITEM_AT_LOW, ItemLocation.ITEM_AT_HIGH);
-        testPreferKeyLine(VerticalGridView.WINDOW_ALIGN_BOTH_EDGE, true, true,
-                ItemLocation.ITEM_AT_LOW, ItemLocation.ITEM_AT_HIGH);
-    }
-}
diff --git a/v17/preference-leanback/api/current.txt b/v17/preference-leanback/api/current.txt
deleted file mode 100644
index 7bae10d..0000000
--- a/v17/preference-leanback/api/current.txt
+++ /dev/null
@@ -1,62 +0,0 @@
-package android.support.v17.preference {
-
-  public abstract class BaseLeanbackPreferenceFragment extends android.support.v14.preference.PreferenceFragment {
-    ctor public BaseLeanbackPreferenceFragment();
-  }
-
-  public class LeanbackListPreferenceDialogFragment extends android.support.v17.preference.LeanbackPreferenceDialogFragment {
-    ctor public LeanbackListPreferenceDialogFragment();
-    method public static android.support.v17.preference.LeanbackListPreferenceDialogFragment newInstanceMulti(java.lang.String);
-    method public static android.support.v17.preference.LeanbackListPreferenceDialogFragment newInstanceSingle(java.lang.String);
-    method public android.support.v7.widget.RecyclerView.Adapter onCreateAdapter();
-  }
-
-  public class LeanbackListPreferenceDialogFragment.AdapterMulti extends android.support.v7.widget.RecyclerView.Adapter implements android.support.v17.preference.LeanbackListPreferenceDialogFragment.ViewHolder.OnItemClickListener {
-    ctor public LeanbackListPreferenceDialogFragment.AdapterMulti(java.lang.CharSequence[], java.lang.CharSequence[], java.util.Set<java.lang.String>);
-    method public int getItemCount();
-    method public void onBindViewHolder(android.support.v17.preference.LeanbackListPreferenceDialogFragment.ViewHolder, int);
-    method public android.support.v17.preference.LeanbackListPreferenceDialogFragment.ViewHolder onCreateViewHolder(android.view.ViewGroup, int);
-    method public void onItemClick(android.support.v17.preference.LeanbackListPreferenceDialogFragment.ViewHolder);
-  }
-
-  public class LeanbackListPreferenceDialogFragment.AdapterSingle extends android.support.v7.widget.RecyclerView.Adapter implements android.support.v17.preference.LeanbackListPreferenceDialogFragment.ViewHolder.OnItemClickListener {
-    ctor public LeanbackListPreferenceDialogFragment.AdapterSingle(java.lang.CharSequence[], java.lang.CharSequence[], java.lang.CharSequence);
-    method public int getItemCount();
-    method public void onBindViewHolder(android.support.v17.preference.LeanbackListPreferenceDialogFragment.ViewHolder, int);
-    method public android.support.v17.preference.LeanbackListPreferenceDialogFragment.ViewHolder onCreateViewHolder(android.view.ViewGroup, int);
-    method public void onItemClick(android.support.v17.preference.LeanbackListPreferenceDialogFragment.ViewHolder);
-  }
-
-  public static class LeanbackListPreferenceDialogFragment.ViewHolder extends android.support.v7.widget.RecyclerView.ViewHolder implements android.view.View.OnClickListener {
-    ctor public LeanbackListPreferenceDialogFragment.ViewHolder(android.view.View, android.support.v17.preference.LeanbackListPreferenceDialogFragment.ViewHolder.OnItemClickListener);
-    method public android.view.ViewGroup getContainer();
-    method public android.widget.TextView getTitleView();
-    method public android.widget.Checkable getWidgetView();
-    method public void onClick(android.view.View);
-  }
-
-  public static abstract interface LeanbackListPreferenceDialogFragment.ViewHolder.OnItemClickListener {
-    method public abstract void onItemClick(android.support.v17.preference.LeanbackListPreferenceDialogFragment.ViewHolder);
-  }
-
-  public class LeanbackPreferenceDialogFragment extends android.app.Fragment {
-    ctor public LeanbackPreferenceDialogFragment();
-    method public android.support.v7.preference.DialogPreference getPreference();
-    field public static final java.lang.String ARG_KEY = "key";
-  }
-
-  public abstract class LeanbackPreferenceFragment extends android.support.v17.preference.BaseLeanbackPreferenceFragment {
-    ctor public LeanbackPreferenceFragment();
-    method public void setTitle(java.lang.CharSequence);
-  }
-
-  public abstract class LeanbackSettingsFragment extends android.app.Fragment {
-    ctor public LeanbackSettingsFragment();
-    method public boolean onPreferenceDisplayDialog(android.support.v14.preference.PreferenceFragment, android.support.v7.preference.Preference);
-    method public abstract void onPreferenceStartInitialScreen();
-    method public void startImmersiveFragment(android.app.Fragment);
-    method public void startPreferenceFragment(android.app.Fragment);
-  }
-
-}
-
diff --git a/v4/Android.mk b/v4/Android.mk
index a9c9145..84fd5c3 100644
--- a/v4/Android.mk
+++ b/v4/Android.mk
@@ -27,7 +27,7 @@
 LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
 # Some projects expect to inherit android-support-annotations from
 # android-support-v4, so we need to keep it static until they can be fixed.
-LOCAL_STATIC_JAVA_LIBRARIES := \
+LOCAL_STATIC_ANDROID_LIBRARIES := \
     android-support-compat \
     android-support-media-compat \
     android-support-core-utils \
diff --git a/v7/appcompat/api/27.0.0.ignore b/v7/appcompat/api/27.0.0.ignore
new file mode 100644
index 0000000..97cc753
--- /dev/null
+++ b/v7/appcompat/api/27.0.0.ignore
@@ -0,0 +1,2 @@
+b0b1638
+dfaf9f5
diff --git a/v7/appcompat/api/current.txt b/v7/appcompat/api/current.txt
index 93d0186..5d4fa6e 100644
--- a/v7/appcompat/api/current.txt
+++ b/v7/appcompat/api/current.txt
@@ -161,6 +161,7 @@
     method public android.widget.ListView getListView();
     method public void setButton(int, java.lang.CharSequence, android.os.Message);
     method public void setButton(int, java.lang.CharSequence, android.content.DialogInterface.OnClickListener);
+    method public void setButton(int, java.lang.CharSequence, android.graphics.drawable.Drawable, android.content.DialogInterface.OnClickListener);
     method public void setCustomTitle(android.view.View);
     method public void setIcon(int);
     method public void setIcon(android.graphics.drawable.Drawable);
@@ -192,14 +193,17 @@
     method public android.support.v7.app.AlertDialog.Builder setMultiChoiceItems(android.database.Cursor, java.lang.String, java.lang.String, android.content.DialogInterface.OnMultiChoiceClickListener);
     method public android.support.v7.app.AlertDialog.Builder setNegativeButton(int, android.content.DialogInterface.OnClickListener);
     method public android.support.v7.app.AlertDialog.Builder setNegativeButton(java.lang.CharSequence, android.content.DialogInterface.OnClickListener);
+    method public android.support.v7.app.AlertDialog.Builder setNegativeButtonIcon(android.graphics.drawable.Drawable);
     method public android.support.v7.app.AlertDialog.Builder setNeutralButton(int, android.content.DialogInterface.OnClickListener);
     method public android.support.v7.app.AlertDialog.Builder setNeutralButton(java.lang.CharSequence, android.content.DialogInterface.OnClickListener);
+    method public android.support.v7.app.AlertDialog.Builder setNeutralButtonIcon(android.graphics.drawable.Drawable);
     method public android.support.v7.app.AlertDialog.Builder setOnCancelListener(android.content.DialogInterface.OnCancelListener);
     method public android.support.v7.app.AlertDialog.Builder setOnDismissListener(android.content.DialogInterface.OnDismissListener);
     method public android.support.v7.app.AlertDialog.Builder setOnItemSelectedListener(android.widget.AdapterView.OnItemSelectedListener);
     method public android.support.v7.app.AlertDialog.Builder setOnKeyListener(android.content.DialogInterface.OnKeyListener);
     method public android.support.v7.app.AlertDialog.Builder setPositiveButton(int, android.content.DialogInterface.OnClickListener);
     method public android.support.v7.app.AlertDialog.Builder setPositiveButton(java.lang.CharSequence, android.content.DialogInterface.OnClickListener);
+    method public android.support.v7.app.AlertDialog.Builder setPositiveButtonIcon(android.graphics.drawable.Drawable);
     method public android.support.v7.app.AlertDialog.Builder setSingleChoiceItems(int, int, android.content.DialogInterface.OnClickListener);
     method public android.support.v7.app.AlertDialog.Builder setSingleChoiceItems(android.database.Cursor, int, java.lang.String, android.content.DialogInterface.OnClickListener);
     method public android.support.v7.app.AlertDialog.Builder setSingleChoiceItems(java.lang.CharSequence[], int, android.content.DialogInterface.OnClickListener);
@@ -211,7 +215,7 @@
     method public android.support.v7.app.AlertDialog show();
   }
 
-  public class AppCompatActivity extends android.support.v4.app.FragmentActivity implements android.support.v7.app.ActionBarDrawerToggle.DelegateProvider android.support.v7.app.AppCompatCallback {
+  public class AppCompatActivity extends android.support.v4.app.FragmentActivity implements android.support.v7.app.ActionBarDrawerToggle.DelegateProvider android.support.v7.app.AppCompatCallback android.support.v4.app.TaskStackBuilder.SupportParentable {
     ctor public AppCompatActivity();
     method public android.support.v7.app.AppCompatDelegate getDelegate();
     method public android.support.v7.app.ActionBarDrawerToggle.Delegate getDrawerToggleDelegate();
@@ -303,6 +307,24 @@
     ctor public AppCompatDialogFragment();
   }
 
+  public class AppCompatViewInflater {
+    ctor public AppCompatViewInflater();
+    method protected android.support.v7.widget.AppCompatAutoCompleteTextView createAutoCompleteTextView(android.content.Context, android.util.AttributeSet);
+    method protected android.support.v7.widget.AppCompatButton createButton(android.content.Context, android.util.AttributeSet);
+    method protected android.support.v7.widget.AppCompatCheckBox createCheckBox(android.content.Context, android.util.AttributeSet);
+    method protected android.support.v7.widget.AppCompatCheckedTextView createCheckedTextView(android.content.Context, android.util.AttributeSet);
+    method protected android.support.v7.widget.AppCompatEditText createEditText(android.content.Context, android.util.AttributeSet);
+    method protected android.support.v7.widget.AppCompatImageButton createImageButton(android.content.Context, android.util.AttributeSet);
+    method protected android.support.v7.widget.AppCompatImageView createImageView(android.content.Context, android.util.AttributeSet);
+    method protected android.support.v7.widget.AppCompatMultiAutoCompleteTextView createMultiAutoCompleteTextView(android.content.Context, android.util.AttributeSet);
+    method protected android.support.v7.widget.AppCompatRadioButton createRadioButton(android.content.Context, android.util.AttributeSet);
+    method protected android.support.v7.widget.AppCompatRatingBar createRatingBar(android.content.Context, android.util.AttributeSet);
+    method protected android.support.v7.widget.AppCompatSeekBar createSeekBar(android.content.Context, android.util.AttributeSet);
+    method protected android.support.v7.widget.AppCompatSpinner createSpinner(android.content.Context, android.util.AttributeSet);
+    method protected android.support.v7.widget.AppCompatTextView createTextView(android.content.Context, android.util.AttributeSet);
+    method protected android.view.View createView(android.content.Context, java.lang.String, android.util.AttributeSet);
+  }
+
 }
 
 package android.support.v7.content.res {
@@ -385,6 +407,15 @@
     method public abstract void onActionViewExpanded();
   }
 
+  public class ContextThemeWrapper extends android.content.ContextWrapper {
+    ctor public ContextThemeWrapper();
+    ctor public ContextThemeWrapper(android.content.Context, int);
+    ctor public ContextThemeWrapper(android.content.Context, android.content.res.Resources.Theme);
+    method public void applyOverrideConfiguration(android.content.res.Configuration);
+    method public int getThemeResId();
+    method protected void onApplyThemeResource(android.content.res.Resources.Theme, int, boolean);
+  }
+
 }
 
 package android.support.v7.widget {
@@ -425,27 +456,41 @@
     method public abstract boolean onMenuItemClick(android.view.MenuItem);
   }
 
-  public class AppCompatAutoCompleteTextView extends android.widget.AutoCompleteTextView {
+  public class AppCompatAutoCompleteTextView extends android.widget.AutoCompleteTextView implements android.support.v4.view.TintableBackgroundView {
     ctor public AppCompatAutoCompleteTextView(android.content.Context);
     ctor public AppCompatAutoCompleteTextView(android.content.Context, android.util.AttributeSet);
     ctor public AppCompatAutoCompleteTextView(android.content.Context, android.util.AttributeSet, int);
+    method public android.content.res.ColorStateList getSupportBackgroundTintList();
+    method public android.graphics.PorterDuff.Mode getSupportBackgroundTintMode();
     method public void setBackgroundDrawable(android.graphics.drawable.Drawable);
+    method public void setSupportBackgroundTintList(android.content.res.ColorStateList);
+    method public void setSupportBackgroundTintMode(android.graphics.PorterDuff.Mode);
     method public void setTextAppearance(android.content.Context, int);
   }
 
-  public class AppCompatButton extends android.widget.Button {
+  public class AppCompatButton extends android.widget.Button implements android.support.v4.widget.AutoSizeableTextView android.support.v4.view.TintableBackgroundView {
     ctor public AppCompatButton(android.content.Context);
     ctor public AppCompatButton(android.content.Context, android.util.AttributeSet);
     ctor public AppCompatButton(android.content.Context, android.util.AttributeSet, int);
+    method public android.content.res.ColorStateList getSupportBackgroundTintList();
+    method public android.graphics.PorterDuff.Mode getSupportBackgroundTintMode();
+    method public void setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) throws java.lang.IllegalArgumentException;
+    method public void setAutoSizeTextTypeUniformWithPresetSizes(int[], int) throws java.lang.IllegalArgumentException;
     method public void setBackgroundDrawable(android.graphics.drawable.Drawable);
     method public void setSupportAllCaps(boolean);
+    method public void setSupportBackgroundTintList(android.content.res.ColorStateList);
+    method public void setSupportBackgroundTintMode(android.graphics.PorterDuff.Mode);
     method public void setTextAppearance(android.content.Context, int);
   }
 
-  public class AppCompatCheckBox extends android.widget.CheckBox {
+  public class AppCompatCheckBox extends android.widget.CheckBox implements android.support.v4.widget.TintableCompoundButton {
     ctor public AppCompatCheckBox(android.content.Context);
     ctor public AppCompatCheckBox(android.content.Context, android.util.AttributeSet);
     ctor public AppCompatCheckBox(android.content.Context, android.util.AttributeSet, int);
+    method public android.content.res.ColorStateList getSupportButtonTintList();
+    method public android.graphics.PorterDuff.Mode getSupportButtonTintMode();
+    method public void setSupportButtonTintList(android.content.res.ColorStateList);
+    method public void setSupportButtonTintMode(android.graphics.PorterDuff.Mode);
   }
 
   public class AppCompatCheckedTextView extends android.widget.CheckedTextView {
@@ -455,40 +500,68 @@
     method public void setTextAppearance(android.content.Context, int);
   }
 
-  public class AppCompatEditText extends android.widget.EditText {
+  public class AppCompatEditText extends android.widget.EditText implements android.support.v4.view.TintableBackgroundView {
     ctor public AppCompatEditText(android.content.Context);
     ctor public AppCompatEditText(android.content.Context, android.util.AttributeSet);
     ctor public AppCompatEditText(android.content.Context, android.util.AttributeSet, int);
+    method public android.content.res.ColorStateList getSupportBackgroundTintList();
+    method public android.graphics.PorterDuff.Mode getSupportBackgroundTintMode();
     method public void setBackgroundDrawable(android.graphics.drawable.Drawable);
+    method public void setSupportBackgroundTintList(android.content.res.ColorStateList);
+    method public void setSupportBackgroundTintMode(android.graphics.PorterDuff.Mode);
     method public void setTextAppearance(android.content.Context, int);
   }
 
-  public class AppCompatImageButton extends android.widget.ImageButton {
+  public class AppCompatImageButton extends android.widget.ImageButton implements android.support.v4.view.TintableBackgroundView android.support.v4.widget.TintableImageSourceView {
     ctor public AppCompatImageButton(android.content.Context);
     ctor public AppCompatImageButton(android.content.Context, android.util.AttributeSet);
     ctor public AppCompatImageButton(android.content.Context, android.util.AttributeSet, int);
+    method public android.content.res.ColorStateList getSupportBackgroundTintList();
+    method public android.graphics.PorterDuff.Mode getSupportBackgroundTintMode();
+    method public android.content.res.ColorStateList getSupportImageTintList();
+    method public android.graphics.PorterDuff.Mode getSupportImageTintMode();
     method public void setBackgroundDrawable(android.graphics.drawable.Drawable);
+    method public void setSupportBackgroundTintList(android.content.res.ColorStateList);
+    method public void setSupportBackgroundTintMode(android.graphics.PorterDuff.Mode);
+    method public void setSupportImageTintList(android.content.res.ColorStateList);
+    method public void setSupportImageTintMode(android.graphics.PorterDuff.Mode);
   }
 
-  public class AppCompatImageView extends android.widget.ImageView {
+  public class AppCompatImageView extends android.widget.ImageView implements android.support.v4.view.TintableBackgroundView android.support.v4.widget.TintableImageSourceView {
     ctor public AppCompatImageView(android.content.Context);
     ctor public AppCompatImageView(android.content.Context, android.util.AttributeSet);
     ctor public AppCompatImageView(android.content.Context, android.util.AttributeSet, int);
+    method public android.content.res.ColorStateList getSupportBackgroundTintList();
+    method public android.graphics.PorterDuff.Mode getSupportBackgroundTintMode();
+    method public android.content.res.ColorStateList getSupportImageTintList();
+    method public android.graphics.PorterDuff.Mode getSupportImageTintMode();
     method public void setBackgroundDrawable(android.graphics.drawable.Drawable);
+    method public void setSupportBackgroundTintList(android.content.res.ColorStateList);
+    method public void setSupportBackgroundTintMode(android.graphics.PorterDuff.Mode);
+    method public void setSupportImageTintList(android.content.res.ColorStateList);
+    method public void setSupportImageTintMode(android.graphics.PorterDuff.Mode);
   }
 
-  public class AppCompatMultiAutoCompleteTextView extends android.widget.MultiAutoCompleteTextView {
+  public class AppCompatMultiAutoCompleteTextView extends android.widget.MultiAutoCompleteTextView implements android.support.v4.view.TintableBackgroundView {
     ctor public AppCompatMultiAutoCompleteTextView(android.content.Context);
     ctor public AppCompatMultiAutoCompleteTextView(android.content.Context, android.util.AttributeSet);
     ctor public AppCompatMultiAutoCompleteTextView(android.content.Context, android.util.AttributeSet, int);
+    method public android.content.res.ColorStateList getSupportBackgroundTintList();
+    method public android.graphics.PorterDuff.Mode getSupportBackgroundTintMode();
     method public void setBackgroundDrawable(android.graphics.drawable.Drawable);
+    method public void setSupportBackgroundTintList(android.content.res.ColorStateList);
+    method public void setSupportBackgroundTintMode(android.graphics.PorterDuff.Mode);
     method public void setTextAppearance(android.content.Context, int);
   }
 
-  public class AppCompatRadioButton extends android.widget.RadioButton {
+  public class AppCompatRadioButton extends android.widget.RadioButton implements android.support.v4.widget.TintableCompoundButton {
     ctor public AppCompatRadioButton(android.content.Context);
     ctor public AppCompatRadioButton(android.content.Context, android.util.AttributeSet);
     ctor public AppCompatRadioButton(android.content.Context, android.util.AttributeSet, int);
+    method public android.content.res.ColorStateList getSupportButtonTintList();
+    method public android.graphics.PorterDuff.Mode getSupportButtonTintMode();
+    method public void setSupportButtonTintList(android.content.res.ColorStateList);
+    method public void setSupportButtonTintMode(android.graphics.PorterDuff.Mode);
   }
 
   public class AppCompatRatingBar extends android.widget.RatingBar {
@@ -503,21 +576,31 @@
     ctor public AppCompatSeekBar(android.content.Context, android.util.AttributeSet, int);
   }
 
-  public class AppCompatSpinner extends android.widget.Spinner {
+  public class AppCompatSpinner extends android.widget.Spinner implements android.support.v4.view.TintableBackgroundView {
     ctor public AppCompatSpinner(android.content.Context);
     ctor public AppCompatSpinner(android.content.Context, int);
     ctor public AppCompatSpinner(android.content.Context, android.util.AttributeSet);
     ctor public AppCompatSpinner(android.content.Context, android.util.AttributeSet, int);
     ctor public AppCompatSpinner(android.content.Context, android.util.AttributeSet, int, int);
     ctor public AppCompatSpinner(android.content.Context, android.util.AttributeSet, int, int, android.content.res.Resources.Theme);
+    method public android.content.res.ColorStateList getSupportBackgroundTintList();
+    method public android.graphics.PorterDuff.Mode getSupportBackgroundTintMode();
     method public void setBackgroundDrawable(android.graphics.drawable.Drawable);
+    method public void setSupportBackgroundTintList(android.content.res.ColorStateList);
+    method public void setSupportBackgroundTintMode(android.graphics.PorterDuff.Mode);
   }
 
-  public class AppCompatTextView extends android.widget.TextView {
+  public class AppCompatTextView extends android.widget.TextView implements android.support.v4.widget.AutoSizeableTextView android.support.v4.view.TintableBackgroundView {
     ctor public AppCompatTextView(android.content.Context);
     ctor public AppCompatTextView(android.content.Context, android.util.AttributeSet);
     ctor public AppCompatTextView(android.content.Context, android.util.AttributeSet, int);
+    method public android.content.res.ColorStateList getSupportBackgroundTintList();
+    method public android.graphics.PorterDuff.Mode getSupportBackgroundTintMode();
+    method public void setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) throws java.lang.IllegalArgumentException;
+    method public void setAutoSizeTextTypeUniformWithPresetSizes(int[], int) throws java.lang.IllegalArgumentException;
     method public void setBackgroundDrawable(android.graphics.drawable.Drawable);
+    method public void setSupportBackgroundTintList(android.content.res.ColorStateList);
+    method public void setSupportBackgroundTintMode(android.graphics.PorterDuff.Mode);
     method public void setTextAppearance(android.content.Context, int);
   }
 
@@ -537,7 +620,6 @@
     method public float getWeightSum();
     method public boolean isBaselineAligned();
     method public boolean isMeasureWithLargestChildEnabled();
-    method protected void onLayout(boolean, int, int, int, int);
     method public void setBaselineAligned(boolean);
     method public void setBaselineAlignedChildIndex(int);
     method public void setDividerDrawable(android.graphics.drawable.Drawable);
@@ -801,7 +883,6 @@
     method public boolean hideOverflowMenu();
     method public void inflateMenu(int);
     method public boolean isOverflowMenuShowing();
-    method protected void onLayout(boolean, int, int, int, int);
     method public void setContentInsetEndWithActions(int);
     method public void setContentInsetStartWithNavigation(int);
     method public void setContentInsetsAbsolute(int, int);
diff --git a/v7/appcompat/build.gradle b/v7/appcompat/build.gradle
index ddf11f2..a3b80a8 100644
--- a/v7/appcompat/build.gradle
+++ b/v7/appcompat/build.gradle
@@ -13,11 +13,13 @@
     api(project(":support-vector-drawable"))
     api(project(":animated-vector-drawable"))
 
-    androidTestImplementation(TEST_RUNNER, libs.exclude_annotations)
-    androidTestImplementation(ESPRESSO_CORE, libs.exclude_annotations)
+    androidTestImplementation(TEST_RUNNER)
+    androidTestImplementation(ESPRESSO_CORE)
     androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
     androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
-    androidTestImplementation(project(":support-testutils"))
+    androidTestImplementation project(':support-testutils'), {
+        exclude group: 'com.android.support', module: 'appcompat-v7'
+    }
 }
 
 android {
diff --git a/v7/appcompat/res/anim/tooltip_enter.xml b/v7/appcompat/res/anim/abc_tooltip_enter.xml
similarity index 100%
rename from v7/appcompat/res/anim/tooltip_enter.xml
rename to v7/appcompat/res/anim/abc_tooltip_enter.xml
diff --git a/v7/appcompat/res/anim/tooltip_exit.xml b/v7/appcompat/res/anim/abc_tooltip_exit.xml
similarity index 100%
rename from v7/appcompat/res/anim/tooltip_exit.xml
rename to v7/appcompat/res/anim/abc_tooltip_exit.xml
diff --git a/v7/appcompat/res/drawable-watch/abc_dialog_material_background.xml b/v7/appcompat/res/drawable-watch/abc_dialog_material_background.xml
new file mode 100644
index 0000000..242761b
--- /dev/null
+++ b/v7/appcompat/res/drawable-watch/abc_dialog_material_background.xml
@@ -0,0 +1,19 @@
+<?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/white" />
+</shape>
diff --git a/v7/appcompat/res/layout-watch/abc_alert_dialog_button_bar_material.xml b/v7/appcompat/res/layout-watch/abc_alert_dialog_button_bar_material.xml
new file mode 100644
index 0000000..1c8bd93
--- /dev/null
+++ b/v7/appcompat/res/layout-watch/abc_alert_dialog_button_bar_material.xml
@@ -0,0 +1,51 @@
+<?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.
+-->
+
+<android.support.v7.widget.ButtonBarLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/buttonPanel"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:gravity="bottom"
+    android:layoutDirection="locale"
+    android:orientation="horizontal"
+    android:paddingBottom="4dp"
+    android:paddingLeft="12dp"
+    android:paddingRight="12dp"
+    android:paddingTop="4dp">
+
+    <Button
+        android:id="@android:id/button3"
+        style="?attr/buttonBarNeutralButtonStyle"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"/>
+
+    <Button
+        android:id="@android:id/button2"
+        style="?attr/buttonBarNegativeButtonStyle"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"/>
+
+    <Button
+        android:id="@android:id/button1"
+        style="?attr/buttonBarPositiveButtonStyle"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"/>
+
+</android.support.v7.widget.ButtonBarLayout>
diff --git a/v7/appcompat/res/layout-watch/abc_alert_dialog_title_material.xml b/v7/appcompat/res/layout-watch/abc_alert_dialog_title_material.xml
new file mode 100644
index 0000000..e100963
--- /dev/null
+++ b/v7/appcompat/res/layout-watch/abc_alert_dialog_title_material.xml
@@ -0,0 +1,58 @@
+<?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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+     android:id="@+id/topPanel"
+     android:layout_width="match_parent"
+     android:layout_height="wrap_content"
+     android:orientation="vertical"
+     android:gravity="top|center_horizontal">
+
+    <!-- If the client uses a customTitle, it will be added here. -->
+
+    <LinearLayout
+        android:id="@+id/title_template"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:gravity="center_horizontal"
+        android:layout_marginTop="24dp"
+        android:orientation="vertical">
+
+        <ImageView
+            android:id="@android:id/icon"
+            android:adjustViewBounds="true"
+            android:maxHeight="24dp"
+            android:maxWidth="24dp"
+            android:layout_gravity="center_horizontal"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content" />
+
+        <android.support.v7.widget.DialogTitle
+            android:id="@+id/alertTitle"
+            style="?android:attr/windowTitleStyle"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center" />
+
+    </LinearLayout>
+
+    <android.support.v4.widget.Space
+        android:id="@+id/titleDividerNoCustom"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/abc_dialog_title_divider_material"
+        android:visibility="gone"/>
+</LinearLayout>
diff --git a/v7/appcompat/res/layout/tooltip.xml b/v7/appcompat/res/layout/abc_tooltip.xml
similarity index 100%
rename from v7/appcompat/res/layout/tooltip.xml
rename to v7/appcompat/res/layout/abc_tooltip.xml
diff --git a/v7/appcompat/res/values-watch/themes_base.xml b/v7/appcompat/res/values-watch/themes_base.xml
new file mode 100644
index 0000000..20d8a7b
--- /dev/null
+++ b/v7/appcompat/res/values-watch/themes_base.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources>
+    <style name="Base.Theme.AppCompat.Dialog" parent="Base.V7.Theme.AppCompat.Dialog" >
+        <item name="android:windowIsFloating">false</item>
+    </style>
+    <style name="Base.Theme.AppCompat.Light.Dialog" parent="Base.V7.Theme.AppCompat.Light.Dialog" >
+        <item name="android:windowIsFloating">false</item>
+    </style>
+    <style name="Base.ThemeOverlay.AppCompat.Dialog" parent="Base.V7.ThemeOverlay.AppCompat.Dialog" >
+        <item name="android:windowIsFloating">false</item>
+    </style>
+</resources>
\ No newline at end of file
diff --git a/v7/appcompat/res/values/attrs.xml b/v7/appcompat/res/values/attrs.xml
index 52ae694..c48b7ba 100644
--- a/v7/appcompat/res/values/attrs.xml
+++ b/v7/appcompat/res/values/attrs.xml
@@ -407,6 +407,8 @@
         <!-- Color used for error states and things that need to be drawn to
              the user's attention. -->
         <attr name="colorError" format="reference|color" />
+
+        <attr name="viewInflaterClass" format="string" />
     </declare-styleable>
 
 
@@ -1138,6 +1140,7 @@
         <attr name="singleChoiceItemLayout" format="reference" />
         <attr name="listItemLayout" format="reference" />
         <attr name="showTitle" format="boolean" />
+        <attr name="buttonIconDimen" format="dimension" />
     </declare-styleable>
 
     <!-- @hide -->
diff --git a/v7/appcompat/res/values/dimens.xml b/v7/appcompat/res/values/dimens.xml
index a02b7c0..5bcd4b4 100644
--- a/v7/appcompat/res/values/dimens.xml
+++ b/v7/appcompat/res/values/dimens.xml
@@ -95,6 +95,9 @@
     <!-- Minimum "smallest width" of the display for cascading menus to be enabled. -->
     <dimen name="abc_cascading_menus_min_smallest_width">720dp</dimen>
 
+    <!-- Dimension of the AlertDialog button icon -->
+    <dimen name="abc_alert_dialog_button_dimen">48dp</dimen>
+
     <!-- Tooltip dimensions. -->
     <!-- Vertical offset from the edge of the anchor view for a touch-triggered tooltip. -->
     <dimen name="tooltip_y_offset_touch">16dp</dimen>
diff --git a/v7/appcompat/res/values/styles_base.xml b/v7/appcompat/res/values/styles_base.xml
index 2b2db12..c1aa3a7 100644
--- a/v7/appcompat/res/values/styles_base.xml
+++ b/v7/appcompat/res/values/styles_base.xml
@@ -520,13 +520,14 @@
         <item name="listItemLayout">@layout/select_dialog_item_material</item>
         <item name="multiChoiceItemLayout">@layout/select_dialog_multichoice_material</item>
         <item name="singleChoiceItemLayout">@layout/select_dialog_singlechoice_material</item>
+        <item name="buttonIconDimen">@dimen/abc_alert_dialog_button_dimen</item>
     </style>
 
     <style name="Base.AlertDialog.AppCompat.Light" parent="Base.AlertDialog.AppCompat" />
 
     <style name="Base.Animation.AppCompat.Tooltip" parent="android:Animation">
-        <item name="android:windowEnterAnimation">@anim/tooltip_enter</item>
-        <item name="android:windowExitAnimation">@anim/tooltip_exit</item>
+        <item name="android:windowEnterAnimation">@anim/abc_tooltip_enter</item>
+        <item name="android:windowExitAnimation">@anim/abc_tooltip_exit</item>
     </style>
 
 </resources>
diff --git a/v7/appcompat/res/values/themes_base.xml b/v7/appcompat/res/values/themes_base.xml
index a5be8ad..a9acfce 100644
--- a/v7/appcompat/res/values/themes_base.xml
+++ b/v7/appcompat/res/values/themes_base.xml
@@ -119,6 +119,7 @@
 
     <!-- Base platform-dependent theme providing an action bar in a dark-themed activity. -->
     <style name="Base.V7.Theme.AppCompat" parent="Platform.AppCompat">
+        <item name="viewInflaterClass">android.support.v7.app.AppCompatViewInflater</item>
         <item name="windowNoTitle">false</item>
         <item name="windowActionBar">true</item>
         <item name="windowActionBarOverlay">false</item>
@@ -287,6 +288,8 @@
 
     <!-- Base platform-dependent theme providing an action bar in a light-themed activity. -->
     <style name="Base.V7.Theme.AppCompat.Light" parent="Platform.AppCompat.Light">
+        <item name="viewInflaterClass">android.support.v7.app.AppCompatViewInflater</item>
+
         <item name="windowNoTitle">false</item>
         <item name="windowActionBar">true</item>
         <item name="windowActionBarOverlay">false</item>
diff --git a/v7/appcompat/src/main/java/android/support/v7/app/AlertController.java b/v7/appcompat/src/main/java/android/support/v7/app/AlertController.java
index 5ff4537..01bc449 100644
--- a/v7/appcompat/src/main/java/android/support/v7/app/AlertController.java
+++ b/v7/appcompat/src/main/java/android/support/v7/app/AlertController.java
@@ -65,6 +65,7 @@
     private final Context mContext;
     final AppCompatDialog mDialog;
     private final Window mWindow;
+    private final int mButtonIconDimen;
 
     private CharSequence mTitle;
     private CharSequence mMessage;
@@ -82,14 +83,17 @@
     Button mButtonPositive;
     private CharSequence mButtonPositiveText;
     Message mButtonPositiveMessage;
+    private Drawable mButtonPositiveIcon;
 
     Button mButtonNegative;
     private CharSequence mButtonNegativeText;
     Message mButtonNegativeMessage;
+    private Drawable mButtonNegativeIcon;
 
     Button mButtonNeutral;
     private CharSequence mButtonNeutralText;
     Message mButtonNeutralMessage;
+    private Drawable mButtonNeutralIcon;
 
     NestedScrollView mScrollView;
 
@@ -192,6 +196,7 @@
                 .getResourceId(R.styleable.AlertDialog_singleChoiceItemLayout, 0);
         mListItemLayout = a.getResourceId(R.styleable.AlertDialog_listItemLayout, 0);
         mShowTitle = a.getBoolean(R.styleable.AlertDialog_showTitle, true);
+        mButtonIconDimen = a.getDimensionPixelSize(R.styleable.AlertDialog_buttonIconDimen, 0);
 
         a.recycle();
 
@@ -298,8 +303,8 @@
     }
 
     /**
-     * Sets a click listener or a message to be sent when the button is clicked.
-     * You only need to pass one of {@code listener} or {@code msg}.
+     * Sets an icon, a click listener or a message to be sent when the button is clicked.
+     * You only need to pass one of {@code icon}, {@code listener} or {@code msg}.
      *
      * @param whichButton Which button, can be one of
      *                    {@link DialogInterface#BUTTON_POSITIVE},
@@ -308,9 +313,11 @@
      * @param text        The text to display in positive button.
      * @param listener    The {@link DialogInterface.OnClickListener} to use.
      * @param msg         The {@link Message} to be sent when clicked.
+     * @param icon        The (@link Drawable) to be used as an icon for the button.
+     *
      */
     public void setButton(int whichButton, CharSequence text,
-            DialogInterface.OnClickListener listener, Message msg) {
+            DialogInterface.OnClickListener listener, Message msg, Drawable icon) {
 
         if (msg == null && listener != null) {
             msg = mHandler.obtainMessage(whichButton, listener);
@@ -321,16 +328,19 @@
             case DialogInterface.BUTTON_POSITIVE:
                 mButtonPositiveText = text;
                 mButtonPositiveMessage = msg;
+                mButtonPositiveIcon = icon;
                 break;
 
             case DialogInterface.BUTTON_NEGATIVE:
                 mButtonNegativeText = text;
                 mButtonNegativeMessage = msg;
+                mButtonNegativeIcon = icon;
                 break;
 
             case DialogInterface.BUTTON_NEUTRAL:
                 mButtonNeutralText = text;
                 mButtonNeutralMessage = msg;
+                mButtonNeutralIcon = icon;
                 break;
 
             default:
@@ -752,35 +762,45 @@
         mButtonPositive = (Button) buttonPanel.findViewById(android.R.id.button1);
         mButtonPositive.setOnClickListener(mButtonHandler);
 
-        if (TextUtils.isEmpty(mButtonPositiveText)) {
+        if (TextUtils.isEmpty(mButtonPositiveText) && mButtonPositiveIcon == null) {
             mButtonPositive.setVisibility(View.GONE);
         } else {
             mButtonPositive.setText(mButtonPositiveText);
+            if (mButtonPositiveIcon != null) {
+                mButtonPositiveIcon.setBounds(0, 0, mButtonIconDimen, mButtonIconDimen);
+                mButtonPositive.setCompoundDrawables(mButtonPositiveIcon, null, null, null);
+            }
             mButtonPositive.setVisibility(View.VISIBLE);
             whichButtons = whichButtons | BIT_BUTTON_POSITIVE;
         }
 
-        mButtonNegative = (Button) buttonPanel.findViewById(android.R.id.button2);
+        mButtonNegative = buttonPanel.findViewById(android.R.id.button2);
         mButtonNegative.setOnClickListener(mButtonHandler);
 
-        if (TextUtils.isEmpty(mButtonNegativeText)) {
+        if (TextUtils.isEmpty(mButtonNegativeText) && mButtonNegativeIcon == null) {
             mButtonNegative.setVisibility(View.GONE);
         } else {
             mButtonNegative.setText(mButtonNegativeText);
+            if (mButtonNegativeIcon != null) {
+                mButtonNegativeIcon.setBounds(0, 0, mButtonIconDimen, mButtonIconDimen);
+                mButtonNegative.setCompoundDrawables(mButtonNegativeIcon, null, null, null);
+            }
             mButtonNegative.setVisibility(View.VISIBLE);
-
             whichButtons = whichButtons | BIT_BUTTON_NEGATIVE;
         }
 
         mButtonNeutral = (Button) buttonPanel.findViewById(android.R.id.button3);
         mButtonNeutral.setOnClickListener(mButtonHandler);
 
-        if (TextUtils.isEmpty(mButtonNeutralText)) {
+        if (TextUtils.isEmpty(mButtonNeutralText) && mButtonNeutralIcon == null) {
             mButtonNeutral.setVisibility(View.GONE);
         } else {
             mButtonNeutral.setText(mButtonNeutralText);
+            if (mButtonPositiveIcon != null) {
+                mButtonPositiveIcon.setBounds(0, 0, mButtonIconDimen, mButtonIconDimen);
+                mButtonPositive.setCompoundDrawables(mButtonPositiveIcon, null, null, null);
+            }
             mButtonNeutral.setVisibility(View.VISIBLE);
-
             whichButtons = whichButtons | BIT_BUTTON_NEUTRAL;
         }
 
@@ -852,10 +872,13 @@
         public View mCustomTitleView;
         public CharSequence mMessage;
         public CharSequence mPositiveButtonText;
+        public Drawable mPositiveButtonIcon;
         public DialogInterface.OnClickListener mPositiveButtonListener;
         public CharSequence mNegativeButtonText;
+        public Drawable mNegativeButtonIcon;
         public DialogInterface.OnClickListener mNegativeButtonListener;
         public CharSequence mNeutralButtonText;
+        public Drawable mNeutralButtonIcon;
         public DialogInterface.OnClickListener mNeutralButtonListener;
         public boolean mCancelable;
         public DialogInterface.OnCancelListener mOnCancelListener;
@@ -923,17 +946,17 @@
             if (mMessage != null) {
                 dialog.setMessage(mMessage);
             }
-            if (mPositiveButtonText != null) {
+            if (mPositiveButtonText != null || mPositiveButtonIcon != null) {
                 dialog.setButton(DialogInterface.BUTTON_POSITIVE, mPositiveButtonText,
-                        mPositiveButtonListener, null);
+                        mPositiveButtonListener, null, mPositiveButtonIcon);
             }
-            if (mNegativeButtonText != null) {
+            if (mNegativeButtonText != null || mNegativeButtonIcon != null) {
                 dialog.setButton(DialogInterface.BUTTON_NEGATIVE, mNegativeButtonText,
-                        mNegativeButtonListener, null);
+                        mNegativeButtonListener, null, mNegativeButtonIcon);
             }
-            if (mNeutralButtonText != null) {
+            if (mNeutralButtonText != null || mNeutralButtonIcon != null) {
                 dialog.setButton(DialogInterface.BUTTON_NEUTRAL, mNeutralButtonText,
-                        mNeutralButtonListener, null);
+                        mNeutralButtonListener, null, mNeutralButtonIcon);
             }
             // For a list, the client can either supply an array of items or an
             // adapter or a cursor
diff --git a/v7/appcompat/src/main/java/android/support/v7/app/AlertDialog.java b/v7/appcompat/src/main/java/android/support/v7/app/AlertDialog.java
index 4b87dcc..1712f20 100644
--- a/v7/appcompat/src/main/java/android/support/v7/app/AlertDialog.java
+++ b/v7/appcompat/src/main/java/android/support/v7/app/AlertDialog.java
@@ -207,7 +207,7 @@
      * @param msg         The {@link Message} to be sent when clicked.
      */
     public void setButton(int whichButton, CharSequence text, Message msg) {
-        mAlert.setButton(whichButton, text, null, msg);
+        mAlert.setButton(whichButton, text, null, msg, null);
     }
 
     /**
@@ -222,7 +222,25 @@
      * @param listener    The {@link DialogInterface.OnClickListener} to use.
      */
     public void setButton(int whichButton, CharSequence text, OnClickListener listener) {
-        mAlert.setButton(whichButton, text, listener, null);
+        mAlert.setButton(whichButton, text, listener, null, null);
+    }
+
+    /**
+     * Sets an icon to be displayed along with the button text and a listener to be invoked when
+     * the positive button of the dialog is pressed. This method has no effect if called after
+     * {@link #show()}.
+     *
+     * @param whichButton Which button to set the listener on, can be one of
+     *                    {@link DialogInterface#BUTTON_POSITIVE},
+     *                    {@link DialogInterface#BUTTON_NEGATIVE}, or
+     *                    {@link DialogInterface#BUTTON_NEUTRAL}
+     * @param text        The text to display in positive button.
+     * @param listener    The {@link DialogInterface.OnClickListener} to use.
+     * @param icon        The {@link Drawable} to be set as an icon for the button.
+     */
+    public void setButton(int whichButton, CharSequence text, Drawable icon,
+            OnClickListener listener) {
+        mAlert.setButton(whichButton, text, listener, null,  icon);
     }
 
     /**
@@ -470,6 +488,16 @@
         }
 
         /**
+         * Set an icon to be displayed for the positive button.
+         * @param icon The icon to be displayed
+         * @return This Builder object to allow for chaining of calls to set methods
+         */
+        public Builder setPositiveButtonIcon(Drawable icon) {
+            P.mPositiveButtonIcon = icon;
+            return this;
+        }
+
+        /**
          * Set a listener to be invoked when the negative button of the dialog is pressed.
          * @param textId The resource id of the text to display in the negative button
          * @param listener The {@link DialogInterface.OnClickListener} to use.
@@ -496,6 +524,16 @@
         }
 
         /**
+         * Set an icon to be displayed for the negative button.
+         * @param icon The icon to be displayed
+         * @return This Builder object to allow for chaining of calls to set methods
+         */
+        public Builder setNegativeButtonIcon(Drawable icon) {
+            P.mNegativeButtonIcon = icon;
+            return this;
+        }
+
+        /**
          * Set a listener to be invoked when the neutral button of the dialog is pressed.
          * @param textId The resource id of the text to display in the neutral button
          * @param listener The {@link DialogInterface.OnClickListener} to use.
@@ -522,6 +560,16 @@
         }
 
         /**
+         * Set an icon to be displayed for the neutral button.
+         * @param icon The icon to be displayed
+         * @return This Builder object to allow for chaining of calls to set methods
+         */
+        public Builder setNeutralButtonIcon(Drawable icon) {
+            P.mNeutralButtonIcon = icon;
+            return this;
+        }
+
+        /**
          * Sets whether the dialog is cancelable or not.  Default is true.
          *
          * @return This Builder object to allow for chaining of calls to set methods
diff --git a/v7/appcompat/src/main/java/android/support/v7/app/AppCompatDelegateImplV9.java b/v7/appcompat/src/main/java/android/support/v7/app/AppCompatDelegateImplV9.java
index 056e33e..5b53401 100644
--- a/v7/appcompat/src/main/java/android/support/v7/app/AppCompatDelegateImplV9.java
+++ b/v7/appcompat/src/main/java/android/support/v7/app/AppCompatDelegateImplV9.java
@@ -1001,7 +1001,26 @@
     public View createView(View parent, final String name, @NonNull Context context,
             @NonNull AttributeSet attrs) {
         if (mAppCompatViewInflater == null) {
-            mAppCompatViewInflater = new AppCompatViewInflater();
+            TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
+            String viewInflaterClassName =
+                    a.getString(R.styleable.AppCompatTheme_viewInflaterClass);
+            if ((viewInflaterClassName == null)
+                    || AppCompatViewInflater.class.getName().equals(viewInflaterClassName)) {
+                // Either default class name or set explicitly to null. In both cases
+                // create the base inflater (no reflection)
+                mAppCompatViewInflater = new AppCompatViewInflater();
+            } else {
+                try {
+                    Class viewInflaterClass = Class.forName(viewInflaterClassName);
+                    mAppCompatViewInflater =
+                            (AppCompatViewInflater) viewInflaterClass.getDeclaredConstructor()
+                                    .newInstance();
+                } catch (Throwable t) {
+                    Log.i(TAG, "Failed to instantiate custom view inflater "
+                            + viewInflaterClassName + ". Falling back to default.", t);
+                    mAppCompatViewInflater = new AppCompatViewInflater();
+                }
+            }
         }
 
         boolean inheritContext = false;
diff --git a/v7/appcompat/src/main/java/android/support/v7/app/AppCompatViewInflater.java b/v7/appcompat/src/main/java/android/support/v7/app/AppCompatViewInflater.java
index 54d01bc..87a1a3c 100644
--- a/v7/appcompat/src/main/java/android/support/v7/app/AppCompatViewInflater.java
+++ b/v7/appcompat/src/main/java/android/support/v7/app/AppCompatViewInflater.java
@@ -51,14 +51,12 @@
 import java.util.Map;
 
 /**
- * This class is responsible for manually inflating our tinted widgets which are used on devices
- * running {@link android.os.Build.VERSION_CODES#KITKAT KITKAT} or below. As such, this class
- * should only be used when running on those devices.
+ * This class is responsible for manually inflating our tinted widgets.
  * <p>This class two main responsibilities: the first is to 'inject' our tinted views in place of
  * the framework versions in layout inflation; the second is backport the {@code android:theme}
  * functionality for any inflated widgets. This include theme inheritance from its parent.
  */
-class AppCompatViewInflater {
+public class AppCompatViewInflater {
 
     private static final Class<?>[] sConstructorSignature = new Class[]{
             Context.class, AttributeSet.class};
@@ -77,7 +75,7 @@
 
     private final Object[] mConstructorArgs = new Object[2];
 
-    public final View createView(View parent, final String name, @NonNull Context context,
+    final View createView(View parent, final String name, @NonNull Context context,
             @NonNull AttributeSet attrs, boolean inheritContext,
             boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
         final Context originalContext = context;
@@ -100,44 +98,63 @@
         // We need to 'inject' our tint aware Views in place of the standard framework versions
         switch (name) {
             case "TextView":
-                view = new AppCompatTextView(context, attrs);
+                view = createTextView(context, attrs);
+                verifyNotNull(view, name);
                 break;
             case "ImageView":
-                view = new AppCompatImageView(context, attrs);
+                view = createImageView(context, attrs);
+                verifyNotNull(view, name);
                 break;
             case "Button":
-                view = new AppCompatButton(context, attrs);
+                view = createButton(context, attrs);
+                verifyNotNull(view, name);
                 break;
             case "EditText":
-                view = new AppCompatEditText(context, attrs);
+                view = createEditText(context, attrs);
+                verifyNotNull(view, name);
                 break;
             case "Spinner":
-                view = new AppCompatSpinner(context, attrs);
+                view = createSpinner(context, attrs);
+                verifyNotNull(view, name);
                 break;
             case "ImageButton":
-                view = new AppCompatImageButton(context, attrs);
+                view = createImageButton(context, attrs);
+                verifyNotNull(view, name);
                 break;
             case "CheckBox":
-                view = new AppCompatCheckBox(context, attrs);
+                view = createCheckBox(context, attrs);
+                verifyNotNull(view, name);
                 break;
             case "RadioButton":
-                view = new AppCompatRadioButton(context, attrs);
+                view = createRadioButton(context, attrs);
+                verifyNotNull(view, name);
                 break;
             case "CheckedTextView":
-                view = new AppCompatCheckedTextView(context, attrs);
+                view = createCheckedTextView(context, attrs);
+                verifyNotNull(view, name);
                 break;
             case "AutoCompleteTextView":
-                view = new AppCompatAutoCompleteTextView(context, attrs);
+                view = createAutoCompleteTextView(context, attrs);
+                verifyNotNull(view, name);
                 break;
             case "MultiAutoCompleteTextView":
-                view = new AppCompatMultiAutoCompleteTextView(context, attrs);
+                view = createMultiAutoCompleteTextView(context, attrs);
+                verifyNotNull(view, name);
                 break;
             case "RatingBar":
-                view = new AppCompatRatingBar(context, attrs);
+                view = createRatingBar(context, attrs);
+                verifyNotNull(view, name);
                 break;
             case "SeekBar":
-                view = new AppCompatSeekBar(context, attrs);
+                view = createSeekBar(context, attrs);
+                verifyNotNull(view, name);
                 break;
+            default:
+                // The fallback that allows extending class to take over view inflation
+                // for other tags. Note that we don't check that the result is not-null.
+                // That allows the custom inflater path to fall back on the default one
+                // later in this method.
+                view = createView(context, name, attrs);
         }
 
         if (view == null && originalContext != context) {
@@ -154,6 +171,85 @@
         return view;
     }
 
+    @NonNull
+    protected AppCompatTextView createTextView(Context context, AttributeSet attrs) {
+        return new AppCompatTextView(context, attrs);
+    }
+
+    @NonNull
+    protected AppCompatImageView createImageView(Context context, AttributeSet attrs) {
+        return new AppCompatImageView(context, attrs);
+    }
+
+    @NonNull
+    protected AppCompatButton createButton(Context context, AttributeSet attrs) {
+        return new AppCompatButton(context, attrs);
+    }
+
+    @NonNull
+    protected AppCompatEditText createEditText(Context context, AttributeSet attrs) {
+        return new AppCompatEditText(context, attrs);
+    }
+
+    @NonNull
+    protected AppCompatSpinner createSpinner(Context context, AttributeSet attrs) {
+        return new AppCompatSpinner(context, attrs);
+    }
+
+    @NonNull
+    protected AppCompatImageButton createImageButton(Context context, AttributeSet attrs) {
+        return new AppCompatImageButton(context, attrs);
+    }
+
+    @NonNull
+    protected AppCompatCheckBox createCheckBox(Context context, AttributeSet attrs) {
+        return new AppCompatCheckBox(context, attrs);
+    }
+
+    @NonNull
+    protected AppCompatRadioButton createRadioButton(Context context, AttributeSet attrs) {
+        return new AppCompatRadioButton(context, attrs);
+    }
+
+    @NonNull
+    protected AppCompatCheckedTextView createCheckedTextView(Context context, AttributeSet attrs) {
+        return new AppCompatCheckedTextView(context, attrs);
+    }
+
+    @NonNull
+    protected AppCompatAutoCompleteTextView createAutoCompleteTextView(Context context,
+            AttributeSet attrs) {
+        return new AppCompatAutoCompleteTextView(context, attrs);
+    }
+
+    @NonNull
+    protected AppCompatMultiAutoCompleteTextView createMultiAutoCompleteTextView(Context context,
+            AttributeSet attrs) {
+        return new AppCompatMultiAutoCompleteTextView(context, attrs);
+    }
+
+    @NonNull
+    protected AppCompatRatingBar createRatingBar(Context context, AttributeSet attrs) {
+        return new AppCompatRatingBar(context, attrs);
+    }
+
+    @NonNull
+    protected AppCompatSeekBar createSeekBar(Context context, AttributeSet attrs) {
+        return new AppCompatSeekBar(context, attrs);
+    }
+
+    private void verifyNotNull(View view, String name) {
+        if (view == null) {
+            throw new IllegalStateException(this.getClass().getName()
+                    + " asked to inflate view for <" + name + ">, but returned null");
+        }
+    }
+
+    @Nullable
+    protected View createView(Context context, String name, AttributeSet attrs) {
+        return null;
+    }
+
     private View createViewFromTag(Context context, String name, AttributeSet attrs) {
         if (name.equals("view")) {
             name = attrs.getAttributeValue(null, "class");
@@ -165,14 +261,14 @@
 
             if (-1 == name.indexOf('.')) {
                 for (int i = 0; i < sClassPrefixList.length; i++) {
-                    final View view = createView(context, name, sClassPrefixList[i]);
+                    final View view = createViewByPrefix(context, name, sClassPrefixList[i]);
                     if (view != null) {
                         return view;
                     }
                 }
                 return null;
             } else {
-                return createView(context, name, null);
+                return createViewByPrefix(context, name, null);
             }
         } catch (Exception e) {
             // We do not want to catch these, lets return null and let the actual LayoutInflater
@@ -209,7 +305,7 @@
         a.recycle();
     }
 
-    private View createView(Context context, String name, String prefix)
+    private View createViewByPrefix(Context context, String name, String prefix)
             throws ClassNotFoundException, InflateException {
         Constructor<? extends View> constructor = sConstructorMap.get(name);
 
diff --git a/v7/appcompat/src/main/java/android/support/v7/view/ContextThemeWrapper.java b/v7/appcompat/src/main/java/android/support/v7/view/ContextThemeWrapper.java
index aa5b36e..cc63480 100644
--- a/v7/appcompat/src/main/java/android/support/v7/view/ContextThemeWrapper.java
+++ b/v7/appcompat/src/main/java/android/support/v7/view/ContextThemeWrapper.java
@@ -16,26 +16,19 @@
 
 package android.support.v7.view;
 
-import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
-
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.content.res.AssetManager;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.os.Build;
-import android.support.annotation.RestrictTo;
 import android.support.annotation.StyleRes;
 import android.support.v7.appcompat.R;
 import android.view.LayoutInflater;
 
 /**
- * A ContextWrapper that allows you to modify the theme from what is in the
- * wrapped context.
- *
- * @hide
+ * A context wrapper that allows you to modify or replace the theme of the wrapped context.
  */
-@RestrictTo(LIBRARY_GROUP)
 public class ContextThemeWrapper extends ContextWrapper {
     private int mThemeResource;
     private Resources.Theme mTheme;
@@ -110,15 +103,6 @@
         mOverrideConfiguration = new Configuration(overrideConfiguration);
     }
 
-    /**
-     * Used by ActivityThread to apply the overridden configuration to onConfigurationChange
-     * callbacks.
-     * @hide
-     */
-    public Configuration getOverrideConfiguration() {
-        return mOverrideConfiguration;
-    }
-
     @Override
     public Resources getResources() {
         return getResourcesInternal();
@@ -144,6 +128,10 @@
         }
     }
 
+    /**
+     * Returns the resource ID of the theme that is to be applied on top of the base context's
+     * theme.
+     */
     public int getThemeResId() {
         return mThemeResource;
     }
diff --git a/v7/appcompat/src/main/java/android/support/v7/view/menu/CascadingMenuPopup.java b/v7/appcompat/src/main/java/android/support/v7/view/menu/CascadingMenuPopup.java
index 73499cf..834f854 100644
--- a/v7/appcompat/src/main/java/android/support/v7/view/menu/CascadingMenuPopup.java
+++ b/v7/appcompat/src/main/java/android/support/v7/view/menu/CascadingMenuPopup.java
@@ -54,7 +54,6 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
-import java.util.LinkedList;
 import java.util.List;
 
 /**
@@ -85,7 +84,7 @@
     final Handler mSubMenuHoverHandler;
 
     /** List of menus that were added before this popup was shown. */
-    private final List<MenuBuilder> mPendingMenus = new LinkedList<>();
+    private final List<MenuBuilder> mPendingMenus = new ArrayList<>();
 
     /**
      * List of open menus. The first item is the root menu and each
@@ -404,14 +403,14 @@
             final boolean showOnRight = nextMenuPosition == HORIZ_POSITION_RIGHT;
             mLastPosition = nextMenuPosition;
 
-            final int parentOffsetLeft;
-            final int parentOffsetTop;
+            final int parentOffsetX;
+            final int parentOffsetY;
             if (Build.VERSION.SDK_INT >= 26) {
                 // Anchor the submenu directly to the parent menu item view. This allows for
                 // accurate submenu positioning when the parent menu is being moved.
                 popupWindow.setAnchorView(parentView);
-                parentOffsetLeft = 0;
-                parentOffsetTop = 0;
+                parentOffsetX = 0;
+                parentOffsetY = 0;
             } else {
                 // Framework does not allow anchoring to a view in another popup window. Use the
                 // same top-level anchor as the parent menu is using, with appropriate offsets.
@@ -428,10 +427,19 @@
                 final int[] parentViewScreenLocation = new int[2];
                 parentView.getLocationOnScreen(parentViewScreenLocation);
 
+                // For Gravity.LEFT case, the baseline is just the left border of the view. So we
+                // can use the X of the location directly. But for Gravity.RIGHT case, the baseline
+                // is the right border. So we need add view's width with the location to make the
+                // baseline as the right border correctly.
+                if ((mDropDownGravity & (Gravity.RIGHT | Gravity.LEFT)) == Gravity.RIGHT) {
+                    anchorScreenLocation[0] += mAnchorView.getWidth();
+                    parentViewScreenLocation[0] += parentView.getWidth();
+                }
+
                 // If used as horizontal/vertical offsets, these values would position the submenu
                 // at the exact same position as the parent item.
-                parentOffsetLeft = parentViewScreenLocation[0] - anchorScreenLocation[0];
-                parentOffsetTop = parentViewScreenLocation[1] - anchorScreenLocation[1];
+                parentOffsetX = parentViewScreenLocation[0] - anchorScreenLocation[0];
+                parentOffsetY = parentViewScreenLocation[1] - anchorScreenLocation[1];
             }
 
             // Adjust the horizontal offset to display the submenu to the right or to the left
@@ -441,22 +449,22 @@
             final int x;
             if ((mDropDownGravity & Gravity.RIGHT) == Gravity.RIGHT) {
                 if (showOnRight) {
-                    x = parentOffsetLeft + menuWidth;
+                    x = parentOffsetX + menuWidth;
                 } else {
-                    x = parentOffsetLeft - parentView.getWidth();
+                    x = parentOffsetX - parentView.getWidth();
                 }
             } else {
                 if (showOnRight) {
-                    x = parentOffsetLeft + parentView.getWidth();
+                    x = parentOffsetX + parentView.getWidth();
                 } else {
-                    x = parentOffsetLeft - menuWidth;
+                    x = parentOffsetX - menuWidth;
                 }
             }
             popupWindow.setHorizontalOffset(x);
 
             // Vertically align with the parent item.
             popupWindow.setOverlapAnchor(true);
-            popupWindow.setVerticalOffset(parentOffsetTop);
+            popupWindow.setVerticalOffset(parentOffsetY);
         } else {
             if (mHasXOffset) {
                 popupWindow.setHorizontalOffset(mXOffset);
diff --git a/v7/appcompat/src/main/java/android/support/v7/widget/ActionMenuView.java b/v7/appcompat/src/main/java/android/support/v7/widget/ActionMenuView.java
index 76e06da..14723a0 100644
--- a/v7/appcompat/src/main/java/android/support/v7/widget/ActionMenuView.java
+++ b/v7/appcompat/src/main/java/android/support/v7/widget/ActionMenuView.java
@@ -268,10 +268,10 @@
                 // Mark indices of children that can receive an extra cell.
                 if (lp.cellsUsed < minCells) {
                     minCells = lp.cellsUsed;
-                    minCellsAt = 1 << i;
+                    minCellsAt = 1L << i;
                     minCellsItemCount = 1;
                 } else if (lp.cellsUsed == minCells) {
-                    minCellsAt |= 1 << i;
+                    minCellsAt |= 1L << i;
                     minCellsItemCount++;
                 }
             }
diff --git a/v7/appcompat/src/main/java/android/support/v7/widget/AppCompatTextViewAutoSizeHelper.java b/v7/appcompat/src/main/java/android/support/v7/widget/AppCompatTextViewAutoSizeHelper.java
index e82e469..7e98494 100644
--- a/v7/appcompat/src/main/java/android/support/v7/widget/AppCompatTextViewAutoSizeHelper.java
+++ b/v7/appcompat/src/main/java/android/support/v7/widget/AppCompatTextViewAutoSizeHelper.java
@@ -45,8 +45,8 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.Hashtable;
 import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
 
 /**
  * Utility class which encapsulates the logic for the TextView auto-size text feature added to
@@ -66,7 +66,8 @@
     private static final int DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX = 1;
     // Cache of TextView methods used via reflection; the key is the method name and the value is
     // the method itself or null if it can not be found.
-    private static Hashtable<String, Method> sTextViewMethodByNameCache = new Hashtable<>();
+    private static ConcurrentHashMap<String, Method> sTextViewMethodByNameCache =
+            new ConcurrentHashMap<>();
     // Use this to specify that any of the auto-size configuration int values have not been set.
     static final float UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE = -1f;
     // Ported from TextView#VERY_WIDE. Represents a maximum width in pixels the TextView takes when
diff --git a/v7/appcompat/src/main/java/android/support/v7/widget/ListPopupWindow.java b/v7/appcompat/src/main/java/android/support/v7/widget/ListPopupWindow.java
index edc9781..b98197c 100644
--- a/v7/appcompat/src/main/java/android/support/v7/widget/ListPopupWindow.java
+++ b/v7/appcompat/src/main/java/android/support/v7/widget/ListPopupWindow.java
@@ -283,7 +283,7 @@
             mAdapter.unregisterDataSetObserver(mObserver);
         }
         mAdapter = adapter;
-        if (mAdapter != null) {
+        if (adapter != null) {
             adapter.registerDataSetObserver(mObserver);
         }
 
diff --git a/v7/appcompat/src/main/java/android/support/v7/widget/TooltipPopup.java b/v7/appcompat/src/main/java/android/support/v7/widget/TooltipPopup.java
index f707c8f..396fe05 100644
--- a/v7/appcompat/src/main/java/android/support/v7/widget/TooltipPopup.java
+++ b/v7/appcompat/src/main/java/android/support/v7/widget/TooltipPopup.java
@@ -31,6 +31,7 @@
 import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.view.ViewGroup;
 import android.view.WindowManager;
 import android.widget.TextView;
 
@@ -56,7 +57,7 @@
     TooltipPopup(Context context) {
         mContext = context;
 
-        mContentView = LayoutInflater.from(mContext).inflate(R.layout.tooltip, null);
+        mContentView = LayoutInflater.from(mContext).inflate(R.layout.abc_tooltip, null);
         mMessageView = (TextView) mContentView.findViewById(R.id.message);
 
         mLayoutParams.setTitle(getClass().getSimpleName());
@@ -99,6 +100,7 @@
 
     private void computePosition(View anchorView, int anchorX, int anchorY, boolean fromTouch,
             WindowManager.LayoutParams outParams) {
+        outParams.token = anchorView.getApplicationWindowToken();
         final int tooltipPreciseAnchorThreshold = mContext.getResources().getDimensionPixelOffset(
                 R.dimen.tooltip_precise_anchor_threshold);
 
@@ -157,7 +159,7 @@
         mTmpAnchorPos[1] -= mTmpAppPos[1];
         // mTmpAnchorPos is now relative to the main app window.
 
-        outParams.x = mTmpAnchorPos[0] + offsetX - mTmpDisplayFrame.width() / 2;
+        outParams.x = mTmpAnchorPos[0] + offsetX - appView.getWidth() / 2;
 
         final int spec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
         mContentView.measure(spec, spec);
@@ -181,6 +183,16 @@
     }
 
     private static View getAppRootView(View anchorView) {
+        View rootView = anchorView.getRootView();
+        ViewGroup.LayoutParams lp = rootView.getLayoutParams();
+        if (lp instanceof WindowManager.LayoutParams
+                && (((WindowManager.LayoutParams) lp).type
+                    == WindowManager.LayoutParams.TYPE_APPLICATION)) {
+            // This covers regular app windows and Dialog windows.
+            return rootView;
+        }
+        // For non-application window types (such as popup windows) try to find the main app window
+        // through the context.
         Context context = anchorView.getContext();
         while (context instanceof ContextWrapper) {
             if (context instanceof Activity) {
@@ -189,6 +201,8 @@
                 context = ((ContextWrapper) context).getBaseContext();
             }
         }
-        return anchorView.getRootView();
+        // Main app window not found, fall back to the anchor's root view. There is no guarantee
+        // that the tooltip position will be computed correctly.
+        return rootView;
     }
 }
diff --git a/v7/appcompat/tests/AndroidManifest.xml b/v7/appcompat/tests/AndroidManifest.xml
index f986869..a85e4ab 100644
--- a/v7/appcompat/tests/AndroidManifest.xml
+++ b/v7/appcompat/tests/AndroidManifest.xml
@@ -121,6 +121,21 @@
         <activity
             android:name="android.support.v7.app.AppCompatVectorDrawableIntegrationActivity"/>
 
+        <activity
+            android:name="android.support.v7.app.AppCompatInflaterDefaultActivity"/>
+
+        <activity
+            android:name="android.support.v7.app.AppCompatInflaterBadClassNameActivity"
+            android:theme="@style/Theme.CustomInflaterBadClassName"/>
+
+        <activity
+            android:name="android.support.v7.app.AppCompatInflaterNullActivity"
+            android:theme="@style/Theme.CustomInflaterNull"/>
+
+        <activity
+            android:name="android.support.v7.app.AppCompatInflaterCustomActivity"
+            android:theme="@style/Theme.CustomInflater"/>
+
     </application>
 
 </manifest>
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_inflater_activity.xml b/v7/appcompat/tests/res/layout/appcompat_inflater_activity.xml
new file mode 100644
index 0000000..3a27041
--- /dev/null
+++ b/v7/appcompat/tests/res/layout/appcompat_inflater_activity.xml
@@ -0,0 +1,84 @@
+<?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.
+  -->
+
+<ScrollView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/container"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+
+        <Button
+            android:id="@+id/button"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/sample_text1" />
+
+        <android.support.v7.widget.AppCompatButton
+            android:id="@+id/ac_button"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/sample_text1" />
+
+        <TextView
+            android:id="@+id/textview"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/sample_text2" />
+
+        <android.support.v7.widget.AppCompatTextView
+            android:id="@+id/ac_textview"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/sample_text2" />
+
+        <RadioButton
+            android:id="@+id/radiobutton"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/sample_text1" />
+
+        <ImageButton
+            android:id="@+id/imagebutton"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:src="@drawable/test_drawable_blue" />
+
+        <Spinner
+            android:id="@+id/spinner"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:entries="@array/planets_array" />
+
+        <ToggleButton
+            android:id="@+id/togglebutton"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/sample_text1" />
+
+        <ScrollView
+            android:id="@+id/scrollview"
+            android:layout_width="match_parent"
+            android:layout_height="100dp" />
+
+    </LinearLayout>
+
+</ScrollView>
diff --git a/v7/appcompat/tests/res/layout/appcompat_textview_activity.xml b/v7/appcompat/tests/res/layout/appcompat_textview_activity.xml
index 3841206..1ca3459 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/res/values/styles.xml b/v7/appcompat/tests/res/values/styles.xml
index 68aa09f..9693b3a 100644
--- a/v7/appcompat/tests/res/values/styles.xml
+++ b/v7/appcompat/tests/res/values/styles.xml
@@ -31,6 +31,18 @@
         <item name="android:textColorLink">@color/color_state_list_link</item>
     </style>
 
+    <style name="Theme.CustomInflater" parent="@style/Theme.AppCompat.Light">
+        <item name="viewInflaterClass">android.support.v7.app.inflater.CustomViewInflater</item>
+    </style>
+
+    <style name="Theme.CustomInflaterBadClassName" parent="@style/Theme.AppCompat.Light">
+        <item name="viewInflaterClass">invalid.class.name</item>
+    </style>
+
+    <style name="Theme.CustomInflaterNull" parent="@style/Theme.AppCompat.Light">
+        <item name="viewInflaterClass">@null</item>
+    </style>
+
     <style name="TextStyleAllCapsOn" parent="@android:style/TextAppearance.Medium">
         <item name="textAllCaps">true</item>
     </style>
@@ -86,4 +98,6 @@
     <style name="TextView_Typeface_Monospace">
         <item name="android:typeface">monospace</item>
     </style>
+
+    <style name="TextAppearance" parent="TextAppearance.AppCompat" />
 </resources>
diff --git a/v7/appcompat/tests/src/android/support/v7/app/AppCompatInflaterBadClassNameActivity.java b/v7/appcompat/tests/src/android/support/v7/app/AppCompatInflaterBadClassNameActivity.java
new file mode 100644
index 0000000..2d64277
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/app/AppCompatInflaterBadClassNameActivity.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v7.app;
+
+import android.support.v7.appcompat.test.R;
+import android.support.v7.testutils.BaseTestActivity;
+
+public class AppCompatInflaterBadClassNameActivity extends BaseTestActivity {
+    @Override
+    protected int getContentViewLayoutResId() {
+        return R.layout.appcompat_inflater_activity;
+    }
+}
diff --git a/v7/appcompat/tests/src/android/support/v7/app/AppCompatInflaterBadClassNameTest.java b/v7/appcompat/tests/src/android/support/v7/app/AppCompatInflaterBadClassNameTest.java
new file mode 100644
index 0000000..5f6e824
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/app/AppCompatInflaterBadClassNameTest.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v7.app;
+
+import android.support.test.filters.SmallTest;
+
+/**
+ * Testing the default view inflation where appcompat views are used for specific
+ * views in layouts specified in XML.
+ */
+@SmallTest
+public class AppCompatInflaterBadClassNameTest extends
+        AppCompatInflaterPassTest<AppCompatInflaterBadClassNameActivity> {
+    public AppCompatInflaterBadClassNameTest() {
+        super(AppCompatInflaterBadClassNameActivity.class);
+    }
+}
diff --git a/v7/appcompat/tests/src/android/support/v7/app/AppCompatInflaterCustomActivity.java b/v7/appcompat/tests/src/android/support/v7/app/AppCompatInflaterCustomActivity.java
new file mode 100644
index 0000000..7ec9cdc
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/app/AppCompatInflaterCustomActivity.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v7.app;
+
+import android.support.v7.appcompat.test.R;
+import android.support.v7.testutils.BaseTestActivity;
+
+public class AppCompatInflaterCustomActivity extends BaseTestActivity {
+    @Override
+    protected int getContentViewLayoutResId() {
+        return R.layout.appcompat_inflater_activity;
+    }
+}
diff --git a/v7/appcompat/tests/src/android/support/v7/app/AppCompatInflaterCustomTest.java b/v7/appcompat/tests/src/android/support/v7/app/AppCompatInflaterCustomTest.java
new file mode 100644
index 0000000..1e66635
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/app/AppCompatInflaterCustomTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v7.app;
+
+import static org.junit.Assert.assertEquals;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.v7.app.inflater.CustomViewInflater;
+import android.support.v7.appcompat.test.R;
+import android.support.v7.widget.AppCompatButton;
+import android.support.v7.widget.AppCompatRadioButton;
+import android.support.v7.widget.AppCompatSpinner;
+import android.support.v7.widget.AppCompatTextView;
+import android.view.ViewGroup;
+import android.widget.ScrollView;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+/**
+ * Testing the custom view inflation where app-specific views are used for specific
+ * views in layouts specified in XML.
+ */
+@SmallTest
+public class AppCompatInflaterCustomTest {
+    private ViewGroup mContainer;
+    private AppCompatInflaterCustomActivity mActivity;
+
+    @Rule
+    public final ActivityTestRule<AppCompatInflaterCustomActivity> mActivityTestRule =
+            new ActivityTestRule<>(AppCompatInflaterCustomActivity.class);
+
+    @Before
+    public void setUp() {
+        mActivity = mActivityTestRule.getActivity();
+        mContainer = mActivity.findViewById(R.id.container);
+    }
+
+    @Test
+    public void testViewClasses() {
+        // View defined as <Button> should be inflated as CustomButton
+        assertEquals(CustomViewInflater.CustomButton.class,
+                mContainer.findViewById(R.id.button).getClass());
+
+        // View defined as <AppCompatButton> should be inflated as AppCompatButton
+        assertEquals(AppCompatButton.class, mContainer.findViewById(R.id.ac_button).getClass());
+
+        // View defined as <TextView> should be inflated as CustomTextView
+        assertEquals(CustomViewInflater.CustomTextView.class,
+                mContainer.findViewById(R.id.textview).getClass());
+
+        // View defined as <AppCompatTextView> should be inflated as AppCompatTextView
+        assertEquals(AppCompatTextView.class, mContainer.findViewById(R.id.ac_textview).getClass());
+
+        // View defined as <RadioButton> should be inflated as AppCompatRadioButton
+        assertEquals(AppCompatRadioButton.class,
+                mContainer.findViewById(R.id.radiobutton).getClass());
+
+        // View defined as <ImageButton> should be inflated as CustomImageButton
+        assertEquals(CustomViewInflater.CustomImageButton.class,
+                mContainer.findViewById(R.id.imagebutton).getClass());
+
+        // View defined as <Spinner> should be inflated as AppCompatSpinner
+        assertEquals(AppCompatSpinner.class, mContainer.findViewById(R.id.spinner).getClass());
+
+        // View defined as <ToggleButton> should be inflated as CustomToggleButton
+        assertEquals(CustomViewInflater.CustomToggleButton.class,
+                mContainer.findViewById(R.id.togglebutton).getClass());
+
+        // View defined as <ScrollView> should be inflated as ScrollView
+        assertEquals(ScrollView.class,
+                mContainer.findViewById(R.id.scrollview).getClass());
+    }
+
+}
diff --git a/v7/appcompat/tests/src/android/support/v7/app/AppCompatInflaterDefaultActivity.java b/v7/appcompat/tests/src/android/support/v7/app/AppCompatInflaterDefaultActivity.java
new file mode 100644
index 0000000..28b99ce
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/app/AppCompatInflaterDefaultActivity.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v7.app;
+
+import android.support.v7.appcompat.test.R;
+import android.support.v7.testutils.BaseTestActivity;
+
+public class AppCompatInflaterDefaultActivity extends BaseTestActivity {
+    @Override
+    protected int getContentViewLayoutResId() {
+        return R.layout.appcompat_inflater_activity;
+    }
+}
diff --git a/v7/appcompat/tests/src/android/support/v7/app/AppCompatInflaterDefaultTest.java b/v7/appcompat/tests/src/android/support/v7/app/AppCompatInflaterDefaultTest.java
new file mode 100644
index 0000000..d96060b
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/app/AppCompatInflaterDefaultTest.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v7.app;
+
+import android.support.test.filters.SmallTest;
+
+/**
+ * Testing the default view inflation where appcompat views are used for specific
+ * views in layouts specified in XML.
+ */
+@SmallTest
+public class AppCompatInflaterDefaultTest extends
+        AppCompatInflaterPassTest<AppCompatInflaterDefaultActivity> {
+    public AppCompatInflaterDefaultTest() {
+        super(AppCompatInflaterDefaultActivity.class);
+    }
+}
diff --git a/v7/appcompat/tests/src/android/support/v7/app/AppCompatInflaterNullActivity.java b/v7/appcompat/tests/src/android/support/v7/app/AppCompatInflaterNullActivity.java
new file mode 100644
index 0000000..db75790
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/app/AppCompatInflaterNullActivity.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v7.app;
+
+import android.support.v7.appcompat.test.R;
+import android.support.v7.testutils.BaseTestActivity;
+
+public class AppCompatInflaterNullActivity extends BaseTestActivity {
+    @Override
+    protected int getContentViewLayoutResId() {
+        return R.layout.appcompat_inflater_activity;
+    }
+}
diff --git a/v7/appcompat/tests/src/android/support/v7/app/AppCompatInflaterNullTest.java b/v7/appcompat/tests/src/android/support/v7/app/AppCompatInflaterNullTest.java
new file mode 100644
index 0000000..b1d39e1
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/app/AppCompatInflaterNullTest.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v7.app;
+
+import android.support.test.filters.SmallTest;
+
+/**
+ * Testing the default view inflation where appcompat views are used for specific
+ * views in layouts specified in XML.
+ */
+@SmallTest
+public class AppCompatInflaterNullTest extends
+        AppCompatInflaterPassTest<AppCompatInflaterNullActivity> {
+    public AppCompatInflaterNullTest() {
+        super(AppCompatInflaterNullActivity.class);
+    }
+}
diff --git a/v7/appcompat/tests/src/android/support/v7/app/AppCompatInflaterPassTest.java b/v7/appcompat/tests/src/android/support/v7/app/AppCompatInflaterPassTest.java
new file mode 100644
index 0000000..e8a2763
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/app/AppCompatInflaterPassTest.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v7.app;
+
+import static org.junit.Assert.assertEquals;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v7.appcompat.test.R;
+import android.support.v7.testutils.BaseTestActivity;
+import android.support.v7.widget.AppCompatButton;
+import android.support.v7.widget.AppCompatImageButton;
+import android.support.v7.widget.AppCompatRadioButton;
+import android.support.v7.widget.AppCompatSpinner;
+import android.support.v7.widget.AppCompatTextView;
+import android.view.ViewGroup;
+import android.widget.ScrollView;
+import android.widget.ToggleButton;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Testing the default view inflation where appcompat views are used for specific
+ * views in layouts specified in XML.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public abstract class AppCompatInflaterPassTest<A extends BaseTestActivity> {
+    private ViewGroup mContainer;
+    private A mActivity;
+
+    @Rule
+    public final ActivityTestRule<A> mActivityTestRule;
+
+    public AppCompatInflaterPassTest(Class clazz) {
+        mActivityTestRule = new ActivityTestRule<A>(clazz);
+    }
+
+    @Before
+    public void setUp() {
+        mActivity = mActivityTestRule.getActivity();
+        mContainer = mActivity.findViewById(R.id.container);
+    }
+
+    @Test
+    public void testViewClasses() {
+        // View defined as <Button> should be inflated as AppCompatButton
+        assertEquals(AppCompatButton.class, mContainer.findViewById(R.id.button).getClass());
+
+        // View defined as <AppCompatButton> should be inflated as AppCompatButton
+        assertEquals(AppCompatButton.class, mContainer.findViewById(R.id.ac_button).getClass());
+
+        // View defined as <TextView> should be inflated as AppCompatTextView
+        assertEquals(AppCompatTextView.class, mContainer.findViewById(R.id.textview).getClass());
+
+        // View defined as <AppCompatTextView> should be inflated as AppCompatTextView
+        assertEquals(AppCompatTextView.class, mContainer.findViewById(R.id.ac_textview).getClass());
+
+        // View defined as <RadioButton> should be inflated as AppCompatRadioButton
+        assertEquals(AppCompatRadioButton.class,
+                mContainer.findViewById(R.id.radiobutton).getClass());
+
+        // View defined as <ImageButton> should be inflated as AppCompatImageButton
+        assertEquals(AppCompatImageButton.class,
+                mContainer.findViewById(R.id.imagebutton).getClass());
+
+        // View defined as <Spinner> should be inflated as AppCompatSpinner
+        assertEquals(AppCompatSpinner.class, mContainer.findViewById(R.id.spinner).getClass());
+
+        // View defined as <ToggleButton> should be inflated as ToggleButton
+        assertEquals(ToggleButton.class,
+                mContainer.findViewById(R.id.togglebutton).getClass());
+
+        // View defined as <ScrollView> should be inflated as ScrollView
+        assertEquals(ScrollView.class,
+                mContainer.findViewById(R.id.scrollview).getClass());
+    }
+
+}
diff --git a/v7/appcompat/tests/src/android/support/v7/app/NightModeTestCase.java b/v7/appcompat/tests/src/android/support/v7/app/NightModeTestCase.java
index 2981ad4..d42174f 100644
--- a/v7/appcompat/tests/src/android/support/v7/app/NightModeTestCase.java
+++ b/v7/appcompat/tests/src/android/support/v7/app/NightModeTestCase.java
@@ -23,13 +23,15 @@
 import static android.support.v7.app.NightModeActivity.TOP_ACTIVITY;
 import static android.support.v7.testutils.TestUtilsMatchers.isBackground;
 
-import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
 import android.app.Instrumentation;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.LargeTest;
 import android.support.test.rule.ActivityTestRule;
 import android.support.test.runner.AndroidJUnit4;
+import android.support.testutils.AppCompatActivityUtils;
+import android.support.testutils.RecreatedAppCompatActivity;
 import android.support.v4.content.ContextCompat;
 import android.support.v7.appcompat.test.R;
 
@@ -38,6 +40,9 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
 @LargeTest
 @RunWith(AndroidJUnit4.class)
 public class NightModeTestCase {
@@ -100,30 +105,31 @@
         TwilightManager.setInstance(twilightManager);
 
         final NightModeActivity activity = mActivityTestRule.getActivity();
-        final AppCompatDelegateImplV14 delegate = (AppCompatDelegateImplV14) activity.getDelegate();
 
         // Verify that we're currently in day mode
         onView(withId(R.id.text_night_mode)).check(matches(withText(STRING_DAY)));
 
-        // Now set MODE_NIGHT_AUTO so that we will change to night mode automatically
-        setLocalNightModeAndWaitForRecreate(activity, AppCompatDelegate.MODE_NIGHT_AUTO);
+        // Set MODE_NIGHT_AUTO so that we will change to night mode automatically
+        final NightModeActivity newActivity =
+                setLocalNightModeAndWaitForRecreate(activity, AppCompatDelegate.MODE_NIGHT_AUTO);
+        final AppCompatDelegateImplV14 newDelegate =
+                (AppCompatDelegateImplV14) newActivity.getDelegate();
 
-        // Assert that the original Activity has not been destroyed yet
-        assertFalse(activity.isDestroyed());
-
-        // Now update the fake twilight manager to be in night and trigger a fake 'time' change
+        // Update the fake twilight manager to be in night and trigger a fake 'time' change
         mActivityTestRule.runOnUiThread(new Runnable() {
             @Override
             public void run() {
                 twilightManager.setIsNight(true);
-                delegate.getAutoNightModeManager().dispatchTimeChanged();
+                newDelegate.getAutoNightModeManager().dispatchTimeChanged();
             }
         });
 
-        // Now wait for the recreate
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        RecreatedAppCompatActivity.sResumed = new CountDownLatch(1);
+        assertTrue(RecreatedAppCompatActivity.sResumed.await(1, TimeUnit.SECONDS));
+        // At this point recreate that has been triggered by dispatchTimeChanged call
+        // has completed
 
-        // Now check that the text has changed, signifying that night resources are being used
+        // Check that the text has changed, signifying that night resources are being used
         onView(withId(R.id.text_night_mode)).check(matches(withText(STRING_NIGHT)));
     }
 
@@ -133,28 +139,30 @@
         final FakeTwilightManager twilightManager = new FakeTwilightManager();
         TwilightManager.setInstance(twilightManager);
 
-        final NightModeActivity activity = mActivityTestRule.getActivity();
+        NightModeActivity activity = mActivityTestRule.getActivity();
 
         // Set MODE_NIGHT_AUTO so that we will change to night mode automatically
-        setLocalNightModeAndWaitForRecreate(activity, AppCompatDelegate.MODE_NIGHT_AUTO);
+        activity = setLocalNightModeAndWaitForRecreate(activity, AppCompatDelegate.MODE_NIGHT_AUTO);
         // Verify that we're currently in day mode
         onView(withId(R.id.text_night_mode)).check(matches(withText(STRING_DAY)));
 
+        final NightModeActivity toTest = activity;
+
         mActivityTestRule.runOnUiThread(new Runnable() {
             @Override
             public void run() {
                 final Instrumentation instrumentation =
                         InstrumentationRegistry.getInstrumentation();
                 // Now fool the Activity into thinking that it has gone into the background
-                instrumentation.callActivityOnPause(activity);
-                instrumentation.callActivityOnStop(activity);
+                instrumentation.callActivityOnPause(toTest);
+                instrumentation.callActivityOnStop(toTest);
 
                 // Now update the twilight manager while the Activity is in the 'background'
                 twilightManager.setIsNight(true);
 
                 // Now tell the Activity that it has gone into the foreground again
-                instrumentation.callActivityOnStart(activity);
-                instrumentation.callActivityOnResume(activity);
+                instrumentation.callActivityOnStart(toTest);
+                instrumentation.callActivityOnResume(toTest);
             }
         });
 
@@ -179,7 +187,8 @@
         }
     }
 
-    private void setLocalNightModeAndWaitForRecreate(final AppCompatActivity activity,
+    private NightModeActivity setLocalNightModeAndWaitForRecreate(
+            final NightModeActivity activity,
             @AppCompatDelegate.NightMode final int nightMode) throws Throwable {
         final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
         mActivityTestRule.runOnUiThread(new Runnable() {
@@ -188,6 +197,11 @@
                 activity.getDelegate().setLocalNightMode(nightMode);
             }
         });
+        final NightModeActivity result =
+                AppCompatActivityUtils.recreateActivity(mActivityTestRule, activity);
+        AppCompatActivityUtils.waitForExecution(mActivityTestRule);
+
         instrumentation.waitForIdleSync();
+        return result;
     }
 }
diff --git a/v7/appcompat/tests/src/android/support/v7/app/inflater/CustomViewInflater.java b/v7/appcompat/tests/src/android/support/v7/app/inflater/CustomViewInflater.java
new file mode 100644
index 0000000..7876499
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/app/inflater/CustomViewInflater.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.app.inflater;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v7.app.AppCompatViewInflater;
+import android.support.v7.widget.AppCompatButton;
+import android.support.v7.widget.AppCompatImageButton;
+import android.support.v7.widget.AppCompatTextView;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ToggleButton;
+
+/**
+ * Custom view inflater that takes over the inflation of a few widget types.
+ */
+public class CustomViewInflater extends AppCompatViewInflater {
+    public static class CustomTextView extends AppCompatTextView {
+        public CustomTextView(Context context) {
+            super(context);
+        }
+
+        public CustomTextView(Context context,
+                @Nullable AttributeSet attrs) {
+            super(context, attrs);
+        }
+
+        public CustomTextView(Context context,
+                @Nullable AttributeSet attrs, int defStyleAttr) {
+            super(context, attrs, defStyleAttr);
+        }
+    }
+
+    public static class CustomButton extends AppCompatButton {
+        public CustomButton(Context context) {
+            super(context);
+        }
+
+        public CustomButton(Context context, AttributeSet attrs) {
+            super(context, attrs);
+        }
+
+        public CustomButton(Context context, AttributeSet attrs, int defStyleAttr) {
+            super(context, attrs, defStyleAttr);
+        }
+    }
+
+    public static class CustomImageButton extends AppCompatImageButton {
+        public CustomImageButton(Context context) {
+            super(context);
+        }
+
+        public CustomImageButton(Context context, AttributeSet attrs) {
+            super(context, attrs);
+        }
+
+        public CustomImageButton(Context context, AttributeSet attrs, int defStyleAttr) {
+            super(context, attrs, defStyleAttr);
+        }
+    }
+
+    public static class CustomToggleButton extends ToggleButton {
+        public CustomToggleButton(Context context, AttributeSet attrs, int defStyleAttr,
+                int defStyleRes) {
+            super(context, attrs, defStyleAttr, defStyleRes);
+        }
+
+        public CustomToggleButton(Context context, AttributeSet attrs, int defStyleAttr) {
+            super(context, attrs, defStyleAttr);
+        }
+
+        public CustomToggleButton(Context context, AttributeSet attrs) {
+            super(context, attrs);
+        }
+
+        public CustomToggleButton(Context context) {
+            super(context);
+        }
+    }
+
+    @NonNull
+    @Override
+    protected AppCompatButton createButton(Context context, AttributeSet attrs) {
+        return new CustomButton(context, attrs);
+    }
+
+    @NonNull
+    @Override
+    protected AppCompatTextView createTextView(Context context, AttributeSet attrs) {
+        return new CustomTextView(context, attrs);
+    }
+
+    @NonNull
+    @Override
+    protected AppCompatImageButton createImageButton(Context context, AttributeSet attrs) {
+        return new CustomImageButton(context, attrs);
+    }
+
+    @Nullable
+    @Override
+    protected View createView(Context context, String name, AttributeSet attrs) {
+        if (name.equals("ToggleButton")) {
+            return new CustomToggleButton(context, attrs);
+        }
+        return null;
+    }
+}
diff --git a/v7/appcompat/tests/src/android/support/v7/app/inflater/MisbehavingViewInflater.java b/v7/appcompat/tests/src/android/support/v7/app/inflater/MisbehavingViewInflater.java
new file mode 100644
index 0000000..21c4ffc
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/app/inflater/MisbehavingViewInflater.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.app.inflater;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.v7.app.AppCompatViewInflater;
+import android.support.v7.widget.AppCompatButton;
+import android.util.AttributeSet;
+
+/**
+ * Custom view inflater that declares that it takes over the view inflation but
+ * does not honor the contract to return non-null instance in its
+ * {@link #createButton(Context, AttributeSet)} method.
+ */
+public class MisbehavingViewInflater extends AppCompatViewInflater {
+    @NonNull
+    @Override
+    protected AppCompatButton createButton(Context context, AttributeSet attrs) {
+        return null;
+    }
+}
diff --git a/v7/appcompat/tests/src/android/support/v7/testutils/BaseTestActivity.java b/v7/appcompat/tests/src/android/support/v7/testutils/BaseTestActivity.java
index 8ed22ad..e4dbf26 100644
--- a/v7/appcompat/tests/src/android/support/v7/testutils/BaseTestActivity.java
+++ b/v7/appcompat/tests/src/android/support/v7/testutils/BaseTestActivity.java
@@ -19,7 +19,7 @@
 import android.os.Bundle;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
-import android.support.v7.app.AppCompatActivity;
+import android.support.testutils.RecreatedAppCompatActivity;
 import android.support.v7.app.AppCompatCallback;
 import android.support.v7.appcompat.test.R;
 import android.support.v7.view.ActionMode;
@@ -28,7 +28,7 @@
 import android.view.MenuItem;
 import android.view.WindowManager;
 
-public abstract class BaseTestActivity extends AppCompatActivity {
+public abstract class BaseTestActivity extends RecreatedAppCompatActivity {
 
     private Menu mMenu;
 
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/view/ContextThemeWrapperTest.java b/v7/appcompat/tests/src/android/support/v7/view/ContextThemeWrapperTest.java
new file mode 100644
index 0000000..ab6b1da
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/view/ContextThemeWrapperTest.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.view;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.content.res.TypedArray;
+import android.os.Build;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v7.appcompat.test.R;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ContextThemeWrapperTest {
+    private static final int SYSTEM_DEFAULT_THEME = 0;
+
+    private Context mContext;
+
+    private static class MockContextThemeWrapper extends ContextThemeWrapper {
+        boolean mIsOnApplyThemeResourceCalled;
+        MockContextThemeWrapper(Context base, int themeres) {
+            super(base, themeres);
+        }
+
+        @Override
+        protected void onApplyThemeResource(Theme theme, int resid, boolean first) {
+            mIsOnApplyThemeResourceCalled = true;
+            super.onApplyThemeResource(theme, resid, first);
+        }
+    }
+
+    @Before
+    public void setup() {
+        mContext = InstrumentationRegistry.getTargetContext();
+    }
+
+    @Test
+    public void testConstructor() {
+        new ContextThemeWrapper();
+
+        new ContextThemeWrapper(mContext, R.style.TextAppearance);
+
+        new ContextThemeWrapper(mContext, mContext.getTheme());
+    }
+
+    @Test
+    public void testAccessTheme() {
+        ContextThemeWrapper contextThemeWrapper = new ContextThemeWrapper(
+                mContext, SYSTEM_DEFAULT_THEME);
+        // set Theme to TextAppearance
+        contextThemeWrapper.setTheme(R.style.TextAppearance);
+        TypedArray ta = contextThemeWrapper.getTheme().obtainStyledAttributes(
+                R.styleable.TextAppearance);
+
+        // assert theme style of TextAppearance
+        verifyIdenticalTextAppearanceStyle(ta);
+    }
+
+    @Test
+    public void testGetSystemService() {
+        // Note that we can't use Mockito since ContextThemeWrapper.onApplyThemeResource is
+        // protected
+        final MockContextThemeWrapper contextThemeWrapper =
+                new MockContextThemeWrapper(mContext, R.style.TextAppearance);
+        contextThemeWrapper.getTheme();
+        assertTrue(contextThemeWrapper.mIsOnApplyThemeResourceCalled);
+
+        // All service get from contextThemeWrapper just the same as this context get,
+        // except Context.LAYOUT_INFLATER_SERVICE.
+        assertEquals(mContext.getSystemService(Context.ACTIVITY_SERVICE),
+                contextThemeWrapper.getSystemService(Context.ACTIVITY_SERVICE));
+        assertNotSame(mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE),
+                contextThemeWrapper.getSystemService(Context.LAYOUT_INFLATER_SERVICE));
+    }
+
+    @Test
+    public void testAttachBaseContext() {
+        assertTrue((new ContextThemeWrapper() {
+            public boolean test() {
+                // Set two different context to ContextThemeWrapper
+                // it should throw a exception when set it at second time.
+                // As ContextThemeWrapper is a context, we will attachBaseContext to
+                // two different ContextThemeWrapper instances.
+                try {
+                    attachBaseContext(new ContextThemeWrapper(mContext,
+                            R.style.TextAppearance));
+                } catch (IllegalStateException e) {
+                    fail("test attachBaseContext fail");
+                }
+
+                try {
+                    attachBaseContext(new ContextThemeWrapper());
+                    fail("test attachBaseContext fail");
+                } catch (IllegalStateException e) {
+                    // expected
+                }
+                return true;
+            }
+        }).test());
+    }
+
+    @Test
+    public void testApplyOverrideConfiguration() {
+        // Configuration.densityApi is only available on API 17 and above
+        if (Build.VERSION.SDK_INT >= 17) {
+            final int realDensity = mContext.getResources().getConfiguration().densityDpi;
+            final int expectedDensity = realDensity + 1;
+
+            ContextThemeWrapper contextThemeWrapper = new ContextThemeWrapper(
+                    mContext, SYSTEM_DEFAULT_THEME);
+
+            Configuration overrideConfig = new Configuration();
+            overrideConfig.densityDpi = expectedDensity;
+            contextThemeWrapper.applyOverrideConfiguration(overrideConfig);
+
+            Configuration actualConfiguration =
+                    contextThemeWrapper.getResources().getConfiguration();
+            assertEquals(expectedDensity, actualConfiguration.densityDpi);
+        }
+    }
+
+    private void verifyIdenticalTextAppearanceStyle(TypedArray ta) {
+        final int defValue = -1;
+        // get Theme and assert
+        Resources.Theme expected = mContext.getResources().newTheme();
+        expected.setTo(mContext.getTheme());
+        expected.applyStyle(R.style.TextAppearance, true);
+        TypedArray expectedTa = expected.obtainStyledAttributes(R.styleable.TextAppearance);
+        assertEquals(expectedTa.getIndexCount(), ta.getIndexCount());
+        assertEquals(expectedTa.getColor(
+                android.support.v7.appcompat.R.styleable.TextAppearance_android_textColor,
+                defValue),
+                ta.getColor(
+                        android.support.v7.appcompat.R.styleable.TextAppearance_android_textColor,
+                        defValue));
+        assertEquals(expectedTa.getColor(
+                android.support.v7.appcompat.R.styleable.TextAppearance_android_textColorHint,
+                defValue),
+                ta.getColor(
+                        android.support.v7.appcompat.R.styleable
+                                .TextAppearance_android_textColorHint,
+                        defValue));
+        assertEquals(expectedTa.getColor(
+                android.support.v7.appcompat.R.styleable.TextAppearance_android_textColorLink,
+                defValue),
+                ta.getColor(
+                        android.support.v7.appcompat.R.styleable
+                                .TextAppearance_android_textColorLink,
+                        defValue));
+        assertEquals(expectedTa.getDimension(
+                android.support.v7.appcompat.R.styleable.TextAppearance_android_textSize,
+                defValue),
+                ta.getDimension(
+                        android.support.v7.appcompat.R.styleable.TextAppearance_android_textSize,
+                        defValue), 0.0f);
+        assertEquals(expectedTa.getInt(
+                android.support.v7.appcompat.R.styleable.TextAppearance_android_textStyle,
+                defValue),
+                ta.getInt(android.support.v7.appcompat.R.styleable
+                                .TextAppearance_android_textStyle,
+                        defValue));
+    }
+}
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 34890ed..eb52653 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,40 @@
 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.SdkSuppress;
 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 +62,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);
@@ -306,10 +354,11 @@
         assertEquals(Typeface.SERIF, textView.getTypeface());
     }
 
+    @SdkSuppress(minSdkVersion = 16)
     @Test
     @UiThreadTest
     public void testfontFamilyNamespaceHierarchy() {
-        // This view has fontFamilyset in both the app and android namespace. App should be used.
+        // This view has fontFamily set in both the app and android namespace. App should be used.
         TextView textView = mContainer.findViewById(R.id.textview_app_and_android_fontfamily);
 
         assertEquals(Typeface.MONOSPACE, textView.getTypeface());
diff --git a/v7/cardview/res/values/attrs.xml b/v7/cardview/res/values/attrs.xml
index deed51b..8bac9cc 100644
--- a/v7/cardview/res/values/attrs.xml
+++ b/v7/cardview/res/values/attrs.xml
@@ -15,6 +15,9 @@
 -->
 
 <resources>
+    <!-- Default CardView style -->
+    <attr name="cardViewStyle" format="reference" />
+
     <declare-styleable name="CardView">
         <!-- Background color for CardView. -->
         <attr name="cardBackgroundColor" format="color" />
diff --git a/v7/cardview/src/main/java/android/support/v7/widget/CardView.java b/v7/cardview/src/main/java/android/support/v7/widget/CardView.java
index 58a04f0..a45ee98 100644
--- a/v7/cardview/src/main/java/android/support/v7/widget/CardView.java
+++ b/v7/cardview/src/main/java/android/support/v7/widget/CardView.java
@@ -108,18 +108,57 @@
     final Rect mShadowBounds = new Rect();
 
     public CardView(@NonNull Context context) {
-        super(context);
-        initialize(context, null, 0);
+        this(context, null);
     }
 
     public CardView(@NonNull Context context, @Nullable AttributeSet attrs) {
-        super(context, attrs);
-        initialize(context, attrs, 0);
+        this(context, attrs, R.attr.cardViewStyle);
     }
 
     public CardView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
-        initialize(context, attrs, defStyleAttr);
+
+        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CardView, defStyleAttr,
+                R.style.CardView);
+        ColorStateList backgroundColor;
+        if (a.hasValue(R.styleable.CardView_cardBackgroundColor)) {
+            backgroundColor = a.getColorStateList(R.styleable.CardView_cardBackgroundColor);
+        } else {
+            // There isn't one set, so we'll compute one based on the theme
+            final TypedArray aa = getContext().obtainStyledAttributes(COLOR_BACKGROUND_ATTR);
+            final int themeColorBackground = aa.getColor(0, 0);
+            aa.recycle();
+
+            // If the theme colorBackground is light, use our own light color, otherwise dark
+            final float[] hsv = new float[3];
+            Color.colorToHSV(themeColorBackground, hsv);
+            backgroundColor = ColorStateList.valueOf(hsv[2] > 0.5f
+                    ? getResources().getColor(R.color.cardview_light_background)
+                    : getResources().getColor(R.color.cardview_dark_background));
+        }
+        float radius = a.getDimension(R.styleable.CardView_cardCornerRadius, 0);
+        float elevation = a.getDimension(R.styleable.CardView_cardElevation, 0);
+        float maxElevation = a.getDimension(R.styleable.CardView_cardMaxElevation, 0);
+        mCompatPadding = a.getBoolean(R.styleable.CardView_cardUseCompatPadding, false);
+        mPreventCornerOverlap = a.getBoolean(R.styleable.CardView_cardPreventCornerOverlap, true);
+        int defaultPadding = a.getDimensionPixelSize(R.styleable.CardView_contentPadding, 0);
+        mContentPadding.left = a.getDimensionPixelSize(R.styleable.CardView_contentPaddingLeft,
+                defaultPadding);
+        mContentPadding.top = a.getDimensionPixelSize(R.styleable.CardView_contentPaddingTop,
+                defaultPadding);
+        mContentPadding.right = a.getDimensionPixelSize(R.styleable.CardView_contentPaddingRight,
+                defaultPadding);
+        mContentPadding.bottom = a.getDimensionPixelSize(R.styleable.CardView_contentPaddingBottom,
+                defaultPadding);
+        if (elevation > maxElevation) {
+            maxElevation = elevation;
+        }
+        mUserSetMinWidth = a.getDimensionPixelSize(R.styleable.CardView_android_minWidth, 0);
+        mUserSetMinHeight = a.getDimensionPixelSize(R.styleable.CardView_android_minHeight, 0);
+        a.recycle();
+
+        IMPL.initialize(mCardViewDelegate, context, backgroundColor, radius,
+                elevation, maxElevation);
     }
 
     @Override
@@ -220,50 +259,6 @@
         }
     }
 
-    private void initialize(Context context, AttributeSet attrs, int defStyleAttr) {
-        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CardView, defStyleAttr,
-                R.style.CardView);
-        ColorStateList backgroundColor;
-        if (a.hasValue(R.styleable.CardView_cardBackgroundColor)) {
-            backgroundColor = a.getColorStateList(R.styleable.CardView_cardBackgroundColor);
-        } else {
-            // There isn't one set, so we'll compute one based on the theme
-            final TypedArray aa = getContext().obtainStyledAttributes(COLOR_BACKGROUND_ATTR);
-            final int themeColorBackground = aa.getColor(0, 0);
-            aa.recycle();
-
-            // If the theme colorBackground is light, use our own light color, otherwise dark
-            final float[] hsv = new float[3];
-            Color.colorToHSV(themeColorBackground, hsv);
-            backgroundColor = ColorStateList.valueOf(hsv[2] > 0.5f
-                    ? getResources().getColor(R.color.cardview_light_background)
-                    : getResources().getColor(R.color.cardview_dark_background));
-        }
-        float radius = a.getDimension(R.styleable.CardView_cardCornerRadius, 0);
-        float elevation = a.getDimension(R.styleable.CardView_cardElevation, 0);
-        float maxElevation = a.getDimension(R.styleable.CardView_cardMaxElevation, 0);
-        mCompatPadding = a.getBoolean(R.styleable.CardView_cardUseCompatPadding, false);
-        mPreventCornerOverlap = a.getBoolean(R.styleable.CardView_cardPreventCornerOverlap, true);
-        int defaultPadding = a.getDimensionPixelSize(R.styleable.CardView_contentPadding, 0);
-        mContentPadding.left = a.getDimensionPixelSize(R.styleable.CardView_contentPaddingLeft,
-                defaultPadding);
-        mContentPadding.top = a.getDimensionPixelSize(R.styleable.CardView_contentPaddingTop,
-                defaultPadding);
-        mContentPadding.right = a.getDimensionPixelSize(R.styleable.CardView_contentPaddingRight,
-                defaultPadding);
-        mContentPadding.bottom = a.getDimensionPixelSize(R.styleable.CardView_contentPaddingBottom,
-                defaultPadding);
-        if (elevation > maxElevation) {
-            maxElevation = elevation;
-        }
-        mUserSetMinWidth = a.getDimensionPixelSize(R.styleable.CardView_android_minWidth, 0);
-        mUserSetMinHeight = a.getDimensionPixelSize(R.styleable.CardView_android_minHeight, 0);
-        a.recycle();
-
-        IMPL.initialize(mCardViewDelegate, context, backgroundColor, radius,
-                elevation, maxElevation);
-    }
-
     @Override
     public void setMinimumWidth(int minWidth) {
         mUserSetMinWidth = minWidth;
diff --git a/v7/gridlayout/api/27.0.0.ignore b/v7/gridlayout/api/27.0.0.ignore
new file mode 100644
index 0000000..938da3f
--- /dev/null
+++ b/v7/gridlayout/api/27.0.0.ignore
@@ -0,0 +1 @@
+7420ef1
diff --git a/v7/gridlayout/api/current.txt b/v7/gridlayout/api/current.txt
index 1fc6e1d..9f12b89 100644
--- a/v7/gridlayout/api/current.txt
+++ b/v7/gridlayout/api/current.txt
@@ -15,7 +15,6 @@
     method public boolean getUseDefaultMargins();
     method public boolean isColumnOrderPreserved();
     method public boolean isRowOrderPreserved();
-    method protected void onLayout(boolean, int, int, int, int);
     method public void setAlignmentMode(int);
     method public void setColumnCount(int);
     method public void setColumnOrderPreserved(boolean);
diff --git a/v7/gridlayout/build.gradle b/v7/gridlayout/build.gradle
index dc3a494..7df5397 100644
--- a/v7/gridlayout/build.gradle
+++ b/v7/gridlayout/build.gradle
@@ -10,8 +10,8 @@
     api(project(":support-compat"))
     api(project(":support-core-ui"))
 
-    androidTestImplementation(TEST_RUNNER, libs.exclude_annotations)
-    androidTestImplementation(ESPRESSO_CORE, libs.exclude_annotations)
+    androidTestImplementation(TEST_RUNNER)
+    androidTestImplementation(ESPRESSO_CORE)
 }
 
 android {
diff --git a/v7/mediarouter/OWNERS b/v7/mediarouter/OWNERS
new file mode 100644
index 0000000..e67af3b
--- /dev/null
+++ b/v7/mediarouter/OWNERS
@@ -0,0 +1,3 @@
+akersten@google.com
+jaewan@google.com
+sungsoo@google.com
diff --git a/v7/mediarouter/build.gradle b/v7/mediarouter/build.gradle
index 0c94194..dbf3da5 100644
--- a/v7/mediarouter/build.gradle
+++ b/v7/mediarouter/build.gradle
@@ -11,8 +11,8 @@
     api(project(":appcompat-v7"))
     api(project(":palette-v7"))
 
-    androidTestImplementation(TEST_RUNNER, libs.exclude_annotations)
-    androidTestImplementation(ESPRESSO_CORE, libs.exclude_annotations)
+    androidTestImplementation(TEST_RUNNER)
+    androidTestImplementation(ESPRESSO_CORE)
     androidTestImplementation(TEST_RULES)
 }
 
diff --git a/v7/mediarouter/src/android/support/v7/media/MediaRouter.java b/v7/mediarouter/src/android/support/v7/media/MediaRouter.java
index cf6fc1f..cc372ec 100644
--- a/v7/mediarouter/src/android/support/v7/media/MediaRouter.java
+++ b/v7/mediarouter/src/android/support/v7/media/MediaRouter.java
@@ -2560,12 +2560,16 @@
             // TODO: Remove the following logging when no longer needed.
             if (sGlobal == null || (mBluetoothRoute != null && route.isDefault())) {
                 final StackTraceElement[] callStack = Thread.currentThread().getStackTrace();
-                StringBuffer sb = new StringBuffer();
+                StringBuilder sb = new StringBuilder();
                 // callStack[3] is the caller of this method.
                 for (int i = 3; i < callStack.length; i++) {
                     StackTraceElement caller = callStack[i];
-                    sb.append(caller.getClassName() + "." + caller.getMethodName()
-                            + ":" + caller.getLineNumber()).append("  ");
+                    sb.append(caller.getClassName())
+                            .append(".")
+                            .append(caller.getMethodName())
+                            .append(":")
+                            .append(caller.getLineNumber())
+                            .append("  ");
                 }
                 if (sGlobal == null) {
                     Log.w(TAG, "setSelectedRouteInternal is called while sGlobal is null: pkgName="
diff --git a/v7/mediarouter/src/android/support/v7/media/package.html b/v7/mediarouter/src/android/support/v7/media/package.html
index 0866a42..be2aaf2 100644
--- a/v7/mediarouter/src/android/support/v7/media/package.html
+++ b/v7/mediarouter/src/android/support/v7/media/package.html
@@ -4,7 +4,6 @@
 
 <p>Contains APIs that control the routing of media channels and streams from the current device
   to external speakers and destination devices.</p>
-<p>Compatible with API level 7 and higher.</p>
 
 </body>
 </html>
diff --git a/v7/palette/build.gradle b/v7/palette/build.gradle
index 95c0799..a1b1fc9 100644
--- a/v7/palette/build.gradle
+++ b/v7/palette/build.gradle
@@ -10,7 +10,7 @@
     api(project(":support-compat"))
     api(project(":support-core-utils"))
 
-    androidTestImplementation(TEST_RUNNER, libs.exclude_annotations)
+    androidTestImplementation(TEST_RUNNER)
 }
 
 android {
diff --git a/v7/preference/api/current.txt b/v7/preference/api/current.txt
index 04c7329..1b2a746 100644
--- a/v7/preference/api/current.txt
+++ b/v7/preference/api/current.txt
@@ -275,6 +275,7 @@
     method protected void dispatchRestoreInstanceState(android.os.Bundle);
     method protected void dispatchSaveInstanceState(android.os.Bundle);
     method public android.support.v7.preference.Preference findPreference(java.lang.CharSequence);
+    method public int getInitialExpandedChildrenCount();
     method public android.support.v7.preference.Preference getPreference(int);
     method public int getPreferenceCount();
     method protected boolean isOnSameScreenAsChildren();
@@ -282,6 +283,7 @@
     method protected boolean onPrepareAddPreference(android.support.v7.preference.Preference);
     method public void removeAll();
     method public boolean removePreference(android.support.v7.preference.Preference);
+    method public void setInitialExpandedChildrenCount(int);
     method public void setOrderingAsAdded(boolean);
   }
 
diff --git a/v7/preference/build.gradle b/v7/preference/build.gradle
index 16af11e..698afb6 100644
--- a/v7/preference/build.gradle
+++ b/v7/preference/build.gradle
@@ -27,8 +27,8 @@
     api(project(":appcompat-v7"))
     api(project(":recyclerview-v7"))
 
-    androidTestImplementation(TEST_RUNNER, libs.exclude_annotations)
-    androidTestImplementation(ESPRESSO_CORE, libs.exclude_annotations)
+    androidTestImplementation(TEST_RUNNER)
+    androidTestImplementation(ESPRESSO_CORE)
     androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
     androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
 }
diff --git a/v7/preference/res/drawable/ic_arrow_down_24dp.xml b/v7/preference/res/drawable/ic_arrow_down_24dp.xml
new file mode 100644
index 0000000..7c5866d
--- /dev/null
+++ b/v7/preference/res/drawable/ic_arrow_down_24dp.xml
@@ -0,0 +1,26 @@
+<?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="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0"
+        android:tint="?android:attr/colorAccent">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M7.41,7.84L12,12.42l4.59,-4.58L18,9.25l-6,6 -6,-6z"/>
+</vector>
diff --git a/v7/preference/res/layout-v7/expand_button.xml b/v7/preference/res/layout-v7/expand_button.xml
new file mode 100644
index 0000000..35faae8
--- /dev/null
+++ b/v7/preference/res/layout-v7/expand_button.xml
@@ -0,0 +1,76 @@
+<?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.
+  -->
+
+<!-- Based off frameworks/base/core/res/res/layout/preference_material.xml -->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="?android:attr/listPreferredItemHeight"
+    android:gravity="center_vertical"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:background="?android:attr/selectableItemBackground"
+    android:clipToPadding="false">
+
+    <LinearLayout
+        android:id="@android:id/icon_frame"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:gravity="start|center_vertical"
+        android:orientation="horizontal"
+        android:layout_marginStart="-4dp"
+        android:minWidth="60dp"
+        android:paddingEnd="12dp"
+        android:paddingTop="4dp"
+        android:paddingBottom="4dp">
+        <android.support.v7.internal.widget.PreferenceImageView
+            android:id="@android:id/icon"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:maxWidth="48dp"
+            android:maxHeight="48dp"/>
+    </LinearLayout>
+
+    <RelativeLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:paddingTop="16dp"
+        android:paddingBottom="16dp">
+
+        <TextView
+            android:id="@android:id/title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textAppearance="?android:attr/textAppearanceListItem"
+            android:ellipsize="marquee"/>
+
+        <TextView
+            android:id="@android:id/summary"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@android:id/title"
+            android:layout_alignStart="@android:id/title"
+            android:textAppearance="?android:attr/textAppearanceListItemSecondary"
+            android:textColor="?android:attr/textColorSecondary"
+            android:ellipsize="marquee"
+            android:singleLine="true"/>
+
+    </RelativeLayout>
+
+</LinearLayout>
diff --git a/v7/preference/res/values/attrs.xml b/v7/preference/res/values/attrs.xml
index f204d45..8ab8de1 100644
--- a/v7/preference/res/values/attrs.xml
+++ b/v7/preference/res/values/attrs.xml
@@ -91,6 +91,15 @@
              default to alphabetic for those without the order attribute. -->
         <attr name="orderingFromXml" format="boolean" />
         <attr name="android:orderingFromXml" />
+        <!-- The maximal number of children that are shown when the preference group is launched
+             where the rest of the children will be hidden. If some children are hidden an expand
+             button will be provided to show all the hidden children.
+             Any child in any level of the hierarchy that is also a preference group (e.g.
+             preference category) will not be counted towards the limit. But instead the children of
+             such group will be counted.
+             By default, all children will be shown, so the default value of this attribute is equal
+             to Integer.MAX_VALUE. -->
+        <attr name="initialExpandedChildrenCount" format="integer" />
     </declare-styleable>
 
     <!-- Base attributes available to Preference. -->
diff --git a/v7/preference/res/values/strings.xml b/v7/preference/res/values/strings.xml
index 3414e44..1788f13 100644
--- a/v7/preference/res/values/strings.xml
+++ b/v7/preference/res/values/strings.xml
@@ -1,5 +1,9 @@
 <?xml version="1.0" encoding="utf-8"?>
-<resources>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="v7_preference_on">ON</string>
     <string name="v7_preference_off">OFF</string>
+    <!-- Title for the preference expand button [CHAR LIMIT=30] -->
+    <string name="expand_button_title">Advanced</string>
+    <!-- Summary for the preference expand button. This is used to format preference summaries as a list. [CHAR_LIMIT=NONE] -->
+    <string name="summary_collapsed_preference_list"><xliff:g id="current_items">%1$s</xliff:g>, <xliff:g id="added_items">%2$s</xliff:g></string>
 </resources>
diff --git a/v7/preference/src/main/java/android/support/v7/preference/CollapsiblePreferenceGroupController.java b/v7/preference/src/main/java/android/support/v7/preference/CollapsiblePreferenceGroupController.java
new file mode 100644
index 0000000..b63ff75
--- /dev/null
+++ b/v7/preference/src/main/java/android/support/v7/preference/CollapsiblePreferenceGroupController.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.preference;
+
+import android.content.Context;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A controller to handle advanced children display logic with collapsible functionality.
+ */
+final class CollapsiblePreferenceGroupController
+        implements PreferenceGroup.PreferenceInstanceStateCallback {
+
+    private final PreferenceGroupAdapter mPreferenceGroupAdapter;
+    private int mMaxPreferenceToShow;
+    private final Context mContext;
+
+    CollapsiblePreferenceGroupController(PreferenceGroup preferenceGroup,
+            PreferenceGroupAdapter preferenceGroupAdapter) {
+        mPreferenceGroupAdapter = preferenceGroupAdapter;
+        mMaxPreferenceToShow = preferenceGroup.getInitialExpandedChildrenCount();
+        mContext = preferenceGroup.getContext();
+        preferenceGroup.setPreferenceInstanceStateCallback(this);
+    }
+
+    /**
+     * Creates the visible portion of the flattened preferences.
+     *
+     * @param flattenedPreferenceList the flattened children of the preference group
+     * @return the visible portion of the flattened preferences
+     */
+    public List<Preference> createVisiblePreferencesList(List<Preference> flattenedPreferenceList) {
+        int visiblePreferenceCount = 0;
+        final List<Preference> visiblePreferenceList =
+                new ArrayList<>(flattenedPreferenceList.size());
+        // Copy only the visible preferences to the active list up to the maximum specified
+        for (final Preference preference : flattenedPreferenceList) {
+            if (preference.isVisible()) {
+                if (visiblePreferenceCount < mMaxPreferenceToShow) {
+                    visiblePreferenceList.add(preference);
+                }
+                // Do no count PreferenceGroup as expanded preference because the list of its child
+                // is already contained in the flattenedPreferenceList
+                if (!(preference instanceof PreferenceGroup)) {
+                    visiblePreferenceCount++;
+                }
+            }
+        }
+        // If there are any visible preferences being hidden, add an expand button to show the rest
+        // of the preferences. Clicking the expand button will show all the visible preferences and
+        // reset mMaxPreferenceToShow
+        if (showLimitedChildren() && visiblePreferenceCount > mMaxPreferenceToShow) {
+            final ExpandButton expandButton  = createExpandButton(visiblePreferenceList,
+                    flattenedPreferenceList);
+            visiblePreferenceList.add(expandButton);
+        }
+        return visiblePreferenceList;
+    }
+
+    /**
+     * Called when a preference has changed its visibility.
+     *
+     * @param preference The preference whose visibility has changed.
+     * @return {@code true} if view update has been handled by this controller.
+     */
+    public boolean onPreferenceVisibilityChange(Preference preference) {
+        if (showLimitedChildren()) {
+            // We only want to show up to the max number of preferences. Preference visibility
+            // change can result in the expand button being added/removed, as well as expand button
+            // summary change. Rebulid the data to ensure the correct data is shown.
+            mPreferenceGroupAdapter.onPreferenceHierarchyChange(preference);
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public Parcelable saveInstanceState(Parcelable state) {
+        final SavedState myState = new SavedState(state);
+        myState.mMaxPreferenceToShow = mMaxPreferenceToShow;
+        return myState;
+    }
+
+    @Override
+    public Parcelable restoreInstanceState(Parcelable state) {
+        if (state == null || !state.getClass().equals(SavedState.class)) {
+            // Didn't save state for us in saveInstanceState
+            return state;
+        }
+        SavedState myState = (SavedState) state;
+        final int restoredMaxToShow = myState.mMaxPreferenceToShow;
+        if (mMaxPreferenceToShow != restoredMaxToShow) {
+            mMaxPreferenceToShow = restoredMaxToShow;
+            mPreferenceGroupAdapter.onPreferenceHierarchyChange(null);
+        }
+        return myState.getSuperState();
+    }
+
+    private ExpandButton createExpandButton(List<Preference> visiblePreferenceList,
+            List<Preference> flattenedPreferenceList) {
+        final ExpandButton preference = new ExpandButton(mContext, visiblePreferenceList,
+                flattenedPreferenceList);
+        preference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
+            @Override
+            public boolean onPreferenceClick(Preference preference) {
+                mMaxPreferenceToShow = Integer.MAX_VALUE;
+                mPreferenceGroupAdapter.onPreferenceHierarchyChange(preference);
+                return true;
+            }
+        });
+        return preference;
+    }
+
+    private boolean showLimitedChildren() {
+        return mMaxPreferenceToShow != Integer.MAX_VALUE;
+    }
+
+    /**
+     * A {@link Preference} that provides capability to expand the collapsed items in the
+     * {@link PreferenceGroup}.
+     */
+    static class ExpandButton extends Preference {
+        ExpandButton(Context context, List<Preference> visiblePreferenceList,
+                List<Preference> flattenedPreferenceList) {
+            super(context);
+            initLayout();
+            setSummary(visiblePreferenceList, flattenedPreferenceList);
+        }
+
+        private void initLayout() {
+            setLayoutResource(R.layout.expand_button);
+            setIcon(R.drawable.ic_arrow_down_24dp);
+            setTitle(R.string.expand_button_title);
+            // Sets a high order so that the expand button will be placed at the bottom of the group
+            setOrder(999);
+        }
+
+        /*
+         * The summary of this will be the list of title for collapsed preferences. Iterate through
+         * the preferences not in the visible list and add its title to the summary text.
+         */
+        private void setSummary(List<Preference> visiblePreferenceList,
+                List<Preference> flattenedPreferenceList) {
+            final Preference lastVisiblePreference =
+                    visiblePreferenceList.get(visiblePreferenceList.size() - 1);
+            final int collapsedIndex = flattenedPreferenceList.indexOf(lastVisiblePreference) + 1;
+            CharSequence summary = null;
+            for (int i = collapsedIndex; i < flattenedPreferenceList.size(); i++) {
+                final Preference preference = flattenedPreferenceList.get(i);
+                if (preference instanceof PreferenceGroup || !preference.isVisible()) {
+                    continue;
+                }
+                final CharSequence title = preference.getTitle();
+                if (!TextUtils.isEmpty(title)) {
+                    if (summary == null) {
+                        summary = title;
+                    } else {
+                        summary = getContext().getString(
+                                R.string.summary_collapsed_preference_list, summary, title);
+                    }
+                }
+            }
+            setSummary(summary);
+        }
+
+        @Override
+        public void onBindViewHolder(PreferenceViewHolder holder) {
+            super.onBindViewHolder(holder);
+            holder.setDividerAllowedAbove(false);
+        }
+    }
+
+    /**
+     * A class for managing the instance state of a {@link PreferenceGroup}.
+     */
+    static class SavedState extends Preference.BaseSavedState {
+        int mMaxPreferenceToShow;
+
+        SavedState(Parcel source) {
+            super(source);
+            mMaxPreferenceToShow = source.readInt();
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            super.writeToParcel(dest, flags);
+            dest.writeInt(mMaxPreferenceToShow);
+        }
+
+        SavedState(Parcelable superState) {
+            super(superState);
+        }
+
+        public static final Parcelable.Creator<SavedState> CREATOR =
+                new Parcelable.Creator<SavedState>() {
+                    @Override
+                    public SavedState createFromParcel(Parcel in) {
+                        return new SavedState(in);
+                    }
+
+                    @Override
+                    public SavedState[] newArray(int size) {
+                        return new SavedState[size];
+                    }
+                };
+    }
+}
diff --git a/v7/preference/src/main/java/android/support/v7/preference/PreferenceGroup.java b/v7/preference/src/main/java/android/support/v7/preference/PreferenceGroup.java
index d285ee6..a951e70 100644
--- a/v7/preference/src/main/java/android/support/v7/preference/PreferenceGroup.java
+++ b/v7/preference/src/main/java/android/support/v7/preference/PreferenceGroup.java
@@ -22,7 +22,9 @@
 import android.content.res.TypedArray;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.Parcelable;
 import android.support.annotation.RestrictTo;
+import android.support.annotation.VisibleForTesting;
 import android.support.v4.content.res.TypedArrayUtils;
 import android.support.v4.util.SimpleArrayMap;
 import android.text.TextUtils;
@@ -45,6 +47,7 @@
  * </div>
  *
  * @attr name android:orderingFromXml
+ * @attr name initialExpandedChildrenCount
  */
 public abstract class PreferenceGroup extends Preference {
     /**
@@ -60,6 +63,9 @@
 
     private boolean mAttachedToHierarchy = false;
 
+    private int mInitialExpandedChildrenCount = Integer.MAX_VALUE;
+    private PreferenceInstanceStateCallback mPreferenceInstanceStateCallback;
+
     private final SimpleArrayMap<String, Long> mIdRecycleCache = new SimpleArrayMap<>();
     private final Handler mHandler = new Handler();
     private final Runnable mClearRecycleCacheRunnable = new Runnable() {
@@ -83,6 +89,11 @@
                 TypedArrayUtils.getBoolean(a, R.styleable.PreferenceGroup_orderingFromXml,
                         R.styleable.PreferenceGroup_orderingFromXml, true);
 
+        if (a.hasValue(R.styleable.PreferenceGroup_initialExpandedChildrenCount)) {
+            mInitialExpandedChildrenCount = TypedArrayUtils.getInt(
+                    a, R.styleable.PreferenceGroup_initialExpandedChildrenCount,
+                            R.styleable.PreferenceGroup_initialExpandedChildrenCount, -1);
+        }
         a.recycle();
     }
 
@@ -120,6 +131,35 @@
     }
 
     /**
+     * Sets the maximal number of children that are shown when the preference group is launched
+     * where the rest of the children will be hidden.
+     * If some children are hidden an expand button will be provided to show all the hidden
+     * children. Any child in any level of the hierarchy that is also a preference group (e.g.
+     * preference category) will not be counted towards the limit. But instead the children of such
+     * group will be counted.
+     * By default, all children will be shown, so the default value of this attribute is equal to
+     * Integer.MAX_VALUE.
+     *
+     * @param expandedCount the number of children that is initially shown.
+     *
+     * @attr ref R.styleable#PreferenceGroup_initialExpandedChildrenCount
+     */
+    public void setInitialExpandedChildrenCount(int expandedCount) {
+        mInitialExpandedChildrenCount = expandedCount;
+    }
+
+    /**
+     * Gets the maximal number of children that is initially shown.
+     *
+     * @return the maximal number of children that is initially shown.
+     *
+     * @attr ref R.styleable#PreferenceGroup_initialExpandedChildrenCount
+     */
+    public int getInitialExpandedChildrenCount() {
+        return mInitialExpandedChildrenCount;
+    }
+
+    /**
      * Called by the inflater to add an item to this group.
      */
     public void addItemFromInflater(Preference preference) {
@@ -400,6 +440,44 @@
         }
     }
 
+    @Override
+    protected Parcelable onSaveInstanceState() {
+        final Parcelable superState = super.onSaveInstanceState();
+        if (mPreferenceInstanceStateCallback != null) {
+            return mPreferenceInstanceStateCallback.saveInstanceState(superState);
+        }
+        return superState;
+    }
+
+    @Override
+    protected void onRestoreInstanceState(Parcelable state) {
+        if (mPreferenceInstanceStateCallback != null) {
+            state = mPreferenceInstanceStateCallback.restoreInstanceState(state);
+        }
+        super.onRestoreInstanceState(state);
+    }
+
+    /**
+     * Sets the instance state callback.
+     *
+     * @param callback The callback.
+     * @see #onSaveInstanceState()
+     * @see #onRestoreInstanceState()
+     */
+    final void setPreferenceInstanceStateCallback(PreferenceInstanceStateCallback callback) {
+        mPreferenceInstanceStateCallback = callback;
+    }
+
+    /**
+     * Gets the instance state callback.
+     *
+     * @return the instance state callback.
+     */
+    @VisibleForTesting
+    final PreferenceInstanceStateCallback getPreferenceInstanceStateCallback() {
+        return mPreferenceInstanceStateCallback;
+    }
+
     /**
      * Interface for PreferenceGroup Adapters to implement so that
      * {@link android.support.v14.preference.PreferenceFragment#scrollToPreference(String)} and
@@ -426,4 +504,29 @@
          */
         int getPreferenceAdapterPosition(Preference preference);
     }
+
+    /**
+     * Interface for callback to implement so that they can save and restore the preference group's
+     * instance state.
+     */
+    interface PreferenceInstanceStateCallback {
+
+        /**
+         * Save the internal state that can later be used to create a new instance with that
+         * same state.
+         *
+         * @param state The Parcelable to save the current dynamic state.
+         */
+        Parcelable saveInstanceState(Parcelable state);
+
+        /**
+         * Restore the previously saved state from the given parcelable.
+         *
+         * @param state The Parcelable that holds the previously saved state.
+         * @return the super state if data has been saved in the state in {@link saveInstanceState}
+         *         or state otherwise
+         */
+        Parcelable restoreInstanceState(Parcelable state);
+    }
+
 }
diff --git a/v7/preference/src/main/java/android/support/v7/preference/PreferenceGroupAdapter.java b/v7/preference/src/main/java/android/support/v7/preference/PreferenceGroupAdapter.java
index d1c630f..00a0c5b 100644
--- a/v7/preference/src/main/java/android/support/v7/preference/PreferenceGroupAdapter.java
+++ b/v7/preference/src/main/java/android/support/v7/preference/PreferenceGroupAdapter.java
@@ -22,6 +22,7 @@
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
 import android.support.annotation.RestrictTo;
+import android.support.annotation.VisibleForTesting;
 import android.support.v4.content.ContextCompat;
 import android.support.v4.view.ViewCompat;
 import android.support.v7.util.DiffUtil;
@@ -73,7 +74,9 @@
 
     private PreferenceLayout mTempPreferenceLayout = new PreferenceLayout();
 
-    private Handler mHandler = new Handler();
+    private Handler mHandler;
+
+    private CollapsiblePreferenceGroupController mPreferenceGroupController;
 
     private Runnable mSyncRunnable = new Runnable() {
         @Override
@@ -117,7 +120,14 @@
     }
 
     public PreferenceGroupAdapter(PreferenceGroup preferenceGroup) {
+        this(preferenceGroup, new Handler());
+    }
+
+    private PreferenceGroupAdapter(PreferenceGroup preferenceGroup, Handler handler) {
         mPreferenceGroup = preferenceGroup;
+        mHandler = handler;
+        mPreferenceGroupController =
+                new CollapsiblePreferenceGroupController(preferenceGroup, this);
         // If this group gets or loses any children, let us know
         mPreferenceGroup.setOnPreferenceChangeInternalListener(this);
 
@@ -134,6 +144,12 @@
         syncMyPreferences();
     }
 
+    @VisibleForTesting
+    static PreferenceGroupAdapter createInstanceWithCustomHandler(PreferenceGroup preferenceGroup,
+            Handler handler) {
+        return new PreferenceGroupAdapter(preferenceGroup, handler);
+    }
+
     private void syncMyPreferences() {
         for (final Preference preference : mPreferenceListInternal) {
             // Clear out the listeners in anticipation of some items being removed. This listener
@@ -143,13 +159,8 @@
         final List<Preference> fullPreferenceList = new ArrayList<>(mPreferenceListInternal.size());
         flattenPreferenceGroup(fullPreferenceList, mPreferenceGroup);
 
-        final List<Preference> visiblePreferenceList = new ArrayList<>(fullPreferenceList.size());
-        // Copy only the visible preferences to the active list
-        for (final Preference preference : fullPreferenceList) {
-            if (preference.isVisible()) {
-                visiblePreferenceList.add(preference);
-            }
-        }
+        final List<Preference> visiblePreferenceList =
+                mPreferenceGroupController.createVisiblePreferencesList(fullPreferenceList);
 
         final List<Preference> oldVisibleList = mPreferenceList;
         mPreferenceList = visiblePreferenceList;
@@ -277,6 +288,9 @@
         if (!mPreferenceListInternal.contains(preference)) {
             return;
         }
+        if (mPreferenceGroupController.onPreferenceVisibilityChange(preference)) {
+            return;
+        }
         if (preference.isVisible()) {
             // The preference has become visible, we need to add it in the correct location.
 
diff --git a/v7/preference/tests/src/android/support/v7/preference/PreferenceGroupInitialExpandedChildrenCountTest.java b/v7/preference/tests/src/android/support/v7/preference/PreferenceGroupInitialExpandedChildrenCountTest.java
new file mode 100644
index 0000000..499e2c1
--- /dev/null
+++ b/v7/preference/tests/src/android/support/v7/preference/PreferenceGroupInitialExpandedChildrenCountTest.java
@@ -0,0 +1,403 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.preference;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyLong;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Parcelable;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.annotation.UiThreadTest;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class PreferenceGroupInitialExpandedChildrenCountTest {
+
+    private static final int INITIAL_EXPANDED_COUNT = 5;
+    private static final int TOTAL_PREFERENCE = 10;
+    private static final String PREFERENCE_TITLE_PREFIX = "Preference_";
+
+    private Context mContext;
+    private PreferenceManager mPreferenceManager;
+    private PreferenceScreen mScreen;
+    private Handler mHandler;
+    private List<Preference> mPreferenceList;
+
+    @Before
+    @UiThreadTest
+    public void setup() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mContext = InstrumentationRegistry.getTargetContext();
+        mPreferenceManager = new PreferenceManager(mContext);
+        mScreen = mPreferenceManager.createPreferenceScreen(mContext);
+
+        // Add 10 preferences to the screen and to the cache
+        mPreferenceList = new ArrayList<>();
+        createTestPreferences(mScreen, mPreferenceList, TOTAL_PREFERENCE);
+
+        // Execute the handler task immediately
+        mHandler = spy(new Handler());
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                Object[] args = invocation.getArguments();
+                Message message = (Message) args[0];
+                mHandler.dispatchMessage(message);
+                return null;
+            }
+        }).when(mHandler).sendMessageDelayed(any(Message.class), anyLong());
+    }
+
+    /**
+     * Verifies that when PreferenceGroupAdapter is created, the PreferenceInstanceStateCallback
+     * is set on the PreferenceGroup.
+     */
+    @Test
+    @UiThreadTest
+    public void createPreferenceGroupAdapter_setPreferenceInstanceStateCallback() {
+        PreferenceGroupAdapter preferenceGroupAdapter = new PreferenceGroupAdapter(mScreen);
+        assertNotNull(mScreen.getPreferenceInstanceStateCallback());
+    }
+
+    /**
+     * Verifies that PreferenceGroupAdapter is showing the preferences on the screen correctly with
+     * and without the collapsed child count set.
+     */
+    @Test
+    @UiThreadTest
+    public void createPreferenceGroupAdapter_displayTopLevelPreferences() {
+        // No limit, should display all 10 preferences
+        PreferenceGroupAdapter preferenceGroupAdapter = new PreferenceGroupAdapter(mScreen);
+        assertPreferencesAreExpanded(preferenceGroupAdapter);
+
+        // Limit > child count, should display all 10 preferences
+        mScreen.setInitialExpandedChildrenCount(TOTAL_PREFERENCE + 4);
+        preferenceGroupAdapter = new PreferenceGroupAdapter(mScreen);
+        assertPreferencesAreExpanded(preferenceGroupAdapter);
+
+        // Limit = child count, should display all 10 preferences
+        mScreen.setInitialExpandedChildrenCount(TOTAL_PREFERENCE);
+        preferenceGroupAdapter = new PreferenceGroupAdapter(mScreen);
+        assertPreferencesAreExpanded(preferenceGroupAdapter);
+
+        // Limit < child count, should display up to the limit + expand button
+        mScreen.setInitialExpandedChildrenCount(INITIAL_EXPANDED_COUNT);
+        preferenceGroupAdapter = new PreferenceGroupAdapter(mScreen);
+        assertPreferencesAreCollapsed(preferenceGroupAdapter);
+        for (int i = 0; i < INITIAL_EXPANDED_COUNT; i++) {
+            assertEquals(mPreferenceList.get(i), preferenceGroupAdapter.getItem(i));
+        }
+        assertEquals(CollapsiblePreferenceGroupController.ExpandButton.class,
+                preferenceGroupAdapter.getItem(INITIAL_EXPANDED_COUNT).getClass());
+    }
+
+    /**
+     * Verifies that PreferenceGroupAdapter is showing nested preferences on the screen correctly
+     * with and without the collapsed child count set.
+     */
+    @Test
+    @UiThreadTest
+    public void createPreferenceGroupAdapter_displayNestedPreferences() {
+        final PreferenceScreen screen = mPreferenceManager.createPreferenceScreen(mContext);
+        final List<Preference> preferenceList = new ArrayList<>();
+
+        // Add 2 preferences and 2 categories to screen
+        createTestPreferences(screen, preferenceList, 2);
+        createTestPreferencesCategory(screen, preferenceList, 4);
+        createTestPreferencesCategory(screen, preferenceList, 4);
+
+        // No limit, should display all 10 preferences + 2 categories
+        PreferenceGroupAdapter preferenceGroupAdapter = new PreferenceGroupAdapter(screen);
+        assertEquals(TOTAL_PREFERENCE + 2, preferenceGroupAdapter.getItemCount());
+
+        // Limit > child count, should display all 10 preferences + 2 categories
+        screen.setInitialExpandedChildrenCount(TOTAL_PREFERENCE + 4);
+        preferenceGroupAdapter = new PreferenceGroupAdapter(screen);
+        assertEquals(TOTAL_PREFERENCE + 2, preferenceGroupAdapter.getItemCount());
+
+        // Limit = child count, should display all 10 preferences + 2 categories
+        screen.setInitialExpandedChildrenCount(TOTAL_PREFERENCE);
+        preferenceGroupAdapter = new PreferenceGroupAdapter(screen);
+        assertEquals(TOTAL_PREFERENCE + 2, preferenceGroupAdapter.getItemCount());
+
+        // Limit < child count, should display 2 preferences and the first 3 preference in the
+        // category + expand button
+        screen.setInitialExpandedChildrenCount(INITIAL_EXPANDED_COUNT);
+        preferenceGroupAdapter = new PreferenceGroupAdapter(screen);
+        assertEquals(INITIAL_EXPANDED_COUNT + 2, preferenceGroupAdapter.getItemCount());
+        for (int i = 0; i <= INITIAL_EXPANDED_COUNT; i++) {
+            assertEquals(preferenceList.get(i), preferenceGroupAdapter.getItem(i));
+        }
+        assertEquals(CollapsiblePreferenceGroupController.ExpandButton.class,
+                preferenceGroupAdapter.getItem(INITIAL_EXPANDED_COUNT + 1).getClass());
+    }
+
+    /**
+     * Verifies that correct summary is set for the expand button.
+     */
+    @Test
+    @UiThreadTest
+    public void createPreferenceGroupAdapter_setExpandButtonSummary() {
+        mScreen.setInitialExpandedChildrenCount(INITIAL_EXPANDED_COUNT);
+        PreferenceGroupAdapter preferenceGroupAdapter = new PreferenceGroupAdapter(mScreen);
+        // Preference 5 to Preference 9 are collapsed
+        CharSequence summary = mPreferenceList.get(INITIAL_EXPANDED_COUNT).getTitle();
+        for (int i = INITIAL_EXPANDED_COUNT + 1; i < TOTAL_PREFERENCE; i++) {
+            summary = mContext.getString(R.string.summary_collapsed_preference_list,
+                    summary, mPreferenceList.get(i).getTitle());
+        }
+        final Preference expandButton = preferenceGroupAdapter.getItem(INITIAL_EXPANDED_COUNT);
+        assertEquals(summary, expandButton.getSummary());
+    }
+
+    /**
+     * Verifies that summary for the expand button only lists visible preferences.
+     */
+    @Test
+    @UiThreadTest
+    public void createPreferenceGroupAdapter_expandButtonSummaryShouldListVisiblePreferencesOnly() {
+        mScreen.setInitialExpandedChildrenCount(INITIAL_EXPANDED_COUNT);
+        mPreferenceList.get(INITIAL_EXPANDED_COUNT + 1).setVisible(false);
+        mPreferenceList.get(INITIAL_EXPANDED_COUNT + 4).setVisible(false);
+        PreferenceGroupAdapter preferenceGroupAdapter = new PreferenceGroupAdapter(mScreen);
+        // Preference 5 to Preference 9 are collapsed, only preferences 5, 7, 8 are visible
+        CharSequence summary = mPreferenceList.get(INITIAL_EXPANDED_COUNT).getTitle();
+        summary = mContext.getString(R.string.summary_collapsed_preference_list,
+                summary, mPreferenceList.get(INITIAL_EXPANDED_COUNT + 2).getTitle());
+        summary = mContext.getString(R.string.summary_collapsed_preference_list,
+                summary, mPreferenceList.get(INITIAL_EXPANDED_COUNT + 3).getTitle());
+        final Preference expandButton = preferenceGroupAdapter.getItem(INITIAL_EXPANDED_COUNT);
+        assertEquals(summary, expandButton.getSummary());
+    }
+
+    /**
+     * Verifies that clicking the expand button will show all preferences.
+     */
+    @Test
+    @UiThreadTest
+    public void clickExpandButton_shouldShowAllPreferences() {
+        mScreen.setInitialExpandedChildrenCount(INITIAL_EXPANDED_COUNT);
+
+        // First showing 5 preference with expand button
+        PreferenceGroupAdapter preferenceGroupAdapter =
+                PreferenceGroupAdapter.createInstanceWithCustomHandler(mScreen, mHandler);
+        assertPreferencesAreCollapsed(preferenceGroupAdapter);
+
+        // Click the expand button, should review all preferences
+        final Preference expandButton = preferenceGroupAdapter.getItem(INITIAL_EXPANDED_COUNT);
+        expandButton.performClick();
+        assertPreferencesAreExpanded(preferenceGroupAdapter);
+    }
+
+    /**
+     * Verifies that when preference visibility changes, it will sync the preferences only if some
+     * preferences are collapsed.
+     */
+    @Test
+    @UiThreadTest
+    public void onPreferenceVisibilityChange_shouldSyncPreferencesIfCollapsed() {
+        // No limit set, should not sync preference
+        PreferenceGroupAdapter preferenceGroupAdapter =
+                PreferenceGroupAdapter.createInstanceWithCustomHandler(mScreen, mHandler);
+        preferenceGroupAdapter.onPreferenceVisibilityChange(mPreferenceList.get(3));
+        verify(mHandler, never()).sendMessageDelayed(any(Message.class), anyLong());
+
+        // Has limit set, should sync preference
+        mScreen.setInitialExpandedChildrenCount(INITIAL_EXPANDED_COUNT);
+        preferenceGroupAdapter =
+                PreferenceGroupAdapter.createInstanceWithCustomHandler(mScreen, mHandler);
+        preferenceGroupAdapter.onPreferenceVisibilityChange(mPreferenceList.get(3));
+        verify(mHandler).sendMessageDelayed(any(Message.class), anyLong());
+
+        // Preferences expanded already, should not sync preference
+        final Preference expandButton = preferenceGroupAdapter.getItem(INITIAL_EXPANDED_COUNT);
+        expandButton.performClick();
+        reset(mHandler);
+        preferenceGroupAdapter.onPreferenceVisibilityChange(mPreferenceList.get(3));
+        verify(mHandler, never()).sendMessageDelayed(any(Message.class), anyLong());
+    }
+
+    /**
+     * Verifies that the correct maximum number of preferences to show is being saved in the
+     * instance state.
+     */
+    @Test
+    @UiThreadTest
+    public void saveInstanceState_shouldSaveMaxNumberOfChildrenToShow() {
+        // No limit set, should save max value
+        PreferenceGroupAdapter preferenceGroupAdapter = new PreferenceGroupAdapter(mScreen);
+        Parcelable state = mScreen.onSaveInstanceState();
+        assertEquals(CollapsiblePreferenceGroupController.SavedState.class, state.getClass());
+        assertEquals(Integer.MAX_VALUE,
+                ((CollapsiblePreferenceGroupController.SavedState) state).mMaxPreferenceToShow);
+
+        // Has limit set, should save limit
+        mScreen.setInitialExpandedChildrenCount(INITIAL_EXPANDED_COUNT);
+        preferenceGroupAdapter = new PreferenceGroupAdapter(mScreen);
+        state = mScreen.onSaveInstanceState();
+        assertEquals(CollapsiblePreferenceGroupController.SavedState.class, state.getClass());
+        assertEquals(INITIAL_EXPANDED_COUNT,
+                ((CollapsiblePreferenceGroupController.SavedState) state).mMaxPreferenceToShow);
+
+        // Preferences expanded already, should save max value
+        final Preference expandButton = preferenceGroupAdapter.getItem(INITIAL_EXPANDED_COUNT);
+        expandButton.performClick();
+        state = mScreen.onSaveInstanceState();
+        assertEquals(CollapsiblePreferenceGroupController.SavedState.class, state.getClass());
+        assertEquals(Integer.MAX_VALUE,
+                ((CollapsiblePreferenceGroupController.SavedState) state).mMaxPreferenceToShow);
+    }
+
+    /**
+     * Verifies that if we restore to the same number of preferences to show, it will not update
+     * anything.
+     */
+    @Test
+    @UiThreadTest
+    public void restoreInstanceState_noChange_shouldDoNothing() {
+        Parcelable baseState = Preference.BaseSavedState.EMPTY_STATE;
+        // Initialized as expanded, restore with no saved data, should remain expanded
+        PreferenceGroupAdapter preferenceGroupAdapter =
+                PreferenceGroupAdapter.createInstanceWithCustomHandler(mScreen, mHandler);
+        mScreen.onRestoreInstanceState(baseState);
+        assertPreferencesAreExpanded(preferenceGroupAdapter);
+        verify(mHandler, never()).sendMessageDelayed(any(Message.class), anyLong());
+
+        // Initialized as collapsed, restore with no saved data, should remain collapsed
+        mScreen.setInitialExpandedChildrenCount(INITIAL_EXPANDED_COUNT);
+        preferenceGroupAdapter =
+                PreferenceGroupAdapter.createInstanceWithCustomHandler(mScreen, mHandler);
+        mScreen.onRestoreInstanceState(baseState);
+        assertPreferencesAreCollapsed(preferenceGroupAdapter);
+        verify(mHandler, never()).sendMessageDelayed(any(Message.class), anyLong());
+
+        CollapsiblePreferenceGroupController.SavedState state =
+                new CollapsiblePreferenceGroupController.SavedState(baseState);
+        // Initialized as expanded, restore as expanded, should remain expanded
+        state.mMaxPreferenceToShow = Integer.MAX_VALUE;
+        mScreen.setInitialExpandedChildrenCount(Integer.MAX_VALUE);
+        preferenceGroupAdapter =
+                PreferenceGroupAdapter.createInstanceWithCustomHandler(mScreen, mHandler);
+        mScreen.onRestoreInstanceState(state);
+        assertPreferencesAreExpanded(preferenceGroupAdapter);
+        verify(mHandler, never()).sendMessageDelayed(any(Message.class), anyLong());
+
+        // Initialized as collapsed, restore as collapsed, should remain collapsed
+        state.mMaxPreferenceToShow = INITIAL_EXPANDED_COUNT;
+        mScreen.setInitialExpandedChildrenCount(INITIAL_EXPANDED_COUNT);
+        preferenceGroupAdapter =
+                PreferenceGroupAdapter.createInstanceWithCustomHandler(mScreen, mHandler);
+        mScreen.onRestoreInstanceState(state);
+        assertPreferencesAreCollapsed(preferenceGroupAdapter);
+        verify(mHandler, never()).sendMessageDelayed(any(Message.class), anyLong());
+    }
+
+    /**
+     * Verifies that if the children is collapsed previously, they should be collapsed after the
+     * state is being restored.
+     */
+    @Test
+    @UiThreadTest
+    public void restoreHierarchyState_previouslyCollapsed_shouldRestoreToCollapsedState() {
+        CollapsiblePreferenceGroupController.SavedState state =
+                new CollapsiblePreferenceGroupController.SavedState(
+                        Preference.BaseSavedState.EMPTY_STATE);
+        // Initialized as expanded, restore as collapsed, should collapse
+        state.mMaxPreferenceToShow = INITIAL_EXPANDED_COUNT;
+        mScreen.setInitialExpandedChildrenCount(Integer.MAX_VALUE);
+        PreferenceGroupAdapter preferenceGroupAdapter =
+                PreferenceGroupAdapter.createInstanceWithCustomHandler(mScreen, mHandler);
+        mScreen.onRestoreInstanceState(state);
+        verify(mHandler).sendMessageDelayed(any(Message.class), anyLong());
+        assertPreferencesAreCollapsed(preferenceGroupAdapter);
+    }
+
+    /**
+     * Verifies that if the children is expanded previously, they should be expanded after the
+     * state is being restored.
+     */
+    @Test
+    @UiThreadTest
+    public void restoreHierarchyState_previouslyExpanded_shouldRestoreToExpandedState() {
+        CollapsiblePreferenceGroupController.SavedState state =
+                new CollapsiblePreferenceGroupController.SavedState(
+                        Preference.BaseSavedState.EMPTY_STATE);
+        // Initialized as collapsed, restore as expanded, should expand
+        state.mMaxPreferenceToShow = Integer.MAX_VALUE;
+        mScreen.setInitialExpandedChildrenCount(INITIAL_EXPANDED_COUNT);
+        PreferenceGroupAdapter preferenceGroupAdapter =
+                PreferenceGroupAdapter.createInstanceWithCustomHandler(mScreen, mHandler);
+        mScreen.onRestoreInstanceState(state);
+        verify(mHandler).sendMessageDelayed(any(Message.class), anyLong());
+        assertPreferencesAreExpanded(preferenceGroupAdapter);
+    }
+
+    // assert that the preferences are all expanded
+    private void assertPreferencesAreExpanded(PreferenceGroupAdapter adapter) {
+        assertEquals(TOTAL_PREFERENCE, adapter.getItemCount());
+    }
+
+    // assert that the preferences exceeding the limit are collapsed
+    private void assertPreferencesAreCollapsed(PreferenceGroupAdapter adapter) {
+        // list shows preferences up to the limit and the expand button
+        assertEquals(INITIAL_EXPANDED_COUNT + 1, adapter.getItemCount());
+    }
+
+    // create the number of preference in the corresponding preference group and add it to the cache
+    private void createTestPreferences(PreferenceGroup preferenceGroup,
+            List<Preference> preferenceList, int numPreference) {
+        for (int i = 0; i < numPreference; i++) {
+            final Preference preference = new Preference(mContext);
+            preference.setTitle(PREFERENCE_TITLE_PREFIX + i);
+            preferenceGroup.addPreference(preference);
+            preferenceList.add(preference);
+        }
+    }
+
+    // add a preference category and add the number of preference to it and the cache
+    private void createTestPreferencesCategory(PreferenceGroup preferenceGroup,
+            List<Preference> preferenceList, int numPreference) {
+        PreferenceCategory category = new PreferenceCategory(mContext);
+        preferenceGroup.addPreference(category);
+        preferenceList.add(category);
+        createTestPreferences(category, preferenceList, numPreference);
+    }
+
+}
diff --git a/v7/recyclerview/api/27.0.0.ignore b/v7/recyclerview/api/27.0.0.ignore
new file mode 100644
index 0000000..357c296
--- /dev/null
+++ b/v7/recyclerview/api/27.0.0.ignore
@@ -0,0 +1 @@
+d69e9e2
diff --git a/v7/recyclerview/api/current.txt b/v7/recyclerview/api/current.txt
index 9b4500a..0909d7d 100644
--- a/v7/recyclerview/api/current.txt
+++ b/v7/recyclerview/api/current.txt
@@ -55,6 +55,13 @@
     method public void dispatchUpdatesTo(android.support.v7.util.ListUpdateCallback);
   }
 
+  public static abstract class DiffUtil.ItemCallback<T> {
+    ctor public DiffUtil.ItemCallback();
+    method public abstract boolean areContentsTheSame(T, T);
+    method public abstract boolean areItemsTheSame(T, T);
+    method public java.lang.Object getChangePayload(T, T);
+  }
+
   public abstract interface ListUpdateCallback {
     method public abstract void onChanged(int, int, java.lang.Object);
     method public abstract void onInserted(int, int);
@@ -77,6 +84,9 @@
     method public void recalculatePositionOfItemAt(int);
     method public boolean remove(T);
     method public T removeItemAt(int);
+    method public void replaceAll(T[], boolean);
+    method public void replaceAll(T...);
+    method public void replaceAll(java.util.Collection<T>);
     method public int size();
     method public void updateItemAt(int, T);
     field public static final int INVALID_POSITION = -1; // 0xffffffff
@@ -99,6 +109,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);
   }
@@ -182,6 +193,7 @@
     method public boolean getStackFromEnd();
     method protected boolean isLayoutRTL();
     method public boolean isSmoothScrollbarEnabled();
+    method public void prepareForDrop(android.view.View, android.view.View, int, int);
     method public void scrollToPositionWithOffset(int, int);
     method public void setInitialPrefetchItemCount(int);
     method public void setOrientation(int);
@@ -269,7 +281,7 @@
     method public int findTargetSnapPosition(android.support.v7.widget.RecyclerView.LayoutManager, int, int);
   }
 
-  public class RecyclerView extends android.view.ViewGroup {
+  public class RecyclerView extends android.view.ViewGroup implements android.support.v4.view.NestedScrollingChild2 android.support.v4.view.ScrollingView {
     ctor public RecyclerView(android.content.Context);
     ctor public RecyclerView(android.content.Context, android.util.AttributeSet);
     ctor public RecyclerView(android.content.Context, android.util.AttributeSet, int);
@@ -305,6 +317,7 @@
     method public android.support.v7.widget.RecyclerView.ViewHolder getChildViewHolder(android.view.View);
     method public android.support.v7.widget.RecyclerViewAccessibilityDelegate getCompatAccessibilityDelegate();
     method public void getDecoratedBoundsWithMargins(android.view.View, android.graphics.Rect);
+    method public android.support.v7.widget.RecyclerView.EdgeEffectFactory getEdgeEffectFactory();
     method public android.support.v7.widget.RecyclerView.ItemAnimator getItemAnimator();
     method public android.support.v7.widget.RecyclerView.ItemDecoration getItemDecorationAt(int);
     method public int getItemDecorationCount();
@@ -327,7 +340,6 @@
     method public void onChildAttachedToWindow(android.view.View);
     method public void onChildDetachedFromWindow(android.view.View);
     method public void onDraw(android.graphics.Canvas);
-    method protected void onLayout(boolean, int, int, int, int);
     method public void onScrollStateChanged(int);
     method public void onScrolled(int, int);
     method public void removeItemDecoration(android.support.v7.widget.RecyclerView.ItemDecoration);
@@ -339,6 +351,7 @@
     method public void setAccessibilityDelegateCompat(android.support.v7.widget.RecyclerViewAccessibilityDelegate);
     method public void setAdapter(android.support.v7.widget.RecyclerView.Adapter);
     method public void setChildDrawingOrderCallback(android.support.v7.widget.RecyclerView.ChildDrawingOrderCallback);
+    method public void setEdgeEffectFactory(android.support.v7.widget.RecyclerView.EdgeEffectFactory);
     method public void setHasFixedSize(boolean);
     method public void setItemAnimator(android.support.v7.widget.RecyclerView.ItemAnimator);
     method public void setItemViewCacheSize(int);
@@ -417,6 +430,18 @@
     method public abstract int onGetChildDrawingOrder(int, int);
   }
 
+  public static class RecyclerView.EdgeEffectFactory {
+    ctor public RecyclerView.EdgeEffectFactory();
+    method protected android.widget.EdgeEffect createEdgeEffect(android.support.v7.widget.RecyclerView, int);
+    field public static final int DIRECTION_BOTTOM = 3; // 0x3
+    field public static final int DIRECTION_LEFT = 0; // 0x0
+    field public static final int DIRECTION_RIGHT = 2; // 0x2
+    field public static final int DIRECTION_TOP = 1; // 0x1
+  }
+
+  public static abstract class RecyclerView.EdgeEffectFactory.EdgeDirection implements java.lang.annotation.Annotation {
+  }
+
   public static abstract class RecyclerView.ItemAnimator {
     ctor public RecyclerView.ItemAnimator();
     method public abstract boolean animateAppearance(android.support.v7.widget.RecyclerView.ViewHolder, android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo, android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo);
diff --git a/v7/recyclerview/build.gradle b/v7/recyclerview/build.gradle
index b98e7f7..0a83989 100644
--- a/v7/recyclerview/build.gradle
+++ b/v7/recyclerview/build.gradle
@@ -11,8 +11,8 @@
     api(project(":support-compat"))
     api(project(":support-core-ui"))
 
-    androidTestImplementation(TEST_RUNNER, libs.exclude_annotations)
-    androidTestImplementation(ESPRESSO_CORE, libs.exclude_annotations)
+    androidTestImplementation(TEST_RUNNER)
+    androidTestImplementation(ESPRESSO_CORE)
     androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
     androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
     androidTestImplementation(JUNIT)
@@ -20,7 +20,7 @@
 
     testImplementation(JUNIT)
     testImplementation(MOCKITO_CORE)
-    testImplementation(TEST_RUNNER, libs.exclude_annotations)
+    testImplementation(TEST_RUNNER)
 }
 
 android {
diff --git a/v7/recyclerview/src/main/java/android/support/v7/util/DiffUtil.java b/v7/recyclerview/src/main/java/android/support/v7/util/DiffUtil.java
index 6302666..ebc33f3 100644
--- a/v7/recyclerview/src/main/java/android/support/v7/util/DiffUtil.java
+++ b/v7/recyclerview/src/main/java/android/support/v7/util/DiffUtil.java
@@ -16,6 +16,7 @@
 
 package android.support.v7.util;
 
+import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.annotation.VisibleForTesting;
 import android.support.v7.widget.RecyclerView;
@@ -348,6 +349,72 @@
     }
 
     /**
+     * Callback for calculating the diff between two non-null items in a list.
+     * <p>
+     * {@link Callback} serves two roles - list indexing, and item diffing. ItemCallback handles
+     * just the second of these, which allows separation of code that indexes into an array or List
+     * from the presentation-layer and content specific diffing code.
+     *
+     * @param <T> Type of items to compare.
+     */
+    public abstract static class ItemCallback<T> {
+        /**
+         * Called to check whether two objects represent the same item.
+         * <p>
+         * For example, if your items have unique ids, this method should check their id equality.
+         *
+         * @param oldItem The item in the old list.
+         * @param newItem The item in the new list.
+         * @return True if the two items represent the same object or false if they are different.
+         *
+         * @see Callback#areItemsTheSame(int, int)
+         */
+        public abstract boolean areItemsTheSame(@NonNull T oldItem, @NonNull T newItem);
+
+        /**
+         * Called to check whether two items have the same data.
+         * <p>
+         * This information is used to detect if the contents of an item have changed.
+         * <p>
+         * This method to check equality instead of {@link Object#equals(Object)} so that you can
+         * change its behavior depending on your UI.
+         * <p>
+         * For example, if you are using DiffUtil with a
+         * {@link android.support.v7.widget.RecyclerView.Adapter RecyclerView.Adapter}, you should
+         * return whether the items' visual representations are the same.
+         * <p>
+         * This method is called only if {@link #areItemsTheSame(T, T)} returns {@code true} for
+         * these items.
+         *
+         * @param oldItem The item in the old list.
+         * @param newItem The item in the new list.
+         * @return True if the contents of the items are the same or false if they are different.
+         *
+         * @see Callback#areContentsTheSame(int, int)
+         */
+        public abstract boolean areContentsTheSame(@NonNull T oldItem, @NonNull T newItem);
+
+        /**
+         * When {@link #areItemsTheSame(T, T)} returns {@code true} for two items and
+         * {@link #areContentsTheSame(T, T)} returns false for them, this method is called to
+         * get a payload about the change.
+         * <p>
+         * For example, if you are using DiffUtil with {@link 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}.
+         *
+         * @see Callback#getChangePayload(int, int)
+         */
+        @SuppressWarnings({"WeakerAccess", "unused"})
+        public Object getChangePayload(@NonNull T oldItem, @NonNull T newItem) {
+            return null;
+        }
+    }
+
+    /**
      * Snakes represent a match between two lists. It is optionally prefixed or postfixed with an
      * add or remove operation. See the Myers' paper for details.
      */
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..bd07b01 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,9 @@
 
 package android.support.v7.util;
 
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
 import java.lang.reflect.Array;
 import java.util.Arrays;
 import java.util.Collection;
@@ -49,17 +52,23 @@
     T[] mData;
 
     /**
-     * A copy of the previous list contents used during the merge phase of addAll.
+     * A reference to the previous set of data that is kept during a mutation operation (addAll or
+     * replaceAll).
      */
     private T[] mOldData;
+
+    /**
+     * The current index into mOldData that has not yet been processed during a mutation operation
+     * (addAll or replaceAll).
+     */
     private int mOldDataStart;
     private int mOldDataSize;
 
     /**
-     * The size of the valid portion of mData during the merge phase of addAll.
+     * The current index into the new data that has not yet been processed during a mutation
+     * operation (addAll or replaceAll).
      */
-    private int mMergedSize;
-
+    private int mNewDataStart;
 
     /**
      * The callback instance that controls the behavior of the SortedList and get notified when
@@ -131,7 +140,7 @@
      * @see Callback#areContentsTheSame(Object, Object)}
      */
     public int add(T item) {
-        throwIfMerging();
+        throwIfInMutationOperation();
         return add(item, true);
     }
 
@@ -140,30 +149,30 @@
      * except the callback events may be in a different order/granularity since addAll can batch
      * them for better performance.
      * <p>
-     * If allowed, may modify the input array and even take the ownership over it in order
-     * to avoid extra memory allocation during sorting and deduplication.
-     * </p>
+     * If allowed, will reference the input array during, and possibly after, the operation to avoid
+     * extra memory allocation, in which case you should not continue to reference or modify the
+     * array yourself.
+     * <p>
      * @param items Array of items to be added into the list.
-     * @param mayModifyInput If true, SortedList is allowed to modify the input.
-     * @see SortedList#addAll(Object[] items)
+     * @param mayModifyInput If true, SortedList is allowed to modify and permanently reference the
+     *                       input array.
+     * @see SortedList#addAll(T[] items)
      */
     public void addAll(T[] items, boolean mayModifyInput) {
-        throwIfMerging();
+        throwIfInMutationOperation();
         if (items.length == 0) {
             return;
         }
+
         if (mayModifyInput) {
             addAllInternal(items);
         } else {
-            T[] copy = (T[]) Array.newInstance(mTClass, items.length);
-            System.arraycopy(items, 0, copy, 0, items.length);
-            addAllInternal(copy);
+            addAllInternal(copyArray(items));
         }
-
     }
 
     /**
-     * Adds the given items to the list. Does not modify the input.
+     * Adds the given items to the list. Does not modify or retain the input.
      *
      * @see SortedList#addAll(T[] items, boolean mayModifyInput)
      *
@@ -174,7 +183,7 @@
     }
 
     /**
-     * Adds the given items to the list. Does not modify the input.
+     * Adds the given items to the list. Does not modify or retain the input.
      *
      * @see SortedList#addAll(T[] items, boolean mayModifyInput)
      *
@@ -185,26 +194,133 @@
         addAll(items.toArray(copy), true);
     }
 
+    /**
+     * Replaces the current items with the new items, dispatching {@link ListUpdateCallback} events
+     * for each change detected as appropriate.
+     * <p>
+     * If allowed, will reference the input array during, and possibly after, the operation to avoid
+     * extra memory allocation, in which case you should not continue to reference or modify the
+     * array yourself.
+     * <p>
+     * Note: this method does not detect moves or dispatch
+     * {@link ListUpdateCallback#onMoved(int, int)} events. It instead treats moves as a remove
+     * followed by an add and therefore dispatches {@link ListUpdateCallback#onRemoved(int, int)}
+     * and {@link ListUpdateCallback#onRemoved(int, int)} events.  See {@link DiffUtil} if you want
+     * your implementation to dispatch move events.
+     * <p>
+     * @param items Array of items to replace current items.
+     * @param mayModifyInput If true, SortedList is allowed to modify and permanently reference the
+     *                       input array.
+     * @see #replaceAll(T[])
+     */
+    public void replaceAll(@NonNull T[] items, boolean mayModifyInput) {
+        throwIfInMutationOperation();
+
+        if (mayModifyInput) {
+            replaceAllInternal(items);
+        } else {
+            replaceAllInternal(copyArray(items));
+        }
+    }
+
+    /**
+     * Replaces the current items with the new items, dispatching {@link ListUpdateCallback} events
+     * for each change detected as appropriate.  Does not modify or retain the input.
+     *
+     * @see #replaceAll(T[], boolean)
+     *
+     * @param items Array of items to replace current items.
+     */
+    public void replaceAll(@NonNull T... items) {
+        replaceAll(items, false);
+    }
+
+    /**
+     * Replaces the current items with the new items, dispatching {@link ListUpdateCallback} events
+     * for each change detected as appropriate. Does not modify or retain the input.
+     *
+     * @see #replaceAll(T[], boolean)
+     *
+     * @param items Array of items to replace current items.
+     */
+    public void replaceAll(@NonNull Collection<T> items) {
+        T[] copy = (T[]) Array.newInstance(mTClass, items.size());
+        replaceAll(items.toArray(copy), true);
+    }
+
     private void addAllInternal(T[] newItems) {
+        if (newItems.length < 1) {
+            return;
+        }
+
+        final int newSize = sortAndDedup(newItems);
+
+        if (mSize == 0) {
+            mData = newItems;
+            mSize = newSize;
+            mCallback.onInserted(0, newSize);
+        } else {
+            merge(newItems, newSize);
+        }
+    }
+
+    private void replaceAllInternal(@NonNull T[] newData) {
         final boolean forceBatchedUpdates = !(mCallback instanceof BatchedCallback);
         if (forceBatchedUpdates) {
             beginBatchedUpdates();
         }
 
-        mOldData = mData;
         mOldDataStart = 0;
         mOldDataSize = mSize;
+        mOldData = mData;
 
-        Arrays.sort(newItems, mCallback);  // Arrays.sort is stable.
+        mNewDataStart = 0;
+        int newSize = sortAndDedup(newData);
+        mData = (T[]) Array.newInstance(mTClass, newSize);
 
-        final int newSize = deduplicate(newItems);
-        if (mSize == 0) {
-            mData = newItems;
-            mSize = newSize;
-            mMergedSize = newSize;
-            mCallback.onInserted(0, newSize);
-        } else {
-            merge(newItems, newSize);
+        while (mNewDataStart < newSize || mOldDataStart < mOldDataSize) {
+            if (mOldDataStart >= mOldDataSize) {
+                int insertIndex = mNewDataStart;
+                int itemCount = newSize - mNewDataStart;
+                System.arraycopy(newData, insertIndex, mData, insertIndex, itemCount);
+                mNewDataStart += itemCount;
+                mSize += itemCount;
+                mCallback.onInserted(insertIndex, itemCount);
+                break;
+            }
+            if (mNewDataStart >= newSize) {
+                int itemCount = mOldDataSize - mOldDataStart;
+                mSize -= itemCount;
+                mCallback.onRemoved(mNewDataStart, itemCount);
+                break;
+            }
+
+            T oldItem = mOldData[mOldDataStart];
+            T newItem = newData[mNewDataStart];
+
+            int result = mCallback.compare(oldItem, newItem);
+            if (result < 0) {
+                replaceAllRemove();
+            } else if (result > 0) {
+                replaceAllInsert(newItem);
+            } else {
+                if (!mCallback.areItemsTheSame(oldItem, newItem)) {
+                    // The items aren't the same even though they were supposed to occupy the same
+                    // place, so both notify to remove and add an item in the current location.
+                    replaceAllRemove();
+                    replaceAllInsert(newItem);
+                } else {
+                    mData[mNewDataStart] = newItem;
+                    mOldDataStart++;
+                    mNewDataStart++;
+                    if (!mCallback.areContentsTheSame(oldItem, newItem)) {
+                        // The item is the same but the contents have changed, so notify that an
+                        // onChanged event has occurred.
+                        mCallback.onChanged(mNewDataStart - 1, 1,
+                                mCallback.getChangePayload(oldItem, newItem));
+                    }
+                }
+            }
         }
 
         mOldData = null;
@@ -214,17 +330,33 @@
         }
     }
 
+    private void replaceAllInsert(T newItem) {
+        mData[mNewDataStart] = newItem;
+        mNewDataStart++;
+        mSize++;
+        mCallback.onInserted(mNewDataStart - 1, 1);
+    }
+
+    private void replaceAllRemove() {
+        mSize--;
+        mOldDataStart++;
+        mCallback.onRemoved(mNewDataStart, 1);
+    }
+
     /**
-     * Remove duplicate items, leaving only the last item from each group of "same" items.
-     * Move the remaining items to the beginning of the array.
+     * Sorts and removes duplicate items, leaving only the last item from each group of "same"
+     * items. Move the remaining items to the beginning of the array.
      *
      * @return Number of deduplicated items at the beginning of the array.
      */
-    private int deduplicate(T[] items) {
+    private int sortAndDedup(@NonNull T[] items) {
         if (items.length == 0) {
-            throw new IllegalArgumentException("Input array must be non-empty");
+            return 0;
         }
 
+        // Arrays.sort is stable.
+        Arrays.sort(items, mCallback);
+
         // Keep track of the range of equal items at the end of the output.
         // Start with the range containing just the first item.
         int rangeStart = 0;
@@ -234,9 +366,6 @@
             T currentItem = items[i];
 
             int compare = mCallback.compare(items[rangeStart], currentItem);
-            if (compare > 0) {
-                throw new IllegalArgumentException("Input must be sorted in ascending order.");
-            }
 
             if (compare == 0) {
                 // The range of equal items continues, update it.
@@ -276,27 +405,36 @@
      * This method assumes that newItems are sorted and deduplicated.
      */
     private void merge(T[] newData, int newDataSize) {
+        final boolean forceBatchedUpdates = !(mCallback instanceof BatchedCallback);
+        if (forceBatchedUpdates) {
+            beginBatchedUpdates();
+        }
+
+        mOldData = mData;
+        mOldDataStart = 0;
+        mOldDataSize = mSize;
+
         final int mergedCapacity = mSize + newDataSize + CAPACITY_GROWTH;
         mData = (T[]) Array.newInstance(mTClass, mergedCapacity);
-        mMergedSize = 0;
+        mNewDataStart = 0;
 
         int newDataStart = 0;
         while (mOldDataStart < mOldDataSize || newDataStart < newDataSize) {
             if (mOldDataStart == mOldDataSize) {
                 // No more old items, copy the remaining new items.
                 int itemCount = newDataSize - newDataStart;
-                System.arraycopy(newData, newDataStart, mData, mMergedSize, itemCount);
-                mMergedSize += itemCount;
+                System.arraycopy(newData, newDataStart, mData, mNewDataStart, itemCount);
+                mNewDataStart += itemCount;
                 mSize += itemCount;
-                mCallback.onInserted(mMergedSize - itemCount, itemCount);
+                mCallback.onInserted(mNewDataStart - itemCount, itemCount);
                 break;
             }
 
             if (newDataStart == newDataSize) {
                 // No more new items, copy the remaining old items.
                 int itemCount = mOldDataSize - mOldDataStart;
-                System.arraycopy(mOldData, mOldDataStart, mData, mMergedSize, itemCount);
-                mMergedSize += itemCount;
+                System.arraycopy(mOldData, mOldDataStart, mData, mNewDataStart, itemCount);
+                mNewDataStart += itemCount;
                 break;
             }
 
@@ -305,35 +443,47 @@
             int compare = mCallback.compare(oldItem, newItem);
             if (compare > 0) {
                 // New item is lower, output it.
-                mData[mMergedSize++] = newItem;
+                mData[mNewDataStart++] = newItem;
                 mSize++;
                 newDataStart++;
-                mCallback.onInserted(mMergedSize - 1, 1);
+                mCallback.onInserted(mNewDataStart - 1, 1);
             } else if (compare == 0 && mCallback.areItemsTheSame(oldItem, newItem)) {
                 // Items are the same. Output the new item, but consume both.
-                mData[mMergedSize++] = newItem;
+                mData[mNewDataStart++] = newItem;
                 newDataStart++;
                 mOldDataStart++;
                 if (!mCallback.areContentsTheSame(oldItem, newItem)) {
-                    mCallback.onChanged(mMergedSize - 1, 1);
+                    mCallback.onChanged(mNewDataStart - 1, 1,
+                            mCallback.getChangePayload(oldItem, newItem));
                 }
             } else {
                 // Old item is lower than or equal to (but not the same as the new). Output it.
                 // New item with the same sort order will be inserted later.
-                mData[mMergedSize++] = oldItem;
+                mData[mNewDataStart++] = oldItem;
                 mOldDataStart++;
             }
         }
-    }
 
-    private void throwIfMerging() {
-        if (mOldData != null) {
-            throw new IllegalStateException("Cannot call this method from within addAll");
+        mOldData = null;
+
+        if (forceBatchedUpdates) {
+            endBatchedUpdates();
         }
     }
 
     /**
-     * Batches adapter updates that happen between calling this method until calling
+     * Throws an exception if called while we are in the middle of a mutation operation (addAll or
+     * replaceAll).
+     */
+    private void throwIfInMutationOperation() {
+        if (mOldData != null) {
+            throw new IllegalStateException("Data cannot be mutated in the middle of a batch "
+                    + "update operation such as addAll or replaceAll.");
+        }
+    }
+
+    /**
+     * Batches adapter updates that happen after calling this method and before calling
      * {@link #endBatchedUpdates()}. For example, if you add multiple items in a loop
      * and they are placed into consecutive indices, SortedList calls
      * {@link Callback#onInserted(int, int)} only once with the proper item count. If an event
@@ -365,7 +515,7 @@
      * has no effect.
      */
     public void beginBatchedUpdates() {
-        throwIfMerging();
+        throwIfInMutationOperation();
         if (mCallback instanceof BatchedCallback) {
             return;
         }
@@ -379,7 +529,7 @@
      * Ends the update transaction and dispatches any remaining event to the callback.
      */
     public void endBatchedUpdates() {
-        throwIfMerging();
+        throwIfInMutationOperation();
         if (mCallback instanceof BatchedCallback) {
             ((BatchedCallback) mCallback).dispatchLastEvent();
         }
@@ -401,7 +551,7 @@
                     return index;
                 } else {
                     mData[index] = item;
-                    mCallback.onChanged(index, 1);
+                    mCallback.onChanged(index, 1, mCallback.getChangePayload(existing, item));
                     return index;
                 }
             }
@@ -421,7 +571,7 @@
      * @return True if item is removed, false if item cannot be found in the list.
      */
     public boolean remove(T item) {
-        throwIfMerging();
+        throwIfInMutationOperation();
         return remove(item, true);
     }
 
@@ -433,7 +583,7 @@
      * @return The removed item.
      */
     public T removeItemAt(int index) {
-        throwIfMerging();
+        throwIfInMutationOperation();
         T item = get(index);
         removeItemAtIndex(index, true);
         return item;
@@ -478,7 +628,7 @@
      * @see #add(Object)
      */
     public void updateItemAt(int index, T item) {
-        throwIfMerging();
+        throwIfInMutationOperation();
         final T existing = get(index);
         // assume changed if the same object is given back
         boolean contentsChanged = existing == item || !mCallback.areContentsTheSame(existing, item);
@@ -488,13 +638,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);
@@ -532,7 +682,7 @@
      * @see #add(Object)
      */
     public void recalculatePositionOfItemAt(int index) {
-        throwIfMerging();
+        throwIfInMutationOperation();
         // TODO can be improved
         final T item = get(index);
         removeItemAtIndex(index, false);
@@ -559,8 +709,8 @@
         if (mOldData != null) {
             // The call is made from a callback during addAll execution. The data is split
             // between mData and mOldData.
-            if (index >= mMergedSize) {
-                return mOldData[index - mMergedSize + mOldDataStart];
+            if (index >= mNewDataStart) {
+                return mOldData[index - mNewDataStart + mOldDataStart];
             }
         }
         return mData[index];
@@ -576,13 +726,13 @@
      */
     public int indexOf(T item) {
         if (mOldData != null) {
-            int index = findIndexOf(item, mData, 0, mMergedSize, LOOKUP);
+            int index = findIndexOf(item, mData, 0, mNewDataStart, LOOKUP);
             if (index != INVALID_POSITION) {
                 return index;
             }
             index = findIndexOf(item, mOldData, mOldDataStart, mOldDataSize, LOOKUP);
             if (index != INVALID_POSITION) {
-                return index - mOldDataStart + mMergedSize;
+                return index - mOldDataStart + mNewDataStart;
             }
             return INVALID_POSITION;
         }
@@ -659,11 +809,17 @@
         mSize++;
     }
 
+    private T[] copyArray(T[] items) {
+        T[] copy = (T[]) Array.newInstance(mTClass, items.length);
+        System.arraycopy(items, 0, copy, 0, items.length);
+        return copy;
+    }
+
     /**
      * Removes all items from the SortedList.
      */
     public void clear() {
-        throwIfMerging();
+        throwIfInMutationOperation();
         if (mSize == 0) {
             return;
         }
@@ -719,8 +875,8 @@
          * so
          * that you can change its behavior depending on your UI.
          * <p>
-         * For example, if you are using SortedList with a {@link android.support.v7.widget.RecyclerView.Adapter
-         * RecyclerView.Adapter}, you should
+         * For example, if you are using SortedList with a
+         * {@link android.support.v7.widget.RecyclerView.Adapter RecyclerView.Adapter}, you should
          * return whether the items' visual representations are the same or not.
          *
          * @param oldItem The previous representation of the object.
@@ -731,7 +887,7 @@
         abstract public boolean areContentsTheSame(T2 oldItem, T2 newItem);
 
         /**
-         * Called by the SortedList to decide whether two object represent the same Item or not.
+         * Called by the SortedList to decide whether two objects represent the same Item or not.
          * <p>
          * For example, if your items have unique ids, this method should check their equality.
          *
@@ -741,6 +897,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 +979,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 +993,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/RecyclerView.java b/v7/recyclerview/src/main/java/android/support/v7/widget/RecyclerView.java
index cfa28e8..afadfc9 100644
--- a/v7/recyclerview/src/main/java/android/support/v7/widget/RecyclerView.java
+++ b/v7/recyclerview/src/main/java/android/support/v7/widget/RecyclerView.java
@@ -44,6 +44,7 @@
 import android.support.annotation.RestrictTo;
 import android.support.annotation.VisibleForTesting;
 import android.support.v4.os.TraceCompat;
+import android.support.v4.util.Preconditions;
 import android.support.v4.view.AbsSavedState;
 import android.support.v4.view.InputDeviceCompat;
 import android.support.v4.view.MotionEventCompat;
@@ -385,8 +386,8 @@
     private List<OnChildAttachStateChangeListener> mOnChildAttachStateListeners;
 
     /**
-     * Set to true when an adapter data set changed notification is received.
-     * In that case, we cannot run any animations since we don't know what happened until layout.
+     * True after an event occurs that signals that the entire data set has changed. In that case,
+     * we cannot run any animations since we don't know what happened until layout.
      *
      * Attached items are invalid until next layout, at which point layout will animate/replace
      * items as necessary, building up content from the (effectively) new adapter from scratch.
@@ -394,11 +395,20 @@
      * Cached items must be discarded when setting this to true, so that the cache may be freely
      * used by prefetching until the next layout occurs.
      *
-     * @see #setDataSetChangedAfterLayout()
+     * @see #processDataSetCompletelyChanged(boolean)
      */
     boolean mDataSetHasChangedAfterLayout = false;
 
     /**
+     * True after the data set has completely changed and
+     * {@link LayoutManager#onItemsChanged(RecyclerView)} should be called during the subsequent
+     * measure/layout.
+     *
+     * @see #processDataSetCompletelyChanged(boolean)
+     */
+    boolean mDispatchItemsChangedEvent = false;
+
+    /**
      * This variable is incremented during a dispatchLayout and/or scroll.
      * Some methods should not be called during these periods (e.g. adapter data change).
      * Doing so will create hard to find bugs so we better check it and throw an exception.
@@ -417,6 +427,8 @@
      */
     private int mDispatchScrollCounter = 0;
 
+    @NonNull
+    private EdgeEffectFactory mEdgeEffectFactory = new EdgeEffectFactory();
     private EdgeEffect mLeftGlow, mTopGlow, mRightGlow, mBottomGlow;
 
     ItemAnimator mItemAnimator = new DefaultItemAnimator();
@@ -1041,6 +1053,7 @@
         // bail out if layout is frozen
         setLayoutFrozen(false);
         setAdapterInternal(adapter, true, removeAndRecycleExistingViews);
+        processDataSetCompletelyChanged(true);
         requestLayout();
     }
     /**
@@ -1056,6 +1069,7 @@
         // bail out if layout is frozen
         setLayoutFrozen(false);
         setAdapterInternal(adapter, false, true);
+        processDataSetCompletelyChanged(false);
         requestLayout();
     }
 
@@ -1109,7 +1123,6 @@
         }
         mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious);
         mState.mStructureChanged = true;
-        setDataSetChangedAfterLayout();
     }
 
     /**
@@ -2306,7 +2319,7 @@
         if (mLeftGlow != null) {
             return;
         }
-        mLeftGlow = new EdgeEffect(getContext());
+        mLeftGlow = mEdgeEffectFactory.createEdgeEffect(this, EdgeEffectFactory.DIRECTION_LEFT);
         if (mClipToPadding) {
             mLeftGlow.setSize(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(),
                     getMeasuredWidth() - getPaddingLeft() - getPaddingRight());
@@ -2319,7 +2332,7 @@
         if (mRightGlow != null) {
             return;
         }
-        mRightGlow = new EdgeEffect(getContext());
+        mRightGlow = mEdgeEffectFactory.createEdgeEffect(this, EdgeEffectFactory.DIRECTION_RIGHT);
         if (mClipToPadding) {
             mRightGlow.setSize(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(),
                     getMeasuredWidth() - getPaddingLeft() - getPaddingRight());
@@ -2332,7 +2345,7 @@
         if (mTopGlow != null) {
             return;
         }
-        mTopGlow = new EdgeEffect(getContext());
+        mTopGlow = mEdgeEffectFactory.createEdgeEffect(this, EdgeEffectFactory.DIRECTION_TOP);
         if (mClipToPadding) {
             mTopGlow.setSize(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
                     getMeasuredHeight() - getPaddingTop() - getPaddingBottom());
@@ -2346,7 +2359,7 @@
         if (mBottomGlow != null) {
             return;
         }
-        mBottomGlow = new EdgeEffect(getContext());
+        mBottomGlow = mEdgeEffectFactory.createEdgeEffect(this, EdgeEffectFactory.DIRECTION_BOTTOM);
         if (mClipToPadding) {
             mBottomGlow.setSize(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
                     getMeasuredHeight() - getPaddingTop() - getPaddingBottom());
@@ -2360,6 +2373,32 @@
     }
 
     /**
+     * Set a {@link EdgeEffectFactory} for this {@link RecyclerView}.
+     * <p>
+     * When a new {@link EdgeEffectFactory} is set, any existing over-scroll effects are cleared
+     * and new effects are created as needed using
+     * {@link EdgeEffectFactory#createEdgeEffect(RecyclerView, int)}
+     *
+     * @param edgeEffectFactory The {@link EdgeEffectFactory} instance.
+     */
+    public void setEdgeEffectFactory(@NonNull EdgeEffectFactory edgeEffectFactory) {
+        Preconditions.checkNotNull(edgeEffectFactory);
+        mEdgeEffectFactory = edgeEffectFactory;
+        invalidateGlows();
+    }
+
+    /**
+     * Retrieves the previously set {@link EdgeEffectFactory} or the default factory if nothing
+     * was set.
+     *
+     * @return The previously set {@link EdgeEffectFactory}
+     * @see #setEdgeEffectFactory(EdgeEffectFactory)
+     */
+    public EdgeEffectFactory getEdgeEffectFactory() {
+        return mEdgeEffectFactory;
+    }
+
+    /**
      * Since RecyclerView is a collection ViewGroup that includes virtual children (items that are
      * in the Adapter but not visible in the UI), it employs a more involved focus search strategy
      * that differs from other ViewGroups.
@@ -2480,9 +2519,17 @@
         if (next == null || next == this) {
             return false;
         }
+        // panic, result view is not a child anymore, maybe workaround b/37864393
+        if (findContainingItemView(next) == null) {
+            return false;
+        }
         if (focused == null) {
             return true;
         }
+        // panic, focused view is not a child anymore, maybe workaround b/37864393
+        if (findContainingItemView(focused) == null) {
+            return true;
+        }
 
         mTempRect.set(0, 0, focused.getWidth(), focused.getHeight());
         mTempRect2.set(0, 0, next.getWidth(), next.getHeight());
@@ -3192,7 +3239,8 @@
     }
 
     /**
-     * Used when onMeasure is called before layout manager is set
+     * An implementation of {@link View#onMeasure(int, int)} to fall back to in various scenarios
+     * where this RecyclerView is otherwise lacking better information.
      */
     void defaultOnMeasure(int widthSpec, int heightSpec) {
         // calling LayoutManager here is not pretty but that API is already public and it is better
@@ -3369,7 +3417,9 @@
             // Processing these items have no value since data set changed unexpectedly.
             // Instead, we just reset it.
             mAdapterHelper.reset();
-            mLayout.onItemsChanged(this);
+            if (mDispatchItemsChangedEvent) {
+                mLayout.onItemsChanged(this);
+            }
         }
         // simple animations are a subset of advanced animations (which will cause a
         // pre-layout step)
@@ -3792,6 +3842,7 @@
         mLayout.removeAndRecycleScrapInt(mRecycler);
         mState.mPreviousLayoutItemCount = mState.mItemCount;
         mDataSetHasChangedAfterLayout = false;
+        mDispatchItemsChangedEvent = false;
         mState.mRunSimpleAnimations = false;
 
         mState.mRunPredictiveAnimations = false;
@@ -4259,19 +4310,21 @@
                 viewHolder.getUnmodifiedPayloads());
     }
 
-
     /**
-     * Call this method to signal that *all* adapter content has changed (generally, because of
-     * setAdapter, swapAdapter, or notifyDataSetChanged), and that once layout occurs, all
-     * attached items should be discarded or animated.
+     * Processes the fact that, as far as we can tell, the data set has completely changed.
      *
-     * Attached items are labeled as invalid, and all cached items are discarded.
+     * <ul>
+     *   <li>Once layout occurs, all attached items should be discarded or animated.
+     *   <li>Attached items are labeled as invalid.
+     *   <li>Because items may still be prefetched between a "data set completely changed"
+     *       event and a layout event, all cached items are discarded.
+     * </ul>
      *
-     * It is still possible for items to be prefetched while mDataSetHasChangedAfterLayout == true,
-     * so this method must always discard all cached views so that the only valid items that remain
-     * in the cache, once layout occurs, are valid prefetched items.
+     * @param dispatchItemsChanged Whether to call
+     * {@link LayoutManager#onItemsChanged(RecyclerView)} during measure/layout.
      */
-    void setDataSetChangedAfterLayout() {
+    void processDataSetCompletelyChanged(boolean dispatchItemsChanged) {
+        mDispatchItemsChangedEvent |= dispatchItemsChanged;
         mDataSetHasChangedAfterLayout = true;
         markKnownViewsInvalid();
     }
@@ -5081,7 +5134,7 @@
             assertNotInLayoutOrScroll(null);
             mState.mStructureChanged = true;
 
-            setDataSetChangedAfterLayout();
+            processDataSetCompletelyChanged(true);
             if (!mAdapterHelper.hasPendingUpdates()) {
                 requestLayout();
             }
@@ -5130,6 +5183,46 @@
     }
 
     /**
+     * EdgeEffectFactory lets you customize the over-scroll edge effect for RecyclerViews.
+     *
+     * @see RecyclerView#setEdgeEffectFactory(EdgeEffectFactory)
+     */
+    public static class EdgeEffectFactory {
+
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef({DIRECTION_LEFT, DIRECTION_TOP, DIRECTION_RIGHT, DIRECTION_BOTTOM})
+        public @interface EdgeDirection {}
+
+        /**
+         * Direction constant for the left edge
+         */
+        public static final int DIRECTION_LEFT = 0;
+
+        /**
+         * Direction constant for the top edge
+         */
+        public static final int DIRECTION_TOP = 1;
+
+        /**
+         * Direction constant for the right edge
+         */
+        public static final int DIRECTION_RIGHT = 2;
+
+        /**
+         * Direction constant for the bottom edge
+         */
+        public static final int DIRECTION_BOTTOM = 3;
+
+        /**
+         * Create a new EdgeEffect for the provided direction.
+         */
+        protected @NonNull EdgeEffect createEdgeEffect(RecyclerView view,
+                @EdgeDirection int direction) {
+            return new EdgeEffect(view.getContext());
+        }
+    }
+
+    /**
      * RecycledViewPool lets you share Views between multiple RecyclerViews.
      * <p>
      * If you want to recycle views across RecyclerViews, create an instance of RecycledViewPool
@@ -6451,7 +6544,8 @@
          * @see #getItemViewType(int)
          * @see #onBindViewHolder(ViewHolder, int)
          */
-        public abstract VH onCreateViewHolder(ViewGroup parent, int viewType);
+        @NonNull
+        public abstract VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType);
 
         /**
          * Called by RecyclerView to display the data at the specified position. This method should
@@ -6473,7 +6567,7 @@
          *        item at the given position in the data set.
          * @param position The position of the item within the adapter's data set.
          */
-        public abstract void onBindViewHolder(VH holder, int position);
+        public abstract void onBindViewHolder(@NonNull VH holder, int position);
 
         /**
          * Called by RecyclerView to display the data at the specified position. This method
@@ -6504,7 +6598,8 @@
          * @param payloads A non-null list of merged payloads. Can be empty list if requires full
          *                 update.
          */
-        public void onBindViewHolder(VH holder, int position, List<Object> payloads) {
+        public void onBindViewHolder(@NonNull VH holder, int position,
+                @NonNull List<Object> payloads) {
             onBindViewHolder(holder, position);
         }
 
@@ -6514,7 +6609,7 @@
          *
          * @see #onCreateViewHolder(ViewGroup, int)
          */
-        public final VH createViewHolder(ViewGroup parent, int viewType) {
+        public final VH createViewHolder(@NonNull ViewGroup parent, int viewType) {
             TraceCompat.beginSection(TRACE_CREATE_VIEW_TAG);
             final VH holder = onCreateViewHolder(parent, viewType);
             holder.mItemViewType = viewType;
@@ -6529,7 +6624,7 @@
          *
          * @see #onBindViewHolder(ViewHolder, int)
          */
-        public final void bindViewHolder(VH holder, int position) {
+        public final void bindViewHolder(@NonNull VH holder, int position) {
             holder.mPosition = position;
             if (hasStableIds()) {
                 holder.mItemId = getItemId(position);
@@ -6626,7 +6721,7 @@
          *
          * @param holder The ViewHolder for the view being recycled
          */
-        public void onViewRecycled(VH holder) {
+        public void onViewRecycled(@NonNull VH holder) {
         }
 
         /**
@@ -6663,7 +6758,7 @@
          * RecyclerView will check the View's transient state again before giving a final decision.
          * Default implementation returns false.
          */
-        public boolean onFailedToRecycleView(VH holder) {
+        public boolean onFailedToRecycleView(@NonNull VH holder) {
             return false;
         }
 
@@ -6677,7 +6772,7 @@
          *
          * @param holder Holder of the view being attached
          */
-        public void onViewAttachedToWindow(VH holder) {
+        public void onViewAttachedToWindow(@NonNull VH holder) {
         }
 
         /**
@@ -6689,7 +6784,7 @@
          *
          * @param holder Holder of the view being detached
          */
-        public void onViewDetachedFromWindow(VH holder) {
+        public void onViewDetachedFromWindow(@NonNull VH holder) {
         }
 
         /**
@@ -6717,7 +6812,7 @@
          *
          * @see #unregisterAdapterDataObserver(RecyclerView.AdapterDataObserver)
          */
-        public void registerAdapterDataObserver(AdapterDataObserver observer) {
+        public void registerAdapterDataObserver(@NonNull AdapterDataObserver observer) {
             mObservable.registerObserver(observer);
         }
 
@@ -6731,7 +6826,7 @@
          *
          * @see #registerAdapterDataObserver(RecyclerView.AdapterDataObserver)
          */
-        public void unregisterAdapterDataObserver(AdapterDataObserver observer) {
+        public void unregisterAdapterDataObserver(@NonNull AdapterDataObserver observer) {
             mObservable.unregisterObserver(observer);
         }
 
@@ -6743,7 +6838,7 @@
          * @param recyclerView The RecyclerView instance which started observing this adapter.
          * @see #onDetachedFromRecyclerView(RecyclerView)
          */
-        public void onAttachedToRecyclerView(RecyclerView recyclerView) {
+        public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
         }
 
         /**
@@ -6752,7 +6847,7 @@
          * @param recyclerView The RecyclerView instance which stopped observing this adapter.
          * @see #onAttachedToRecyclerView(RecyclerView)
          */
-        public void onDetachedFromRecyclerView(RecyclerView recyclerView) {
+        public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) {
         }
 
         /**
@@ -6828,7 +6923,7 @@
          *
          * @see #notifyItemRangeChanged(int, int)
          */
-        public final void notifyItemChanged(int position, Object payload) {
+        public final void notifyItemChanged(int position, @Nullable Object payload) {
             mObservable.notifyItemRangeChanged(position, 1, payload);
         }
 
@@ -6876,7 +6971,8 @@
          *
          * @see #notifyItemChanged(int)
          */
-        public final void notifyItemRangeChanged(int positionStart, int itemCount, Object payload) {
+        public final void notifyItemRangeChanged(int positionStart, int itemCount,
+                @Nullable Object payload) {
             mObservable.notifyItemRangeChanged(positionStart, itemCount, payload);
         }
 
@@ -7350,9 +7446,10 @@
          * wants to handle the layout measurements itself.
          * <p>
          * This method is usually called by the LayoutManager with value {@code true} if it wants
-         * to support WRAP_CONTENT. If you are using a public LayoutManager but want to customize
-         * the measurement logic, you can call this method with {@code false} and override
-         * {@link LayoutManager#onMeasure(int, int)} to implement your custom measurement logic.
+         * to support {@link ViewGroup.LayoutParams#WRAP_CONTENT}. If you are using a public
+         * LayoutManager but want to customize the measurement logic, you can call this method with
+         * {@code false} and override {@link LayoutManager#onMeasure(Recycler, State, int, int)} to
+         * implement your custom measurement logic.
          * <p>
          * AutoMeasure is a convenience mechanism for LayoutManagers to easily wrap their content or
          * handle various specs provided by the RecyclerView's parent.
@@ -7426,24 +7523,26 @@
         }
 
         /**
-         * Returns whether this LayoutManager supports automatic item animations.
-         * A LayoutManager wishing to support item animations should obey certain
-         * rules as outlined in {@link #onLayoutChildren(Recycler, State)}.
-         * The default return value is <code>false</code>, so subclasses of LayoutManager
-         * will not get predictive item animations by default.
-         *
-         * <p>Whether item animations are enabled in a RecyclerView is determined both
-         * by the return value from this method and the
+         * Returns whether this LayoutManager supports "predictive item animations".
+         * <p>
+         * "Predictive item animations" are automatically created animations that show
+         * where items came from, and where they are going to, as items are added, removed,
+         * or moved within a layout.
+         * <p>
+         * A LayoutManager wishing to support predictive item animations must override this
+         * method to return true (the default implementation returns false) and must obey certain
+         * behavioral contracts outlined in {@link #onLayoutChildren(Recycler, State)}.
+         * <p>
+         * Whether item animations actually occur in a RecyclerView is actually determined by both
+         * the return value from this method and the
          * {@link RecyclerView#setItemAnimator(ItemAnimator) ItemAnimator} set on the
          * RecyclerView itself. If the RecyclerView has a non-null ItemAnimator but this
-         * method returns false, then simple item animations will be enabled, in which
-         * views that are moving onto or off of the screen are simply faded in/out. If
-         * the RecyclerView has a non-null ItemAnimator and this method returns true,
-         * then there will be two calls to {@link #onLayoutChildren(Recycler, State)} to
-         * setup up the information needed to more intelligently predict where appearing
-         * and disappearing views should be animated from/to.</p>
+         * method returns false, then only "simple item animations" will be enabled in the
+         * RecyclerView, in which views whose position are changing are simply faded in/out. If the
+         * RecyclerView has a non-null ItemAnimator and this method returns true, then predictive
+         * item animations will be enabled in the RecyclerView.
          *
-         * @return true if predictive item animations should be enabled, false otherwise
+         * @return true if this LayoutManager supports predictive item animations, false otherwise.
          */
         public boolean supportsPredictiveItemAnimations() {
             return false;
@@ -9422,9 +9521,11 @@
         }
 
         /**
-         * Called if the RecyclerView this LayoutManager is bound to has a different adapter set.
-         * The LayoutManager may use this opportunity to clear caches and configure state such
-         * that it can relayout appropriately with the new data and potentially new view types.
+         * Called if the RecyclerView this LayoutManager is bound to has a different adapter set via
+         * {@link RecyclerView#setAdapter(Adapter)} or
+         * {@link RecyclerView#swapAdapter(Adapter, boolean)}. The LayoutManager may use this
+         * opportunity to clear caches and configure state such that it can relayout appropriately
+         * with the new data and potentially new view types.
          *
          * <p>The default implementation removes all currently attached views.</p>
          *
@@ -9466,8 +9567,9 @@
         }
 
         /**
-         * Called when {@link Adapter#notifyDataSetChanged()} is triggered instead of giving
-         * detailed information on what has actually changed.
+         * Called in response to a call to {@link Adapter#notifyDataSetChanged()} or
+         * {@link RecyclerView#swapAdapter(Adapter, boolean)} ()} and signals that the the entire
+         * data set has changed.
          *
          * @param recyclerView
          */
@@ -10734,8 +10836,12 @@
          */
         private void onEnteredHiddenState(RecyclerView parent) {
             // While the view item is in hidden state, make it invisible for the accessibility.
-            mWasImportantForAccessibilityBeforeHidden =
-                    ViewCompat.getImportantForAccessibility(itemView);
+            if (mPendingAccessibilityState != PENDING_ACCESSIBILITY_STATE_NOT_SET) {
+                mWasImportantForAccessibilityBeforeHidden = mPendingAccessibilityState;
+            } else {
+                mWasImportantForAccessibilityBeforeHidden =
+                        ViewCompat.getImportantForAccessibility(itemView);
+            }
             parent.setChildImportantForAccessibilityInternal(this,
                     ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
         }
@@ -11094,7 +11200,7 @@
             // do nothing
         }
 
-        public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
+        public void onItemRangeChanged(int positionStart, int itemCount, @Nullable Object payload) {
             // fallback to onItemRangeChanged(positionStart, itemCount) if app
             // does not override this method.
             onItemRangeChanged(positionStart, itemCount);
@@ -11571,7 +11677,8 @@
             notifyItemRangeChanged(positionStart, itemCount, null);
         }
 
-        public void notifyItemRangeChanged(int positionStart, int itemCount, Object payload) {
+        public void notifyItemRangeChanged(int positionStart, int itemCount,
+                @Nullable Object payload) {
             // since onItemRangeChanged() is implemented by the app, it could do anything, including
             // removing itself from {@link mObservers} - and that could cause problems if
             // an iterator is used on the ArrayList {@link mObservers}.
@@ -11725,6 +11832,11 @@
 
         boolean mStructureChanged = false;
 
+        /**
+         * True if the associated {@link RecyclerView} is in the pre-layout step where it is having
+         * its {@link LayoutManager} layout items where they will be at the beginning of a set of
+         * predictive item animations.
+         */
         boolean mInPreLayout = false;
 
         boolean mTrackOldChangeHolders = false;
@@ -11800,8 +11912,9 @@
         }
 
         /**
-         * Returns true if
-         * @return
+         * Returns true if the {@link RecyclerView} is in the pre-layout step where it is having its
+         * {@link LayoutManager} layout items where they will be at the beginning of a set of
+         * predictive item animations.
          */
         public boolean isPreLayout() {
             return mInPreLayout;
@@ -11958,6 +12071,7 @@
                     + "mTargetPosition=" + mTargetPosition
                     + ", mData=" + mData
                     + ", mItemCount=" + mItemCount
+                    + ", mIsMeasuring=" + mIsMeasuring
                     + ", mPreviousLayoutItemCount=" + mPreviousLayoutItemCount
                     + ", mDeletedInvisibleItemCountSincePreviousLayout="
                     + mDeletedInvisibleItemCountSincePreviousLayout
diff --git a/v7/recyclerview/src/main/java/android/support/v7/widget/helper/ItemTouchHelper.java b/v7/recyclerview/src/main/java/android/support/v7/widget/helper/ItemTouchHelper.java
index aee48df..d2b6a20 100644
--- a/v7/recyclerview/src/main/java/android/support/v7/widget/helper/ItemTouchHelper.java
+++ b/v7/recyclerview/src/main/java/android/support/v7/widget/helper/ItemTouchHelper.java
@@ -457,7 +457,7 @@
             destroyCallbacks();
         }
         mRecyclerView = recyclerView;
-        if (mRecyclerView != null) {
+        if (recyclerView != null) {
             final Resources resources = recyclerView.getResources();
             mSwipeEscapeVelocity = resources
                     .getDimension(R.dimen.item_touch_helper_swipe_escape_velocity);
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..f8bc496 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;
@@ -26,11 +27,15 @@
 import org.junit.runners.JUnit4;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.LinkedList;
 import java.util.List;
+import java.util.Queue;
 import java.util.Random;
+import java.util.concurrent.atomic.AtomicInteger;
 
 @RunWith(JUnit4.class)
 @SmallTest
@@ -41,6 +46,10 @@
     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<>();
+    Queue<AssertListStateRunnable> mCallbackRunnables;
+    List<Event> mEvents = new ArrayList<>();
     private SortedList.Callback<Item> mCallback;
     InsertedCallback<Item> mInsertedCallback;
     ChangedCallback<Item> mChangedCallback;
@@ -64,6 +73,7 @@
     @Before
     public void setUp() throws Exception {
         super.setUp();
+
         mCallback = new SortedList.Callback<Item>() {
             @Override
             public int compare(Item o1, Item o2) {
@@ -72,45 +82,100 @@
 
             @Override
             public void onInserted(int position, int count) {
+                mEvents.add(new Event(TYPE.ADD, position, count));
                 mAdditions.add(new Pair(position, count));
                 if (mInsertedCallback != null) {
                     mInsertedCallback.onInserted(position, count);
                 }
+                pollAndRun(mCallbackRunnables);
             }
 
             @Override
             public void onRemoved(int position, int count) {
+                mEvents.add(new Event(TYPE.REMOVE, position, count));
                 mRemovals.add(new Pair(position, count));
+                pollAndRun(mCallbackRunnables);
             }
 
             @Override
             public void onMoved(int fromPosition, int toPosition) {
+                mEvents.add(new Event(TYPE.MOVE, fromPosition, toPosition));
                 mMoves.add(new Pair(fromPosition, toPosition));
             }
 
             @Override
             public void onChanged(int position, int count) {
+                mEvents.add(new Event(TYPE.CHANGE, position, count));
                 mUpdates.add(new Pair(position, count));
                 if (mChangedCallback != null) {
                     mChangedCallback.onChanged(position, count);
                 }
+                pollAndRun(mCallbackRunnables);
+            }
+
+            @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;
+                return oldItem.data == newItem.data;
             }
 
             @Override
             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;
         mList = new SortedList<Item>(Item.class, mCallback);
     }
 
+    private void pollAndRun(Queue<AssertListStateRunnable> queue) {
+        if (queue != null) {
+            Runnable runnable = queue.poll();
+            assertNotNull(runnable);
+            runnable.run();
+        }
+    }
+
+    @Test
+    public void testValidMethodsDuringOnInsertedCallbackFromEmptyList() {
+
+        final Item[] items =
+                new Item[] {new Item(0), new Item(1), new Item(2)};
+
+        final AtomicInteger atomicInteger = new AtomicInteger(0);
+        mInsertedCallback = new InsertedCallback<Item>() {
+            @Override
+            public void onInserted(int position, int count) {
+                for (int i = 0; i < count; i++) {
+                    assertEquals(mList.get(i), items[i]);
+                    assertEquals(mList.indexOf(items[i]), i);
+                    atomicInteger.incrementAndGet();
+                }
+            }
+        };
+
+        mList.add(items[0]);
+        mList.clear();
+        mList.addAll(items, false);
+        assertEquals(4, atomicInteger.get());
+    }
+
     @Test
     public void testEmpty() {
         assertEquals("empty", mList.size(), 0);
@@ -118,16 +183,16 @@
 
     @Test
     public void testAdd() {
-        Item item = new Item();
+        Item item = new Item(1);
         assertEquals(insert(item), 0);
         assertEquals(size(), 1);
         assertTrue(mAdditions.contains(new Pair(0, 1)));
-        Item item2 = new Item();
+        Item item2 = new Item(2);
         item2.cmpField = item.cmpField + 1;
         assertEquals(insert(item2), 1);
         assertEquals(size(), 2);
         assertTrue(mAdditions.contains(new Pair(1, 1)));
-        Item item3 = new Item();
+        Item item3 = new Item(3);
         item3.cmpField = item.cmpField - 1;
         mAdditions.clear();
         assertEquals(insert(item3), 0);
@@ -137,9 +202,8 @@
 
     @Test
     public void testAddDuplicate() {
-        Item item = new Item();
-        Item item2 = new Item(item.id, item.cmpField);
-        item2.data = item.data;
+        Item item = new Item(1);
+        Item item2 = new Item(item.id);
         insert(item);
         assertEquals(0, insert(item2));
         assertEquals(1, size());
@@ -149,7 +213,7 @@
 
     @Test
     public void testRemove() {
-        Item item = new Item();
+        Item item = new Item(1);
         assertFalse(remove(item));
         assertEquals(0, mRemovals.size());
         insert(item);
@@ -163,8 +227,8 @@
 
     @Test
     public void testRemove2() {
-        Item item = new Item();
-        Item item2 = new Item(item.cmpField);
+        Item item = new Item(1);
+        Item item2 = new Item(2, 1, 1);
         insert(item);
         assertFalse(remove(item2));
         assertEquals(0, mRemovals.size());
@@ -197,11 +261,12 @@
         Random random = new Random(System.nanoTime());
         List<Item> copy = new ArrayList<Item>();
         StringBuilder log = new StringBuilder();
+        int id = 1;
         try {
             for (int i = 0; i < 10000; i++) {
                 switch (random.nextInt(3)) {
                     case 0://ADD
-                        Item item = new Item();
+                        Item item = new Item(id++);
                         copy.add(item);
                         insert(item);
                         log.append("add ").append(item).append("\n");
@@ -220,12 +285,13 @@
                             int index = random.nextInt(mList.size());
                             item = mList.get(index);
                             // TODO this cannot work
-                            Item newItem = new Item(item.id, item.cmpField);
-                            log.append("update ").append(item).append(" to ").append(newItem)
-                                    .append("\n");
+                            Item newItem =
+                                    new Item(item.id, item.cmpField, random.nextInt(1000));
                             while (newItem.data == item.data) {
                                 newItem.data = random.nextInt(1000);
                             }
+                            log.append("update ").append(item).append(" to ").append(newItem)
+                                    .append("\n");
                             int itemIndex = mList.add(newItem);
                             copy.remove(item);
                             copy.add(newItem);
@@ -237,10 +303,12 @@
                         if (copy.size() > 0) {
                             int index = random.nextInt(mList.size());
                             item = mList.get(index);
-                            Item newItem = new Item(item.id, random.nextInt());
+                            Item newItem = new Item(item.id, random.nextInt(), random.nextInt());
                             mList.updateItemAt(index, newItem);
                             copy.remove(item);
                             copy.add(newItem);
+                            log.append("update at ").append(index).append(" ").append(item)
+                                    .append(" to ").append(newItem).append("\n");
                         }
                 }
                 int lastCmp = Integer.MIN_VALUE;
@@ -278,14 +346,21 @@
         Item[] items = new Item[count];
         int id = idFrom;
         for (int i = 0; i < count; i++) {
-            Item item = new Item(id, id);
-            item.data = id;
+            Item item = new Item(id);
             items[i] = item;
             id += idStep;
         }
         return items;
     }
 
+    private static Item[] createItemsFromInts(int ... ints) {
+        Item[] items = new Item[ints.length];
+        for (int i = ints.length - 1; i >= 0; i--) {
+            items[i] = new Item(ints[i]);
+        }
+        return items;
+    }
+
     private static Item[] shuffle(Item[] items) {
         Random random = new Random(System.nanoTime());
         final int count = items.length;
@@ -472,8 +547,7 @@
             int uniqueId = 0;
             for (int cmpField = 0; cmpField < maxCmpField; cmpField++) {
                 for (int id = 0; id < idsPerCmpField; id++) {
-                    Item item = new Item(uniqueId++, cmpField);
-                    item.data = generation;
+                    Item item = new Item(uniqueId++, cmpField, generation);
                     items[index++] = item;
                 }
             }
@@ -527,13 +601,13 @@
     @Test
     public void testAddAllStableSort() {
         int id = 0;
-        Item item = new Item(id++, 0);
+        Item item = new Item(id++, 0, 0);
         mList.add(item);
 
         // Create a few items with the same sort order.
         Item[] items = new Item[3];
         for (int i = 0; i < 3; i++) {
-            items[i] = new Item(id++, item.cmpField);
+            items[i] = new Item(id++, item.cmpField, 0);
             assertEquals(0, mCallback.compare(item, items[i]));
         }
 
@@ -555,6 +629,7 @@
             item.data = 1;
         }
 
+
         mInsertedCallback = new InsertedCallback<Item>() {
             @Override
             public void onInserted(int position, int count) {
@@ -564,6 +639,7 @@
                     assertEquals(i * 2, mList.get(i).id);
                 }
                 assertIntegrity(5, "onInserted(" + position + ", " + count + ")");
+
             }
         };
 
@@ -618,7 +694,7 @@
             @Override
             public void onInserted(int position, int count) {
                 try {
-                    mList.add(new Item());
+                    mList.add(new Item(1));
                     fail("add must throw from within a callback");
                 } catch (IllegalStateException e) {
                 }
@@ -705,6 +781,718 @@
         assertTrue(mAdditions.contains(new Pair(0, 6)));
     }
 
+    @Test
+    public void testAddExistingItemCallsChangeWithPayload() {
+        mList.addAll(
+                new Item(1),
+                new Item(2),
+                new Item(3)
+        );
+        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);
+        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),
+                new Item(2),
+                new Item(3)
+        );
+        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);
+        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),
+                new Item(2),
+                new Item(3)
+        );
+        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);
+        twoUpdate.data = 222;
+        final Item threeUpdate = new Item(3);
+        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());
+    }
+
+    @Test
+    public void replaceAll_mayModifyInputFalse_doesNotModify() {
+        mList.addAll(
+                new Item(1),
+                new Item(2)
+        );
+        Item replacement0 = new Item(4);
+        Item replacement1 = new Item(3);
+        Item[] replacements = new Item[]{
+                replacement0,
+                replacement1
+        };
+
+        mList.replaceAll(replacements, false);
+
+        assertSame(replacement0, replacements[0]);
+        assertSame(replacement1, replacements[1]);
+    }
+
+    @Test
+    public void replaceAll_varArgs_isEquivalentToDefault() {
+        mList.addAll(
+                new Item(1),
+                new Item(2)
+        );
+        Item replacement0 = new Item(3);
+        Item replacement1 = new Item(4);
+
+        mList.replaceAll(replacement0, replacement1);
+
+        assertEquals(mList.get(0), replacement0);
+        assertEquals(mList.get(1), replacement1);
+        assertEquals(2, mList.size());
+    }
+
+    @Test
+    public void replaceAll_collection_isEquivalentToDefaultWithMayModifyInputFalse() {
+        mList.addAll(
+                new Item(1),
+                new Item(2)
+        );
+        Item replacement0 = new Item(4);
+        Item replacement1 = new Item(3);
+        List<Item> replacements = new ArrayList<>();
+        replacements.add(replacement0);
+        replacements.add(replacement1);
+
+        mList.replaceAll(replacements);
+
+        assertEquals(mList.get(0), replacement1);
+        assertEquals(mList.get(1), replacement0);
+        assertSame(replacements.get(0), replacement0);
+        assertSame(replacements.get(1), replacement1);
+        assertEquals(2, mList.size());
+    }
+
+    @Test
+    public void replaceAll_callsChangeWithPayload() {
+        mList.addAll(
+                new Item(1),
+                new Item(2),
+                new Item(3)
+        );
+        mPayloadChanges = true;
+        final Item twoUpdate = new Item(2);
+        twoUpdate.data = 222;
+        final Item threeUpdate = new Item(3);
+        threeUpdate.data = 333;
+
+        mList.replaceAll(twoUpdate, threeUpdate);
+
+        assertEquals(2, mPayloadUpdates.size());
+        final PayloadChange update1 = mPayloadUpdates.get(0);
+        assertEquals(0, update1.position);
+        assertEquals(1, update1.count);
+        assertEquals(222, update1.payload);
+        final PayloadChange update2 = mPayloadUpdates.get(1);
+        assertEquals(1, update2.position);
+        assertEquals(1, update2.count);
+        assertEquals(333, update2.payload);
+    }
+
+    @Test
+    public void replaceAll_totallyEquivalentData_worksCorrectly() {
+        Item[] items1 = createItemsFromInts(1, 2, 3);
+        Item[] items2 = createItemsFromInts(1, 2, 3);
+        mList.addAll(items1);
+        mEvents.clear();
+
+        mList.replaceAll(items2);
+
+        assertEquals(0, mEvents.size());
+        assertTrue(sortedListEquals(mList, items2));
+    }
+
+    @Test
+    public void replaceAll_removalsAndAdds1_worksCorrectly() {
+        Item[] items1 = createItemsFromInts(1, 3, 5);
+        Item[] items2 = createItemsFromInts(2, 4);
+        mList.addAll(items1);
+        mEvents.clear();
+
+        mCallbackRunnables = new LinkedList<>();
+        mCallbackRunnables.add(new AssertListStateRunnable(createItemsFromInts(2, 3, 5)));
+        mCallbackRunnables.add(new AssertListStateRunnable(createItemsFromInts(2, 5)));
+        mCallbackRunnables.add(new AssertListStateRunnable(createItemsFromInts(2, 4, 5)));
+        mCallbackRunnables.add(new AssertListStateRunnable(items2));
+        mCallbackRunnables.add(new AssertListStateRunnable(items2));
+
+        mList.replaceAll(items2);
+
+        assertEquals(new Event(TYPE.REMOVE, 0, 1), mEvents.get(0));
+        assertEquals(new Event(TYPE.ADD, 0, 1), mEvents.get(1));
+        assertEquals(new Event(TYPE.REMOVE, 1, 1), mEvents.get(2));
+        assertEquals(new Event(TYPE.ADD, 1, 1), mEvents.get(3));
+        assertEquals(new Event(TYPE.REMOVE, 2, 1), mEvents.get(4));
+        assertEquals(5, mEvents.size());
+        assertTrue(sortedListEquals(mList, items2));
+        assertTrue(mCallbackRunnables.isEmpty());
+    }
+
+    @Test
+    public void replaceAll_removalsAndAdds2_worksCorrectly() {
+        Item[] items1 = createItemsFromInts(2, 4);
+        Item[] items2 = createItemsFromInts(1, 3, 5);
+        mList.addAll(items1);
+        mEvents.clear();
+
+        mCallbackRunnables = new LinkedList<>();
+        mCallbackRunnables.add(new AssertListStateRunnable(createItemsFromInts(1, 4)));
+        mCallbackRunnables.add(new AssertListStateRunnable(createItemsFromInts(1, 3, 4)));
+        mCallbackRunnables.add(new AssertListStateRunnable(createItemsFromInts(1, 3)));
+        mCallbackRunnables.add(new AssertListStateRunnable(items2));
+        mCallbackRunnables.add(new AssertListStateRunnable(items2));
+
+        mList.replaceAll(items2);
+
+        assertEquals(new Event(TYPE.ADD, 0, 1), mEvents.get(0));
+        assertEquals(new Event(TYPE.REMOVE, 1, 1), mEvents.get(1));
+        assertEquals(new Event(TYPE.ADD, 1, 1), mEvents.get(2));
+        assertEquals(new Event(TYPE.REMOVE, 2, 1), mEvents.get(3));
+        assertEquals(new Event(TYPE.ADD, 2, 1), mEvents.get(4));
+        assertEquals(5, mEvents.size());
+        assertTrue(sortedListEquals(mList, items2));
+        assertTrue(mCallbackRunnables.isEmpty());
+    }
+
+    @Test
+    public void replaceAll_removalsAndAdds3_worksCorrectly() {
+        Item[] items1 = createItemsFromInts(1, 3, 5);
+        Item[] items2 = createItemsFromInts(2, 3, 4);
+        mList.addAll(items1);
+        mEvents.clear();
+
+        mCallbackRunnables = new LinkedList<>();
+        mCallbackRunnables.add(new AssertListStateRunnable(createItemsFromInts(2, 3, 5)));
+        mCallbackRunnables.add(new AssertListStateRunnable(createItemsFromInts(2, 3, 4, 5)));
+        mCallbackRunnables.add(new AssertListStateRunnable(items2));
+        mCallbackRunnables.add(new AssertListStateRunnable(items2));
+
+        mList.replaceAll(items2);
+
+        assertEquals(new Event(TYPE.REMOVE, 0, 1), mEvents.get(0));
+        assertEquals(new Event(TYPE.ADD, 0, 1), mEvents.get(1));
+        assertEquals(new Event(TYPE.ADD, 2, 1), mEvents.get(2));
+        assertEquals(new Event(TYPE.REMOVE, 3, 1), mEvents.get(3));
+        assertEquals(4, mEvents.size());
+        assertTrue(sortedListEquals(mList, items2));
+        assertTrue(mCallbackRunnables.isEmpty());
+    }
+
+    @Test
+    public void replaceAll_removalsAndAdds4_worksCorrectly() {
+        Item[] items1 = createItemsFromInts(2, 3, 4);
+        Item[] items2 = createItemsFromInts(1, 3, 5);
+        mList.addAll(items1);
+        mEvents.clear();
+
+        mCallbackRunnables = new LinkedList<>();
+        mCallbackRunnables.add(new AssertListStateRunnable(createItemsFromInts(1, 3, 4)));
+        mCallbackRunnables.add(new AssertListStateRunnable(createItemsFromInts(1, 3)));
+        mCallbackRunnables.add(new AssertListStateRunnable(items2));
+        mCallbackRunnables.add(new AssertListStateRunnable(items2));
+
+        mList.replaceAll(items2);
+
+        assertEquals(new Event(TYPE.ADD, 0, 1), mEvents.get(0));
+        assertEquals(new Event(TYPE.REMOVE, 1, 1), mEvents.get(1));
+        assertEquals(new Event(TYPE.REMOVE, 2, 1), mEvents.get(2));
+        assertEquals(new Event(TYPE.ADD, 2, 1), mEvents.get(3));
+        assertEquals(4, mEvents.size());
+        assertTrue(sortedListEquals(mList, items2));
+        assertTrue(mCallbackRunnables.isEmpty());
+    }
+
+    @Test
+    public void replaceAll_removalsAndAdds5_worksCorrectly() {
+        Item[] items1 = createItemsFromInts(1, 2, 3);
+        Item[] items2 = createItemsFromInts(3, 4, 5);
+        mList.addAll(items1);
+        mEvents.clear();
+
+        mCallbackRunnables = new LinkedList<>();
+        mCallbackRunnables.add(new AssertListStateRunnable(items2));
+        mCallbackRunnables.add(new AssertListStateRunnable(items2));
+
+        mList.replaceAll(items2);
+
+        assertEquals(new Event(TYPE.REMOVE, 0, 2), mEvents.get(0));
+        assertEquals(new Event(TYPE.ADD, 1, 2), mEvents.get(1));
+        assertEquals(2, mEvents.size());
+        assertTrue(sortedListEquals(mList, items2));
+        assertTrue(mCallbackRunnables.isEmpty());
+    }
+
+    @Test
+    public void replaceAll_removalsAndAdds6_worksCorrectly() {
+        Item[] items1 = createItemsFromInts(3, 4, 5);
+        Item[] items2 = createItemsFromInts(1, 2, 3);
+        mList.addAll(items1);
+        mEvents.clear();
+
+        mCallbackRunnables = new LinkedList<>();
+        mCallbackRunnables.add(new AssertListStateRunnable(items2));
+        mCallbackRunnables.add(new AssertListStateRunnable(items2));
+
+        mList.replaceAll(items2);
+
+        assertEquals(new Event(TYPE.ADD, 0, 2), mEvents.get(0));
+        assertEquals(new Event(TYPE.REMOVE, 3, 2), mEvents.get(1));
+        assertEquals(2, mEvents.size());
+        assertTrue(sortedListEquals(mList, items2));
+        assertTrue(mCallbackRunnables.isEmpty());
+    }
+
+    @Test
+    public void replaceAll_move1_worksCorrectly() {
+        Item[] items1 = createItemsFromInts(1, 2, 3);
+        Item[] items2 = new Item[]{
+                new Item(2),
+                new Item(3),
+                new Item(1, 4, 1)};
+        mList.addAll(items1);
+        mEvents.clear();
+
+        mCallbackRunnables = new LinkedList<>();
+        mCallbackRunnables.add(new AssertListStateRunnable(items2));
+        mCallbackRunnables.add(new AssertListStateRunnable(items2));
+
+        mList.replaceAll(items2);
+
+        assertEquals(new Event(TYPE.REMOVE, 0, 1), mEvents.get(0));
+        assertEquals(new Event(TYPE.ADD, 2, 1), mEvents.get(1));
+        assertEquals(2, mEvents.size());
+        assertTrue(sortedListEquals(mList, items2));
+        assertTrue(mCallbackRunnables.isEmpty());
+    }
+
+    @Test
+    public void replaceAll_move2_worksCorrectly() {
+        Item[] items1 = createItemsFromInts(1, 2, 3);
+        Item[] items2 = new Item[]{
+                new Item(3, 0, 3),
+                new Item(1),
+                new Item(2)};
+        mList.addAll(items1);
+        mEvents.clear();
+
+        mCallbackRunnables = new LinkedList<>();
+        mCallbackRunnables.add(new AssertListStateRunnable(items2));
+        mCallbackRunnables.add(new AssertListStateRunnable(items2));
+
+        mList.replaceAll(items2);
+
+        assertEquals(new Event(TYPE.ADD, 0, 1), mEvents.get(0));
+        assertEquals(new Event(TYPE.REMOVE, 3, 1), mEvents.get(1));
+        assertEquals(2, mEvents.size());
+        assertTrue(sortedListEquals(mList, items2));
+        assertTrue(mCallbackRunnables.isEmpty());
+    }
+
+    @Test
+    public void replaceAll_move3_worksCorrectly() {
+        Item[] items1 = createItemsFromInts(1, 3, 5, 7, 9);
+        Item[] items2 = new Item[]{
+                new Item(3, 0, 3),
+                new Item(1),
+                new Item(5),
+                new Item(9),
+                new Item(7, 10, 7),
+        };
+        mList.addAll(items1);
+        mEvents.clear();
+
+        mCallbackRunnables = new LinkedList<>();
+        mCallbackRunnables.add(new AssertListStateRunnable(
+                new Item(3, 0, 3),
+                new Item(1),
+                new Item(5),
+                new Item(7),
+                new Item(9)
+        ));
+        mCallbackRunnables.add(new AssertListStateRunnable(
+                new Item(3, 0, 3),
+                new Item(1),
+                new Item(5),
+                new Item(9)
+        ));
+        mCallbackRunnables.add(new AssertListStateRunnable(items2));
+        mCallbackRunnables.add(new AssertListStateRunnable(items2));
+
+        mList.replaceAll(items2);
+
+        assertEquals(new Event(TYPE.ADD, 0, 1), mEvents.get(0));
+        assertEquals(new Event(TYPE.REMOVE, 2, 1), mEvents.get(1));
+        assertEquals(new Event(TYPE.REMOVE, 3, 1), mEvents.get(2));
+        assertEquals(new Event(TYPE.ADD, 4, 1), mEvents.get(3));
+        assertEquals(4, mEvents.size());
+        assertTrue(sortedListEquals(mList, items2));
+        assertTrue(mCallbackRunnables.isEmpty());
+    }
+
+    @Test
+    public void replaceAll_move4_worksCorrectly() {
+        Item[] items1 = createItemsFromInts(1, 3, 5, 7, 9);
+        Item[] items2 = new Item[]{
+                new Item(3),
+                new Item(1, 4, 1),
+                new Item(5),
+                new Item(9, 6, 9),
+                new Item(7),
+        };
+        mList.addAll(items1);
+        mEvents.clear();
+
+        mCallbackRunnables = new LinkedList<>();
+        mCallbackRunnables.add(new AssertListStateRunnable(
+                new Item(3),
+                new Item(1, 4, 1),
+                new Item(5),
+                new Item(7),
+                new Item(9)
+        ));
+        mCallbackRunnables.add(new AssertListStateRunnable(
+                new Item(3),
+                new Item(1, 4, 1),
+                new Item(5),
+                new Item(9, 6, 9),
+                new Item(7),
+                new Item(9)
+        ));
+        mCallbackRunnables.add(new AssertListStateRunnable(items2));
+        mCallbackRunnables.add(new AssertListStateRunnable(items2));
+
+        mList.replaceAll(items2);
+
+        assertEquals(new Event(TYPE.REMOVE, 0, 1), mEvents.get(0));
+        assertEquals(new Event(TYPE.ADD, 1, 1), mEvents.get(1));
+        assertEquals(new Event(TYPE.ADD, 3, 1), mEvents.get(2));
+        assertEquals(new Event(TYPE.REMOVE, 5, 1), mEvents.get(3));
+        assertEquals(4, mEvents.size());
+        assertTrue(sortedListEquals(mList, items2));
+        assertTrue(mCallbackRunnables.isEmpty());
+    }
+
+    @Test
+    public void replaceAll_move5_worksCorrectly() {
+        Item[] items1 = createItemsFromInts(1, 3, 5, 7, 9);
+        Item[] items2 = new Item[]{
+                new Item(9, 1, 9),
+                new Item(7, 3, 7),
+                new Item(5),
+                new Item(3, 7, 3),
+                new Item(1, 9, 1),
+        };
+        mList.addAll(items1);
+        mEvents.clear();
+
+        mCallbackRunnables = new LinkedList<>();
+        mCallbackRunnables.add(new AssertListStateRunnable(
+                new Item(9, 1, 9),
+                new Item(3),
+                new Item(5),
+                new Item(7),
+                new Item(9)
+        ));
+        mCallbackRunnables.add(new AssertListStateRunnable(
+                new Item(9, 1, 9),
+                new Item(5),
+                new Item(7),
+                new Item(9)
+        ));
+        mCallbackRunnables.add(new AssertListStateRunnable(
+                new Item(9, 1, 9),
+                new Item(7, 3, 7),
+                new Item(5),
+                new Item(7),
+                new Item(9)
+        ));
+        mCallbackRunnables.add(new AssertListStateRunnable(
+                new Item(9, 1, 9),
+                new Item(7, 3, 7),
+                new Item(5),
+                new Item(9)
+        ));
+        mCallbackRunnables.add(new AssertListStateRunnable(
+                new Item(9, 1, 9),
+                new Item(7, 3, 7),
+                new Item(5),
+                new Item(3, 7, 3),
+                new Item(9)
+        ));
+        mCallbackRunnables.add(new AssertListStateRunnable(
+                new Item(9, 1, 9),
+                new Item(7, 3, 7),
+                new Item(5),
+                new Item(3, 7, 3)
+        ));
+        mCallbackRunnables.add(new AssertListStateRunnable(items2));
+        mCallbackRunnables.add(new AssertListStateRunnable(items2));
+
+        mList.replaceAll(items2);
+
+        assertEquals(new Event(TYPE.REMOVE, 0, 1), mEvents.get(0));
+        assertEquals(new Event(TYPE.ADD, 0, 1), mEvents.get(1));
+        assertEquals(new Event(TYPE.REMOVE, 1, 1), mEvents.get(2));
+        assertEquals(new Event(TYPE.ADD, 1, 1), mEvents.get(3));
+        assertEquals(new Event(TYPE.REMOVE, 3, 1), mEvents.get(4));
+        assertEquals(new Event(TYPE.ADD, 3, 1), mEvents.get(5));
+        assertEquals(new Event(TYPE.REMOVE, 4, 1), mEvents.get(6));
+        assertEquals(new Event(TYPE.ADD, 4, 1), mEvents.get(7));
+        assertEquals(8, mEvents.size());
+        assertTrue(sortedListEquals(mList, items2));
+        assertTrue(mCallbackRunnables.isEmpty());
+    }
+
+    @Test
+    public void replaceAll_orderSameItemDifferent_worksCorrectly() {
+        Item[] items1 = new Item[]{
+                new Item(1),
+                new Item(2, 3, 2),
+                new Item(5)
+        };
+        Item[] items2 = new Item[]{
+                new Item(1),
+                new Item(4, 3, 4),
+                new Item(5)
+        };
+        mList.addAll(items1);
+        mEvents.clear();
+
+        mCallbackRunnables = new LinkedList<>();
+        mCallbackRunnables.add(new AssertListStateRunnable(items2));
+        mCallbackRunnables.add(new AssertListStateRunnable(items2));
+
+        mList.replaceAll(items2);
+
+        assertEquals(new Event(TYPE.REMOVE, 1, 1), mEvents.get(0));
+        assertEquals(new Event(TYPE.ADD, 1, 1), mEvents.get(1));
+        assertEquals(2, mEvents.size());
+        assertTrue(sortedListEquals(mList, items2));
+        assertTrue(mCallbackRunnables.isEmpty());
+    }
+
+    @Test
+    public void replaceAll_orderSameItemSameContentsDifferent_worksCorrectly() {
+        Item[] items1 = new Item[]{
+                new Item(1),
+                new Item(3, 3, 2),
+                new Item(5)
+        };
+        Item[] items2 = new Item[]{
+                new Item(1),
+                new Item(3, 3, 4),
+                new Item(5)
+        };
+        mList.addAll(items1);
+        mEvents.clear();
+
+        mCallbackRunnables = new LinkedList<>();
+        mCallbackRunnables.add(new AssertListStateRunnable(items2));
+
+        mList.replaceAll(items2);
+
+        assertEquals(new Event(TYPE.CHANGE, 1, 1), mEvents.get(0));
+        assertEquals(1, mEvents.size());
+        assertTrue(sortedListEquals(mList, items2));
+        assertTrue(mCallbackRunnables.isEmpty());
+    }
+
+    @Test
+    public void replaceAll_allTypesOfChanges1_worksCorrectly() {
+        Item[] items1 = createItemsFromInts(2, 5, 6);
+        Item[] items2 = new Item[]{
+                new Item(1),
+                new Item(3, 2, 3),
+                new Item(6, 6, 7)
+        };
+        mList.addAll(items1);
+        mEvents.clear();
+
+        mCallbackRunnables = new LinkedList<>();
+        mCallbackRunnables.add(new AssertListStateRunnable(createItemsFromInts(1, 5, 6)));
+        mCallbackRunnables.add(new AssertListStateRunnable(
+                new Item(1),
+                new Item(3, 2, 3),
+                new Item(5),
+                new Item(6)
+        ));
+        mCallbackRunnables.add(new AssertListStateRunnable(
+                new Item(1),
+                new Item(3, 2, 3),
+                new Item(6)
+        ));
+        mCallbackRunnables.add(new AssertListStateRunnable(items2));
+        mCallbackRunnables.add(new AssertListStateRunnable(items2));
+
+        mList.replaceAll(items2);
+
+        assertEquals(new Event(TYPE.ADD, 0, 1), mEvents.get(0));
+        assertEquals(new Event(TYPE.REMOVE, 1, 1), mEvents.get(1));
+        assertEquals(new Event(TYPE.ADD, 1, 1), mEvents.get(2));
+        assertEquals(new Event(TYPE.REMOVE, 2, 1), mEvents.get(3));
+        assertEquals(new Event(TYPE.CHANGE, 2, 1), mEvents.get(4));
+        assertEquals(5, mEvents.size());
+        assertTrue(sortedListEquals(mList, items2));
+        assertTrue(mCallbackRunnables.isEmpty());
+    }
+
+    @Test
+    public void replaceAll_allTypesOfChanges2_worksCorrectly() {
+        Item[] items1 = createItemsFromInts(1, 4, 6);
+        Item[] items2 = new Item[]{
+                new Item(1, 1, 2),
+                new Item(3),
+                new Item(5, 4, 5)
+        };
+        mList.addAll(items1);
+        mEvents.clear();
+
+        mCallbackRunnables = new LinkedList<>();
+        mCallbackRunnables.add(new AssertListStateRunnable(
+                new Item(1, 1, 2),
+                new Item(3),
+                new Item(4),
+                new Item(6)
+        ));
+        mCallbackRunnables.add(new AssertListStateRunnable(
+                new Item(1, 1, 2),
+                new Item(3),
+                new Item(6)
+        ));
+        mCallbackRunnables.add(new AssertListStateRunnable(
+                new Item(1, 1, 2),
+                new Item(3),
+                new Item(5, 4, 5),
+                new Item(6)
+        ));
+        mCallbackRunnables.add(new AssertListStateRunnable(items2));
+        mCallbackRunnables.add(new AssertListStateRunnable(items2));
+
+        mList.replaceAll(items2);
+
+        assertEquals(new Event(TYPE.CHANGE, 0, 1), mEvents.get(0));
+        assertEquals(new Event(TYPE.ADD, 1, 1), mEvents.get(1));
+        assertEquals(new Event(TYPE.REMOVE, 2, 1), mEvents.get(2));
+        assertEquals(new Event(TYPE.ADD, 2, 1), mEvents.get(3));
+        assertEquals(new Event(TYPE.REMOVE, 3, 1), mEvents.get(4));
+        assertEquals(5, mEvents.size());
+        assertTrue(sortedListEquals(mList, items2));
+        assertTrue(mCallbackRunnables.isEmpty());
+    }
+
+    @Test
+    public void replaceAll_allTypesOfChanges3_worksCorrectly() {
+        Item[] items1 = createItemsFromInts(1, 2);
+        Item[] items2 = new Item[]{
+                new Item(2, 2, 3),
+                new Item(3, 2, 4),
+                new Item(5)
+        };
+        mList.addAll(items1);
+        mEvents.clear();
+
+        mCallbackRunnables = new LinkedList<>();
+        mCallbackRunnables.add(new AssertListStateRunnable(
+                new Item(2, 2, 3)
+        ));
+        mCallbackRunnables.add(new AssertListStateRunnable(items2));
+        mCallbackRunnables.add(new AssertListStateRunnable(items2));
+
+        mList.replaceAll(items2);
+
+        assertEquals(new Event(TYPE.REMOVE, 0, 1), mEvents.get(0));
+        assertEquals(new Event(TYPE.CHANGE, 0, 1), mEvents.get(1));
+        assertEquals(new Event(TYPE.ADD, 1, 2), mEvents.get(2));
+        assertEquals(3, mEvents.size());
+        assertTrue(sortedListEquals(mList, items2));
+        assertTrue(mCallbackRunnables.isEmpty());
+    }
+
+    @Test
+    public void replaceAll_newItemsAreIdentical_resultIsDeduped() {
+        Item[] items = createItemsFromInts(1, 1);
+        mList.replaceAll(items);
+
+        assertEquals(new Item(1), mList.get(0));
+        assertEquals(1, mList.size());
+    }
+
+    @Test
+    public void replaceAll_newItemsUnsorted_resultIsSorted() {
+        Item[] items = createItemsFromInts(2, 1);
+        mList.replaceAll(items);
+
+        assertEquals(new Item(1), mList.get(0));
+        assertEquals(new Item(2), mList.get(1));
+        assertEquals(2, mList.size());
+    }
+
+    @Test
+    public void replaceAll_calledAfterBeginBatchedUpdates_worksCorrectly() {
+        Item[] items1 = createItemsFromInts(1, 2, 3);
+        Item[] items2 = createItemsFromInts(4, 5, 6);
+        mList.addAll(items1);
+        mEvents.clear();
+
+        mCallbackRunnables = new LinkedList<>();
+        mCallbackRunnables.add(new AssertListStateRunnable(items2));
+        mCallbackRunnables.add(new AssertListStateRunnable(items2));
+
+        mList.beginBatchedUpdates();
+        mList.replaceAll(items2);
+        mList.endBatchedUpdates();
+
+        assertEquals(new Event(TYPE.REMOVE, 0, 3), mEvents.get(0));
+        assertEquals(new Event(TYPE.ADD, 0, 3), mEvents.get(1));
+        assertEquals(2, mEvents.size());
+        assertTrue(sortedListEquals(mList, items2));
+        assertTrue(mCallbackRunnables.isEmpty());
+    }
+
     private int size() {
         return mList.size();
     }
@@ -719,63 +1507,33 @@
 
     static class Item {
 
-        static int idCounter = 0;
         final int id;
-
         int cmpField;
+        int data;
 
-        int data = (int) (Math.random() * 1000);//used for comparison
-
-        public Item() {
-            id = idCounter++;
-            cmpField = (int) (Math.random() * 1000);
+        Item(int allFields) {
+            this(allFields, allFields, allFields);
         }
 
-        public Item(int cmpField) {
-            id = idCounter++;
-            this.cmpField = cmpField;
-        }
-
-        public Item(int id, int cmpField) {
+        Item(int id, int compField, int data) {
             this.id = id;
-            this.cmpField = cmpField;
+            this.cmpField = compField;
+            this.data = data;
         }
 
         @Override
         public boolean equals(Object o) {
-            if (this == o) {
-                return true;
-            }
-            if (o == null || getClass() != o.getClass()) {
-                return false;
-            }
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
 
             Item item = (Item) o;
 
-            if (cmpField != item.cmpField) {
-                return false;
-            }
-            if (id != item.id) {
-                return false;
-            }
-
-            return true;
-        }
-
-        @Override
-        public int hashCode() {
-            int result = id;
-            result = 31 * result + cmpField;
-            return result;
+            return id == item.id && cmpField == item.cmpField && data == item.data;
         }
 
         @Override
         public String toString() {
-            return "Item{" +
-                    "id=" + id +
-                    ", cmpField=" + cmpField +
-                    ", data=" + data +
-                    '}';
+            return "Item(id=" + id + ", cmpField=" + cmpField + ", data=" + data + ')';
         }
     }
 
@@ -821,4 +1579,116 @@
             return result;
         }
     }
+
+    private enum TYPE {
+        ADD, REMOVE, MOVE, CHANGE
+    }
+
+    private final class AssertListStateRunnable implements Runnable {
+
+        private Item[] mExpectedItems;
+
+        AssertListStateRunnable(Item... expectedItems) {
+            this.mExpectedItems = expectedItems;
+        }
+
+        @Override
+        public void run() {
+            try {
+                assertEquals(mExpectedItems.length, mList.size());
+                for (int i = mExpectedItems.length - 1; i >= 0; i--) {
+                    assertEquals(mExpectedItems[i], mList.get(i));
+                    assertEquals(i, mList.indexOf(mExpectedItems[i]));
+                }
+            } catch (AssertionError assertionError) {
+                throw new AssertionError(
+                        assertionError.getMessage()
+                        + "\nExpected: "
+                        + Arrays.toString(mExpectedItems)
+                        + "\nActual: "
+                        + sortedListToString(mList));
+            }
+        }
+    }
+
+    private static final class Event {
+        private final TYPE mType;
+        private final int mVal1;
+        private final int mVal2;
+
+        Event(TYPE type, int val1, int val2) {
+            this.mType = type;
+            this.mVal1 = val1;
+            this.mVal2 = val2;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            Event that = (Event) o;
+            return mType == that.mType && mVal1 == that.mVal1 && mVal2 == that.mVal2;
+        }
+
+        @Override
+        public String toString() {
+            return "Event(" + mType + ", " + mVal1 + ", " + mVal2 + ")";
+        }
+    }
+
+    private <T> boolean sortedListEquals(SortedList<T> sortedList, T[] array) {
+        if (sortedList.size() != array.length) {
+            return false;
+        }
+        for (int i = sortedList.size() - 1; i >= 0; i--) {
+            if (!sortedList.get(i).equals(array[i])) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private static String sortedListToString(SortedList sortedList) {
+        StringBuilder stringBuilder = new StringBuilder("[");
+        int size = sortedList.size();
+        for (int i = 0; i < size; i++) {
+            stringBuilder.append(sortedList.get(i).toString() + ", ");
+        }
+        stringBuilder.delete(stringBuilder.length() - 2, stringBuilder.length());
+        stringBuilder.append("]");
+        return stringBuilder.toString();
+    }
+
+    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/v7/recyclerview/tests/src/android/support/v7/util/ImeCleanUpTestRule.java b/v7/recyclerview/tests/src/android/support/v7/util/ImeCleanUpTestRule.java
deleted file mode 100644
index f4caad3..0000000
--- a/v7/recyclerview/tests/src/android/support/v7/util/ImeCleanUpTestRule.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.v7.util;
-
-import android.graphics.Rect;
-import android.support.testutils.PollingCheck;
-import android.view.View;
-
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
-/**
- * A JUnit rule that ensures that IME is closed after a test is finished (or exception thrown).
- * A test that triggers IME open/close should call setContainerView with the activity's container
- * view in order to trigger this cleanup at the end of the test. Otherwise, no cleanup happens.
- */
-public class ImeCleanUpTestRule implements TestRule {
-
-    private View mContainerView;
-
-    @Override
-    public Statement apply(final Statement base, Description description) {
-        return new Statement() {
-            @Override
-            public void evaluate() throws Throwable {
-                try {
-                    base.evaluate();
-                } finally {
-                    closeImeIfOpen();
-                }
-            }
-        };
-    }
-
-    /**
-     * Sets the container view used to calculate the total screen height and the height available
-     * to the test activity.
-     */
-    public void setContainerView(View containerView) {
-        mContainerView = containerView;
-    }
-
-    private void closeImeIfOpen() {
-        if (mContainerView == null) {
-            return;
-        }
-        // Ensuring that IME is closed after starting each test.
-        final Rect r = new Rect();
-        mContainerView.getWindowVisibleDisplayFrame(r);
-        // This is the entire height of the screen available to both the view and IME
-        final int screenHeight = mContainerView.getRootView().getHeight();
-
-        // r.bottom is the position above IME if it's open or device button.
-        // if IME is shown, r.bottom is smaller than screenHeight.
-        int imeHeight = screenHeight - r.bottom;
-
-        // Picking a threshold to detect when IME is open
-        if (imeHeight > screenHeight * 0.15) {
-            // Soft keyboard is shown, will wait for it to close after running the test. Note that
-            // we don't press back button here as the IME should close by itself when a test
-            // finishes. If the wait isn't done here, the IME can mess up with the layout of the
-            // next test.
-            PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
-                @Override
-                public boolean canProceed() {
-                    mContainerView.getWindowVisibleDisplayFrame(r);
-                    int imeHeight = screenHeight - r.bottom;
-                    return imeHeight < screenHeight * 0.15;
-                }
-            });
-        }
-    }
-}
diff --git a/v7/recyclerview/tests/src/android/support/v7/util/TouchUtils.java b/v7/recyclerview/tests/src/android/support/v7/util/TouchUtils.java
index 1a64e3c..418d33f 100644
--- a/v7/recyclerview/tests/src/android/support/v7/util/TouchUtils.java
+++ b/v7/recyclerview/tests/src/android/support/v7/util/TouchUtils.java
@@ -154,6 +154,18 @@
         inst.waitForIdleSync();
     }
 
+    public static void scrollView(int axis, int axisValue, int inputDevice, View v) {
+        MotionEvent.PointerProperties[] pointerProperties = { new MotionEvent.PointerProperties() };
+        MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords();
+        coords.setAxisValue(axis, axisValue);
+        MotionEvent.PointerCoords[] pointerCoords = { coords };
+        MotionEvent e = MotionEvent.obtain(
+                0, System.currentTimeMillis(), MotionEvent.ACTION_SCROLL,
+                1, pointerProperties, pointerCoords, 0, 0, 1, 1, 0, 0, inputDevice, 0);
+        v.onGenericMotionEvent(e);
+        e.recycle();
+    }
+
     public static void dragViewToTop(Instrumentation inst, View v) {
         dragViewToTop(inst, v, calculateStepsForDistance(v.getTop()));
     }
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/BaseGridLayoutManagerTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/BaseGridLayoutManagerTest.java
index 13dd1e4..f4a8e37 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/BaseGridLayoutManagerTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/BaseGridLayoutManagerTest.java
@@ -325,16 +325,16 @@
         }
     }
 
-    class GridEditTextAdapter extends EditTextAdapter {
+    class GridFocusableAdapter extends FocusableAdapter {
 
         Set<Integer> mFullSpanItems = new HashSet<Integer>();
         int mSpanPerItem = 1;
 
-        GridEditTextAdapter(int count) {
+        GridFocusableAdapter(int count) {
             this(count, 1);
         }
 
-        GridEditTextAdapter(int count, int spanPerItem) {
+        GridFocusableAdapter(int count, int spanPerItem) {
             super(count);
             mSpanPerItem = spanPerItem;
         }
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java
index 157fb12..eed7d20 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java
@@ -35,14 +35,13 @@
 import android.support.annotation.Nullable;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.rule.ActivityTestRule;
+import android.support.testutils.PollingCheck;
 import android.support.v4.view.ViewCompat;
 import android.support.v7.recyclerview.test.R;
-import android.text.Editable;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
-import android.widget.EditText;
 import android.widget.FrameLayout;
 import android.widget.TextView;
 
@@ -325,6 +324,12 @@
                 result[0] = view.requestFocus();
             }
         });
+        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return view.hasFocus();
+            }
+        });
         if (waitForScroll && result[0]) {
             waitForIdleScroll(mRecyclerView);
         }
@@ -789,34 +794,32 @@
         }
     }
 
-    public class EditTextAdapter extends RecyclerView.Adapter<TestViewHolder> {
+    public class FocusableAdapter extends RecyclerView.Adapter<TestViewHolder> {
 
-        final ArrayList<Editable> mEditables;
-        public EditTextAdapter(int count) {
-            mEditables = new ArrayList<>();
-            for (int i = 0; i < count; ++i) {
-                mEditables.add(Editable.Factory.getInstance().newEditable("Sample Text " + i));
-            }
+        private int mCount;
+
+        FocusableAdapter(int count) {
+            mCount = count;
         }
 
         @Override
         public TestViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
-            final EditText editText = new EditText(parent.getContext());
-            editText.setLayoutParams(new ViewGroup.LayoutParams(
+            final TextView textView = new TextView(parent.getContext());
+            textView.setLayoutParams(new ViewGroup.LayoutParams(
                     ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
-            final TestViewHolder viewHolder = new TestViewHolder(editText);
-            return viewHolder;
+            textView.setFocusable(true);
+            textView.setBackgroundResource(R.drawable.item_bg);
+            return new TestViewHolder(textView);
         }
 
         @Override
         public void onBindViewHolder(TestViewHolder holder, int position) {
-            ((EditText) holder.itemView).setText(Editable.Factory.getInstance().newEditable(
-                    mEditables.get(position)));
+            ((TextView) holder.itemView).setText("Item " + position);
         }
 
         @Override
         public int getItemCount() {
-            return mEditables.size();
+            return mCount;
         }
     }
 
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/CustomEdgeEffectTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/CustomEdgeEffectTest.java
new file mode 100644
index 0000000..0644416
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/CustomEdgeEffectTest.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.widget;
+
+
+import static android.support.v7.widget.RecyclerView.EdgeEffectFactory;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v4.view.InputDeviceCompat;
+import android.support.v7.util.TouchUtils;
+import android.view.MotionEvent;
+import android.view.ViewGroup;
+import android.widget.EdgeEffect;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests custom edge effect are properly applied when scrolling.
+ */
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class CustomEdgeEffectTest extends BaseRecyclerViewInstrumentationTest {
+
+    private static final int NUM_ITEMS = 10;
+
+    private LinearLayoutManager mLayoutManager;
+    private RecyclerView mRecyclerView;
+
+    @Before
+    public void setup() throws Throwable {
+        mLayoutManager = new LinearLayoutManager(getActivity());
+        mLayoutManager.ensureLayoutState();
+
+        mRecyclerView = new RecyclerView(getActivity());
+        mRecyclerView.setLayoutManager(mLayoutManager);
+        mRecyclerView.setAdapter(new TestAdapter(NUM_ITEMS) {
+
+            @Override
+            public TestViewHolder onCreateViewHolder(ViewGroup parent,
+                    int viewType) {
+                TestViewHolder holder = super.onCreateViewHolder(parent, viewType);
+                holder.itemView.setMinimumHeight(mRecyclerView.getMeasuredHeight() * 2 / NUM_ITEMS);
+                return holder;
+            }
+        });
+        setRecyclerView(mRecyclerView);
+        getInstrumentation().waitForIdleSync();
+        assertThat("Test sanity", mRecyclerView.getChildCount() > 0, is(true));
+    }
+
+    @Test
+    public void testEdgeEffectDirections() throws Throwable {
+        TestEdgeEffectFactory factory = new TestEdgeEffectFactory();
+        mRecyclerView.setEdgeEffectFactory(factory);
+        scrollToPosition(0);
+        waitForIdleScroll(mRecyclerView);
+        scrollViewBy(3);
+        assertNull(factory.mBottom);
+        assertNotNull(factory.mTop);
+        assertTrue(factory.mTop.mPullDistance > 0);
+
+        scrollToPosition(NUM_ITEMS - 1);
+        waitForIdleScroll(mRecyclerView);
+        scrollViewBy(-3);
+
+        assertNotNull(factory.mBottom);
+        assertTrue(factory.mBottom.mPullDistance > 0);
+    }
+
+    @Test
+    public void testEdgeEffectReplaced() throws Throwable {
+        TestEdgeEffectFactory factory1 = new TestEdgeEffectFactory();
+        mRecyclerView.setEdgeEffectFactory(factory1);
+        scrollToPosition(0);
+        waitForIdleScroll(mRecyclerView);
+
+        scrollViewBy(3);
+        assertNotNull(factory1.mTop);
+        float oldPullDistance = factory1.mTop.mPullDistance;
+
+        waitForIdleScroll(mRecyclerView);
+        TestEdgeEffectFactory factory2 = new TestEdgeEffectFactory();
+        mRecyclerView.setEdgeEffectFactory(factory2);
+        scrollViewBy(30);
+        assertNotNull(factory2.mTop);
+
+        assertTrue(factory2.mTop.mPullDistance > oldPullDistance);
+        assertEquals(oldPullDistance, factory1.mTop.mPullDistance, 0.1f);
+    }
+
+    private void scrollViewBy(final int value) throws Throwable {
+        mActivityRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                TouchUtils.scrollView(MotionEvent.AXIS_VSCROLL, value,
+                        InputDeviceCompat.SOURCE_CLASS_POINTER, mRecyclerView);
+            }
+        });
+    }
+
+    private class TestEdgeEffectFactory extends EdgeEffectFactory {
+
+        TestEdgeEffect mTop, mBottom;
+
+        @NonNull
+        @Override
+        protected EdgeEffect createEdgeEffect(RecyclerView view, int direction) {
+            TestEdgeEffect effect = new TestEdgeEffect(view.getContext());
+            if (direction == EdgeEffectFactory.DIRECTION_TOP) {
+                mTop = effect;
+            } else if (direction == EdgeEffectFactory.DIRECTION_BOTTOM) {
+                mBottom = effect;
+            }
+            return effect;
+        }
+    }
+
+    private class TestEdgeEffect extends EdgeEffect {
+
+        private float mPullDistance;
+
+        TestEdgeEffect(Context context) {
+            super(context);
+        }
+
+        @Override
+        public void onPull(float deltaDistance, float displacement) {
+            onPull(deltaDistance);
+        }
+
+        @Override
+        public void onPull(float deltaDistance) {
+            mPullDistance = deltaDistance;
+        }
+    }
+}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/DummyItemAnimator.java b/v7/recyclerview/tests/src/android/support/v7/widget/DummyItemAnimator.java
new file mode 100644
index 0000000..a1491fa
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/DummyItemAnimator.java
@@ -0,0 +1,263 @@
+/*
+ * 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.v7.widget;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.CLASS;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * This is a dummy ItemAnimator class that does not depends on Duration, Tests would use this class
+ * to control whenever they want the Animator to finish.
+ * 1. Test MUST call endAnimation(ViewHolder) on UI thread to finish animation of a given ViewHolder
+ *    Or Test calls endAnimations() on UI thread to end animations for all.
+ * 2. Test can call getAddAnimations() etc. to get ViewHolders that currently running animation.
+ * 3. Test can call {@link #expect(int, int)} and {@link #waitFor(int)} to wait given
+ *    Events are fired.
+ */
+public class DummyItemAnimator extends SimpleItemAnimator {
+
+    static final long TIMEOUT_SECOND = 10;
+
+    ArrayList<RecyclerView.ViewHolder> mAdds = new ArrayList();
+    ArrayList<RecyclerView.ViewHolder> mRemoves = new ArrayList();
+    ArrayList<RecyclerView.ViewHolder> mMoves = new ArrayList();
+    ArrayList<RecyclerView.ViewHolder> mChangesOld = new ArrayList();
+    ArrayList<RecyclerView.ViewHolder> mChangesNew = new ArrayList();
+
+    @Retention(CLASS)
+    @Target({PARAMETER, METHOD, LOCAL_VARIABLE, FIELD})
+    public @interface CountDownLatchIndex {
+    }
+
+    @CountDownLatchIndex
+    public static final int ADD_START = 0;
+
+    @CountDownLatchIndex
+    public static final int ADD_FINISHED = 1;
+
+    @CountDownLatchIndex
+    public static final int REMOVE_START = 2;
+
+    @CountDownLatchIndex
+    public static final int REMOVE_FINISHED = 3;
+
+    @CountDownLatchIndex
+    public static final int MOVE_START = 4;
+
+    @CountDownLatchIndex
+    public static final int MOVE_FINISHED = 5;
+
+    @CountDownLatchIndex
+    public static final int CHANGE_OLD_START = 6;
+
+    @CountDownLatchIndex
+    public static final int CHANGE_OLD_FINISHED = 7;
+
+    @CountDownLatchIndex
+    public static final int CHANGE_NEW_START = 8;
+
+    @CountDownLatchIndex
+    public static final int CHANGE_NEW_FINISHED = 9;
+
+    static final int NUM_COUNT_DOWN_LATCH = 10;
+
+    CountDownLatch[] mCountDownLatches = new CountDownLatch[NUM_COUNT_DOWN_LATCH];
+
+
+    public List<RecyclerView.ViewHolder> getAddAnimations() {
+        return mAdds;
+    }
+
+    public List<RecyclerView.ViewHolder> getRemoveAnimations() {
+        return mRemoves;
+    }
+
+    public List<RecyclerView.ViewHolder> getMovesAnimations() {
+        return mMoves;
+    }
+
+    public List<RecyclerView.ViewHolder> getChangesOldAnimations() {
+        return mChangesOld;
+    }
+
+    public List<RecyclerView.ViewHolder> getChangesNewAnimations() {
+        return mChangesNew;
+    }
+
+    @Override
+    public boolean animateRemove(RecyclerView.ViewHolder holder) {
+        mRemoves.add(holder);
+        dispatchRemoveStarting(holder);
+        return false;
+    }
+
+    @Override
+    public boolean animateAdd(RecyclerView.ViewHolder holder) {
+        mAdds.add(holder);
+        dispatchAddStarting(holder);
+        return false;
+    }
+
+    @Override
+    public boolean animateMove(RecyclerView.ViewHolder holder, int fromX, int fromY, int toX,
+            int toY) {
+        mMoves.add(holder);
+        dispatchMoveStarting(holder);
+        return false;
+    }
+
+    @Override
+    public boolean animateChange(RecyclerView.ViewHolder oldHolder,
+            RecyclerView.ViewHolder newHolder, int fromLeft, int fromTop, int toLeft, int toTop) {
+        mChangesOld.add(oldHolder);
+        mChangesNew.add(newHolder);
+        dispatchChangeStarting(oldHolder, true);
+        dispatchChangeStarting(newHolder, false);
+        return false;
+    }
+
+    public void expect(@CountDownLatchIndex int index, int count) {
+        mCountDownLatches[index] = new CountDownLatch(count);
+    }
+
+    public void waitFor(@CountDownLatchIndex int index)
+            throws InterruptedException {
+        mCountDownLatches[index].await(TIMEOUT_SECOND, TimeUnit.SECONDS);
+    }
+
+    @Override
+    public void onChangeStarting(RecyclerView.ViewHolder item, boolean oldItem) {
+        CountDownLatch latch = mCountDownLatches[oldItem ? CHANGE_OLD_START : CHANGE_NEW_START];
+        if (latch != null) {
+            latch.countDown();
+        }
+    }
+
+    @Override
+    public void onMoveStarting(RecyclerView.ViewHolder item) {
+        CountDownLatch latch = mCountDownLatches[MOVE_START];
+        if (latch != null) {
+            latch.countDown();
+        }
+    }
+
+    @Override
+    public void onAddStarting(RecyclerView.ViewHolder item) {
+        CountDownLatch latch = mCountDownLatches[ADD_START];
+        if (latch != null) {
+            latch.countDown();
+        }
+    }
+
+    @Override
+    public void onRemoveStarting(RecyclerView.ViewHolder item) {
+        CountDownLatch latch = mCountDownLatches[REMOVE_START];
+        if (latch != null) {
+            latch.countDown();
+        }
+    }
+
+    @Override
+    public void onChangeFinished(RecyclerView.ViewHolder item, boolean oldItem) {
+        CountDownLatch latch = mCountDownLatches[oldItem
+                ? CHANGE_OLD_FINISHED : CHANGE_NEW_FINISHED];
+        if (latch != null) {
+            latch.countDown();
+        }
+    }
+
+    @Override
+    public void onMoveFinished(RecyclerView.ViewHolder item) {
+        CountDownLatch latch = mCountDownLatches[MOVE_FINISHED];
+        if (latch != null) {
+            latch.countDown();
+        }
+    }
+
+    @Override
+    public void onAddFinished(RecyclerView.ViewHolder item) {
+        CountDownLatch latch = mCountDownLatches[ADD_FINISHED];
+        if (latch != null) {
+            latch.countDown();
+        }
+    }
+
+    @Override
+    public void onRemoveFinished(RecyclerView.ViewHolder item) {
+        CountDownLatch latch = mCountDownLatches[REMOVE_FINISHED];
+        if (latch != null) {
+            latch.countDown();
+        }
+    }
+
+    @Override
+    public void runPendingAnimations() {
+    }
+
+    @Override
+    public void endAnimation(RecyclerView.ViewHolder item) {
+        if (mAdds.remove(item)) {
+            dispatchAddFinished(item);
+        } else if (mRemoves.remove(item)) {
+            dispatchRemoveFinished(item);
+        } else if (mMoves.remove(item)) {
+            dispatchMoveFinished(item);
+        } else if (mChangesOld.remove(item)) {
+            dispatchChangeFinished(item, true);
+        } else if (mChangesNew.remove(item)) {
+            dispatchChangeFinished(item, false);
+        }
+    }
+
+    @Override
+    public void endAnimations() {
+        for (int i = mAdds.size() - 1; i >= 0; i--) {
+            endAnimation(mAdds.get(i));
+        }
+        for (int i = mRemoves.size() - 1; i >= 0; i--) {
+            endAnimation(mRemoves.get(i));
+        }
+        for (int i = mMoves.size() - 1; i >= 0; i--) {
+            endAnimation(mMoves.get(i));
+        }
+        for (int i = mChangesOld.size() - 1; i >= 0; i--) {
+            endAnimation(mChangesOld.get(i));
+        }
+        for (int i = mChangesNew.size() - 1; i >= 0; i--) {
+            endAnimation(mChangesNew.get(i));
+        }
+    }
+
+    @Override
+    public boolean isRunning() {
+        return mAdds.size() != 0
+                || mRemoves.size() != 0
+                || mMoves.size() != 0
+                || mChangesOld.size() != 0
+                || mChangesNew.size() != 0;
+    }
+}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerTest.java
index 23eaf52..1a5892d 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerTest.java
@@ -20,7 +20,6 @@
 import static android.support.v7.widget.LinearLayoutManager.VERTICAL;
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
-import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
 
 import static org.hamcrest.CoreMatchers.is;
 import static org.junit.Assert.assertEquals;
@@ -37,20 +36,15 @@
 import android.support.test.filters.LargeTest;
 import android.support.test.filters.SdkSuppress;
 import android.support.test.runner.AndroidJUnit4;
-import android.support.testutils.PollingCheck;
 import android.support.v4.view.AccessibilityDelegateCompat;
 import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
-import android.support.v7.util.ImeCleanUpTestRule;
-import android.support.v7.util.TouchUtils;
 import android.test.UiThreadTest;
 import android.util.SparseIntArray;
 import android.util.StateSet;
-import android.view.KeyEvent;
 import android.view.View;
 import android.view.ViewGroup;
 
 import org.hamcrest.CoreMatchers;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -64,9 +58,6 @@
 @RunWith(AndroidJUnit4.class)
 public class GridLayoutManagerTest extends BaseGridLayoutManagerTest {
 
-    @Rule
-    public final ImeCleanUpTestRule imeCleanUp = new ImeCleanUpTestRule();
-
     @Test
     public void focusSearchFailureUp() throws Throwable {
         focusSearchFailure(false);
@@ -179,100 +170,140 @@
         }
     }
 
+    /**
+     * Tests that the GridLayoutManager retains the focused element after multiple measure
+     * calls to the RecyclerView.  There was a bug where the focused view was lost when the soft
+     * keyboard opened.  This test simulates the measure/layout events triggered by the opening
+     * of the soft keyboard by making two calls to measure.  A simulation was done because using
+     * the soft keyboard in the test caused many issues on API levels 15, 17 and 19.
+     */
     @Test
-    public void editTextVisibility() throws Throwable {
+    public void focusedChildStaysInViewWhenRecyclerViewShrinks() throws Throwable {
+
+        // Arrange.
+
         final int spanCount = 3;
         final int itemCount = 100;
 
-        imeCleanUp.setContainerView(getActivity().getContainer());
-        RecyclerView recyclerView = new WrappedRecyclerView(getActivity());
-        GridEditTextAdapter editTextAdapter = new GridEditTextAdapter(itemCount) {
-            @Override
-            public TestViewHolder onCreateViewHolder(ViewGroup parent,
-                    int viewType) {
-                TestViewHolder testViewHolder = super.onCreateViewHolder(parent, viewType);
-                // Good to have colors for debugging
-                StateListDrawable stl = new StateListDrawable();
-                stl.addState(new int[]{android.R.attr.state_focused},
-                        new ColorDrawable(Color.RED));
-                stl.addState(StateSet.WILD_CARD, new ColorDrawable(Color.BLUE));
-                //noinspection deprecation using this for kitkat tests
-                testViewHolder.itemView.setBackgroundDrawable(stl);
-                return testViewHolder;
-            }
-        };
-        mActivityRule.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mActivityRule.getActivity().getWindow().setSoftInputMode(SOFT_INPUT_ADJUST_RESIZE);
-            }
-        });
-
-        recyclerView.setLayoutParams(
-                new ViewGroup.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
+        final RecyclerView recyclerView = inflateWrappedRV();
+        ViewGroup.LayoutParams lp = recyclerView.getLayoutParams();
+        lp.height = WRAP_CONTENT;
+        lp.width = MATCH_PARENT;
 
         Config config = new Config(spanCount, itemCount);
         mGlm = new WrappedGridLayoutManager(getActivity(), config.mSpanCount, config.mOrientation,
                 config.mReverseLayout);
-        editTextAdapter.assignSpanSizeLookup(mGlm);
-        recyclerView.setAdapter(editTextAdapter);
         recyclerView.setLayoutManager(mGlm);
-        waitForFirstLayout(recyclerView);
 
-        // First focus on the last fully visible EditText located at span index #1.
-        View toFocus = findLastFullyVisibleChild(mRecyclerView);
-        int focusIndex = mRecyclerView.getChildAdapterPosition(toFocus);
-        focusIndex = (focusIndex / spanCount) * spanCount + 1;
-        toFocus = mRecyclerView.findViewHolderForAdapterPosition(focusIndex).itemView;
-        assertTrue(focusIndex >= 1 && focusIndex < itemCount);
+        GridFocusableAdapter gridFocusableAdapter = new GridFocusableAdapter(itemCount);
+        gridFocusableAdapter.assignSpanSizeLookup(mGlm);
+        recyclerView.setAdapter(gridFocusableAdapter);
 
-        final int heightBeforeImeOpen = mRecyclerView.getHeight();
-        TouchUtils.tapView(getInstrumentation(), mRecyclerView, toFocus);
-        getInstrumentation().waitForIdleSync();
-        // Wait for IME to pop up.
-        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+        mGlm.expectLayout(1);
+        mActivityRule.runOnUiThread(new Runnable() {
             @Override
-            public boolean canProceed() {
-                return mRecyclerView.getHeight() < heightBeforeImeOpen;
+            public void run() {
+                getActivity().getContainer().addView(recyclerView);
             }
         });
+        mGlm.waitForLayout(3);
+
+        int width = recyclerView.getWidth();
+        int height = recyclerView.getHeight();
+        final int widthMeasureSpec =
+                View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY);
+        final int fullHeightMeasureSpec =
+                View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.AT_MOST);
+        // "MinusOne" so that a measure call will appropriately trigger onMeasure after RecyclerView
+        // was previously laid out with the full height version.
+        final int fullHeightMinusOneMeasureSpec =
+                View.MeasureSpec.makeMeasureSpec(height - 1, View.MeasureSpec.AT_MOST);
+        final int halfHeightMeasureSpec =
+                View.MeasureSpec.makeMeasureSpec(height / 2, View.MeasureSpec.AT_MOST);
+
+        // Act 1.
+
+        // First focus on the last fully visible child located at span index #1.
+        View toFocus = findLastFullyVisibleChild(recyclerView);
+        int focusIndex = recyclerView.getChildAdapterPosition(toFocus);
+        focusIndex = (focusIndex / spanCount) * spanCount + 1;
+        toFocus = recyclerView.findViewHolderForAdapterPosition(focusIndex).itemView;
+        assertTrue(focusIndex >= 1 && focusIndex < itemCount);
+
+        requestFocus(toFocus, false);
+
+        mGlm.expectLayout(1);
+        mActivityRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                recyclerView.measure(widthMeasureSpec, fullHeightMinusOneMeasureSpec);
+                recyclerView.measure(widthMeasureSpec, halfHeightMeasureSpec);
+                recyclerView.layout(
+                        0,
+                        0,
+                        recyclerView.getMeasuredWidth(),
+                        recyclerView.getMeasuredHeight());
+            }
+        });
+        mGlm.waitForLayout(3);
+
+        // Assert 1.
 
         assertThat("Child at position " + focusIndex + " should be focused",
                 toFocus.hasFocus(), is(true));
         assertTrue("Child view at adapter pos " + focusIndex + " should be fully visible.",
-                isViewPartiallyInBound(mRecyclerView, toFocus));
+                isViewPartiallyInBound(recyclerView, toFocus));
 
-        // Close IME
-        final int heightBeforeImeClose = mRecyclerView.getHeight();
-        getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_BACK);
-        getInstrumentation().waitForIdleSync();
-        // Wait for IME to close
-        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+        // Act 2.
+
+        mGlm.expectLayout(1);
+        mActivityRule.runOnUiThread(new Runnable() {
             @Override
-            public boolean canProceed() {
-                return mRecyclerView.getHeight() > heightBeforeImeClose;
+            public void run() {
+                recyclerView.measure(widthMeasureSpec, fullHeightMeasureSpec);
+                recyclerView.layout(
+                        0,
+                        0,
+                        recyclerView.getMeasuredWidth(),
+                        recyclerView.getMeasuredHeight());
             }
         });
+        mGlm.waitForLayout(3);
+
+        // Assert 2.
+
         assertTrue("Child view at adapter pos " + focusIndex + " should be fully visible.",
-                isViewPartiallyInBound(mRecyclerView, toFocus));
+                isViewPartiallyInBound(recyclerView, toFocus));
+
+        // Act 3.
 
         // Now focus on the first fully visible EditText located at the last span index.
-        toFocus = findFirstFullyVisibleChild(mRecyclerView);
-        focusIndex = mRecyclerView.getChildAdapterPosition(toFocus);
+        toFocus = findFirstFullyVisibleChild(recyclerView);
+        focusIndex = recyclerView.getChildAdapterPosition(toFocus);
         focusIndex = (focusIndex / spanCount) * spanCount + (spanCount - 1);
-        toFocus = mRecyclerView.findViewHolderForAdapterPosition(focusIndex).itemView;
-        final int heightBeforeImeOpen2 = mRecyclerView.getHeight();
-        TouchUtils.tapView(getInstrumentation(), mRecyclerView, toFocus);
-        getInstrumentation().waitForIdleSync();
-        // Wait for IME to pop up
-        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+        toFocus = recyclerView.findViewHolderForAdapterPosition(focusIndex).itemView;
+
+        requestFocus(toFocus, false);
+
+        mGlm.expectLayout(1);
+        mActivityRule.runOnUiThread(new Runnable() {
             @Override
-            public boolean canProceed() {
-                return mRecyclerView.getHeight() < heightBeforeImeOpen2;
+            public void run() {
+                recyclerView.measure(widthMeasureSpec, fullHeightMinusOneMeasureSpec);
+                recyclerView.measure(widthMeasureSpec, halfHeightMeasureSpec);
+                recyclerView.layout(
+                        0,
+                        0,
+                        recyclerView.getMeasuredWidth(),
+                        recyclerView.getMeasuredHeight());
             }
         });
+        mGlm.waitForLayout(3);
+
+        // Assert 3.
+
         assertTrue("Child view at adapter pos " + focusIndex + " should be fully visible.",
-                isViewPartiallyInBound(mRecyclerView, toFocus));
+                isViewPartiallyInBound(recyclerView, toFocus));
     }
 
     @Test
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerTest.java
index 91d0dbf..77585b5 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerTest.java
@@ -20,7 +20,6 @@
 import static android.support.v7.widget.LinearLayoutManager.VERTICAL;
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
-import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
 
 import static org.hamcrest.CoreMatchers.is;
 import static org.junit.Assert.assertEquals;
@@ -37,19 +36,14 @@
 import android.os.Build;
 import android.support.test.filters.LargeTest;
 import android.support.test.filters.SdkSuppress;
-import android.support.testutils.PollingCheck;
 import android.support.v4.view.AccessibilityDelegateCompat;
-import android.support.v7.util.ImeCleanUpTestRule;
-import android.support.v7.util.TouchUtils;
+import android.support.v4.view.ViewCompat;
 import android.util.Log;
 import android.util.StateSet;
-import android.view.KeyEvent;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityEvent;
-import android.widget.LinearLayout;
 
-import org.junit.Rule;
 import org.junit.Test;
 
 import java.util.ArrayList;
@@ -69,68 +63,76 @@
 @LargeTest
 public class LinearLayoutManagerTest extends BaseLinearLayoutManagerTest {
 
-    @Rule
-    public final ImeCleanUpTestRule imeCleanUp = new ImeCleanUpTestRule();
-
+    /**
+     * Tests that the LinearLayoutManager retains the focused element after multiple measure
+     * calls to the RecyclerView.  There was a bug where the focused view was lost when the soft
+     * keyboard opened.  This test simulates the measure/layout events triggered by the opening
+     * of the soft keyboard by making two calls to measure.  A simulation was done because using
+     * the soft keyboard in the test caused many issues on API levels 15, 17 and 19.
+     */
     @Test
-    public void editTextVisibility() throws Throwable {
+    public void focusedChildStaysInViewWhenRecyclerViewShrinks() throws Throwable {
 
-        // Simulating a scenario where an EditText is tapped (which will receive focus).
-        // The soft keyboard that's opened overlaps the focused EditText which will shrink RV's
-        // padded bounded area. LLM should still lay out the focused EditText so that it becomes
-        // visible above the soft keyboard.
-        // The condition for this test is setting RV's height to a non-exact height, so that measure
-        // is called twice (once with the larger height and another time with smaller height when
-        // the keyboard shows up). To ensure this resizing of RV, SOFT_INPUT_ADJUST_RESIZE is set.
-        imeCleanUp.setContainerView(getActivity().getContainer());
-        final LinearLayout container = new LinearLayout(getActivity());
-        container.setOrientation(LinearLayout.VERTICAL);
-        container.setLayoutParams(
-                new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup
-                        .LayoutParams.MATCH_PARENT));
+        // Arrange.
 
-        final EditTextAdapter editTextAdapter = new EditTextAdapter(50);
-
-        mRecyclerView = inflateWrappedRV();
-        ViewGroup.LayoutParams lp = mRecyclerView.getLayoutParams();
+        final RecyclerView recyclerView = inflateWrappedRV();
+        ViewGroup.LayoutParams lp = recyclerView.getLayoutParams();
         lp.height = WRAP_CONTENT;
         lp.width = MATCH_PARENT;
+        recyclerView.setHasFixedSize(true);
 
-        mRecyclerView.setHasFixedSize(true);
-        mRecyclerView.setAdapter(editTextAdapter);
+        final FocusableAdapter focusableAdapter =
+                new FocusableAdapter(50);
+        recyclerView.setAdapter(focusableAdapter);
+
         mLayoutManager = new WrappedLinearLayoutManager(getActivity(), VERTICAL, false);
-        mRecyclerView.setLayoutManager(mLayoutManager);
-
-        container.addView(mRecyclerView);
+        recyclerView.setLayoutManager(mLayoutManager);
 
         mLayoutManager.expectLayouts(1);
         mActivityRule.runOnUiThread(new Runnable() {
             @Override
             public void run() {
-                getActivity().getContainer().addView(container);
+                getActivity().getContainer().addView(recyclerView);
             }
         });
+        mLayoutManager.waitForLayout(3);
+
+        int width = recyclerView.getWidth();
+        int height = recyclerView.getHeight();
+        final int widthMeasureSpec =
+                View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY);
+        final int fullHeightMeasureSpec =
+                View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.AT_MOST);
+        // "MinusOne" so that a measure call will appropriately trigger onMeasure after RecyclerView
+        // was previously laid out with the full height version.
+        final int fullHeightMinusOneMeasureSpec =
+                View.MeasureSpec.makeMeasureSpec(height - 1, View.MeasureSpec.AT_MOST);
+        final int halfHeightMeasureSpec =
+                View.MeasureSpec.makeMeasureSpec(height / 2, View.MeasureSpec.AT_MOST);
+
+        // Act 1.
+
+        View toFocus = findLastFullyVisibleChild(recyclerView);
+        int focusIndex = recyclerView.getChildAdapterPosition(toFocus);
+
+        requestFocus(toFocus, false);
+
+        mLayoutManager.expectLayouts(1);
         mActivityRule.runOnUiThread(new Runnable() {
             @Override
             public void run() {
-                mActivityRule.getActivity().getWindow().setSoftInputMode(SOFT_INPUT_ADJUST_RESIZE);
+                recyclerView.measure(widthMeasureSpec, fullHeightMinusOneMeasureSpec);
+                recyclerView.measure(widthMeasureSpec, halfHeightMeasureSpec);
+                recyclerView.layout(
+                        0,
+                        0,
+                        recyclerView.getMeasuredWidth(),
+                        recyclerView.getMeasuredHeight());
             }
         });
+        mLayoutManager.waitForLayout(3);
 
-        // First focus on the last fully visible EditText.
-        View toFocus = findLastFullyVisibleChild(mRecyclerView);
-        int focusIndex = mRecyclerView.getChildAdapterPosition(toFocus);
-
-        final int heightBeforeImeOpen = mRecyclerView.getHeight();
-        TouchUtils.tapView(getInstrumentation(), mRecyclerView, toFocus);
-        getInstrumentation().waitForIdleSync();
-       // Wait for IME to pop up.
-        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return mRecyclerView.getHeight() < heightBeforeImeOpen;
-            }
-        });
+        // Verify 1.
 
         assertThat("Child at position " + focusIndex + " should be focused",
                 toFocus.hasFocus(), is(true));
@@ -138,40 +140,59 @@
         // requestRectangleOnScreen (inside bringPointIntoView) for the focused view with a rect
         // containing the content area. This rect is guaranteed to be fully visible whereas a
         // portion of TextView could be out of bounds.
-        assertTrue("Child view at adapter pos " + focusIndex + " should be fully visible.",
-                isViewPartiallyInBound(mRecyclerView, toFocus));
+        assertThat("Child view at adapter pos " + focusIndex + " should be fully visible.",
+                isViewPartiallyInBound(recyclerView, toFocus), is(true));
 
-        // Close IME
-        final int heightBeforeImeClose = mRecyclerView.getHeight();
-        getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_BACK);
-        getInstrumentation().waitForIdleSync();
-        // Wait for IME to close
-        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+        // Act 2.
+
+        mLayoutManager.expectLayouts(1);
+        mActivityRule.runOnUiThread(new Runnable() {
             @Override
-            public boolean canProceed() {
-                return mRecyclerView.getHeight() > heightBeforeImeClose;
+            public void run() {
+                recyclerView.measure(widthMeasureSpec, fullHeightMeasureSpec);
+                recyclerView.layout(
+                        0,
+                        0,
+                        recyclerView.getMeasuredWidth(),
+                        recyclerView.getMeasuredHeight());
             }
         });
+        mLayoutManager.waitForLayout(3);
+
+        // Verify 2.
+
         assertThat("Child at position " + focusIndex + " should be focused",
                 toFocus.hasFocus(), is(true));
         assertTrue("Child view at adapter pos " + focusIndex + " should be fully visible.",
-                isViewPartiallyInBound(mRecyclerView, toFocus));
+                isViewPartiallyInBound(recyclerView, toFocus));
+
+        // Act 3.
 
         // Now focus on the first fully visible EditText.
-        toFocus = findFirstFullyVisibleChild(mRecyclerView);
-        focusIndex = mRecyclerView.getChildAdapterPosition(toFocus);
-        final int heightBeforeImeOpen2 = mRecyclerView.getHeight();
-        TouchUtils.tapView(getInstrumentation(), mRecyclerView, toFocus);
-        getInstrumentation().waitForIdleSync();
-        // Wait for IME to pop up
-        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+        toFocus = findFirstFullyVisibleChild(recyclerView);
+        focusIndex = recyclerView.getChildAdapterPosition(toFocus);
+
+        requestFocus(toFocus, false);
+
+        mLayoutManager.expectLayouts(1);
+        mActivityRule.runOnUiThread(new Runnable() {
             @Override
-            public boolean canProceed() {
-                return mRecyclerView.getHeight() < heightBeforeImeOpen2;
+            public void run() {
+                recyclerView.measure(widthMeasureSpec, fullHeightMinusOneMeasureSpec);
+                recyclerView.measure(widthMeasureSpec, halfHeightMeasureSpec);
+                recyclerView.layout(
+                        0,
+                        0,
+                        recyclerView.getMeasuredWidth(),
+                        recyclerView.getMeasuredHeight());
             }
         });
+        mLayoutManager.waitForLayout(3);
+
+        // Assert 3.
+
         assertTrue("Child view at adapter pos " + focusIndex + " should be fully visible.",
-                isViewPartiallyInBound(mRecyclerView, toFocus));
+                isViewPartiallyInBound(recyclerView, toFocus));
     }
 
     @Test
@@ -592,7 +613,7 @@
                     @Override
                     public void onFocusChange(View v, boolean hasFocus) {
                         assertNull("Focus just got cleared and no children should be holding"
-                                        + " focus now.", mRecyclerView.getFocusedChild());
+                                + " focus now.", mRecyclerView.getFocusedChild());
                         try {
                             // Calling focusSearch should be a no-op here since even though there
                             // are unfocusable views down to scroll to, none of RV's children hold
@@ -771,6 +792,118 @@
                 mRecyclerView.getChildCount() <= childCount + 3 /*1 for removed view, 2 for its size*/);
     }
 
+    void waitOneCycle() throws Throwable {
+        mActivityRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+            }
+        });
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.JELLY_BEAN)
+    @Test
+    public void hiddenNoneRemoveViewAccessibility() throws Throwable {
+        final Config config = new Config();
+        int adapterSize = 1000;
+        final boolean[] firstItemSpecialSize = new boolean[] {false};
+        TestAdapter adapter = new TestAdapter(adapterSize) {
+            @Override
+            public void onBindViewHolder(TestViewHolder holder,
+                    int position) {
+                super.onBindViewHolder(holder, position);
+                ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
+                if (!(lp instanceof ViewGroup.MarginLayoutParams)) {
+                    lp = new ViewGroup.MarginLayoutParams(0, 0);
+                    holder.itemView.setLayoutParams(lp);
+                }
+                ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) lp;
+                final int maxSize;
+                if (config.mOrientation == HORIZONTAL) {
+                    maxSize = mRecyclerView.getWidth();
+                    mlp.height = ViewGroup.MarginLayoutParams.MATCH_PARENT;
+                } else {
+                    maxSize = mRecyclerView.getHeight();
+                    mlp.width = ViewGroup.MarginLayoutParams.MATCH_PARENT;
+                }
+
+                final int desiredSize;
+                if (position == 0 && firstItemSpecialSize[0]) {
+                    desiredSize = maxSize / 3;
+                } else {
+                    desiredSize = maxSize / 8;
+                }
+                if (config.mOrientation == HORIZONTAL) {
+                    mlp.width = desiredSize;
+                } else {
+                    mlp.height = desiredSize;
+                }
+            }
+
+            @Override
+            public void onBindViewHolder(TestViewHolder holder,
+                    int position, List<Object> payloads) {
+                onBindViewHolder(holder, position);
+            }
+        };
+        adapter.setHasStableIds(false);
+        config.adapter(adapter);
+        setupByConfig(config, true);
+        final DummyItemAnimator itemAnimator = new DummyItemAnimator();
+        mRecyclerView.setItemAnimator(itemAnimator);
+
+        // push last item out by increasing first item's size
+        final int childBeingPushOut = mLayoutManager.getChildCount() - 1;
+        RecyclerView.ViewHolder itemViewHolder = mRecyclerView
+                .findViewHolderForAdapterPosition(childBeingPushOut);
+        final int originalAccessibility = ViewCompat.getImportantForAccessibility(
+                itemViewHolder.itemView);
+        assertTrue(ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO == originalAccessibility
+                || ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES == originalAccessibility);
+
+        itemAnimator.expect(DummyItemAnimator.MOVE_START, 1);
+        mActivityRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                firstItemSpecialSize[0] = true;
+                mTestAdapter.notifyItemChanged(0, "XXX");
+            }
+        });
+        // wait till itemAnimator starts which will block itemView's accessibility
+        itemAnimator.waitFor(DummyItemAnimator.MOVE_START);
+        // RV Changes accessiblity after onMoveStart, so wait one more cycle.
+        waitOneCycle();
+        assertTrue(itemAnimator.getMovesAnimations().contains(itemViewHolder));
+        assertEquals(ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS,
+                ViewCompat.getImportantForAccessibility(itemViewHolder.itemView));
+
+        // notify Change again to run predictive animation.
+        mLayoutManager.expectLayouts(2);
+        mActivityRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mTestAdapter.notifyItemChanged(0, "XXX");
+            }
+        });
+        mLayoutManager.waitForLayout(1);
+        mActivityRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                itemAnimator.endAnimations();
+            }
+        });
+        // scroll to the view being pushed out, it should get same view from cache as the item
+        // in adapter does not change.
+        smoothScrollToPosition(childBeingPushOut);
+        RecyclerView.ViewHolder itemViewHolder2 = mRecyclerView
+                .findViewHolderForAdapterPosition(childBeingPushOut);
+        assertSame(itemViewHolder, itemViewHolder2);
+        // the important for accessibility should be reset to YES/AUTO:
+        final int newAccessibility = ViewCompat.getImportantForAccessibility(
+                itemViewHolder.itemView);
+        assertTrue(ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO == newAccessibility
+                || ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES == newAccessibility);
+    }
+
     @Test
     public void keepFocusOnRelayout() throws Throwable {
         setupByConfig(new Config(VERTICAL, false, false).itemCount(500), true);
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewBasicTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewBasicTest.java
index 5946940..3357c2f 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewBasicTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewBasicTest.java
@@ -477,7 +477,6 @@
             assertTrue("must contain Adapter class", m.contains(MockAdapter.class.getName()));
             assertTrue("must contain LM class", m.contains(LinearLayoutManager.class.getName()));
             assertTrue("must contain ctx class", m.contains(getContext().getClass().getName()));
-
         }
     }
 
@@ -488,20 +487,71 @@
         mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
         measure();
         layout();
-        assertSame(focusAdapter.mBottomLeft,
-                focusAdapter.mTopRight.focusSearch(View.FOCUS_FORWARD));
-        assertSame(focusAdapter.mBottomRight,
-                focusAdapter.mBottomLeft.focusSearch(View.FOCUS_FORWARD));
+
+        boolean isIcsOrLower = Build.VERSION.SDK_INT <= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1;
+
+        // On API 15 and lower, focus forward get's translated to focus down.
+        View expected = isIcsOrLower ? focusAdapter.mBottomRight : focusAdapter.mBottomLeft;
+        assertEquals(expected, focusAdapter.mTopRight.focusSearch(View.FOCUS_FORWARD));
+
+        // On API 15 and lower, focus forward get's translated to focus down, which in this case
+        // runs out of the RecyclerView, thus returning null.
+        expected = isIcsOrLower ? null : focusAdapter.mBottomRight;
+        assertSame(expected, focusAdapter.mBottomLeft.focusSearch(View.FOCUS_FORWARD));
+
         // we don't want looping within RecyclerView
         assertNull(focusAdapter.mBottomRight.focusSearch(View.FOCUS_FORWARD));
         assertNull(focusAdapter.mTopLeft.focusSearch(View.FOCUS_BACKWARD));
     }
 
+    @Test
+    public void setAdapter_callsCorrectLmMethods() throws Throwable {
+        MockLayoutManager mockLayoutManager = new MockLayoutManager();
+        MockAdapter mockAdapter = new MockAdapter(1);
+        mRecyclerView.setLayoutManager(mockLayoutManager);
+
+        mRecyclerView.setAdapter(mockAdapter);
+        layout();
+
+        assertEquals(1, mockLayoutManager.mAdapterChangedCount);
+        assertEquals(0, mockLayoutManager.mItemsChangedCount);
+    }
+
+    @Test
+    public void swapAdapter_callsCorrectLmMethods() throws Throwable {
+        MockLayoutManager mockLayoutManager = new MockLayoutManager();
+        MockAdapter mockAdapter = new MockAdapter(1);
+        mRecyclerView.setLayoutManager(mockLayoutManager);
+
+        mRecyclerView.swapAdapter(mockAdapter, true);
+        layout();
+
+        assertEquals(1, mockLayoutManager.mAdapterChangedCount);
+        assertEquals(1, mockLayoutManager.mItemsChangedCount);
+    }
+
+    @Test
+    public void notifyDataSetChanged_callsCorrectLmMethods() throws Throwable {
+        MockLayoutManager mockLayoutManager = new MockLayoutManager();
+        MockAdapter mockAdapter = new MockAdapter(1);
+        mRecyclerView.setLayoutManager(mockLayoutManager);
+        mRecyclerView.setAdapter(mockAdapter);
+        mockLayoutManager.mAdapterChangedCount = 0;
+        mockLayoutManager.mItemsChangedCount = 0;
+
+        mockAdapter.notifyDataSetChanged();
+        layout();
+
+        assertEquals(0, mockLayoutManager.mAdapterChangedCount);
+        assertEquals(1, mockLayoutManager.mItemsChangedCount);
+    }
+
     static class MockLayoutManager extends RecyclerView.LayoutManager {
 
         int mLayoutCount = 0;
 
         int mAdapterChangedCount = 0;
+        int mItemsChangedCount = 0;
 
         RecyclerView.Adapter mPrevAdapter;
 
@@ -519,6 +569,11 @@
         }
 
         @Override
+        public void onItemsChanged(RecyclerView recyclerView) {
+            mItemsChangedCount++;
+        }
+
+        @Override
         public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
             mLayoutCount += 1;
         }
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java
index bdc450b..1c03c0f 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java
@@ -253,6 +253,186 @@
     }
 
     @Test
+    public void setAdapter_afterSwapAdapter_callsCorrectLmMethods() throws Throwable {
+        final RecyclerView rv = new RecyclerView(getActivity());
+        final LayoutAllLayoutManager lm = new LayoutAllLayoutManager(true);
+        final TestAdapter testAdapter = new TestAdapter(1);
+
+        lm.expectLayouts(1);
+        rv.setLayoutManager(lm);
+        setRecyclerView(rv);
+        setAdapter(testAdapter);
+        lm.waitForLayout(2);
+
+        lm.onAdapterChagnedCallCount = 0;
+        lm.onItemsChangedCallCount = 0;
+
+        lm.expectLayouts(1);
+        mActivityRule.runOnUiThread(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        rv.swapAdapter(testAdapter, true);
+                        rv.setAdapter(testAdapter);
+                    }
+                });
+        lm.waitForLayout(2);
+
+        assertEquals(2, lm.onAdapterChagnedCallCount);
+        assertEquals(1, lm.onItemsChangedCallCount);
+    }
+
+    @Test
+    public void setAdapter_afterNotifyDataSetChanged_callsCorrectLmMethods() throws Throwable {
+        final RecyclerView rv = new RecyclerView(getActivity());
+        final LayoutAllLayoutManager lm = new LayoutAllLayoutManager(true);
+        final TestAdapter testAdapter = new TestAdapter(1);
+
+        lm.expectLayouts(1);
+        rv.setLayoutManager(lm);
+        setRecyclerView(rv);
+        setAdapter(testAdapter);
+        lm.waitForLayout(2);
+
+        lm.onAdapterChagnedCallCount = 0;
+        lm.onItemsChangedCallCount = 0;
+
+        lm.expectLayouts(1);
+        mActivityRule.runOnUiThread(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        testAdapter.notifyDataSetChanged();
+                        rv.setAdapter(testAdapter);
+                    }
+                });
+        lm.waitForLayout(2);
+
+        assertEquals(1, lm.onAdapterChagnedCallCount);
+        assertEquals(1, lm.onItemsChangedCallCount);
+    }
+
+    @Test
+    public void notifyDataSetChanged_afterSetAdapter_callsCorrectLmMethods() throws Throwable {
+        final RecyclerView rv = new RecyclerView(getActivity());
+        final LayoutAllLayoutManager lm = new LayoutAllLayoutManager(true);
+        final TestAdapter testAdapter = new TestAdapter(1);
+
+        lm.expectLayouts(1);
+        rv.setLayoutManager(lm);
+        setRecyclerView(rv);
+        setAdapter(testAdapter);
+        lm.waitForLayout(2);
+
+        lm.onAdapterChagnedCallCount = 0;
+        lm.onItemsChangedCallCount = 0;
+
+        lm.expectLayouts(1);
+        mActivityRule.runOnUiThread(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        rv.setAdapter(testAdapter);
+                        testAdapter.notifyDataSetChanged();
+                    }
+                });
+        lm.waitForLayout(2);
+
+        assertEquals(1, lm.onAdapterChagnedCallCount);
+        assertEquals(1, lm.onItemsChangedCallCount);
+    }
+
+    @Test
+    public void notifyDataSetChanged_afterSwapAdapter_callsCorrectLmMethods() throws Throwable {
+        final RecyclerView rv = new RecyclerView(getActivity());
+        final LayoutAllLayoutManager lm = new LayoutAllLayoutManager(true);
+        final TestAdapter testAdapter = new TestAdapter(1);
+
+        lm.expectLayouts(1);
+        rv.setLayoutManager(lm);
+        setRecyclerView(rv);
+        setAdapter(testAdapter);
+        lm.waitForLayout(2);
+
+        lm.onAdapterChagnedCallCount = 0;
+        lm.onItemsChangedCallCount = 0;
+
+        lm.expectLayouts(1);
+        mActivityRule.runOnUiThread(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        rv.swapAdapter(testAdapter, true);
+                        testAdapter.notifyDataSetChanged();
+                    }
+                });
+        lm.waitForLayout(2);
+
+        assertEquals(1, lm.onAdapterChagnedCallCount);
+        assertEquals(1, lm.onItemsChangedCallCount);
+    }
+
+    @Test
+    public void swapAdapter_afterSetAdapter_callsCorrectLmMethods() throws Throwable {
+        final RecyclerView rv = new RecyclerView(getActivity());
+        final LayoutAllLayoutManager lm = new LayoutAllLayoutManager(true);
+        final TestAdapter testAdapter = new TestAdapter(1);
+
+        lm.expectLayouts(1);
+        rv.setLayoutManager(lm);
+        setRecyclerView(rv);
+        setAdapter(testAdapter);
+        lm.waitForLayout(2);
+
+        lm.onAdapterChagnedCallCount = 0;
+        lm.onItemsChangedCallCount = 0;
+
+        lm.expectLayouts(1);
+        mActivityRule.runOnUiThread(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        rv.setAdapter(testAdapter);
+                        rv.swapAdapter(testAdapter, true);
+                    }
+                });
+        lm.waitForLayout(2);
+
+        assertEquals(2, lm.onAdapterChagnedCallCount);
+        assertEquals(1, lm.onItemsChangedCallCount);
+    }
+
+    @Test
+    public void swapAdapter_afterNotifyDataSetChanged_callsCorrectLmMethods() throws Throwable {
+        final RecyclerView rv = new RecyclerView(getActivity());
+        final LayoutAllLayoutManager lm = new LayoutAllLayoutManager(true);
+        final TestAdapter testAdapter = new TestAdapter(1);
+
+        lm.expectLayouts(1);
+        rv.setLayoutManager(lm);
+        setRecyclerView(rv);
+        setAdapter(testAdapter);
+        lm.waitForLayout(2);
+
+        lm.onAdapterChagnedCallCount = 0;
+        lm.onItemsChangedCallCount = 0;
+
+        lm.expectLayouts(1);
+        mActivityRule.runOnUiThread(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        testAdapter.notifyDataSetChanged();
+                        rv.swapAdapter(testAdapter, true);
+                    }
+                });
+        lm.waitForLayout(2);
+
+        assertEquals(1, lm.onAdapterChagnedCallCount);
+        assertEquals(1, lm.onItemsChangedCallCount);
+    }
+
+    @Test
     public void setAdapterNotifyItemRangeInsertedCrashTest() throws Throwable {
         final RecyclerView rv = new RecyclerView(getActivity());
         final TestLayoutManager lm = new LayoutAllLayoutManager(true);
@@ -4788,6 +4968,8 @@
 
     public class LayoutAllLayoutManager extends TestLayoutManager {
         private final boolean mAllowNullLayoutLatch;
+        public int onItemsChangedCallCount = 0;
+        public int onAdapterChagnedCallCount = 0;
 
         public LayoutAllLayoutManager() {
             // by default, we don't allow unexpected layouts.
@@ -4797,6 +4979,18 @@
             mAllowNullLayoutLatch = allowNullLayoutLatch;
         }
 
+        @Override
+        public void onItemsChanged(RecyclerView recyclerView) {
+            super.onItemsChanged(recyclerView);
+            onItemsChangedCallCount++;
+        }
+
+        @Override
+        public void onAdapterChanged(RecyclerView.Adapter oldAdapter,
+                RecyclerView.Adapter newAdapter) {
+            super.onAdapterChanged(oldAdapter, newAdapter);
+            onAdapterChagnedCallCount++;
+        }
 
         @Override
         public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewOnGenericMotionEventTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewOnGenericMotionEventTest.java
index aee15dd..2f80156 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewOnGenericMotionEventTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewOnGenericMotionEventTest.java
@@ -24,8 +24,8 @@
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.support.v4.view.InputDeviceCompat;
-import android.support.v4.view.MotionEventCompat;
 import android.support.v4.view.ViewConfigurationCompat;
+import android.support.v7.util.TouchUtils;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewConfiguration;
@@ -60,9 +60,8 @@
         MockLayoutManager layoutManager = new MockLayoutManager(true, true);
         mRecyclerView.setLayoutManager(layoutManager);
         layout();
-        MotionEvent e = obtainScrollMotionEvent(
-                MotionEventCompat.AXIS_SCROLL, 2, InputDeviceCompat.SOURCE_ROTARY_ENCODER);
-        mRecyclerView.onGenericMotionEvent(e);
+        TouchUtils.scrollView(
+                MotionEvent.AXIS_SCROLL, 2, InputDeviceCompat.SOURCE_ROTARY_ENCODER, mRecyclerView);
         assertTotalScroll(0, (int) (-2f * getScaledVerticalScrollFactor()));
     }
 
@@ -73,9 +72,8 @@
         MockLayoutManager layoutManager = new MockLayoutManager(true, false);
         mRecyclerView.setLayoutManager(layoutManager);
         layout();
-        MotionEvent e = obtainScrollMotionEvent(
-                MotionEventCompat.AXIS_SCROLL, 2, InputDeviceCompat.SOURCE_ROTARY_ENCODER);
-        mRecyclerView.onGenericMotionEvent(e);
+        TouchUtils.scrollView(
+                MotionEvent.AXIS_SCROLL, 2, InputDeviceCompat.SOURCE_ROTARY_ENCODER, mRecyclerView);
         assertTotalScroll((int) (2f * getScaledHorizontalScrollFactor()), 0);
     }
 
@@ -84,9 +82,8 @@
         MockLayoutManager layoutManager = new MockLayoutManager(true, true);
         mRecyclerView.setLayoutManager(layoutManager);
         layout();
-        MotionEvent e = obtainScrollMotionEvent(
-                MotionEventCompat.AXIS_VSCROLL, 2, InputDeviceCompat.SOURCE_CLASS_POINTER);
-        mRecyclerView.onGenericMotionEvent(e);
+        TouchUtils.scrollView(
+                MotionEvent.AXIS_VSCROLL, 2, InputDeviceCompat.SOURCE_CLASS_POINTER, mRecyclerView);
         assertTotalScroll(0, (int) (-2f * getScaledVerticalScrollFactor()));
     }
 
@@ -95,9 +92,8 @@
         MockLayoutManager layoutManager = new MockLayoutManager(true, true);
         mRecyclerView.setLayoutManager(layoutManager);
         layout();
-        MotionEvent e = obtainScrollMotionEvent(
-                MotionEventCompat.AXIS_HSCROLL, 2, InputDeviceCompat.SOURCE_CLASS_POINTER);
-        mRecyclerView.onGenericMotionEvent(e);
+        TouchUtils.scrollView(
+                MotionEvent.AXIS_HSCROLL, 2, InputDeviceCompat.SOURCE_CLASS_POINTER, mRecyclerView);
         assertTotalScroll((int) (2f * getScaledHorizontalScrollFactor()), 0);
     }
 
diff --git a/wear/api/27.0.0.ignore b/wear/api/27.0.0.ignore
new file mode 100644
index 0000000..ebc6632
--- /dev/null
+++ b/wear/api/27.0.0.ignore
@@ -0,0 +1 @@
+a55e0de
diff --git a/wear/api/current.txt b/wear/api/current.txt
index e397eb3..3fd2465 100644
--- a/wear/api/current.txt
+++ b/wear/api/current.txt
@@ -2,7 +2,7 @@
 
   public final class AmbientMode extends android.app.Fragment {
     ctor public AmbientMode();
-    method public static <T extends android.app.Activity & android.support.wear.ambient.AmbientMode.AmbientCallbackProvider> android.support.wear.ambient.AmbientMode.AmbientController attachAmbientSupport(T);
+    method public static <T extends android.app.Activity> android.support.wear.ambient.AmbientMode.AmbientController attachAmbientSupport(T);
     field public static final java.lang.String EXTRA_BURN_IN_PROTECTION = "com.google.android.wearable.compat.extra.BURN_IN_PROTECTION";
     field public static final java.lang.String EXTRA_LOWBIT_AMBIENT = "com.google.android.wearable.compat.extra.LOWBIT_AMBIENT";
     field public static final java.lang.String FRAGMENT_TAG = "android.support.wearable.ambient.AmbientMode";
@@ -21,7 +21,6 @@
 
   public final class AmbientMode.AmbientController {
     method public boolean isAmbient();
-    method public void setAutoResumeEnabled(boolean);
   }
 
 }
@@ -49,7 +48,6 @@
     ctor public BoxInsetLayout(android.content.Context, android.util.AttributeSet);
     ctor public BoxInsetLayout(android.content.Context, android.util.AttributeSet, int);
     method public android.support.wear.widget.BoxInsetLayout.LayoutParams generateLayoutParams(android.util.AttributeSet);
-    method protected void onLayout(boolean, int, int, int, int);
   }
 
   public static class BoxInsetLayout.LayoutParams extends android.widget.FrameLayout.LayoutParams {
@@ -183,7 +181,7 @@
     method public void peekDrawer();
   }
 
-  public class WearableDrawerLayout extends android.widget.FrameLayout implements android.view.View.OnLayoutChangeListener {
+  public class WearableDrawerLayout extends android.widget.FrameLayout implements android.support.v4.view.NestedScrollingParent android.view.View.OnLayoutChangeListener {
     ctor public WearableDrawerLayout(android.content.Context);
     ctor public WearableDrawerLayout(android.content.Context, android.util.AttributeSet);
     ctor public WearableDrawerLayout(android.content.Context, android.util.AttributeSet, int);
diff --git a/wear/build.gradle b/wear/build.gradle
index 55320b9..4937619 100644
--- a/wear/build.gradle
+++ b/wear/build.gradle
@@ -13,12 +13,12 @@
     api(project(":percent"))
     api(project(":recyclerview-v7"))
 
-    androidTestImplementation(TEST_RUNNER, libs.exclude_annotations)
-    androidTestImplementation(ESPRESSO_CORE, libs.exclude_annotations)
+    androidTestImplementation(TEST_RUNNER)
+    androidTestImplementation(ESPRESSO_CORE)
     androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
     androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
 
-    provided fileTree(dir: 'wear_stubs', include: ['com.google.android.wearable-stubs.jar'])
+    compileOnly fileTree(dir: 'wear_stubs', include: ['com.google.android.wearable-stubs.jar'])
 }
 
 android {
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/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/src/main/java/android/support/wear/ambient/AmbientMode.java b/wear/src/main/java/android/support/wear/ambient/AmbientMode.java
index 1911a40..0077a5b 100644
--- a/wear/src/main/java/android/support/wear/ambient/AmbientMode.java
+++ b/wear/src/main/java/android/support/wear/ambient/AmbientMode.java
@@ -21,7 +21,9 @@
 import android.content.Context;
 import android.os.Bundle;
 import android.support.annotation.CallSuper;
+import android.support.annotation.Nullable;
 import android.support.annotation.VisibleForTesting;
+import android.util.Log;
 
 import com.google.android.wearable.compat.WearableActivityController;
 
@@ -38,7 +40,7 @@
  * It should be called with an {@link Activity} as an argument and that {@link Activity} will then
  * be able to receive ambient lifecycle events through an {@link AmbientCallback}. The
  * {@link Activity} will also receive a {@link AmbientController} object from the attachment which
- * can be used to query the current status of the ambient mode, or toggle simple settings.
+ * can be used to query the current status of the ambient mode.
  * An example of how to attach {@link AmbientMode} to your {@link Activity} and use
  * the {@link AmbientController} can be found below:
  * <p>
@@ -48,6 +50,7 @@
  * }</pre>
  */
 public final class AmbientMode extends Fragment {
+    private static final String TAG = "AmbientMode";
 
     /**
      * Property in bundle passed to {@code AmbientCallback#onEnterAmbient(Bundle)} to indicate
@@ -104,9 +107,6 @@
          * running (after onResume, before onPause). All drawing should complete by the conclusion
          * of this method. Note that {@code invalidate()} calls will be executed before resuming
          * lower-power mode.
-         * <p>
-         * <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>
          *
          * @param ambientDetails bundle containing information about the display being used.
          *                      It includes information about low-bit color and burn-in protection.
@@ -117,36 +117,40 @@
          * 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
          * running (after onResume, before onPause).
-         * <p>
-         * <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 =
             new AmbientDelegate.AmbientCallback() {
                 @Override
                 public void onEnterAmbient(Bundle ambientDetails) {
-                    mSuppliedCallback.onEnterAmbient(ambientDetails);
+                    if (mSuppliedCallback != null) {
+                        mSuppliedCallback.onEnterAmbient(ambientDetails);
+                    }
                 }
 
                 @Override
                 public void onExitAmbient() {
-                    mSuppliedCallback.onExitAmbient();
+                    if (mSuppliedCallback != null) {
+                        mSuppliedCallback.onExitAmbient();
+                    }
                 }
 
                 @Override
                 public void onUpdateAmbient() {
-                    mSuppliedCallback.onUpdateAmbient();
+                    if (mSuppliedCallback != null) {
+                        mSuppliedCallback.onUpdateAmbient();
+                    }
                 }
             };
     private AmbientDelegate mDelegate;
+    @Nullable
     private AmbientCallback mSuppliedCallback;
     private AmbientController mController;
 
@@ -166,8 +170,7 @@
         if (context instanceof AmbientCallbackProvider) {
             mSuppliedCallback = ((AmbientCallbackProvider) context).getAmbientCallback();
         } else {
-            throw new IllegalArgumentException(
-                    "fragment should attach to an activity that implements AmbientCallback");
+            Log.w(TAG, "No callback provided - enabling only smart resume");
         }
     }
 
@@ -176,7 +179,9 @@
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         mDelegate.onCreate();
-        mDelegate.setAmbientEnabled();
+        if (mSuppliedCallback != null) {
+            mDelegate.setAmbientEnabled();
+        }
     }
 
     @Override
@@ -215,15 +220,19 @@
     }
 
     /**
-     * Attach ambient support to the given activity.
+     * Attach ambient support to the given activity. Calling this method with an Activity
+     * implementing the {@link AmbientCallbackProvider} interface will provide you with an
+     * opportunity to react to ambient events such as {@code onEnterAmbient}. Alternatively,
+     * you can call this method with an Activity which does not implement
+     * the {@link AmbientCallbackProvider} interface and that will only enable the auto-resume
+     * functionality. This is equivalent to providing (@code null} from
+     * the {@link AmbientCallbackProvider}.
      *
-     * @param activity the activity to attach ambient support to. This activity has to also
-     *                implement {@link AmbientCallbackProvider}
+     * @param activity the activity to attach ambient support to.
      * @return the associated {@link AmbientController} which can be used to query the state of
-     * ambient mode and toggle simple settings related to it.
+     * ambient mode.
      */
-    public static <T extends Activity & AmbientCallbackProvider> AmbientController
-            attachAmbientSupport(T activity) {
+    public static <T extends Activity> AmbientController attachAmbientSupport(T activity) {
         FragmentManager fragmentManager = activity.getFragmentManager();
         AmbientMode ambientFragment = (AmbientMode) fragmentManager.findFragmentByTag(FRAGMENT_TAG);
         if (ambientFragment == null) {
@@ -251,9 +260,8 @@
 
     /**
      * A class for interacting with the ambient mode on a wearable device. This class can be used to
-     * query the current state of ambient mode and to enable or disable certain settings.
-     * An instance of this class is returned to the user when they attach their {@link Activity}
-     * to {@link AmbientMode}.
+     * query the current state of ambient mode. An instance of this class is returned to the user
+     * when they attach their {@link Activity} to {@link AmbientMode}.
      */
     public final class AmbientController {
         private static final String TAG = "AmbientController";
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 03f494a..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;
@@ -76,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";
@@ -141,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;
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;
         }
diff --git a/wear/tests/AndroidManifest.xml b/wear/tests/AndroidManifest.xml
index ce78477..67ccd91 100644
--- a/wear/tests/AndroidManifest.xml
+++ b/wear/tests/AndroidManifest.xml
@@ -55,6 +55,13 @@
                 <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
+
+        <activity android:name="android.support.wear.ambient.AmbientModeResumeTestActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
         <!-- Test app is iOS compatible. -->
         <meta-data
             android:name="com.google.android.wearable.standalone"
diff --git a/wear/tests/src/android/support/wear/ambient/AmbientModeResumeTest.java b/wear/tests/src/android/support/wear/ambient/AmbientModeResumeTest.java
new file mode 100644
index 0000000..32de769
--- /dev/null
+++ b/wear/tests/src/android/support/wear/ambient/AmbientModeResumeTest.java
@@ -0,0 +1,48 @@
+/*
+ * 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.wear.ambient;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import android.support.test.filters.MediumTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.wear.widget.util.WakeLockRule;
+
+import com.google.android.wearable.compat.WearableActivityController;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class AmbientModeResumeTest {
+    @Rule
+    public final WakeLockRule mWakeLock = new WakeLockRule();
+
+    @Rule
+    public final ActivityTestRule<AmbientModeResumeTestActivity> mActivityRule =
+            new ActivityTestRule<>(AmbientModeResumeTestActivity.class);
+
+    @Test
+    public void testActivityDefaults() throws Throwable {
+        assertTrue(WearableActivityController.getLastInstance().isAutoResumeEnabled());
+        assertFalse(WearableActivityController.getLastInstance().isAmbientEnabled());
+    }
+}
diff --git a/wear/tests/src/android/support/wear/ambient/AmbientModeResumeTestActivity.java b/wear/tests/src/android/support/wear/ambient/AmbientModeResumeTestActivity.java
new file mode 100644
index 0000000..0ca3c15
--- /dev/null
+++ b/wear/tests/src/android/support/wear/ambient/AmbientModeResumeTestActivity.java
@@ -0,0 +1,29 @@
+/*
+ * 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.wear.ambient;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class AmbientModeResumeTestActivity extends Activity {
+    AmbientMode.AmbientController mAmbientController;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mAmbientController = AmbientMode.attachAmbientSupport(this);
+    }
+}
diff --git a/wear/tests/src/com/google/android/wearable/compat/WearableActivityController.java b/wear/tests/src/com/google/android/wearable/compat/WearableActivityController.java
index 7823f23..0a76af0 100644
--- a/wear/tests/src/com/google/android/wearable/compat/WearableActivityController.java
+++ b/wear/tests/src/com/google/android/wearable/compat/WearableActivityController.java
@@ -33,7 +33,7 @@
 
     private AmbientCallback mCallback;
     private boolean mAmbientEnabled = false;
-    private boolean mAutoResumeEnabled = false;
+    private boolean mAutoResumeEnabled = true;
     private boolean mAmbient = false;
 
     public WearableActivityController(String tag, Activity activity, AmbientCallback callback) {
diff --git a/webkit/.gitignore b/webkit/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/webkit/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/webkit/AndroidManifest.xml b/webkit/AndroidManifest.xml
new file mode 100644
index 0000000..7d2bbc2
--- /dev/null
+++ b/webkit/AndroidManifest.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="androidx.webkit">
+</manifest>
+
diff --git a/webkit/OWNERS b/webkit/OWNERS
new file mode 100644
index 0000000..5d88928
--- /dev/null
+++ b/webkit/OWNERS
@@ -0,0 +1,5 @@
+boliu@google.com
+michaelbai@google.com
+tobiasjs@google.com
+torne@google.com
+gsennton@google.com
diff --git a/webkit/build.gradle b/webkit/build.gradle
new file mode 100644
index 0000000..e4fff11
--- /dev/null
+++ b/webkit/build.gradle
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+apply plugin: android.support.SupportAndroidLibraryPlugin
+
+android {
+    defaultConfig {
+        minSdkVersion 21
+    }
+
+    sourceSets {
+        main.manifest.srcFile 'AndroidManifest.xml'
+    }
+}
+
+supportLibrary {
+    name = "WebView Support Library"
+    inceptionYear = "2017"
+    description = "The WebView Support Library is a static library you can add to your Android application in order to use android.webkit APIs that are not available for older platform versions."
+}
diff --git a/webkit/lint-baseline.xml b/webkit/lint-baseline.xml
new file mode 100644
index 0000000..8bc6f6f
--- /dev/null
+++ b/webkit/lint-baseline.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="4" by="lint 3.0.0-alpha9">
+
+</issues>
diff --git a/media-compat-test-client/tests/NO_DOCS b/webkit/tests/NO_DOCS
similarity index 100%
rename from media-compat-test-client/tests/NO_DOCS
rename to webkit/tests/NO_DOCS